Extensible Markup Language (XML) ist ein Regelsatz für die Codierung von Dokumenten in maschinenlesbares Format. XML ist ein beliebtes Format für den Austausch von Daten im Internet.
Websites, auf denen die Inhalte häufig aktualisiert werden, z. B. Nachrichtenwebsites oder Blogs, stellen häufig einen XML-Feed bereit, damit externe Programme über den Content informiert bleiben können. Änderungen. Das Hochladen und Parsen von XML-Daten ist eine gängige Aufgabe bei Apps. In diesem Thema wird erläutert, wie XML-Dokumente geparst und deren Daten verwendet werden.
Weitere Informationen zum Erstellen webbasierter Inhalte in deiner Android-App findest du unter Webbasierte Inhalte:
Parser auswählen
Wir empfehlen XmlPullParser
, eine effiziente und
eine verwaltbare Methode
zum Parsen von XML unter Android. Android hat zwei
Implementierungen dieser Schnittstelle:
KXmlParser
, mitXmlPullParserFactory.newPullParser()
ExpatPullParser
, mitXml.newPullParser()
Beide Optionen sind in Ordnung. Die
Im Beispiel in diesem Abschnitt werden ExpatPullParser
und
Xml.newPullParser()
.
Feed analysieren
Der erste Schritt beim Parsen eines Feeds besteht darin, zu entscheiden, für welche Felder Sie sich interessieren. Der Parser extrahiert Daten für diese Felder und ignoriert den Rest.
Hier sehen Sie einen Auszug aus einem geparsten Feed in der Beispiel-App. Jedes
Beitrag auf StackOverflow.com wird in der
Feed als entry
-Tag, das mehrere verschachtelte Tags enthält:
<?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>
Beispielanwendung
extrahiert Daten für das entry
-Tag und seine verschachtelten Tags
title
, link
und summary
.
Parser instanziieren
Der nächste Schritt beim Parsen eines Feeds
einen Parser instanziieren und den Parsing-Prozess starten. Dieses Snippet
initialisiert einen Parser, damit er keine Namespaces verarbeitet und den bereitgestellten InputStream
als Eingabe verwendet. Er startet den Parsing-Prozess mit einem Aufruf an
nextTag()
und ruft den
readFeed()
-Methode, die die Daten der App extrahiert und verarbeitet
Interesse an:
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(); } } ... }
Feed lesen
Die Methode readFeed()
übernimmt die Verarbeitung des
Feed. Es wird nach Elementen mit dem Tag „entry“ gesucht. als Ausgangspunkt für rekursive
der Verarbeitung des Feeds. Wenn ein Tag kein entry
-Tag ist, wird es übersprungen. Sobald alle
Feed rekursiv verarbeitet wird, gibt readFeed()
eine
List
mit den Einträgen (einschließlich verschachtelter Datenmitglieder),
die aus dem Feed extrahiert wurden. Diese List
wird dann vom
parser.
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; }
XML parsen
Die Schritte zum Parsen eines XML-Feeds sind folgende:
- Identifizieren Sie wie unter Feed analysieren beschrieben die Tags, die Sie in Ihre App einbinden möchten. Dieses
Im Beispiel werden Daten für das
entry
-Tag und seine verschachtelten Tags extrahiert:title
,link
undsummary
. - Erstellen Sie die folgenden Methoden:
- Ein „Lesen“ für die Tags, die Sie einbinden möchten,
readEntry()
undreadTitle()
. Der Parser liest Tags aus dem Eingabestream. Wenn in diesem Beispiel ein Tag namensentry
,title
,link
odersummary
wird die entsprechende Methode für dieses Tag. Andernfalls wird das Tag übersprungen. - Methoden zum Extrahieren von Daten für jeden Tag-Typ und zum Fortfahren der
mit dem nächsten Tag verbinden. In diesem Beispiel sind folgende Methoden relevant:
<ph type="x-smartling-placeholder">
- </ph>
- Für die Tags
title
undsummary
ruft der Parser aufreadText()
Diese Methode extrahiert Daten für diese Tags, indem sieparser.getText()
- Beim
link
-Tag extrahiert der Parser zuerst Daten für Links, indem er bestimmt, ob es sich um den Typ des Links handelt die für sie interessant sind. Dann wirdparser.getAttributeValue()
verwendet, um um den Linkwert zu extrahieren. - Für das
entry
-Tag ruft der ParserreadEntry()
auf. Diese Methode parst die verschachtelten Tags des Eintrags und gibt einEntry
-Objekt zurück mit den Datenelemententitle
,link
undsummary
.
- Für die Tags
- Eine
skip()
-Hilfsmethode, die rekursiv ist. Weitere Informationen zu diesem Thema finden Sie unter Unwichtige Tags überspringen.
- Ein „Lesen“ für die Tags, die Sie einbinden möchten,
Dieses Snippet zeigt, wie der Parser Einträge, Titel, Links und Zusammenfassungen parst.
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; } ... }
Unwichtige Tags überspringen
Der Parser muss Tags überspringen, die für ihn nicht relevant sind. Hier ist die skip()
-Methode des Parsers:
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; } } }
So funktioniert's:
- Es wird eine Ausnahme ausgelöst, wenn das aktuelle Ereignis kein
START_TAG
- Er verarbeitet die
START_TAG
und alle Ereignisse bis einschließlich den übereinstimmendenEND_TAG
. - Die Verschachtelungstiefe wird erfasst, um sicherzustellen, dass sie am richtigen
END_TAG
und nicht bei dem ersten Tag nach dem ursprünglichenSTART_TAG
.
Wenn also das aktuelle Element verschachtelte Elemente enthält, wird der Wert von
depth
ist erst 0, wenn der Parser alle Ereignisse zwischen
die ursprüngliche START_TAG
und die zugehörige END_TAG
. Für
Stellen Sie sich vor, wie der Parser das Element <author>
überspringt,
mit 2 verschachtelten Elementen, <name>
und
<uri>
:
- Beim ersten Mal durch die
while
-Schleife wird das nächste Tag vom Parser gesendet. Treffer nach<author>
sind dieSTART_TAG
für<name>
. Der Wert fürdepth
erhöht sich in 2. - Beim zweiten Mal durch die
while
-Schleife wird das nächste Tag vom ParserEND_TAG
</name>
. Der Wert fürdepth
wird auf 1 verringert. - Beim dritten Mal durch die
while
-Schleife wird der Parser als NächstesSTART_TAG
<uri>
. Der Wert indepth
-Schritten auf 2. - Beim vierten Mal über die
while
-Schleife wird das nächste Tag vom ParserEND_TAG
</uri>
. Der Wert fürdepth
verringert sich auf 1. - Beim fünften und letzten Mal über die
while
-Schleife, das nächste Mal Tag, auf den der Parser stößt, ist dasEND_TAG
</author>
Der Wert fürdepth
verringert sich auf 0, was bedeutet, dass das<author>
-Element erfolgreich war übersprungen.
XML-Daten verarbeiten
Die Beispielanwendung ruft den XML-Feed asynchron ab und parst ihn. Dadurch wird die Verarbeitung aus dem Haupt-UI-Thread entfernt. Wann?
abgeschlossen ist, aktualisiert die App die UI in der Hauptaktivität,
NetworkActivity
Im folgenden Auszug führt die Methode loadPage()
Folgendes aus:
- Initialisiert eine Stringvariable mit der URL für den XML-Feed.
- Ruft die Methode
downloadXml(url)
auf, wenn die Einstellungen des Nutzers und das Netzwerk Verbindung zulassen. Diese Methode lädt den Feed herunter, parst ihn und gibt ein Zeichenfolgenergebnis zurück, die auf der Benutzeroberfläche angezeigt werden.
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. } }
Die Methode downloadXml
ruft die folgenden Methoden in Kotlin auf:
lifecycleScope.launch(Dispatchers.IO)
, die Kotlin-Coroutinen verwendet, um starte die MethodeloadXmlFromNetwork()
im E/A-Thread. Die Feed-URL wird als . Die MethodeloadXmlFromNetwork()
ruft ab und verarbeitet sie. den Feed. Nach Abschluss des Vorgangs wird ein Ergebnisstring zurückgegeben.withContext(Dispatchers.Main)
, die Kotlin-Koroutinen verwendet, um zum Hauptthread zurückzukehren, verwendet den zurückgegebener String und zeigt ihn auf der Benutzeroberfläche an.
In der Programmiersprache Java läuft der Prozess so ab:
- Ein
Executor
-Objekt wird ausgeführt dieloadXmlFromNetwork()
-Methode in einem Hintergrundthread. Die Feed-URL wird als . Die MethodeloadXmlFromNetwork()
ruft ab und verarbeitet sie. den Feed. Nach Abschluss des Vorgangs wird ein Ergebnisstring zurückgegeben. - Ein
Handler
ruftpost
an um zum Hauptthread zurückzukehren, zurückgegebener String und zeigt ihn auf der Benutzeroberfläche an.
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); }); }); }
Die Methode loadXmlFromNetwork()
, die aus
downloadXml
wird im nächsten Snippet angezeigt. Dabei werden folgende Schritte ausgeführt:
- Instanziiert eine
StackOverflowXmlParser
. Außerdem werden Variablen für einList
vonEntry
Objekten (entries
) und fürtitle
,url
undsummary
für den -Werte, die aus dem XML-Feed für diese Felder extrahiert wurden. downloadUrl()
aufrufen, wodurch der Feed abgerufen und alsInputStream
.- Verwendet
StackOverflowXmlParser
zum Parsen vonInputStream
.StackOverflowXmlParser
füllt einList
vonentries
mit Daten aus dem Feed. - Verarbeitet
entries
List
und kombiniert die Feed-Daten mit HTML-Markup. - Gibt einen HTML-String zurück, der in der Hauptaktivität angezeigt wird UI.
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(); }