Analizza dati XML

L'Extensible Markup Language (XML) è un insieme di regole per la codifica di documenti formato leggibile dalla macchina. XML è un formato popolare per la condivisione di dati su internet.

Siti web che aggiornano di frequente i contenuti, ad esempio siti o blog di notizie spesso forniscono un feed XML per consentire ai programmi esterni di tenersi aggiornati sui contenuti modifiche. Il caricamento e l'analisi dei dati XML sono un'attività comune per i dispositivi connessi alla rete app. Questo argomento spiega come analizzare i documenti XML e utilizzare i relativi dati.

Per scoprire di più sulla creazione di contenuti basati sul web nella tua app per Android, vedi Contenuti basati sul web.

Scegli un parser

Consigliamo XmlPullParser, che è un servizio efficiente un modo gestibile di analizzare il codice XML su Android. Android ha due implementazioni di questa interfaccia:

Entrambe le opzioni va bene. La esempio in questa sezione utilizza ExpatPullParser e Xml.newPullParser().

Analizzare il feed

Il primo passaggio nell'analisi di un feed consiste nel decidere quali campi ti interessano. L'analizzatore sintattico estrae i dati per questi campi e ignora il resto.

Leggi il seguente estratto di un feed analizzato nell'app di esempio. Ciascuna post su StackOverflow.com viene visualizzato nella 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>

App di esempio estrae i dati per il tag entry e i relativi tag nidificati title, link e summary.

Creare un'istanza per il parser

Il passaggio successivo nell'analisi di un feed è creare un'istanza per un parser e avviare il processo di analisi. Questo snippet inizializza un parser in modo che non elabori gli spazi dei nomi e utilizzi il valore InputStream fornito come input. Avvia il processo di analisi con una chiamata a nextTag() e richiama il metodo readFeed(), che estrae ed elabora i dati dell'app interessato a:

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() svolge l'effettivo lavoro di elaborazione feed. Cerca gli elementi taggati "entry" come punto di partenza per la gestione ricorsiva durante l'elaborazione del feed. Se non è un tag entry, un tag viene ignorato. Una volta che l'intera feed viene elaborato in modo ricorsivo, readFeed() restituisce un List contenenti le voci (inclusi i membri di dati nidificati) estratti dal feed. L'elemento List viene quindi restituito analizzatore sintattico.

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:

  1. Come descritto nella sezione Analizzare il feed, identifica i tag da includere nell'app. Questo esempio estrae i dati per il tag entry e i relativi tag nidificati: title, link e summary.
  2. Crea i seguenti metodi:

    • Una "lettura" per ogni tag che vuoi includere, come readEntry() e readTitle(). L'analizzatore sintattico legge i tag dallo stream di input. Quando incontra un tag denominato, in questo esempio, entry, title, link o summary, chiama il metodo appropriato per quel tag. In caso contrario, il tag viene ignorato.
    • Metodi per estrarre i dati per ciascun tipo diverso di tag e per avanzare nella al tag successivo. In questo esempio, i metodi pertinenti sono i seguenti:
      • Per i tag title e summary, il parser chiama readText(). Questo metodo estrae i dati per questi tag richiamando parser.getText().
      • Per il tag link, l'analizzatore sintattico estrae i dati dei link innanzitutto determinare se il link è di tipo a cui è interessato. Quindi utilizza parser.getAttributeValue() per estrarre il valore del link.
      • Per il tag entry, il parser chiama readEntry(). Questo metodo analizza i tag nidificati della voce e restituisce un Entry con i membri di dati title, link e summary.
    • Un metodo skip() helper ricorsivo. Per ulteriori discussioni su questo argomento, vedi Saltare i tag che non ti interessano.

Questo snippet mostra in che modo l'analizzatore sintattico 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;
}
  ...
}

Salta i tag che non ti interessano

L'analizzatore sintattico deve saltare i tag che non sono interessati. 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 è START_TAG.
  • Utilizza START_TAG e tutti gli eventi fino a e inclusi i END_TAG corrispondenti.
  • Tiene traccia della profondità di nidificazione per assicurarsi che si fermi al valore END_TAG corretto e non alla il primo tag che incontra dopo il tag originale START_TAG.

Di conseguenza, se l'elemento corrente ha elementi nidificati, il valore di Il valore di depth sarà pari a 0 finché il parser non avrà consumato tutti gli eventi tra il START_TAG originale e il END_TAG corrispondente. Per ad esempio, considera come l'analizzatore sintattico salta l'elemento <author>, che ha 2 elementi nidificati, <name> e <uri>:

  • La prima volta tramite il loop while, il tag successivo dell'analizzatore sintattico incontri dopo che <author> è il START_TAG per <name>. Il valore di depth viene incrementato a 2.
  • La seconda volta tramite il loop while, il tag successivo dell'analizzatore sintattico incontri è END_TAG </name>. Il valore per depth diminuisci a 1.
  • La terza volta tramite il loop while, il tag successivo dell'analizzatore sintattico incontri è START_TAG <uri>. Il valore per incrementi di depth a 2.
  • La quarta volta tramite il loop while, il tag successivo dell'analizzatore sintattico incontri è END_TAG </uri>. Il valore per depth diminuisce a 1.
  • La quinta e ultima volta nel loop while, la successiva Il tag rilevato dal parser è END_TAG </author>. Il valore per depth diminuisce a 0, a indicare che l'elemento <author> è stato completato correttamente saltata.

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. Quando completa l'elaborazione, l'app aggiorna l'UI nella sua attività principale NetworkActivity.

Nel seguente estratto, il metodo loadPage() svolge queste operazioni:

  • Inizializza una variabile stringa con l'URL per il feed XML.
  • Consente di richiamare il metodo downloadXml(url) se le impostazioni dell'utente e la rete connessione lo consenta. Questo metodo scarica e analizza il feed e restituisce un risultato in formato stringa che visualizzato 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 le coroutine Kotlin per avvia il metodo loadXmlFromNetwork() sul thread di IO. L'URL del feed viene trasmesso come . Il metodo loadXmlFromNetwork() recupera ed elabora il feed. Al termine, restituisce una stringa di risultati.
  • withContext(Dispatchers.Main), che utilizza le coroutine Kotlin per tornare al thread principale, prende il la stringa restituita e la visualizza nella UI.

Nel linguaggio di programmazione Java, il processo è il seguente:

  • Un Executor viene eseguito il metodo loadXmlFromNetwork() su un thread in background. L'URL del feed viene trasmesso come . Il metodo loadXmlFromNetwork() recupera ed elabora il feed. Al termine, restituisce una stringa di risultati.
  • Handler chiama post per tornare al thread principale, prende il la stringa restituita e la visualizza nella UI.

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 visualizzato nello snippet successivo. Esegue le seguenti operazioni:

  1. Crea un'istanza per un StackOverflowXmlParser. Crea anche variabili un List di Entry oggetti (entries) e per title, url e summary, per conservare le estratti dal feed XML per questi campi.
  2. Richiama downloadUrl(), che recupera il feed e lo restituisce come un InputStream.
  3. Utilizza StackOverflowXmlParser per analizzare InputStream. StackOverflowXmlParser compila un List di entries con i dati del feed.
  4. Elabora entries List e combina i dati del feed con il markup HTML.
  5. Restituisce una stringa HTML che viene visualizzata nell'attività principale nell'interfaccia utente.

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();
}