Genişletilebilir Biçimlendirme Dili (XML), belgeleri makine tarafından okunabilir biçimde kodlamak için kullanılan kurallar kümesidir. XML, internet üzerinde veri paylaşımında yaygın olarak kullanılan bir biçimdir.
Haber siteleri veya bloglar gibi içeriklerini sık sık güncelleyen web siteleri, harici programların içerik değişikliklerini takip edebilmeleri için genellikle XML feed'i sağlar. XML verilerinin yüklenmesi ve ayrıştırılması, ağa bağlı uygulamaların yaygın bir görevidir. Bu konuda, XML belgelerinin nasıl ayrıştırılacağı ve verilerinin nasıl kullanılacağı açıklanmaktadır.
Android uygulamanızda web tabanlı içerik oluşturma hakkında daha fazla bilgi edinmek için Web tabanlı içerik konusuna bakın.
Ayrıştırıcı seçin
Android'de XML'i ayrıştırmak için verimli ve sürdürülebilir bir yol olan XmlPullParser
kullanmanızı öneririz. Android'de bu arayüze ait
iki uygulama vardır:
KXmlParser
,XmlPullParserFactory.newPullParser()
kullanılıyorXml.newPullParser()
kullananExpatPullParser
Her iki seçenek de uygundur. Bu bölümdeki örnekte ExpatPullParser
ve Xml.newPullParser()
kullanılmıştır.
Feed'i analiz etme
Bir feed'i ayrıştırmanın ilk adımı, hangi alanlarla ilgilendiğinize karar vermektir. Ayrıştırıcı bu alanlar için verileri ayıklar ve diğerlerini yok sayar.
Örnek uygulamada ayrıştırılmış bir feed'den aşağıdaki alıntıyı inceleyin. StackOverflow.com'daki her yayın, feed'de birkaç iç içe yerleştirilmiş etiket içeren bir entry
etiketi olarak görünür:
<?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>
Örnek uygulama, entry
etiketi ve bu etiketin iç içe yerleştirilmiş etiketleri title
, link
ve summary
için verileri çıkarır.
Ayrıştırıcıyı örneklendirme
Feed ayrıştırmanın bir sonraki adımı, bir ayrıştırıcı çalıştırmak ve ayrıştırma sürecini başlatmaktır. Bu snippet, ad alanlarını işlememek ve sağlanan InputStream
öğesini giriş olarak kullanmak için bir ayrıştırıcı başlatır. Ayrıştırma sürecini nextTag()
çağrısıyla başlatır ve uygulamanın ilgilendiği verileri çıkarıp işleyen readFeed()
yöntemini çağırır:
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(); } } ... }
Feed'i oku
readFeed()
yöntemi, feed'in işlenmesiyle ilgili asıl işi yapar. Feed'i tekrarlı şekilde işlemek için başlangıç noktası olarak "giriş" etiketli öğeleri arar. Bir etiket entry
etiketi değilse bu etiketi atlar. Feed'in tamamı tekrarlı olarak işlendikten sonra, readFeed()
feed'den çıkarılan girişleri (iç içe yerleştirilmiş veri üyeleri dahil) içeren bir List
döndürür. Bu List
, daha sonra ayrıştırıcı tarafından döndürülür.
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; }
XML ayrıştırma
XML feed'ini ayrıştırma adımları aşağıdaki gibidir:
- Feed'i analiz etme bölümünde açıklandığı gibi, uygulamanıza dahil etmek istediğiniz etiketleri tanımlayın. Bu örnekte,
entry
etiketi ve bu etiketin iç içe geçmiş etiketleri için veriler ayıklanır:title
,link
vesummary
. - Aşağıdaki yöntemleri oluşturun:
- Dahil etmek istediğiniz her etiket için bir "okuma" yöntemi (ör.
readEntry()
vereadTitle()
). Ayrıştırıcı, giriş akışındaki etiketleri okur. Bu örnekteentry
,title
,link
veyasummary
adlı bir etiketle karşılaştığında, bu etiket için uygun yöntemi çağırır. Aksi takdirde, etiketi atlar. - Her farklı etiket türü için veri ayıklama ve ayrıştırıcıyı bir sonraki etikete geliştirme yöntemleri. Bu örnekte alakalı yöntemler aşağıda belirtilmiştir:
- Ayrıştırıcı,
title
vesummary
etiketleri içinreadText()
yöntemini çağırır. Bu yöntem,parser.getText()
yöntemini çağırarak bu etiketler için verileri ayıklar. - Ayrıştırıcı,
link
etiketi için öncelikle bağlantının ilgilendiği türde olup olmadığını belirleyerek bağlantılara ait verileri ayıklar. Ardından, bağlantının değerini çıkarmak içinparser.getAttributeValue()
kullanır. - Ayrıştırıcı,
entry
etiketi içinreadEntry()
yöntemini çağırır. Bu yöntem, girişin iç içe yerleştirilmiş etiketlerini ayrıştırır vetitle
,link
vesummary
veri üyelerini içeren birEntry
nesnesi döndürür.
- Ayrıştırıcı,
- Yinelemeli yardımcı
skip()
yöntemi. Bu konuyla ilgili daha fazla ayrıntı için Önemsiz etiketleri atlama konusuna bakın.
- Dahil etmek istediğiniz her etiket için bir "okuma" yöntemi (ör.
Bu snippet, ayrıştırıcının girişleri, başlıkları, bağlantıları ve özetleri nasıl ayrıştırdığını gösterir.
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; } ... }
Sizin için önemli olmayan etiketleri atlayın
Ayrıştırıcının, ilgilenmediği etiketleri atlaması gerekir. Ayrıştırıcının skip()
yöntemi şöyledir:
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; } } }
Bu şöyle işler:
- Geçerli etkinlik bir
START_TAG
değilse bir istisna bildirir. START_TAG
öğesini ve eşleşenEND_TAG
öğesine kadar olan tüm etkinlikleri kullanır.- İç içe yerleştirme derinliğinin, orijinal
START_TAG
öğesinden sonra karşılaştığı ilk etikette değil, doğruEND_TAG
değerinde durduğundan emin olmak için izler.
Bu nedenle, geçerli öğenin iç içe yerleştirilmiş öğeleri varsa ayrıştırıcı, orijinal START_TAG
ile eşleşen END_TAG
arasındaki tüm etkinlikleri tüketene kadar depth
değeri 0 olmaz. Örneğin, ayrıştırıcının <name>
ve <uri>
olmak üzere iç içe yerleştirilmiş 2 öğesi olan <author>
öğesini nasıl atladığını düşünün:
while
döngüsünde ilk kez, ayrıştırıcının<author>
sonrasında karşılaştığı bir sonraki etiket,<name>
içinSTART_TAG
olur.depth
değeri 2'ye yükselir.while
döngüsünde ikinci kez, ayrıştırıcının karşılaştığı bir sonraki etiketEND_TAG
</name>
olur.depth
değeri 1'e düşer.while
döngüsünde üçüncü kez, ayrıştırıcının karşılaştığı bir sonraki etiketSTART_TAG
<uri>
olur.depth
değeri 2'ye yükselir.while
döngüsünde dördüncü kez ayrıştırıcının karşılaştığı bir sonraki etiketEND_TAG
</uri>
olur.depth
değeri 1'e düşer.while
döngüsü boyunca beşinci ve son kez ayrıştırıcının karşılaştığı bir sonraki etiket,END_TAG
</author>
olur.depth
değeri,<author>
öğesinin başarıyla atlandığını gösterir.
XML verilerini kullanma
Örnek uygulama, XML feed'ini eşzamansız olarak getirir ve ayrıştırır. Bu, işlemeyi ana kullanıcı arayüzü iş parçacığından çıkarır. İşleme tamamlandığında, uygulama kullanıcı arayüzünü ana etkinliğinde (NetworkActivity
) günceller.
Aşağıdaki alıntıda loadPage()
yöntemi aşağıdakileri yapar:
- XML feed'inin URL'siyle bir dize değişkenini başlatır.
- Kullanıcının ayarları ve ağ bağlantısı izin veriyorsa
downloadXml(url)
yöntemini çağırır. Bu yöntem, feed'i indirip ayrıştırır ve kullanıcı arayüzünde görüntülenecek bir dize sonucu döndürür.
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. } }
downloadXml
yöntemi, Kotlin'de aşağıdaki yöntemleri çağırır:
lifecycleScope.launch(Dispatchers.IO)
, IO iş parçacığındaloadXmlFromNetwork()
yöntemini başlatmak için Kotlin eş yordamlarını kullanır. Feed URL'sini parametre olarak iletir.loadXmlFromNetwork()
yöntemi feed'i getirir ve işler. İşlem tamamlandığında bir sonuç dizesini geri iletir.- Ana iş parçacığına dönmek için Kotlin eş yordamlarını kullanan
withContext(Dispatchers.Main)
, döndürülen dizeyi alıp kullanıcı arayüzünde gösterir.
Java programlama dilinde süreç aşağıdaki gibidir:
Executor
,loadXmlFromNetwork()
yöntemini bir arka plan iş parçacığında yürütür. Feed URL'sini parametre olarak iletir.loadXmlFromNetwork()
yöntemi feed'i getirir ve işler. İşlem tamamlandığında bir sonuç dizesini geri iletir.Handler
, ana iş parçacığına dönmek içinpost
yöntemini çağırır, döndürülen dizeyi alır ve kullanıcı arayüzünde görüntüler.
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); }); }); }
downloadXml
öğesinden çağrılan loadXmlFromNetwork()
yöntemi sonraki snippet'te gösterilmektedir. Şunları yapar:
StackOverflowXmlParser
temsil eder. AyrıcaEntry
nesneden (entries
) oluşanList
için vetitle
,url
vesummary
için bu alanların XML feed'inden çıkarılan değerleri tutmak amacıyla değişkenler oluşturur.downloadUrl()
işlevini çağırarak feed'i getirir veInputStream
olarak döndürür.InputStream
ayrıştırmak içinStackOverflowXmlParser
kullanır.StackOverflowXmlParser
,entries
değerininList
alanını feed'deki verilerle doldurur.entries
List
öğesini işler ve feed verilerini HTML işaretlemesiyle birleştirir.- Ana etkinlik kullanıcı arayüzünde görüntülenen bir HTML dizesi döndürür.
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(); }