Extensible Markup Language (XML) è un insieme di regole per la codifica di documenti in formato leggibile. XML è un formato popolare per la condivisione di dati su internet.
I siti web che aggiornano spesso i propri contenuti, come i siti di notizie o i blog, spesso forniscono un feed XML in modo che i programmi esterni possano tenersi aggiornati sulle modifiche ai contenuti. Il caricamento e l'analisi dei dati XML è un'attività comune per le app connesse alla rete. Questo argomento spiega come analizzare i documenti XML e come utilizzarli.
Per scoprire di più sulla creazione di contenuti basati sul web nella tua app per Android, consulta Contenuti basati sul web.
Scegli un parser
Consigliamo XmlPullParser
, che è un modo efficiente e
gestibile per analizzare il codice XML su Android. Android ha due
implementazioni di questa interfaccia:
KXmlParser
, conXmlPullParserFactory.newPullParser()
ExpatPullParser
, conXml.newPullParser()
Entrambe le opzioni va bene. L'esempio in questa sezione utilizza ExpatPullParser
e Xml.newPullParser()
.
Analizzare il feed
Il primo passaggio dell'analisi di un feed consiste nel decidere quali campi ti interessano. Il parser estrae i dati per questi campi e ignora il resto.
Consulta il seguente estratto di un feed analizzato nell'app di esempio. Ogni post su StackOverflow.com viene visualizzato nel feed come tag entry
contenente diversi tag nidificati:
<?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>
L'app di esempio estrae i dati per il tag entry
e i relativi tag nidificati title
, link
e summary
.
Crea l'istanza del parser
Il passaggio successivo dell'analisi di un feed
consiste nel creare un parser e avviare la procedura di analisi. Questo snippet inizializza un parser in modo che non elabori gli spazi dei nomi e per utilizzare il valore InputStream
fornito come input. Avvia il processo di analisi con una chiamata all'indirizzo nextTag()
e richiama il metodo readFeed()
, che estrae ed elabora i dati a cui l'app è interessata:
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(); } } ... }
Leggi il feed
Il metodo readFeed()
si occupa dell'elaborazione effettiva del
feed. Cerca gli elementi contrassegnati con "entry" come punto di partenza per l'elaborazione ricorsiva del feed. Se un tag non è entry
, viene ignorato. Una volta che l'intero
feed è stato elaborato in modo ricorsivo, readFeed()
restituisce un
List
contenente le voci (inclusi i membri di dati nidificati) che ha
estratto dal feed. Questo valore List
viene quindi restituito
dal 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; }
Analizza XML
Per analizzare un feed XML, procedi nel seguente modo:
- Come descritto in Analizzare il feed, identifica i tag da includere nella tua app. Questo
esempio estrae i dati per il tag
entry
e i relativi tag nidificati:title
,link
esummary
. - Crea i seguenti metodi:
- Un metodo "read" per ogni tag da includere, ad esempio
readEntry()
ereadTitle()
. Il parser legge i tag dal flusso di input. Quando incontra un tag denominato, in questo esempio,entry
,title
,link
osummary
, richiama il metodo appropriato per quel tag. In caso contrario, il tag viene ignorato. - Metodi per estrarre i dati per ogni tipo di tag diverso e per far avanzare il parser al tag successivo. In questo esempio, i metodi pertinenti sono i seguenti:
- Per i tag
title
esummary
, l'analizzatore sintattico chiamareadText()
. Questo metodo estrae i dati per questi tag chiamandoparser.getText()
. - Per il tag
link
, il parser estrae i dati dei link determinando innanzitutto se il link è il tipo a cui sono interessati. Poi utilizzaparser.getAttributeValue()
per estrarre il valore del collegamento. - Per il tag
entry
, l'analizzatore sintattico chiamareadEntry()
. Questo metodo analizza i tag nidificati della voce e restituisce un oggettoEntry
con i membri dei datititle
,link
esummary
.
- Per i tag
- Un metodo
skip()
helper ricorsivo. Per ulteriori informazioni su questo argomento, vedi Ignorare i tag che non ti interessano.
- Un metodo "read" per ogni tag da includere, ad esempio
Questo snippet mostra in che modo il parser analizza voci, titoli, link e riepiloghi.
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; } ... }
Ignora i tag che non ti interessano
Il parser deve saltare i tag che non gli interessano. Ecco il metodo skip()
del parser:
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; } } }
Ecco come funziona:
- Genera un'eccezione se l'evento attuale non è un
START_TAG
. - Consuma il valore
START_TAG
e tutti gli eventi fino alEND_TAG
corrispondente. - Tiene traccia della profondità di nidificazione per assicurarsi che si fermi al
END_TAG
corretto e non al primo tag rilevato dopo ilSTART_TAG
originale.
Di conseguenza, se l'elemento corrente ha elementi nidificati, il valore di depth
non sarà 0 finché l'analizzatore sintattico non consuma tutti gli eventi compresi tra l'elemento START_TAG
originale e il END_TAG
corrispondente. Ad
esempio, considera in che modo il parser ignora l'elemento <author>
,
che ha due elementi nidificati, <name>
e
<uri>
:
- La prima volta nel loop
while
, il tag successivo rilevato dall'analizzatore sintattico dopo<author>
è il tagSTART_TAG
per<name>
. Il valore didepth
viene incrementato di 2. - La seconda volta nel loop
while
, il tag successivo rilevato dall'analizzatore sintattico è</name>
END_TAG
. Il valore didepth
diminuisce a 1. - La terza volta nel loop
while
, il tag successivo rilevato dall'analizzatore sintattico è<uri>
START_TAG
. Il valore didepth
viene incrementato fino a 2. - La quarta volta nel loop
while
, il tag successivo incontrato dall'analizzatore sintattico è</uri>
END_TAG
. Il valore didepth
diminuisce a 1. - La quinta e ultima volta del loop
while
, il tag successivo rilevato dal parser èEND_TAG
</author>
. Il valore didepth
diminuisce a 0, per indicare che l'elemento<author>
è stato saltato correttamente.
Utilizza dati XML
L'applicazione di esempio recupera e analizza il feed XML in modo asincrono. In questo modo l'elaborazione viene rimossa dal thread dell'interfaccia utente principale. Al termine dell'elaborazione, l'app aggiorna l'interfaccia utente nell'attività principale, NetworkActivity
.
Nel seguente estratto, il metodo loadPage()
:
- Inizializza una variabile stringa con l'URL per il feed XML.
- Richiama il metodo
downloadXml(url)
, se le impostazioni dell'utente e la connessione di rete lo consentono. Questo metodo scarica e analizza il feed e restituisce un risultato stringa da visualizzare nell'interfaccia utente.
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. } }
Il metodo downloadXml
chiama i seguenti metodi in Kotlin:
lifecycleScope.launch(Dispatchers.IO)
, che utilizza coroutine di Kotlin per avviare il metodoloadXmlFromNetwork()
nel thread di IO. Trasmette l'URL del feed come parametro. Il metodoloadXmlFromNetwork()
recupera ed elabora il feed. Al termine, restituisce una stringa di risultati.withContext(Dispatchers.Main)
, che utilizza coroutine Kotlin per tornare al thread principale, prende la stringa restituita e la visualizza nell'interfaccia utente.
Nel linguaggio di programmazione Java, la procedura è la seguente:
- Un
Executor
esegue il metodoloadXmlFromNetwork()
su un thread in background. Trasmette l'URL del feed come parametro. Il metodoloadXmlFromNetwork()
recupera ed elabora il feed. Al termine, restituisce una stringa di risultati. - Un
Handler
chiamapost
per tornare al thread principale, prende la stringa restituita e la visualizza nell'interfaccia utente.
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); }); }); }
Il metodo loadXmlFromNetwork()
richiamato da downloadXml
è mostrato nello snippet successivo. In questo modo:
- Crea un'istanza di
StackOverflowXmlParser
. Crea inoltre variabili per unList
diEntry
oggetti (entries
) e pertitle
,url
esummary
, per mantenere i valori estratti dal feed XML per questi campi. - Richiama
downloadUrl()
, che recupera il feed e lo restituisce comeInputStream
. - Utilizza
StackOverflowXmlParser
per analizzareInputStream
.StackOverflowXmlParser
compila unList
dientries
con i dati del feed. - Elabora l'
List
entries
e combina i dati del feed con il markup HTML. - Restituisce una stringa HTML che viene visualizzata nell'interfaccia utente dell'attività principale.
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(); }