O Google tem o compromisso de promover a igualdade racial para as comunidades negras. Saiba como.

Analisar dados XML

"Extensible Markup Language" (XML) é um conjunto de regras para a codificação de documentos em formato legível por máquina. XML é um formato conhecido para o compartilhamento de dados na Internet. Sites que atualizam seu conteúdo com frequência, como sites de notícias ou blogs, geralmente fornecem um feed XML para que programas externos possam acompanhar as alterações de conteúdo. Carregar e analisar dados XML é uma tarefa comum para apps conectados a uma rede. Esta lição explica como analisar documentos XML e usar seus dados.

Para saber mais sobre como criar conteúdo baseado na Web no seu app para Android, consulte Apps da Web.

Escolher um analisador

Recomendamos o XmlPullParser, que é uma forma eficiente e sustentável de analisar XML no Android. Historicamente, o Android teve duas implementações dessa interface:

As duas opções são adequadas. O exemplo desta seção usa ExpatPullParser via Xml.newPullParser().

Analisar o feed

A primeira etapa na análise de um feed é decidir quais os campos do seu interesse. O analisador extrai dados desses campos e ignora o restante.

Veja um trecho do feed que está sendo analisado no app de amostra. Cada postagem em StackOverflow.com aparece no feed como um entry que contém várias tags aninhadas:

<?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>

O app de amostra extrai dados para a tag entry e as tags aninhadas correspondentes: title, link e summary.

Instanciar o analisador

A próxima etapa é instanciar um analisador e iniciar o processo de análise. No snippet a seguir, um analisador é inicializado para não processar namespaces e usar o InputStream fornecido como entrada. Ele inicia o processo de análise com uma chamada para nextTag() e invoca o método readFeed(), que extrai e processa os dados em que o app está interessado:

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

Ler o feed

O método readFeed() realiza o trabalho efetivo de processamento do feed. Ele procura elementos marcados como "entrada" como ponto de partida para processar o feed recursivamente. Se uma tag não for entry, ela será ignorada. Depois de processar recursivamente o feed todo, readFeed() retornará uma List contendo as entradas (incluindo membros de dados aninhados) extraídas do feed. Depois disso, essa List é retornada pelo analisador.

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

Analisar XML

As etapas para analisar um feed XML são as seguintes:

  1. Conforme descrito em Analisar o feed, identifique as tags que você quer incluir no seu app. Este exemplo extrai dados para a tag entry e as tags aninhadas title, link e summary.
  2. Crie os seguintes métodos:

    • Um método de "leitura" para cada tag do seu interesse. Por exemplo, readEntry(), readTitle() e assim por diante. O analisador lê tags do fluxo de entrada. Quando encontra uma tag com o nome entry, title, link ou summary, o analisador chama o método apropriado para ela. Caso contrário, a tag é ignorada.
    • Métodos para extrair dados para cada tipo diferente de tag e avançar o analisador para a tag seguinte. Por exemplo:
      • Para tags title e summary, o analisador chama readText(). Esse método extrai dados para as tags chamando parser.getText().
      • Para a tag link, o analisador extrai dados dos links determinando, primeiro, se o link é de interesse. Em seguida, ele usa parser.getAttributeValue() para extrair o valor do link.
      • Para a tag entry, o analisar chama readEntry(). Esse método analisa as tags aninhadas de entrada e retorna um objeto Entry com os membros de dados title, link e summary.
    • Um método auxiliar skip() recursivo. Para ver mais informações sobre esse tópico, consulte Ignorar as tags que não importam.

O snippet a seguir mostra como o analisador processa entradas, títulos, links e resumos.

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;
    }
      ...
    }
    

Ignorar as tags que não importam

Uma das etapas na análise de XML descrita acima é como o analisador ignora as tags que não são de interesse. Veja o método skip() do analisador:

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

Veja como funciona:

  • Lança uma exceção se o evento atual não for uma START_TAG.
  • Consome a START_TAG e todos os eventos, inclusive a END_TAG correspondente.
  • Para garantir que ele pare na END_TAG correta e não na primeira tag encontrada após a START_TAG original, ele acompanha a profundidade do aninhamento.

Assim, se o elemento atual tiver elementos aninhados, o valor de depth não será “0” até que o analisador tenha consumido todos os eventos entre a START_TAG original e a END_TAG correspondente. Por exemplo, considere como o analisador ignora o elemento <author>, que tem dois elementos aninhados, <name> e <uri>:

  • Na primeira vez no loop while, a próxima tag encontrada pelo analisador depois de <author> é START_TAG para <name>. O valor da depth aumenta para 2.
  • Na segunda vez por meio do loop while, a próxima tag que o analisador encontra é a END_TAG </name>. O valor da depth diminui para 1.
  • Na terceira vez por meio do loop while, a próxima a tag que o analisador encontra é a START_TAG <uri>. O valor de depth aumenta para 2.
  • Na quarta vez por meio do loop while, a próxima tag que o analisador encontra é a END_TAG </uri>. O valor da depth diminui para 1.
  • Na quinta e última vez por meio do loop while, a próxima tag que o analisador encontra é a END_TAG </author>. O valor de depth é decrementado para “0”, indicando que o elemento <author> foi pulado.

Consumir dados XML

O app de amostra busca e analisa o feed XML em uma AsyncTask. Isso retira o processamento da linha de execução principal da IU. Quando o processamento é concluído, o app atualiza a IU na atividade principal (NetworkActivity).

No trecho mostrado abaixo, o método loadPage() faz o seguinte:

  • Inicializa uma string variável com o URL para o feed XML.
  • Se as configurações do usuário e a conexão de rede permitirem, ele invoca new DownloadXmlTask().execute(url). Isso iniciará um novo objeto DownloadXmlTask (subclasse AsyncTask) e executará o método execute(), que faz o download, analisa o feed e retorna um resultado de string para ser exibido na IU.

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
        }

        ...

        // Uses AsyncTask subclass to download the XML feed from stackoverflow.com.
        // Uses AsyncTask to download the XML feed from stackoverflow.com.
        fun loadPage() {

            if (sPref.equals(ANY) && (wifiConnected || mobileConnected)) {
                DownloadXmlTask().execute(SO_URL)
            } else if (sPref.equals(WIFI) && wifiConnected) {
                DownloadXmlTask().execute(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;

        ...

        // Uses AsyncTask to download the XML feed from stackoverflow.com.
        public void loadPage() {

            if((sPref.equals(ANY)) && (wifiConnected || mobileConnected)) {
                new DownloadXmlTask().execute(URL);
            }
            else if ((sPref.equals(WIFI)) && (wifiConnected)) {
                new DownloadXmlTask().execute(URL);
            } else {
                // show error
            }
        }
    

A subclasse AsyncTask mostrada abaixo, DownloadXmlTask, implementa os métodos de AsyncTask a seguir:

  • doInBackground() executa o método loadXmlFromNetwork(). Ele passa o URL do feed como parâmetro. O método loadXmlFromNetwork() busca e processa o feed. Quando termina, ele retorna uma string de resultados.
  • onPostExecute() toma a string retornada e exibe na IU.

Kotlin

    // Implementation of AsyncTask used to download XML feed from stackoverflow.com.
    private inner class DownloadXmlTask : AsyncTask<String, Void, String>() {
        override fun doInBackground(vararg urls: String): String {
            return try {
                loadXmlFromNetwork(urls[0])
            } catch (e: IOException) {
                resources.getString(R.string.connection_error)
            } catch (e: XmlPullParserException) {
                resources.getString(R.string.xml_error)
            }
        }

        override fun onPostExecute(result: String) {
            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 AsyncTask used to download XML feed from stackoverflow.com.
    private class DownloadXmlTask extends AsyncTask<String, Void, String> {
        @Override
        protected String doInBackground(String... urls) {
            try {
                return loadXmlFromNetwork(urls[0]);
            } catch (IOException e) {
                return getResources().getString(R.string.connection_error);
            } catch (XmlPullParserException e) {
                return getResources().getString(R.string.xml_error);
            }
        }

        @Override
        protected void onPostExecute(String result) {
            setContentView(R.layout.main);
            // Displays the HTML string in the UI via a WebView
            WebView myWebView = (WebView) findViewById(R.id.webview);
            myWebView.loadData(result, "text/html", null);
        }
    }
    

Abaixo, você pode ver o método loadXmlFromNetwork() que é invocado de DownloadXmlTask. Ele faz o seguinte:

  1. Instancia um StackOverflowXmlParser e cria variáveis para uma List de objetos Entry (entries), title, url e summary para manter os valores extraídos do feed XML desses campos.
  2. Ele chama downloadUrl(), que busca o feed e o retorna como um InputStream.
  3. Ele usa StackOverflowXmlParser para analisar o InputStream. StackOverflowXmlParser preenche uma List de entries com dados do feed.
  4. Ele processa as entries List e combina os dados do feed com a marcação HTML.
  5. Além disso, ele retorna uma string HTML que é exibida na IU principal da atividade pelo método AsyncTask onPostExecute().

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