زبان نشانه گذاری توسعه پذیر (XML) مجموعه ای از قوانین برای رمزگذاری اسناد به شکل قابل خواندن توسط ماشین است. XML یک فرمت محبوب برای به اشتراک گذاری داده ها در اینترنت است.
وبسایتهایی که مکرراً محتوای خود را بهروزرسانی میکنند، مانند سایتهای خبری یا وبلاگها، اغلب یک فید XML ارائه میکنند تا برنامههای خارجی بتوانند از تغییرات محتوا مطلع شوند. آپلود و تجزیه داده های XML یک کار معمول برای برنامه های متصل به شبکه است. این مبحث نحوه تجزیه اسناد XML و استفاده از داده های آنها را توضیح می دهد.
برای کسب اطلاعات بیشتر در مورد ایجاد محتوای مبتنی بر وب در برنامه Android خود، به محتوای مبتنی بر وب مراجعه کنید.
تجزیه کننده را انتخاب کنید
ما XmlPullParser
را توصیه می کنیم که راهی کارآمد و قابل نگهداری برای تجزیه XML در اندروید است. اندروید دو پیاده سازی از این رابط دارد:
-
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
نباشد، آن را رد می کند. هنگامی که کل فید به صورت بازگشتی پردازش میشود، 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
. - روش های زیر را ایجاد کنید:
- یک روش "read" برای هر برچسبی که می خواهید اضافه کنید، مانند
readEntry()
وreadTitle()
. تجزیه کننده برچسب ها را از جریان ورودی می خواند. هنگامی که با برچسبی به نام، در این مثال،entry
،title
،link
یاsummary
مواجه می شود، متد مناسب آن تگ را فراخوانی می کند. در غیر این صورت، از تگ می گذرد. - روشهایی برای استخراج دادهها برای هر نوع برچسب مختلف و پیشبرد تجزیهکننده به تگ بعدی. در این مثال، روش های مربوطه به شرح زیر است:
- برای تگ های
title
وsummary
، تجزیه کنندهreadText()
را فراخوانی می کند. این روش با فراخوانیparser.getText()
دادههای این تگها را استخراج میکند. - برای تگ
link
، تجزیهکننده دادهها را برای پیوندها استخراج میکند و ابتدا تعیین میکند که آیا پیوند از نوع مورد علاقهاش است یا خیر. سپس ازparser.getAttributeValue()
برای استخراج ارزش پیوند استفاده میکند. - برای تگ
entry
، تجزیه کنندهreadEntry()
فراخوانی می کند. این روش تگ های تودرتوی ورودی را تجزیه می کند و یک شیءEntry
را باtitle
اعضای داده،link
وsummary
برمی گرداند.
- برای تگ های
- یک روش کمکی
skip()
که بازگشتی است. برای بحث بیشتر در مورد این موضوع، به رد شدن از برچسب هایی که برایتان مهم نیست مراجعه کنید.
- یک روش "read" برای هر برچسبی که می خواهید اضافه کنید، مانند
این قطعه نشان می دهد که چگونه تجزیه کننده ورودی ها، عنوان ها، پیوندها و خلاصه ها را تجزیه می کند.
کاتلین
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
0 نخواهد بود تا زمانی که تجزیه کننده همه رویدادهای بین START_TAG
اصلی و END_TAG
منطبق با آن را مصرف کند. به عنوان مثال، در نظر بگیرید که چگونه تجزیه کننده از عنصر <author>
که دارای 2 عنصر تودرتو است، <name>
و <uri>
می گذرد:
- اولین بار از طریق حلقه
while
، برچسب بعدی که تجزیه کننده بعد از<author>
با آن مواجه می شود،START_TAG
برای<name>
است. مقدار افزایشdepth
تا 2. - دومین بار از طریق حلقه
while
، برچسب بعدی که تجزیه کننده با آن مواجه می شودEND_TAG
</name>
است. مقدارdepth
به 1 کاهش می یابد. - سومین بار از طریق حلقه
while
، برچسب بعدی که تجزیه کننده با آن مواجه می شودSTART_TAG
<uri>
است. مقدار افزایشdepth
تا 2. - چهارمین بار از طریق حلقه
while
، برچسب بعدی که تجزیه کننده با آن مواجه می شودEND_TAG
</uri>
است. مقدارdepth
به 1 کاهش می یابد. - بار پنجم و آخرین بار از طریق حلقه
while
، برچسب بعدی که تجزیه کننده با آن مواجه می شودEND_TAG
</author>
است. مقدارdepth
به 0 کاهش می یابد، که نشان می دهد عنصر<author>
با موفقیت رد شده است.
داده های XML را مصرف کنید
برنامه مثالی فید XML را به صورت ناهمزمان واکشی و تجزیه می کند. این کار پردازش را از رشته اصلی UI حذف می کند. وقتی پردازش کامل شد، برنامه رابط کاربری را در فعالیت اصلی خود، NetworkActivity
، بهروزرسانی میکند.
در گزیده زیر، متد loadPage()
کارهای زیر را انجام می دهد:
- یک متغیر رشته را با URL برای خوراک XML راه اندازی می کند.
- اگر تنظیمات کاربر و اتصال شبکه اجازه دهد، روش
downloadXml(url)
را فراخوانی میکند. این روش فید را دانلود و تجزیه می کند و نتیجه رشته ای را برای نمایش در UI برمی گرداند.
کاتلین
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 استفاده می کند. URL فید را به عنوان پارامتر ارسال می کند. متدloadXmlFromNetwork()
فید را واکشی و پردازش می کند. وقتی تمام شد، یک رشته نتیجه را به عقب ارسال می کند. -
withContext(Dispatchers.Main)
، که از کوروتین های Kotlin برای بازگشت به رشته اصلی استفاده می کند، رشته برگشتی را می گیرد و در UI نمایش می دهد.
در زبان برنامه نویسی جاوا، فرآیند به صورت زیر است:
- یک
Executor
متدloadXmlFromNetwork()
روی یک رشته پس زمینه اجرا می کند. URL فید را به عنوان پارامتر ارسال می کند. متدloadXmlFromNetwork()
فید را واکشی و پردازش می کند. وقتی تمام شد، یک رشته نتیجه را به عقب ارسال می کند. - یک
Handler
post
برای بازگشت به رشته اصلی فراخوانی می کند، رشته برگشتی را می گیرد و آن را در UI نمایش می دهد.
کاتلین
// 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
استفاده می کند.StackOverflowXmlParser
List
ازentries
با داده های فید پر می کند. -
List
entries
را پردازش می کند و داده های فید را با نشانه گذاری HTML ترکیب می کند. - یک رشته HTML را که در UI فعالیت اصلی نمایش داده می شود، برمی گرداند.
کاتلین
// 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(); }