XML (Extensible Markup Language) to zestaw reguł kodowania dokumentów w czytelny dla komputera. XML to popularny format udostępniania danych w internecie.
Witryny, które często aktualizują treść, takie jak strony z wiadomościami lub blogi, często udostępniają kanał XML, dzięki czemu programy zewnętrzne mogą być na bieżąco z treścią. zmian. Przesyłanie i analizowanie danych XML jest częstym zadaniem w przypadku obiektów mających połączenie z siecią aplikacji. W tym temacie wyjaśnimy, jak analizować dokumenty XML i korzystać z ich danych.
Aby dowiedzieć się więcej o tworzeniu treści internetowych w aplikacji na Androida, zobacz Treści internetowe.
Wybierz parser
Zalecamy XmlPullParser, czyli wydajną,
i prosty sposób analizowania formatu XML na Androidzie. Android ma dwa
implementacji tego interfejsu:
KXmlParserprzy użyciu:XmlPullParserFactory.newPullParser()ExpatPullParserprzy użyciu:Xml.newPullParser()
Bez względu na to, którą opcję wybierzesz.
przykład w tej sekcji korzysta z identyfikatorów ExpatPullParser i
Xml.newPullParser()
Analizowanie pliku danych
Pierwszym krokiem podczas analizy pliku danych jest określenie, które pola Cię interesują. Parser wyodrębnia dane z tych pól i ignoruje pozostałe.
Zobacz ten fragment przeanalizowanego kanału w przykładowej aplikacji. Każdy
post na StackOverflow.com, pojawi się w
kanału jako tagu entry, który zawiera kilka zagnieżdżonych tagów:
<?xml version="1.0" encoding="utf-8"?> <feed xmlns="http://www.w3.org/2005/Atom" xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule" ..."> <title type="text">newest questions tagged android - Stack Overflow</title> ... <entry> ... </entry> <entry> <id>http://stackoverflow.com/q/9439999</id> <re:rank scheme="http://stackoverflow.com">0</re:rank> <title type="text">Where is my data file?</title> <category scheme="http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest/tags" term="android"/> <category scheme="http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest/tags" term="file"/> <author> <name>cliff2310</name> <uri>http://stackoverflow.com/users/1128925</uri> </author> <link rel="alternate" href="http://stackoverflow.com/questions/9439999/where-is-my-data-file" /> <published>2012-02-25T00:30:54Z</published> <updated>2012-02-25T00:30:54Z</updated> <summary type="html"> <p>I have an Application that requires a data file...</p> </summary> </entry> <entry> ... </entry> ... </feed>
Przykładowa aplikacja
wyodrębnia dane tagu entry i jego zagnieżdżonych tagów
title, link i summary.
Utwórz instancję parsera
Następnym krokiem analizy pliku danych jest
utworzyć wystąpienie parsera i rozpocząć proces analizy. Ten fragment
inicjuje parser, aby nie przetwarzał przestrzeni nazw i używał podanego InputStream jako danych wejściowych. Rozpoczyna proces analizy od wywołania funkcji
nextTag() i wywołuje metodę
Metoda readFeed(), która wyodrębnia i przetwarza dane aplikacji
interesują mnie:
Kotlin
// We don't use namespaces. private val ns: String? = null class StackOverflowXmlParser { @Throws(XmlPullParserException::class, IOException::class) fun parse(inputStream: InputStream): List<*> { inputStream.use { inputStream -> val parser: XmlPullParser = Xml.newPullParser() parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false) parser.setInput(inputStream, null) parser.nextTag() return readFeed(parser) } } ... }
Java
public class StackOverflowXmlParser { // We don't use namespaces. private static final String ns = null; public List parse(InputStream in) throws XmlPullParserException, IOException { try { XmlPullParser parser = Xml.newPullParser(); parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); parser.setInput(in, null); parser.nextTag(); return readFeed(parser); } finally { in.close(); } } ... }
Czytanie treści
Metoda readFeed() faktycznie przetwarza dane
kanału. Wyszukuje elementy oznaczone tagiem „entry” jako punktu wyjścia
dla rekurencji
podczas przetwarzania pliku danych. Jeśli tag nie jest tagiem entry, pomija go. Gdy już wszystko
plik danych jest przetwarzany rekurencyjnie, readFeed() zwraca błąd
List
zawierający wpisy (wraz z zagnieżdżonymi elementami danych),
wyodrębnione z pliku danych. Wartość List jest następnie zwracana przez funkcję
parsera.
Kotlin
@Throws(XmlPullParserException::class, IOException::class) private fun readFeed(parser: XmlPullParser): List<Entry> { val entries = mutableListOf<Entry>() parser.require(XmlPullParser.START_TAG, ns, "feed") while (parser.next() != XmlPullParser.END_TAG) { if (parser.eventType != XmlPullParser.START_TAG) { continue } // Starts by looking for the entry tag. if (parser.name == "entry") { entries.add(readEntry(parser)) } else { skip(parser) } } return entries }
Java
private List readFeed(XmlPullParser parser) throws XmlPullParserException, IOException { List entries = new ArrayList(); parser.require(XmlPullParser.START_TAG, ns, "feed"); while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } String name = parser.getName(); // Starts by looking for the entry tag. if (name.equals("entry")) { entries.add(readEntry(parser)); } else { skip(parser); } } return entries; }
Analizuj XML
Aby wykonać analizę pliku danych XML:
- Zgodnie z opisem w sekcji Analizowanie pliku danych określ tagi, które chcesz umieścić w aplikacji. Ten
przykład wyodrębniania danych dla tagu
entryi jego zagnieżdżonych tagów:title,linkisummary. - Utwórz te metody:
- Czytaj dla każdego tagu, który chcesz uwzględnić, np.
readEntry()ireadTitle(). Parser odczytuje ze strumienia wejściowego. Gdy napotka tag o nazwie,entry,title,linklubsummary, wywołuje odpowiednią metodę dla tego tagu. W przeciwnym razie tag zostanie pominięty. - Metody wyodrębniania danych dla poszczególnych typów tagów i przenoszenia
z następnego tagu. W tym przykładzie odpowiednie metody są następujące:
- W przypadku tagów
titleisummaryparser wywołujereadText()Ta metoda wyodrębnia dane dla tych tagów przez wywołanieparser.getText() - W przypadku tagu
linkparser wyodrębnia dane dla linków najpierw określająca, czy dany link produktów i usług, którymi jest zainteresowany. Następnie używa funkcjiparser.getAttributeValue()do: wyodrębnienia wartości linku. - W przypadku tagu
entryparser wywołujereadEntry(). Ta metoda analizuje zagnieżdżone tagi wpisu i zwraca wartośćEntryobiekt z elementami danychtitle,linkisummary
- W przypadku tagów
- Metoda pomocnicza
skip(), która jest rekurencyjna. Więcej dyskusji na ten temat znajdziesz w artykule Pomijanie tagów, które Cię nie interesują.
- Czytaj dla każdego tagu, który chcesz uwzględnić, np.
Ten fragment pokazuje, jak parser analizuje wpisy, tytuły, linki i podsumowania.
Kotlin
data class Entry(val title: String?, val summary: String?, val link: String?) // Parses the contents of an entry. If it encounters a title, summary, or link tag, hands them off // to their respective "read" methods for processing. Otherwise, skips the tag. @Throws(XmlPullParserException::class, IOException::class) private fun readEntry(parser: XmlPullParser): Entry { parser.require(XmlPullParser.START_TAG, ns, "entry") var title: String? = null var summary: String? = null var link: String? = null while (parser.next() != XmlPullParser.END_TAG) { if (parser.eventType != XmlPullParser.START_TAG) { continue } when (parser.name) { "title" -> title = readTitle(parser) "summary" -> summary = readSummary(parser) "link" -> link = readLink(parser) else -> skip(parser) } } return Entry(title, summary, link) } // Processes title tags in the feed. @Throws(IOException::class, XmlPullParserException::class) private fun readTitle(parser: XmlPullParser): String { parser.require(XmlPullParser.START_TAG, ns, "title") val title = readText(parser) parser.require(XmlPullParser.END_TAG, ns, "title") return title } // Processes link tags in the feed. @Throws(IOException::class, XmlPullParserException::class) private fun readLink(parser: XmlPullParser): String { var link = "" parser.require(XmlPullParser.START_TAG, ns, "link") val tag = parser.name val relType = parser.getAttributeValue(null, "rel") if (tag == "link") { if (relType == "alternate") { link = parser.getAttributeValue(null, "href") parser.nextTag() } } parser.require(XmlPullParser.END_TAG, ns, "link") return link } // Processes summary tags in the feed. @Throws(IOException::class, XmlPullParserException::class) private fun readSummary(parser: XmlPullParser): String { parser.require(XmlPullParser.START_TAG, ns, "summary") val summary = readText(parser) parser.require(XmlPullParser.END_TAG, ns, "summary") return summary } // For the tags title and summary, extracts their text values. @Throws(IOException::class, XmlPullParserException::class) private fun readText(parser: XmlPullParser): String { var result = "" if (parser.next() == XmlPullParser.TEXT) { result = parser.text parser.nextTag() } return result } ...
Java
public static class Entry { public final String title; public final String link; public final String summary; private Entry(String title, String summary, String link) { this.title = title; this.summary = summary; this.link = link; } } // Parses the contents of an entry. If it encounters a title, summary, or link tag, hands them off // to their respective "read" methods for processing. Otherwise, skips the tag. private Entry readEntry(XmlPullParser parser) throws XmlPullParserException, IOException { parser.require(XmlPullParser.START_TAG, ns, "entry"); String title = null; String summary = null; String link = null; while (parser.next() != XmlPullParser.END_TAG) { if (parser.getEventType() != XmlPullParser.START_TAG) { continue; } String name = parser.getName(); if (name.equals("title")) { title = readTitle(parser); } else if (name.equals("summary")) { summary = readSummary(parser); } else if (name.equals("link")) { link = readLink(parser); } else { skip(parser); } } return new Entry(title, summary, link); } // Processes title tags in the feed. private String readTitle(XmlPullParser parser) throws IOException, XmlPullParserException { parser.require(XmlPullParser.START_TAG, ns, "title"); String title = readText(parser); parser.require(XmlPullParser.END_TAG, ns, "title"); return title; } // Processes link tags in the feed. private String readLink(XmlPullParser parser) throws IOException, XmlPullParserException { String link = ""; parser.require(XmlPullParser.START_TAG, ns, "link"); String tag = parser.getName(); String relType = parser.getAttributeValue(null, "rel"); if (tag.equals("link")) { if (relType.equals("alternate")){ link = parser.getAttributeValue(null, "href"); parser.nextTag(); } } parser.require(XmlPullParser.END_TAG, ns, "link"); return link; } // Processes summary tags in the feed. private String readSummary(XmlPullParser parser) throws IOException, XmlPullParserException { parser.require(XmlPullParser.START_TAG, ns, "summary"); String summary = readText(parser); parser.require(XmlPullParser.END_TAG, ns, "summary"); return summary; } // For the tags title and summary, extracts their text values. private String readText(XmlPullParser parser) throws IOException, XmlPullParserException { String result = ""; if (parser.next() == XmlPullParser.TEXT) { result = parser.getText(); parser.nextTag(); } return result; } ... }
Pomiń tagi, które Cię nie interesują
Parser musi pominąć tagi, które go nie interesują. Oto metoda skip() parsera:
Kotlin
@Throws(XmlPullParserException::class, IOException::class) private fun skip(parser: XmlPullParser) { if (parser.eventType != XmlPullParser.START_TAG) { throw IllegalStateException() } var depth = 1 while (depth != 0) { when (parser.next()) { XmlPullParser.END_TAG -> depth-- XmlPullParser.START_TAG -> depth++ } } }
Java
private void skip(XmlPullParser parser) throws XmlPullParserException, IOException { if (parser.getEventType() != XmlPullParser.START_TAG) { throw new IllegalStateException(); } int depth = 1; while (depth != 0) { switch (parser.next()) { case XmlPullParser.END_TAG: depth--; break; case XmlPullParser.START_TAG: depth++; break; } } }
Proces przebiega następująco:
- Jeśli bieżące zdarzenie nie jest
START_TAG - Wykorzystuje ono
START_TAGi wszystkie zdarzenia do poziomu włącznie pasujący elementEND_TAG. - Śledzi głębokość zagnieżdżenia, aby mieć pewność, że kończy się na właściwej wartości
END_TAG, a nie na pierwszy napotkany tag po pierwotnym taguSTART_TAG.
Jeśli więc bieżący element ma zagnieżdżone elementy, wartość
depth nie będzie mieć wartości 0, dopóki parser nie przetworzy wszystkich zdarzeń między
oryginalny START_TAG i pasujący do niego END_TAG. Dla:
na przykład parser pomija element <author>,
który ma 2 zagnieżdżone elementy: <name> oraz
<uri>:
- Za pierwszym razem za pomocą pętli
whilenastępny tag parsera spotkań po<author>toSTART_TAGdla<name>Wartość w poludepthzwiększa się do 2. - Po raz drugi w pętli
while, kolejnym tagiem parser spotkań toEND_TAG</name>. Wartość dladepthmaleje do 1. - Po raz trzeci w pętli
whilenastępny tag parsera spotkań toSTART_TAG<uri>. Wartość codepthprzyrostu do 2. - Czwarty raz w pętli
while, kolejnym tagiem parser spotkań toEND_TAG</uri>. Wartość poladepthzmniejsza się do 1. - Piąty raz i ostatni raz w pętli
while. tagiem napotkanym przez parser toEND_TAG</author>Wartość parametrudepthzmniejsza się do 0, co oznacza, że element<author>został poprawnie pominięto.
Pobieraj dane XML
Przykładowa aplikacja asynchronicznie pobiera i analizuje kanał XML. Spowoduje to usunięcie przetwarzania z głównego wątku UI. Kiedy
gdy przetwarzanie zostanie ukończone, aplikacja aktualizuje interfejs w swojej głównej aktywności,
NetworkActivity
W tym fragmencie metoda loadPage() wykonuje te działania:
- Inicjuje zmienną ciągu znaków z adresem URL pliku danych XML.
- Wywołuje metodę
downloadXml(url), jeśli ustawienia użytkownika i sieć tylko połączenie. Ta metoda pobiera i analizuje plik danych oraz zwraca wynik w postaci ciągu znaków widoczne w interfejsie.
Kotlin
class NetworkActivity : Activity() { companion object { const val WIFI = "Wi-Fi" const val ANY = "Any" const val SO_URL = "http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest" // Whether there is a Wi-Fi connection. private var wifiConnected = false // Whether there is a mobile connection. private var mobileConnected = false // Whether the display should be refreshed. var refreshDisplay = true // The user's current network preference setting. var sPref: String? = null } ... // Asynchronously downloads the XML feed from stackoverflow.com. fun loadPage() { if (sPref.equals(ANY) && (wifiConnected || mobileConnected)) { downloadXml(SO_URL) } else if (sPref.equals(WIFI) && wifiConnected) { downloadXml(SO_URL) } else { // Show error. } } ... }
Java
public class NetworkActivity extends Activity { public static final String WIFI = "Wi-Fi"; public static final String ANY = "Any"; private static final String URL = "http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest"; // Whether there is a Wi-Fi connection. private static boolean wifiConnected = false; // Whether there is a mobile connection. private static boolean mobileConnected = false; // Whether the display should be refreshed. public static boolean refreshDisplay = true; public static String sPref = null; ... // Asynchronously downloads the XML feed from stackoverflow.com. public void loadPage() { if((sPref.equals(ANY)) && (wifiConnected || mobileConnected)) { downloadXml(URL); } else if ((sPref.equals(WIFI)) && (wifiConnected)) { downloadXml(URL); } else { // Show error. } }
Metoda downloadXml wywołuje w Kotlin następujące metody:
lifecycleScope.launch(Dispatchers.IO), który wykorzystuje współrzędne Kotlin do uruchomić metodęloadXmlFromNetwork()w wątku wejścia/wyjścia. Przekazuje ona adres URL kanału jako . MetodaloadXmlFromNetwork()pobiera i przetwarza dane kanału. Po zakończeniu przesyła z powrotem ciąg znaków z wynikami.withContext(Dispatchers.Main), który używa współrzędnych Kotlina do powrotu do wątku głównego, pobiera makro zwraca ciąg znaków i wyświetla go w interfejsie.
W języku Java proces wygląda tak:
- Wykonanie polecenia
ExecutormetodyloadXmlFromNetwork()w wątku w tle. Przekazuje ona adres URL kanału jako . MetodaloadXmlFromNetwork()pobiera i przetwarza dane kanału. Po zakończeniu przesyła z powrotem ciąg znaków z wynikami. Handlerdzwoni pod numerpostpowrót do wątku głównego, zwraca ciąg znaków i wyświetla go w interfejsie.
Kotlin
// Implementation of Kotlin coroutines used to download XML feed from stackoverflow.com. private fun downloadXml(vararg urls: String) { var result: String? = null lifecycleScope.launch(Dispatchers.IO) { result = try { loadXmlFromNetwork(urls[0]) } catch (e: IOException) { resources.getString(R.string.connection_error) } catch (e: XmlPullParserException) { resources.getString(R.string.xml_error) } withContext(Dispatchers.Main) { setContentView(R.layout.main) // Displays the HTML string in the UI via a WebView. findViewById<WebView>(R.id.webview)?.apply { loadData(result?: "", "text/html", null) } } } }
Java
// Implementation of Executor and Handler used to download XML feed asynchronously from stackoverflow.com. private void downloadXml(String... urls) { ExecutorService executor = Executors.newSingleThreadExecutor(); Handler handler = new Handler(Looper.getMainLooper()); executor.execute(() -> { String result; try { result = loadXmlFromNetwork(urls[0]); } catch (IOException e) { result = getResources().getString(R.string.connection_error); } catch (XmlPullParserException e) { result = getResources().getString(R.string.xml_error); } String finalResult = result; handler.post(() -> { setContentView(R.layout.main); // Displays the HTML string in the UI via a WebView. WebView myWebView = (WebView) findViewById(R.id.webview); myWebView.loadData(finalResult, "text/html", null); }); }); }
Metoda loadXmlFromNetwork() wywoływana z
downloadXml jest widoczne w następnym fragmencie. Wykonuje te działania:
- Tworzy instancję
StackOverflowXmlParser. Tworzy też zmienne dla:ListzEntryobiektów (entries) oraz dlatitle,urlisummarydo przechowywania wartości wyodrębnionych z pliku danych XML dla tych pól. - Wywołuje funkcję
downloadUrl(), która pobiera plik danych i zwraca go jakoInputStream. - Wykorzystuje parametr
StackOverflowXmlParserdo analizowania parametruInputStream.StackOverflowXmlParseruzupełniaListzentriesz danymi z pliku danych. - Przetwarza:
entriesListi łączy dane z kanału ze znacznikami HTML. - Zwraca ciąg HTML wyświetlany w głównej aktywności Interfejs.
Kotlin
// Uploads XML from stackoverflow.com, parses it, and combines it with // HTML markup. Returns HTML string. @Throws(XmlPullParserException::class, IOException::class) private fun loadXmlFromNetwork(urlString: String): String { // Checks whether the user set the preference to include summary text. val pref: Boolean = PreferenceManager.getDefaultSharedPreferences(this)?.run { getBoolean("summaryPref", false) } ?: false val entries: List<Entry> = downloadUrl(urlString)?.use { stream -> // Instantiates the parser. StackOverflowXmlParser().parse(stream) } ?: emptyList() return StringBuilder().apply { append("<h3>${resources.getString(R.string.page_title)}</h3>") append("<em>${resources.getString(R.string.updated)} ") append("${formatter.format(rightNow.time)}</em>") // StackOverflowXmlParser returns a List (called "entries") of Entry objects. // Each Entry object represents a single post in the XML feed. // This section processes the entries list to combine each entry with HTML markup. // Each entry is displayed in the UI as a link that optionally includes // a text summary. entries.forEach { entry -> append("<p><a href='") append(entry.link) append("'>" + entry.title + "</a></p>") // If the user set the preference to include summary text, // adds it to the display. if (pref) { append(entry.summary) } } }.toString() } // Given a string representation of a URL, sets up a connection and gets // an input stream. @Throws(IOException::class) private fun downloadUrl(urlString: String): InputStream? { val url = URL(urlString) return (url.openConnection() as? HttpURLConnection)?.run { readTimeout = 10000 connectTimeout = 15000 requestMethod = "GET" doInput = true // Starts the query. connect() inputStream } }
Java
// Uploads XML from stackoverflow.com, parses it, and combines it with // HTML markup. Returns HTML string. private String loadXmlFromNetwork(String urlString) throws XmlPullParserException, IOException { InputStream stream = null; // Instantiates the parser. StackOverflowXmlParser stackOverflowXmlParser = new StackOverflowXmlParser(); List<Entry> entries = null; String title = null; String url = null; String summary = null; Calendar rightNow = Calendar.getInstance(); DateFormat formatter = new SimpleDateFormat("MMM dd h:mmaa"); // Checks whether the user set the preference to include summary text. SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this); boolean pref = sharedPrefs.getBoolean("summaryPref", false); StringBuilder htmlString = new StringBuilder(); htmlString.append("<h3>" + getResources().getString(R.string.page_title) + "</h3>"); htmlString.append("<em>" + getResources().getString(R.string.updated) + " " + formatter.format(rightNow.getTime()) + "</em>"); try { stream = downloadUrl(urlString); entries = stackOverflowXmlParser.parse(stream); // Makes sure that the InputStream is closed after the app is // finished using it. } finally { if (stream != null) { stream.close(); } } // StackOverflowXmlParser returns a List (called "entries") of Entry objects. // Each Entry object represents a single post in the XML feed. // This section processes the entries list to combine each entry with HTML markup. // Each entry is displayed in the UI as a link that optionally includes // a text summary. for (Entry entry : entries) { htmlString.append("<p><a href='"); htmlString.append(entry.link); htmlString.append("'>" + entry.title + "</a></p>"); // If the user set the preference to include summary text, // adds it to the display. if (pref) { htmlString.append(entry.summary); } } return htmlString.toString(); } // Given a string representation of a URL, sets up a connection and gets // an input stream. private InputStream downloadUrl(String urlString) throws IOException { URL url = new URL(urlString); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setReadTimeout(10000 /* milliseconds */); conn.setConnectTimeout(15000 /* milliseconds */); conn.setRequestMethod("GET"); conn.setDoInput(true); // Starts the query. conn.connect(); return conn.getInputStream(); }