Register now for Android Dev Summit 2019!

Mengurai data XML

Extensible Markup Language (XML) adalah serangkaian aturan untuk mengenkode dokumen dalam bentuk yang dapat dibaca mesin. XML adalah format populer untuk berbagi data di internet. Situs yang sering mengupdate kontennya, seperti situs berita atau blog, sering kali menyediakan feed XML sehingga program eksternal dapat mengikuti perubahan konten. Mengupload dan mengurai data XML adalah tugas umum untuk aplikasi yang terhubung ke jaringan. Pelajaran ini menjelaskan cara mengurai dokumen XML dan menggunakan datanya.

Untuk mempelajari lebih lanjut tentang membuat konten berbasis web di aplikasi Android Anda, lihat Aplikasi web.

Pilih parser

Kami merekomendasikan XmlPullParser, yang merupakan cara yang efisien dan dapat dipelihara untuk mengurai XML di Android. Secara historis Android telah memiliki dua implementasi antarmuka ini:

Pilihan mana pun baik-baik saja. Contoh di bagian ini menggunakan ExpatPullParser, melalui Xml.newPullParser().

Menganalisis feed

Langkah pertama dalam penguraian feed adalah menentukan kolom mana yang Anda minati. Parser mengekstrak data untuk kolom tersebut dan mengabaikan sisanya.

Berikut adalah nukilan dari feed yang diuraikan dalam aplikasi sampel. Setiap postingan ke StackOverflow.com muncul di feed sebagai tag entry yang berisi beberapa tag bertumpuk:

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

Aplikasi sampel mengekstrak data untuk tag entry dan tag bertumpuknya title, link, dan summary.

Membuat instance parser

Langkah selanjutnya adalah membuat instance parser dan memulai proses penguraian. Dalam cuplikan ini, parser diinisialisasi untuk tidak memproses ruang nama, dan menggunakan InputStream yang disediakan sebagai inputnya. Hal ini memulai proses penguraian dengan panggilan ke nextTag() dan memanggil metode readFeed(), yang mengekstrak dan memproses data yang diminati aplikasi:

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

Membaca feed

Metode readFeed() melakukan pekerjaan aktual dalam memproses feed. Tampaknya elemen yang ditandai "entri" sebagai titik awal untuk memproses feed secara rekursif. Jika tag bukan tag entry, tag akan melewatinya. Setelah seluruh feed diproses secara rekursif, readFeed() mengembalikan List yang berisi entri (termasuk anggota data bertumpuk) yang diekstraksi dari feed. List Ini kemudian akan dikembalikan oleh 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;
}

Mengurai XML

Langkah-langkah untuk mengurai umpan XML adalah sebagai berikut:

  1. Seperti yang dijelaskan dalam Menganalisis feed, identifikasi tag yang ingin Anda sertakan dalam aplikasi Anda. Contoh ini mengekstrak data untuk tag entry dan tag bertumpuknya title, link, dan summary.
  2. Buat metode berikut:

    • Metode "baca" untuk masing-masing tag yang Anda minati. Misalnya, readEntry(), readTitle(), dan sebagainya. Parser membaca tag dari aliran input. Jika menemukan tag bernama entry, title, link atau summary, parser akan memanggil yang sesuai metode untuk tag tersebut. Jika tidak, parser akan melompati tag.
    • Metode untuk mengekstrak data untuk setiap jenis tag yang berbeda dan untuk mempercepat parser ke tag berikutnya. Sebagai contoh:
      • Untuk tag title dan summary, parser akan memanggil readText(). Metode ini mengekstrak data untuk tag ini dengan memanggil parser.getText().
      • Untuk tag link, parser akan mengekstrak data untuk link dengan menentukan terlebih dahulu apakah link tersebut jenis yang diminati. Kemudian akan menggunakan parser.getAttributeValue() untuk mengekstrak nilai link.
      • Untuk tag entry, parser memanggil readEntry(). Metode ini mengurai tag bertumpuk entri dan mengembalikan objek Entry dengan anggota data title, link, dan summary.
    • Metode skip() pembantu yang bersifat rekursif. Untuk diskusi lebih lanjut tentang topik ini, lihat Lewati Tag yang Anda Tidak Peduli.

Cuplikan ini menunjukkan cara parser mengurai entri, judul, link, dan ringkasan.

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

Lewati tag yang Anda tidak peduli

Salah satu langkah dalam penguraian XML yang dijelaskan di atas adalah agar parser melewatkan tag yang tidak diinginkannya. Berikut adalah metode skip() 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;
        }
    }
 }

Begini cara kerjanya:

  • Ini memunculkan pengecualian jika peristiwa saat ini bukan START_TAG.
  • Hal ini menggunakan START_TAG, dan semua peristiwa hingga dan termasuk END_TAG yang cocok.
  • Untuk memastikan bahwa hal tersebut berhenti pada END_TAG yang benar dan bukan pada tag pertama yang ditemuinya setelah START_TAG yang asli, maka ini akan melacak kedalaman tumpukan.

Jadi jika elemen saat ini memiliki elemen bertumpuk, nilai depth tidak akan menjadi 0 sampai parser menggunakan semua peristiwa antara START_TAG yang asli dan END_TAG yang cocok. Misalnya, perhatikan cara parser melewati elemen <author>, yang memiliki 2 elemen bertumpuk, <name> dan <uri>:

  • Pertama kali melalui loop while, tag berikutnya yang ditemukan parser setelah <author> adalah START_TAG untuk <name>. Nilai untuk depth bertambah menjadi 2.
  • Selanjutnya melalui loop while, tag berikutnya yang ditemukan parser adalah END_TAG </name>. Nilai untuk depth berkurang menjadi 1.
  • Ketiga kalinya melalui loop while, tag berikutnya yang ditemukan parser adalah START_TAG <uri>. Nilai untuk depth bertambah menjadi 2.
  • Kali keempat melalui loop while, tag berikutnya yang ditemukan parser adalah END_TAG </uri>. Nilai untuk depth berkurang menjadi 1.
  • Kali kelima dan waktu terakhir melalui loop while, tag berikutnya yang ditemukan parser adalah END_TAG </author>. Nilai untuk depth berkurang menjadi 0, yang menunjukkan bahwa elemen <author> telah berhasil dilewati.

Menggunakan data XML

Contoh aplikasi mengambil dan mengurai feed XML dalam AsyncTask. Hal ini menghilangkan pemrosesan dari UI thread utama. Saat pemrosesan selesai, aplikasi mengupdate UI dalam aktivitas utamanya (NetworkActivity).

Dalam nukilan yang ditampilkan di bawah ini, metode loadPage() melakukan hal berikut:

  • Menginisialisasi variabel string dengan URL untuk feed XML.
  • Jika setelan pengguna dan koneksi jaringan memungkinkan, aktifkan new DownloadXmlTask().execute(url). Hal ini membuat instance objek DownloadXmlTask baru (AsyncTask subkelas) dan menjalankan metode execute(), yang mendownload dan mengurai feed dan mengembalikan hasil string yang akan ditampilkan di UI.

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

Subkelas AsyncTask yang ditampilkan di bawah ini, DownloadXmlTask, mengimplementasikan metode AsyncTask berikut:

  • doInBackground() menjalankan metode loadXmlFromNetwork(). Metode ini melewati URL feed sebagai parameter. Metode loadXmlFromNetwork() mengambil dan memproses feed. Jika selesai, maka metode tersebut akan mengembalikan string hasil.
  • onPostExecute() mengambil string yang dikembalikan dan menampilkannya di UI.

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

Di bawah ini adalah metode loadXmlFromNetwork() yang dipanggil dari DownloadXmlTask. Metode ini melakukan hal berikut:

  1. Membuat instance StackOverflowXmlParser. Juga akan membuat variabel untuk objek List dari Entry (entries), dan title, url, dan summary, untuk menyimpan nilai yang diekstrak dari feed XML untuk kolom-kolom tersebut.
  2. Memanggil downloadUrl(), yang mengambil feed dan mengembalikannya sebagai InputStream.
  3. Menggunakan StackOverflowXmlParser untuk mengurai InputStream. StackOverflowXmlParser mengisi List dari entries dengan data dari feed.
  4. Memproses entries List, dan menggabungkan data feed dengan markup HTML.
  5. Mengembalikan string HTML yang ditampilkan di UI aktivitas utama dengan metode 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();
}