«زبان نشانهگذاری گسترشپذیر» (XML) مجموعهای از قوانین برای کدبندی اسناد به شکل ماشینخوانا است. XML قالب محبوبی برای همرسانی دادهها در اینترنت است.
وبسایتهایی که محتوای خود را بهطور مکرر بهروز میکنند، مانند سایتهای خبری یا وبلاگها، اغلب یک فید XML ارائه میدهند تا برنامههای خارجی بتوانند از تغییرات محتوا مطلع شوند. بارگذاری و تجزیه دادههای XML یکی از کارهای رایج برای برنامههای متصل به شبکه است. این موضوع نحوه تجزیه اسناد XML و استفاده از دادههای آنها را توضیح میدهد.
برای کسب اطلاعات بیشتر درباره ایجاد محتوای مبتنی بر وب در برنامه Android، به محتوای مبتنی بر وب مراجعه کنید.
انتخاب تجزیهکننده
XmlPullParser را توصیه میکنیم که روشی کارآمد و
قابلنگهداری برای تجزیه XML در Android است. Android دو پیادهسازی از این میانجی دارد:
KXmlParser، بااستفاده ازXmlPullParserFactory.newPullParser()ExpatPullParser، بااستفاده ازXml.newPullParser()
هرکدام را انتخاب کنید خوب است. مثال
این بخش از ExpatPullParser و
Xml.newPullParser() استفاده میکند.
تجزیهوتحلیل فید
اولین گام در تجزیه فید این است که تصمیم بگیرید به کدام فیلدها علاقه دارید. تجزیهکننده دادههای آن فیلدها را استخراج میکند و بقیه را نادیده میگیرد.
گزیده زیر را از فید تجزیهشده در برنامه نمونه ببینید. هر پست به StackOverflow.com در فید بهعنوان یک برچسب entry ظاهر میشود که شامل چندین برچسب تودرتو است:
<?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>
برنامه نمونه
دادههای برچسب entry و برچسبهای تودرتوی آن
title، link، و summary را استخراج میکند.
نمونهسازی تجزیهکننده
مرحله بعدی در تجزیه فید،
نمونهسازی تجزیهکننده و شروع فرایند تجزیه است. این قطعه کد
تجزیهکنندهای را مقداردهی اولیه میکند تا فضاهای نام را پردازش نکند و از InputStream ارائهشده بهعنوان ورودی استفاده کند. فرایند تجزیه را با فراخوانی
nextTag() شروع میکند و روش
readFeed() را فراخوانی میکند که دادههای موردنظر برنامه را استخراج و پردازش میکند:
کاتلین
// 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) } } ... }
جاوا
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(); } } ... }
خواندن فید
روش readFeed() کار واقعی پردازش فید را انجام میدهد. این برنامه بهدنبال عناصر برچسبگذاریشده با «entry» میگردد تا بهعنوان نقطه شروعی برای پردازش بازگشتی فید استفاده کند. اگر برچسبی برچسب entry نباشد، از آن صرفنظر میکند. وقتی کل فید بهصورت بازگشتی پردازش شد، readFeed() یک List برمیگرداند که حاوی ورودیهایی (ازجمله اعضای دادههای تودرتو) است که از فید استخراج کرده است. سپس این List توسط تجزیهکننده برگردانده میشود.
کاتلین
@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 }
جاوا
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
مراحل تجزیه فید XML از قرار زیر است:
- همانطور که در تجزیهوتحلیل فید توضیح داده شده است، برچسبهایی را که میخواهید در برنامهتان گنجانده شود شناسایی کنید. این
نمونه دادههای برچسب
entryو برچسبهای تودرتوی آن را استخراج میکند:title،link، وsummary. - روشهای زیر را ایجاد کنید:
- یک روش «خواندن» برای هر برچسبی که میخواهید اضافه کنید، مانند
readEntry()وreadTitle(). تجزیهکننده برچسبهای را از جریان ورودی میخواند. وقتی با برچسبی با نام، در این مثال،entry،title،linkیاsummaryمواجه میشود، روش مناسب برای آن برچسب را فراخوانی میکند. درغیراینصورت، برچسب را رد میکند. - روشهای استخراج داده برای هر نوع برچسب متفاوت و پیش بردن تجزیهکننده به برچسب بعدی. در این مثال، روشهای مربوطه بهصورت زیر است:
- برای برچسبهای
titleوsummary، تجزیهکنندهreadText()را فراخوانی میکند. این روش دادههای این برچسبها را با فراخوانیparser.getText()استخراج میکند. - برای برچسب
link، تجزیهکننده ابتدا با تعیین اینکه آیا پیوند از نوعی است که به آن علاقه دارد یا نه، دادههای پیوندها را استخراج میکند. سپس ازparser.getAttributeValue()برای استخراج مقدار پیوند استفاده میکند. - برای برچسب
entry، تجزیهکنندهreadEntry()را فراخوانی میکند. این روش برچسبهای تودرتوی ورودی را تجزیه میکند و یک شیءEntryبا اعضای دادهtitle،link، وsummaryبرمیگرداند.
- برای برچسبهای
- روش کمکی
skip()که بازگشتی است. برای بحث بیشتر درباره این موضوع، به رد شدن از برچسبهایی که برایتان مهم نیست مراجعه کنید.
- یک روش «خواندن» برای هر برچسبی که میخواهید اضافه کنید، مانند
این قطعه کد نشان میدهد که تجزیهکننده چگونه ورودیها، عنوانها، پیوندها، و خلاصهها را تجزیه میکند.
کاتلین
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 } ...
جاوا
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; } ... }
برچسبهایی را که برایتان مهم نیست رد کنید
تجزیهکننده باید برچسبهایی را که به آنها علاقهای ندارد رد کند. در اینجا روش skip() تجزیهکننده آمده است:
کاتلین
@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++ } } }
جاوا
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; } } }
نحوه کار به این صورت است:
- اگر رویداد کنونی
START_TAGنباشد، استثنایی ایجاد میکند. - این
START_TAGو همه رویدادها را تا و ازجملهEND_TAGمنطبق مصرف میکند. - این ویژگی عمق تودرتو را ردیابی میکند تا مطمئن شود که در
END_TAGصحیح متوقف میشود و نه در اولین برچسبی که پسازSTART_TAGاصلی با آن مواجه میشود.
بنابراین، اگر عنصر فعلی دارای عناصر تودرتو باشد، مقدار
depth تا زمانی که تجزیهکننده همه رویدادهای بین
START_TAG اصلی و END_TAG منطبق با آن را مصرف نکند، ۰ نخواهد بود. برای
مثال، درنظر بگیرید که تجزیهکننده چگونه از عنصر <author>،
که دارای ۲ عنصر تودرتو، <name> و
<uri> است، رد میشود:
- اولینباری که تجزیهکننده از حلقه
whileعبور میکند، برچسب بعدی که پساز<author>با آن مواجه میشودSTART_TAGبرای<name>است. مقدارdepthبه ۲ افزایش مییابد. - The second time through the
whileloop, the next tag the parser encounters is theEND_TAG</name>. مقدار برایdepthبه ۱ کاهش مییابد. - در سومین باری که تجزیهکننده از حلقه
whileعبور میکند، برچسب بعدی که با آن مواجه میشودSTART_TAG<uri>است. مقدار برایdepthبه ۲ افزایش مییابد. - در چهارمین باری که حلقه
whileاجرا میشود، برچسب بعدی که تجزیهکننده با آن مواجه میشودEND_TAG</uri>است. مقدارdepthبه ۱ کاهش مییابد. - در پنجمین و آخرین باری که تجزیهکننده در حلقه
whileقرار میگیرد، برچسب بعدی که با آن مواجه میشودEND_TAG</author>است. مقدارdepthبه ۰ کاهش مییابد که نشان میدهد عنصر<author>با موفقیت رد شده است.
مصرف دادههای XML
برنامه نمونه، فید XML را بهصورت ناهمگام واکشی و تجزیه میکند. این کار پردازش را از رشته میانای کاربر اصلی خارج میکند. وقتی
پردازش کامل شد، برنامه واسط کاربر را در فعالیت اصلی خود،
NetworkActivity، بهروز میکند.
در گزیده زیر، روش loadPage() کارهای زیر را انجام میدهد:
- متغیر رشتهای را با نشانی وب برای فید XML مقداردهی اولیه میکند.
- اگر تنظیمات کاربر و اتصال شبکه اجازه دهد، روش
downloadXml(url)را فراخوانی میکند. این روش فید را بارگیری و تجزیه میکند و نتیجه رشتهای را برای نمایش در واسط کاربر برمیگرداند.
کاتلین
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. } } ... }
جاوا
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 روشهای زیر را در Kotlin فراخوانی میکند:
-
lifecycleScope.launch(Dispatchers.IO)، که از روالهای مشترک Kotlin برای راهاندازی روشloadXmlFromNetwork()در رشته IO استفاده میکند. این نشانی وب فید را بهعنوان پارامتر عبور میدهد. روشloadXmlFromNetwork()فید را واکشی و پردازش میکند. وقتی کارش تمام میشود، رشته نتیجه را برمیگرداند. -
withContext(Dispatchers.Main)که از روالهای مشترک Kotlin برای بازگشت به رشته اصلی استفاده میکند، رشته برگرداندهشده را میگیرد و آن را در واسط کاربر نمایش میدهد.
در زبان برنامهنویسی Java، فرایند بهصورت زیر است:
ExecutorروشloadXmlFromNetwork()را در رشته پسزمینه اجرا میکند. این نشانی وب فید را بهعنوان پارامتر عبور میدهد. روشloadXmlFromNetwork()فید را واکشی و پردازش میکند. وقتی کارش تمام میشود، رشته نتیجه را برمیگرداند.-
Handlerباpostتماس میگیرد تا به رشته اصلی برگردد، رشته برگرداندهشده را میگیرد، و آن را در میانای کاربر نمایش میدهد.
کاتلین
// 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) } } } }
جاوا
// 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); }); }); }
روش loadXmlFromNetwork() که از downloadXml فراخوانده میشود در قطعه کد بعدی نشان داده شده است. این ویژگی کارهای زیر را انجام میدهد:
StackOverflowXmlParserرا نمونهسازی میکند. همچنین متغیرهایی برایListاز اشیاءEntry(entries) و برایtitle،url، وsummaryایجاد میکند تا مقادیر استخراجشده از فید XML را برای آن فیلدها نگه دارد.downloadUrl()را فراخوانی میکند که فید را واکشی میکند و آن را بهعنوانInputStreamبرمیگرداند.- از
StackOverflowXmlParserبرای تجزیهInputStreamاستفاده میکند.StackOverflowXmlParserListازentriesرا با دادههای فید پر میکند. entriesListرا پردازش میکند و دادههای فید را با نشانگذاری HTML ترکیب میکند.- رشته HTML را که در واسط کاربر فعالیت اصلی نمایش داده میشود برمیگرداند.
کاتلین
// 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 } }
جاوا
// 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(); }