Analizuj dane XML

XML (Extensible Markup Language) to zestaw reguł kodowania dokumentów w czytelny dla komputera. XML to popularny format udostępniania danych w internecie.

Witryny, które często aktualizują treść, takie jak strony z wiadomościami lub blogi, często udostępniają kanał XML, dzięki czemu programy zewnętrzne mogą być na bieżąco z treścią. zmian. Przesyłanie i analizowanie danych XML jest częstym zadaniem w przypadku obiektów mających połączenie z siecią aplikacji. W tym temacie wyjaśnimy, jak analizować dokumenty XML i korzystać z ich danych.

Aby dowiedzieć się więcej o tworzeniu treści internetowych w aplikacji na Androida, zobacz Treści internetowe.

Wybierz parser

Zalecamy XmlPullParser, czyli wydajną, i prosty sposób analizowania formatu XML na Androidzie. Android ma dwa implementacji tego interfejsu:

Bez względu na to, którą opcję wybierzesz. przykład w tej sekcji korzysta z identyfikatorów ExpatPullParser i Xml.newPullParser()

Analizowanie pliku danych

Pierwszym krokiem podczas analizy pliku danych jest określenie, które pola Cię interesują. Parser wyodrębnia dane z tych pól i ignoruje pozostałe.

Zobacz ten fragment przeanalizowanego kanału w przykładowej aplikacji. Każdy post na StackOverflow.com, pojawi się w kanału jako tagu entry, który zawiera kilka zagnieżdżonych tagów:

<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:creativeCommons="http://backend.userland.com/creativeCommonsRssModule" ...">
<title type="text">newest questions tagged android - Stack Overflow</title>
...
    <entry>
    ...
    </entry>
    <entry>
        <id>http://stackoverflow.com/q/9439999</id>
        <re:rank scheme="http://stackoverflow.com">0</re:rank>
        <title type="text">Where is my data file?</title>
        <category scheme="http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest/tags" term="android"/>
        <category scheme="http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest/tags" term="file"/>
        <author>
            <name>cliff2310</name>
            <uri>http://stackoverflow.com/users/1128925</uri>
        </author>
        <link rel="alternate" href="http://stackoverflow.com/questions/9439999/where-is-my-data-file" />
        <published>2012-02-25T00:30:54Z</published>
        <updated>2012-02-25T00:30:54Z</updated>
        <summary type="html">
            <p>I have an Application that requires a data file...</p>

        </summary>
    </entry>
    <entry>
    ...
    </entry>
...
</feed>

Przykładowa aplikacja wyodrębnia dane tagu entry i jego zagnieżdżonych tagów title, link i summary.

Utwórz instancję parsera

Następnym krokiem analizy pliku danych jest utworzyć wystąpienie parsera i rozpocząć proces analizy. Ten fragment inicjuje parser, aby nie przetwarzał przestrzeni nazw i używał podanego InputStream jako danych wejściowych. Rozpoczyna proces analizy od wywołania funkcji nextTag() i wywołuje metodę Metoda readFeed(), która wyodrębnia i przetwarza dane aplikacji interesują mnie:

Kotlin

// We don't use namespaces.
private val ns: String? = null

class StackOverflowXmlParser {

    @Throws(XmlPullParserException::class, IOException::class)
    fun parse(inputStream: InputStream): List<*> {
        inputStream.use { inputStream ->
            val parser: XmlPullParser = Xml.newPullParser()
            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false)
            parser.setInput(inputStream, null)
            parser.nextTag()
            return readFeed(parser)
        }
    }
 ...
}

Java

public class StackOverflowXmlParser {
    // We don't use namespaces.
    private static final String ns = null;

    public List parse(InputStream in) throws XmlPullParserException, IOException {
        try {
            XmlPullParser parser = Xml.newPullParser();
            parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false);
            parser.setInput(in, null);
            parser.nextTag();
            return readFeed(parser);
        } finally {
            in.close();
        }
    }
 ...
}

Czytanie treści

Metoda readFeed() faktycznie przetwarza dane kanału. Wyszukuje elementy oznaczone tagiem „entry” jako punktu wyjścia dla rekurencji podczas przetwarzania pliku danych. Jeśli tag nie jest tagiem entry, pomija go. Gdy już wszystko plik danych jest przetwarzany rekurencyjnie, readFeed() zwraca błąd List zawierający wpisy (wraz z zagnieżdżonymi elementami danych), wyodrębnione z pliku danych. Wartość List jest następnie zwracana przez funkcję parsera.

Kotlin

@Throws(XmlPullParserException::class, IOException::class)
private fun readFeed(parser: XmlPullParser): List<Entry> {
    val entries = mutableListOf<Entry>()

    parser.require(XmlPullParser.START_TAG, ns, "feed")
    while (parser.next() != XmlPullParser.END_TAG) {
        if (parser.eventType != XmlPullParser.START_TAG) {
            continue
        }
        // Starts by looking for the entry tag.
        if (parser.name == "entry") {
            entries.add(readEntry(parser))
        } else {
            skip(parser)
        }
    }
    return entries
}

Java

private List readFeed(XmlPullParser parser) throws XmlPullParserException, IOException {
    List entries = new ArrayList();

    parser.require(XmlPullParser.START_TAG, ns, "feed");
    while (parser.next() != XmlPullParser.END_TAG) {
        if (parser.getEventType() != XmlPullParser.START_TAG) {
            continue;
        }
        String name = parser.getName();
        // Starts by looking for the entry tag.
        if (name.equals("entry")) {
            entries.add(readEntry(parser));
        } else {
            skip(parser);
        }
    }
    return entries;
}

Analizuj XML

Aby wykonać analizę pliku danych XML:

  1. Zgodnie z opisem w sekcji Analizowanie pliku danych określ tagi, które chcesz umieścić w aplikacji. Ten przykład wyodrębniania danych dla tagu entry i jego zagnieżdżonych tagów: title, link i summary.
  2. Utwórz te metody:

    • Czytaj dla każdego tagu, który chcesz uwzględnić, np. readEntry() i readTitle(). Parser odczytuje ze strumienia wejściowego. Gdy napotka tag o nazwie, entry, title, link lub summary, wywołuje odpowiednią metodę dla tego tagu. W przeciwnym razie tag zostanie pominięty.
    • Metody wyodrębniania danych dla poszczególnych typów tagów i przenoszenia z następnego tagu. W tym przykładzie odpowiednie metody są następujące:
      • W przypadku tagów title i summary parser wywołuje readText() Ta metoda wyodrębnia dane dla tych tagów przez wywołanie parser.getText()
      • W przypadku tagu link parser wyodrębnia dane dla linków najpierw określająca, czy dany link produktów i usług, którymi jest zainteresowany. Następnie używa funkcji parser.getAttributeValue() do: wyodrębnienia wartości linku.
      • W przypadku tagu entry parser wywołuje readEntry(). Ta metoda analizuje zagnieżdżone tagi wpisu i zwraca wartość Entry obiekt z elementami danych title, link i summary
    • Metoda pomocnicza skip(), która jest rekurencyjna. Więcej dyskusji na ten temat znajdziesz w artykule Pomijanie tagów, które Cię nie interesują.

Ten fragment pokazuje, jak parser analizuje wpisy, tytuły, linki i podsumowania.

Kotlin

data class Entry(val title: String?, val summary: String?, val link: String?)

// Parses the contents of an entry. If it encounters a title, summary, or link tag, hands them off
// to their respective "read" methods for processing. Otherwise, skips the tag.
@Throws(XmlPullParserException::class, IOException::class)
private fun readEntry(parser: XmlPullParser): Entry {
    parser.require(XmlPullParser.START_TAG, ns, "entry")
    var title: String? = null
    var summary: String? = null
    var link: String? = null
    while (parser.next() != XmlPullParser.END_TAG) {
        if (parser.eventType != XmlPullParser.START_TAG) {
            continue
        }
        when (parser.name) {
            "title" -> title = readTitle(parser)
            "summary" -> summary = readSummary(parser)
            "link" -> link = readLink(parser)
            else -> skip(parser)
        }
    }
    return Entry(title, summary, link)
}

// Processes title tags in the feed.
@Throws(IOException::class, XmlPullParserException::class)
private fun readTitle(parser: XmlPullParser): String {
    parser.require(XmlPullParser.START_TAG, ns, "title")
    val title = readText(parser)
    parser.require(XmlPullParser.END_TAG, ns, "title")
    return title
}

// Processes link tags in the feed.
@Throws(IOException::class, XmlPullParserException::class)
private fun readLink(parser: XmlPullParser): String {
    var link = ""
    parser.require(XmlPullParser.START_TAG, ns, "link")
    val tag = parser.name
    val relType = parser.getAttributeValue(null, "rel")
    if (tag == "link") {
        if (relType == "alternate") {
            link = parser.getAttributeValue(null, "href")
            parser.nextTag()
        }
    }
    parser.require(XmlPullParser.END_TAG, ns, "link")
    return link
}

// Processes summary tags in the feed.
@Throws(IOException::class, XmlPullParserException::class)
private fun readSummary(parser: XmlPullParser): String {
    parser.require(XmlPullParser.START_TAG, ns, "summary")
    val summary = readText(parser)
    parser.require(XmlPullParser.END_TAG, ns, "summary")
    return summary
}

// For the tags title and summary, extracts their text values.
@Throws(IOException::class, XmlPullParserException::class)
private fun readText(parser: XmlPullParser): String {
    var result = ""
    if (parser.next() == XmlPullParser.TEXT) {
        result = parser.text
        parser.nextTag()
    }
    return result
}
...

Java

public static class Entry {
    public final String title;
    public final String link;
    public final String summary;

    private Entry(String title, String summary, String link) {
        this.title = title;
        this.summary = summary;
        this.link = link;
    }
}

// Parses the contents of an entry. If it encounters a title, summary, or link tag, hands them off
// to their respective "read" methods for processing. Otherwise, skips the tag.
private Entry readEntry(XmlPullParser parser) throws XmlPullParserException, IOException {
    parser.require(XmlPullParser.START_TAG, ns, "entry");
    String title = null;
    String summary = null;
    String link = null;
    while (parser.next() != XmlPullParser.END_TAG) {
        if (parser.getEventType() != XmlPullParser.START_TAG) {
            continue;
        }
        String name = parser.getName();
        if (name.equals("title")) {
            title = readTitle(parser);
        } else if (name.equals("summary")) {
            summary = readSummary(parser);
        } else if (name.equals("link")) {
            link = readLink(parser);
        } else {
            skip(parser);
        }
    }
    return new Entry(title, summary, link);
}

// Processes title tags in the feed.
private String readTitle(XmlPullParser parser) throws IOException, XmlPullParserException {
    parser.require(XmlPullParser.START_TAG, ns, "title");
    String title = readText(parser);
    parser.require(XmlPullParser.END_TAG, ns, "title");
    return title;
}

// Processes link tags in the feed.
private String readLink(XmlPullParser parser) throws IOException, XmlPullParserException {
    String link = "";
    parser.require(XmlPullParser.START_TAG, ns, "link");
    String tag = parser.getName();
    String relType = parser.getAttributeValue(null, "rel");
    if (tag.equals("link")) {
        if (relType.equals("alternate")){
            link = parser.getAttributeValue(null, "href");
            parser.nextTag();
        }
    }
    parser.require(XmlPullParser.END_TAG, ns, "link");
    return link;
}

// Processes summary tags in the feed.
private String readSummary(XmlPullParser parser) throws IOException, XmlPullParserException {
    parser.require(XmlPullParser.START_TAG, ns, "summary");
    String summary = readText(parser);
    parser.require(XmlPullParser.END_TAG, ns, "summary");
    return summary;
}

// For the tags title and summary, extracts their text values.
private String readText(XmlPullParser parser) throws IOException, XmlPullParserException {
    String result = "";
    if (parser.next() == XmlPullParser.TEXT) {
        result = parser.getText();
        parser.nextTag();
    }
    return result;
}
  ...
}

Pomiń tagi, które Cię nie interesują

Parser musi pominąć tagi, które go nie interesują. Oto metoda skip() parsera:

Kotlin

@Throws(XmlPullParserException::class, IOException::class)
private fun skip(parser: XmlPullParser) {
    if (parser.eventType != XmlPullParser.START_TAG) {
        throw IllegalStateException()
    }
    var depth = 1
    while (depth != 0) {
        when (parser.next()) {
            XmlPullParser.END_TAG -> depth--
            XmlPullParser.START_TAG -> depth++
        }
    }
}

Java

private void skip(XmlPullParser parser) throws XmlPullParserException, IOException {
    if (parser.getEventType() != XmlPullParser.START_TAG) {
        throw new IllegalStateException();
    }
    int depth = 1;
    while (depth != 0) {
        switch (parser.next()) {
        case XmlPullParser.END_TAG:
            depth--;
            break;
        case XmlPullParser.START_TAG:
            depth++;
            break;
        }
    }
 }

Proces przebiega następująco:

  • Jeśli bieżące zdarzenie nie jest START_TAG
  • Wykorzystuje ono START_TAG i wszystkie zdarzenia do poziomu włącznie pasujący element END_TAG.
  • Śledzi głębokość zagnieżdżenia, aby mieć pewność, że kończy się na właściwej wartości END_TAG, a nie na pierwszy napotkany tag po pierwotnym tagu START_TAG.

Jeśli więc bieżący element ma zagnieżdżone elementy, wartość depth nie będzie mieć wartości 0, dopóki parser nie przetworzy wszystkich zdarzeń między oryginalny START_TAG i pasujący do niego END_TAG. Dla: na przykład parser pomija element <author>, który ma 2 zagnieżdżone elementy: <name> oraz <uri>:

  • Za pierwszym razem za pomocą pętli while następny tag parsera spotkań po <author> to START_TAG dla <name> Wartość w polu depth zwiększa się do 2.
  • Po raz drugi w pętli while, kolejnym tagiem parser spotkań to END_TAG </name>. Wartość dla depth maleje do 1.
  • Po raz trzeci w pętli while następny tag parsera spotkań to START_TAG <uri>. Wartość co depth przyrostu do 2.
  • Czwarty raz w pętli while, kolejnym tagiem parser spotkań to END_TAG </uri>. Wartość pola depth zmniejsza się do 1.
  • Piąty raz i ostatni raz w pętli while. tagiem napotkanym przez parser to END_TAG </author> Wartość parametru depth zmniejsza się do 0, co oznacza, że element <author> został poprawnie pominięto.

Pobieraj dane XML

Przykładowa aplikacja asynchronicznie pobiera i analizuje kanał XML. Spowoduje to usunięcie przetwarzania z głównego wątku UI. Kiedy gdy przetwarzanie zostanie ukończone, aplikacja aktualizuje interfejs w swojej głównej aktywności, NetworkActivity

W tym fragmencie metoda loadPage() wykonuje te działania:

  • Inicjuje zmienną ciągu znaków z adresem URL pliku danych XML.
  • Wywołuje metodę downloadXml(url), jeśli ustawienia użytkownika i sieć tylko połączenie. Ta metoda pobiera i analizuje plik danych oraz zwraca wynik w postaci ciągu znaków widoczne w interfejsie.

Kotlin

class NetworkActivity : Activity() {

    companion object {

        const val WIFI = "Wi-Fi"
        const val ANY = "Any"
        const val SO_URL = "http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest"
        // Whether there is a Wi-Fi connection.
        private var wifiConnected = false
        // Whether there is a mobile connection.
        private var mobileConnected = false

        // Whether the display should be refreshed.
        var refreshDisplay = true
        // The user's current network preference setting.
        var sPref: String? = null
    }
    ...
    // Asynchronously downloads the XML feed from stackoverflow.com.
    fun loadPage() {

        if (sPref.equals(ANY) && (wifiConnected || mobileConnected)) {
            downloadXml(SO_URL)
        } else if (sPref.equals(WIFI) && wifiConnected) {
            downloadXml(SO_URL)
        } else {
            // Show error.
        }
    }
    ...
}

Java

public class NetworkActivity extends Activity {
    public static final String WIFI = "Wi-Fi";
    public static final String ANY = "Any";
    private static final String URL = "http://stackoverflow.com/feeds/tag?tagnames=android&sort=newest";

    // Whether there is a Wi-Fi connection.
    private static boolean wifiConnected = false;
    // Whether there is a mobile connection.
    private static boolean mobileConnected = false;
    // Whether the display should be refreshed.
    public static boolean refreshDisplay = true;
    public static String sPref = null;
    ...
    // Asynchronously downloads the XML feed from stackoverflow.com.
    public void loadPage() {

        if((sPref.equals(ANY)) && (wifiConnected || mobileConnected)) {
            downloadXml(URL);
        }
        else if ((sPref.equals(WIFI)) && (wifiConnected)) {
            downloadXml(URL);
        } else {
            // Show error.
        }
    }

Metoda downloadXml wywołuje w Kotlin następujące metody:

  • lifecycleScope.launch(Dispatchers.IO), który wykorzystuje współrzędne Kotlin do uruchomić metodę loadXmlFromNetwork() w wątku wejścia/wyjścia. Przekazuje ona adres URL kanału jako . Metoda loadXmlFromNetwork() pobiera i przetwarza dane kanału. Po zakończeniu przesyła z powrotem ciąg znaków z wynikami.
  • withContext(Dispatchers.Main), który używa współrzędnych Kotlina do powrotu do wątku głównego, pobiera makro zwraca ciąg znaków i wyświetla go w interfejsie.

W języku Java proces wygląda tak:

  • Wykonanie polecenia Executor metody loadXmlFromNetwork() w wątku w tle. Przekazuje ona adres URL kanału jako . Metoda loadXmlFromNetwork() pobiera i przetwarza dane kanału. Po zakończeniu przesyła z powrotem ciąg znaków z wynikami.
  • Handler dzwoni pod numer post powrót do wątku głównego, zwraca ciąg znaków i wyświetla go w interfejsie.

Kotlin

// Implementation of Kotlin coroutines used to download XML feed from stackoverflow.com.
private fun downloadXml(vararg urls: String) {
    var result: String? = null
    lifecycleScope.launch(Dispatchers.IO) {
        result = try {
            loadXmlFromNetwork(urls[0])
        } catch (e: IOException) {
            resources.getString(R.string.connection_error)
        } catch (e: XmlPullParserException) {
            resources.getString(R.string.xml_error)
        }
        withContext(Dispatchers.Main) {
            setContentView(R.layout.main)
            // Displays the HTML string in the UI via a WebView.
            findViewById<WebView>(R.id.webview)?.apply {
                loadData(result?: "", "text/html", null)
            }
        }
    }
}

Java

// Implementation of Executor and Handler used to download XML feed asynchronously from stackoverflow.com.
private void downloadXml(String... urls) {
    ExecutorService executor = Executors.newSingleThreadExecutor();
    Handler handler = new Handler(Looper.getMainLooper());
    executor.execute(() -> {
        String result;
            try {
                result = loadXmlFromNetwork(urls[0]);
            } catch (IOException e) {
                result = getResources().getString(R.string.connection_error);
            } catch (XmlPullParserException e) {
                result = getResources().getString(R.string.xml_error);
            }
        String finalResult = result;
        handler.post(() -> {
            setContentView(R.layout.main);
            // Displays the HTML string in the UI via a WebView.
            WebView myWebView = (WebView) findViewById(R.id.webview);
            myWebView.loadData(finalResult, "text/html", null);
        });
    });
}

Metoda loadXmlFromNetwork() wywoływana z downloadXml jest widoczne w następnym fragmencie. Wykonuje te działania:

  1. Tworzy instancję StackOverflowXmlParser. Tworzy też zmienne dla: List z Entry obiektów (entries) oraz dla title, url i summary do przechowywania wartości wyodrębnionych z pliku danych XML dla tych pól.
  2. Wywołuje funkcję downloadUrl(), która pobiera plik danych i zwraca go jako InputStream.
  3. Wykorzystuje parametr StackOverflowXmlParser do analizowania parametru InputStream. StackOverflowXmlParser uzupełnia List z entries z danymi z pliku danych.
  4. Przetwarza: entries List i łączy dane z kanału ze znacznikami HTML.
  5. Zwraca ciąg HTML wyświetlany w głównej aktywności Interfejs.

Kotlin

// Uploads XML from stackoverflow.com, parses it, and combines it with
// HTML markup. Returns HTML string.
@Throws(XmlPullParserException::class, IOException::class)
private fun loadXmlFromNetwork(urlString: String): String {
    // Checks whether the user set the preference to include summary text.
    val pref: Boolean = PreferenceManager.getDefaultSharedPreferences(this)?.run {
        getBoolean("summaryPref", false)
    } ?: false

    val entries: List<Entry> = downloadUrl(urlString)?.use { stream ->
        // Instantiates the parser.
        StackOverflowXmlParser().parse(stream)
    } ?: emptyList()

    return StringBuilder().apply {
        append("<h3>${resources.getString(R.string.page_title)}</h3>")
        append("<em>${resources.getString(R.string.updated)} ")
        append("${formatter.format(rightNow.time)}</em>")
        // StackOverflowXmlParser returns a List (called "entries") of Entry objects.
        // Each Entry object represents a single post in the XML feed.
        // This section processes the entries list to combine each entry with HTML markup.
        // Each entry is displayed in the UI as a link that optionally includes
        // a text summary.
        entries.forEach { entry ->
            append("<p><a href='")
            append(entry.link)
            append("'>" + entry.title + "</a></p>")
            // If the user set the preference to include summary text,
            // adds it to the display.
            if (pref) {
                append(entry.summary)
            }
        }
    }.toString()
}

// Given a string representation of a URL, sets up a connection and gets
// an input stream.
@Throws(IOException::class)
private fun downloadUrl(urlString: String): InputStream? {
    val url = URL(urlString)
    return (url.openConnection() as? HttpURLConnection)?.run {
        readTimeout = 10000
        connectTimeout = 15000
        requestMethod = "GET"
        doInput = true
        // Starts the query.
        connect()
        inputStream
    }
}

Java

// Uploads XML from stackoverflow.com, parses it, and combines it with
// HTML markup. Returns HTML string.
private String loadXmlFromNetwork(String urlString) throws XmlPullParserException, IOException {
    InputStream stream = null;
    // Instantiates the parser.
    StackOverflowXmlParser stackOverflowXmlParser = new StackOverflowXmlParser();
    List<Entry> entries = null;
    String title = null;
    String url = null;
    String summary = null;
    Calendar rightNow = Calendar.getInstance();
    DateFormat formatter = new SimpleDateFormat("MMM dd h:mmaa");

    // Checks whether the user set the preference to include summary text.
    SharedPreferences sharedPrefs = PreferenceManager.getDefaultSharedPreferences(this);
    boolean pref = sharedPrefs.getBoolean("summaryPref", false);

    StringBuilder htmlString = new StringBuilder();
    htmlString.append("<h3>" + getResources().getString(R.string.page_title) + "</h3>");
    htmlString.append("<em>" + getResources().getString(R.string.updated) + " " +
            formatter.format(rightNow.getTime()) + "</em>");

    try {
        stream = downloadUrl(urlString);
        entries = stackOverflowXmlParser.parse(stream);
    // Makes sure that the InputStream is closed after the app is
    // finished using it.
    } finally {
        if (stream != null) {
            stream.close();
        }
     }

    // StackOverflowXmlParser returns a List (called "entries") of Entry objects.
    // Each Entry object represents a single post in the XML feed.
    // This section processes the entries list to combine each entry with HTML markup.
    // Each entry is displayed in the UI as a link that optionally includes
    // a text summary.
    for (Entry entry : entries) {
        htmlString.append("<p><a href='");
        htmlString.append(entry.link);
        htmlString.append("'>" + entry.title + "</a></p>");
        // If the user set the preference to include summary text,
        // adds it to the display.
        if (pref) {
            htmlString.append(entry.summary);
        }
    }
    return htmlString.toString();
}

// Given a string representation of a URL, sets up a connection and gets
// an input stream.
private InputStream downloadUrl(String urlString) throws IOException {
    URL url = new URL(urlString);
    HttpURLConnection conn = (HttpURLConnection) url.openConnection();
    conn.setReadTimeout(10000 /* milliseconds */);
    conn.setConnectTimeout(15000 /* milliseconds */);
    conn.setRequestMethod("GET");
    conn.setDoInput(true);
    // Starts the query.
    conn.connect();
    return conn.getInputStream();
}