Extensible Markup Language (XML) adalah serangkaian aturan untuk mengenkode dokumen dalam bentuk yang bisa dibaca mesin. XML merupakan format populer untuk berbagi data di internet. Situs yang sering memperbarui kontennya, seperti situs berita atau blog, sering menyediakan feed XML sehingga program eksternal dapat terus mengikuti perubahan konten. Mengupload dan menguraikan data XML adalah tugas umum untuk aplikasi yang terhubung ke jaringan. Tutorial ini menjelaskan cara mengurai dokumen XML dan menggunakan datanya.
Untuk mempelajari lebih lanjut cara membuat konten berbasis web di aplikasi Android, lihat Aplikasi web.
Memilih parser
Sebaiknya gunakan XmlPullParser
, sebuah cara yang efisien dan
mudah dipelihara untuk mengurai XML di Android. Secara historis, Android telah memiliki dua
implementasi antarmuka ini:
KXmlParser
melaluiXmlPullParserFactory.newPullParser()
.ExpatPullParser
, melaluiXml.newPullParser()
.
Anda dapat memilih yang mana saja. Contoh
di bagian ini menggunakan ExpatPullParser
, melalui
Xml.newPullParser()
.
Menganalisis feed
Langkah pertama dalam mengurai feed adalah menentukan kolom yang Anda inginkan. Parser mengekstrak data untuk kolom tersebut dan mengabaikan lainnya.
Berikut adalah cuplikan dari feed yang diuraikan dalam aplikasi contoh. Setiap
postingan ke StackOverflow.com akan muncul di
feed sebagai tag entry
yang berisi beberapa tag bertingkat:
<?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 contoh akan
mengekstrak data untuk tag entry
serta title
, link
,
dan summary
tag bertingkat di dalamnya.
Membuat instance parser
Langkah berikutnya adalah
membuat instance parser dan memulai proses penguraian. Dalam cuplikan ini, parser
akan diinisialisasi agar tidak memproses namespace, dan menggunakan InputStream
yang disediakan sebagai inputnya. Ini akan memulai proses penguraian dengan panggilan ke
nextTag()
dan memanggil
metode readFeed()
, yang akan mengekstrak dan memproses data
yang diperlukan 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()
akan melakukan tugas sebenarnya dalam memproses
feed. Metode ini mencari elemen dengan tag "entri" sebagai titik awal untuk memproses
feed secara berulang. Jika tag bukan tag entry
, tag tersebut akan dilewati. Setelah seluruh
feed diproses secara berulang, readFeed()
akan menampilkan List
yang berisi entri (termasuk anggota data bertingkat)
yang diekstrak dari feed. List
ini kemudian akan ditampilkan 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 feed XML adalah sebagai berikut:
- Seperti yang dijelaskan dalam Menganalisis feed, identifikasikan tag yang ingin Anda sertakan dalam aplikasi Anda. Contoh ini
akan mengekstrak data untuk tag
entry
sertatitle
,link
, dansummary
tag bertingkat di dalamnya. - Buat metode berikut:
- Metode "baca" untuk setiap tag yang Anda perlukan. Misalnya,
readEntry()
,readTitle()
, dan sebagainya. Parser akan membaca tag dari aliran input. Jika menemukan tag bernamaentry
,title
,link
, atausummary
, parser akan memanggil metode yang sesuai untuk tag tersebut. Jika tidak, parser akan melewati tag. - Metode untuk mengekstrak data bagi setiap jenis tag yang berbeda dan untuk melanjutkan
parser ke tag berikutnya. Misalnya:
- Untuk tag
title
dansummary
, parser akan memanggilreadText()
. Metode ini akan mengekstrak data untuk tag ini dengan memanggilparser.getText()
. - Untuk tag
link
, parser akan mengekstrak data untuk link dengan menentukan terlebih dahulu apakah link tersebut jenis yang diperlukan. Parser kemudian akan menggunakanparser.getAttributeValue()
untuk mengekstrak nilai link. - Untuk tag
entry
, parser akan memanggilreadEntry()
. Metode ini mengurai tag bertingkat entri dan menampilkan objekEntry
dengantitle
,link
, dansummary
anggota data.
- Untuk tag
- Metode
skip()
helper yang berulang. Untuk pembahasan topik ini lebih lanjut, lihat Lewati Tag yang Tidak Anda Perlukan.
- Metode "baca" untuk setiap tag yang Anda perlukan. Misalnya,
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; } ... }
Melewati tag yang tidak Anda perlukan
Salah satu langkah dalam penguraian XML yang dijelaskan di atas bertujuan agar parser melewati tag yang tidak diperlukannya. Berikut 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:
- Parser akan menampilkan pengecualian jika peristiwa saat ini bukan
START_TAG
. START_TAG
akan digunakan, beserta semua peristiwa hingga dan termasukEND_TAG
yang cocok.- Untuk memastikan parser berhenti pada
END_TAG
yang tepat dan bukan pada tag pertama yang ditemuinya setelahSTART_TAG
asli, parser akan terus melacak kedalaman tingkat.
Dengan begitu, jika elemen saat ini memiliki elemen bertingkat, nilai
depth
tidak akan menjadi 0 sampai parser menggunakan semua peristiwa antara
START_TAG
asli dan END_TAG
yang cocok. Misalnya,
perhatikan cara parser melewati elemen <author>
,
yang memiliki 2 elemen bertingkat, <name>
dan
<uri>
:
- Saat kali pertama melalui loop
while
, tag berikutnya yang ditemukan parser setelah<author>
adalahSTART_TAG
untuk<name>
. Nilai untukdepth
bertambah menjadi 2. - Kali kedua melalui loop
while
, tag berikutnya yang ditemukan parser adalahEND_TAG
</name>
Nilai untukdepth
berkurang menjadi 1. - Kali ketiga melalui loop
while
, tag berikutnya yang ditemukan parser adalahSTART_TAG
<uri>
. Nilai untukdepth
bertambah menjadi 2. - Kali keempat melalui loop
while
, tag berikutnya yang ditemukan parser adalahEND_TAG
</uri>
. Nilai untukdepth
berkurang menjadi 1. - Kali kelima dan saat terakhir melalui loop
while
, tag berikutnya yang ditemukan parser adalahEND_TAG
</author>
. Nilai untukdepth
berkurang menjadi 0, yang menunjukkan bahwa elemen<author>
telah berhasil dilewati.
Menggunakan data XML
Aplikasi contoh akan mengambil dan mengurai feed XML dalam AsyncTask
. Tindakan ini akan menghilangkan pemrosesan UI thread utama. Saat
pemrosesan selesai, aplikasi akan memperbarui UI dalam aktivitas utamanya
(NetworkActivity
).
Dalam cuplikan yang ditunjukkan di bawah ini, metode loadPage()
akan melakukan hal berikut:
- Menginisialisasi variabel string dengan URL untuk feed XML.
new DownloadXmlTask().execute(url)
akan dipanggil jika setelan pengguna dan koneksi jaringan memungkinkannya. Ini akan membuat instance objekDownloadXmlTask
baru (subclassAsyncTask
) dan menjalankan metodeexecute()
, yang akan mendownload dan mengurai feed lalu 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 } }
Subclass AsyncTask
yang ditampilkan di bawah ini,
DownloadXmlTask
, akan mengimplementasikan metode AsyncTask
berikut:
doInBackground()
akan menjalankan metodeloadXmlFromNetwork()
. URL feed akan diteruskan sebagai parameter. MetodeloadXmlFromNetwork()
akan mengambil dan memproses feed. Jika selesai, metode ini akan mengembalikan string hasil.onPostExecute()
akan 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 akan melakukan hal berikut:
- Membuat instance
StackOverflowXmlParser
. Juga akan membuat variabel untukList
objekEntry
(entries
), sertatitle
,url
, dansummary
, untuk menyimpan nilai yang diekstrak dari feed XML untuk kolom tersebut. - Memanggil
downloadUrl()
, yang akan mengambil feed dan menampilkannya sebagaiInputStream
. - Menggunakan
StackOverflowXmlParser
untuk menguraiInputStream
.StackOverflowXmlParser
akan mengisiList
entries
dengan data dari feed. - Memproses
List
entries
, dan menggabungkan data feed dengan markup HTML. - Menampilkan string HTML 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(); }