แยกวิเคราะห์ข้อมูล XML

Extensible Markup Language (XML) เป็นชุดของกฎสำหรับการเข้ารหัสเอกสารใน ที่เครื่องอ่านได้ XML เป็นรูปแบบที่ได้รับความนิยมสำหรับการแชร์ข้อมูลบนอินเทอร์เน็ต

เว็บไซต์ที่อัปเดตเนื้อหาบ่อยๆ เช่น เว็บไซต์ข่าวหรือบล็อก มักจะมีฟีด XML เพื่อให้โปรแกรมภายนอกสามารถติดตามเนื้อหา การเปลี่ยนแปลง การอัปโหลดและแยกวิเคราะห์ข้อมูล XML เป็นงานทั่วไปสำหรับการเชื่อมต่อเครือข่าย แอป หัวข้อนี้จะอธิบายวิธีแยกวิเคราะห์เอกสาร XML และใช้ข้อมูลของเอกสาร

หากต้องการเรียนรู้เพิ่มเติมเกี่ยวกับการสร้างเนื้อหาบนเว็บในแอป Android โปรดดู เนื้อหาบนเว็บ

เลือกโปรแกรมแยกวิเคราะห์

เราขอแนะนำ XmlPullParser ซึ่งเป็นโปรแกรมที่มีประสิทธิภาพและ วิธีแยกวิเคราะห์ XML ใน Android ที่ดูแลรักษาได้ Android มี ของอินเทอร์เฟซนี้

เลือกแบบใดแบบหนึ่งก็ได้ ตัวอย่างในส่วนนี้ใช้ 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() ซึ่งแยกและประมวลผลข้อมูลที่แอปอยู่ สนใจเรื่อง:

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

อ่านฟีด

เมธอด readFeed() จะทำงานจริงในการประมวลผล ฟีด โปรแกรมจะค้นหาองค์ประกอบที่ติดแท็ก "รายการ" เป็นจุดเริ่มต้นของ การประมวลผลฟีด หากแท็กใดไม่ใช่แท็ก entry ก็จะข้ามแท็กนั้น เมื่อเหตุการณ์ทั้งหมด ฟีดได้รับการประมวลผลซ้ำๆ readFeed() จะแสดงผล วันที่ List ที่มีรายการ (รวมถึงสมาชิกข้อมูลที่ซ้อนกัน) ที่แยกออกมาจากฟีด จากนั้น List นี้จะส่งคืนโดย โปรแกรมแยกวิเคราะห์

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

ขั้นตอนในการแยกวิเคราะห์ฟีด XML มีดังนี้

  1. ตามที่อธิบายไว้ในวิเคราะห์ฟีด ให้ระบุแท็กที่ต้องการรวมไว้ในแอป ช่วงเวลานี้ ตัวอย่างดึงข้อมูลสำหรับแท็ก entry และแท็กที่ซ้อนกัน title, link และ summary
  2. สร้างวิธีการต่อไปนี้

    • "อ่าน" สำหรับแต่ละแท็กที่คุณต้องการรวม เช่น readEntry() และ readTitle() โปรแกรมแยกวิเคราะห์จะอ่าน แท็กจากสตรีมอินพุต เมื่อพบแท็กที่มีชื่อ ในตัวอย่างนี้ entry title link หรือ summary เรียกเมธอดที่เหมาะสม สำหรับแท็กนั้น มิฉะนั้นระบบจะข้ามแท็ก
    • วิธีการดึงข้อมูลสำหรับแท็กแต่ละประเภทและความก้าวหน้า ไปยังแท็กถัดไป ในตัวอย่างนี้ วิธีการที่เกี่ยวข้องมีดังนี้
      • สำหรับแท็ก title และ summary โปรแกรมแยกวิเคราะห์จะเรียกใช้ readText() วิธีนี้จะแยกข้อมูลของแท็กเหล่านี้โดยการเรียกใช้ parser.getText()
      • สำหรับแท็ก link โปรแกรมแยกวิเคราะห์จะแยกข้อมูลของลิงก์ก่อน เพื่อตัดสินว่าลิงก์นั้นๆ เป็นประเภท ที่ผู้ใช้สนใจ จากนั้นใช้ parser.getAttributeValue() เพื่อ แยกค่าของลิงก์
      • สำหรับแท็ก entry โปรแกรมแยกวิเคราะห์จะเรียก readEntry() วิธีนี้จะแยกวิเคราะห์แท็กที่ซ้อนกันของรายการและแสดงผล Entry ที่มีสมาชิกข้อมูล title, link และ summary
    • เมธอดช่วย skip() ที่เกิดซ้ำ หากต้องการการสนทนาเพิ่มเติมเกี่ยวกับหัวข้อนี้ โปรดดูที่ข้ามแท็กที่คุณไม่สนใจ

ข้อมูลโค้ดนี้จะแสดงวิธีที่โปรแกรมแยกวิเคราะห์แยกวิเคราะห์รายการ ชื่อ ลิงก์ และสรุป

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

ข้ามแท็กที่คุณไม่ต้องการ

โปรแกรมแยกวิเคราะห์จะต้องข้ามแท็กที่ไม่สนใจ เมธอด skip() ของโปรแกรมแยกวิเคราะห์มีดังนี้

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

วิธีการทำงานมีดังนี้:

  • โดยจะมีข้อยกเว้นหากเหตุการณ์ปัจจุบันไม่ใช่ 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 เป็นครั้งที่ 2 ระบบจะติดแท็กถัดไปให้โปรแกรมแยกวิเคราะห์ ที่พบคือ </name> END_TAG ค่า สำหรับการลด depth เป็น 1
  • ครั้งที่ 3 ในการวนซ้ำ while ระบบจะแท็กโปรแกรมแยกวิเคราะห์ถัดไป ที่พบคือ <uri> START_TAG ค่า สำหรับ depth เพิ่มขึ้นทีละ 2
  • ครั้งที่ 4 ในการวนซ้ำ while จากนั้นแท็กโปรแกรมแยกวิเคราะห์ถัดไป ที่พบคือ </uri> END_TAG ค่าของ depth ลดลงเหลือ 1
  • ครั้งที่ 5 และเป็นครั้งสุดท้ายผ่านลูป while ครั้งถัดไป แท็กที่โปรแกรมแยกวิเคราะห์พบคือ END_TAG </author> ค่าของการลดลง depth เป็น 0 หมายความว่าองค์ประกอบ <author> สำเร็จแล้ว ข้าม

ใช้ข้อมูล XML

แอปพลิเคชันตัวอย่างจะดึงและแยกวิเคราะห์ฟีด XML แบบไม่พร้อมกัน การดำเนินการนี้จะนำการประมวลผลออกจากเทรด UI หลัก วันและเวลา การประมวลผลเสร็จสมบูรณ์ แอปจะอัปเดต UI ในกิจกรรมหลัก NetworkActivity

ในตัวอย่างข้อความต่อไปนี้ เมธอด loadPage() จะดำเนินการต่อไปนี้

  • เริ่มต้นตัวแปรสตริงด้วย URL สำหรับฟีด XML
  • เรียกใช้เมธอด downloadXml(url) หากการตั้งค่าของผู้ใช้และเครือข่าย ให้เชื่อมต่อได้ วิธีนี้จะดาวน์โหลดและแยกวิเคราะห์ฟีด และแสดงผลสตริงเป็น ที่แสดงใน 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
    }
    ...
    // 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 เรียกใช้เมธอดต่อไปนี้ใน Kotlin

  • lifecycleScope.launch(Dispatchers.IO) ซึ่งใช้โครูทีนของ Kotlin ในการ เปิดเมธอด loadXmlFromNetwork() ในชุดข้อความ IO ส่ง URL ฟีดเป็น พารามิเตอร์ วิธีการ loadXmlFromNetwork() ดึงข้อมูลและประมวลผล ฟีด เมื่อเสร็จแล้ว ระบบจะส่งสตริงผลลัพธ์กลับมา
  • withContext(Dispatchers.Main) ซึ่งใช้ Kotlin coroutines เพื่อกลับไปยังเทรดหลัก จะใช้เมธอด แสดงผลสตริง และแสดงผลใน UI

ในภาษาโปรแกรม Java มีกระบวนการดังนี้:

  • Executor ดำเนินการ เมธอด loadXmlFromNetwork() ในชุดข้อความเบื้องหลัง ส่ง URL ฟีดเป็น พารามิเตอร์ วิธีการ loadXmlFromNetwork() ดึงข้อมูลและประมวลผล ฟีด เมื่อเสร็จแล้ว ระบบจะส่งสตริงผลลัพธ์กลับมา
  • Handler โทรหา post เพื่อกลับไปยังเทรดหลัก ให้ใช้ แสดงผลสตริง และแสดงผลใน UI

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

เมธอด loadXmlFromNetwork() ที่เรียกใช้จาก downloadXml จะแสดงในข้อมูลโค้ดถัดไป โดยมีการทำงานดังต่อไปนี้

  1. สร้างอินสแตนซ์ StackOverflowXmlParser และยังสร้างตัวแปรสำหรับ วัตถุ List จาก Entry รายการ (entries) และสำหรับ title, url และ summary เพื่อคง ที่ดึงมาจากฟีด XML สำหรับช่องเหล่านั้น
  2. เรียกใช้ downloadUrl() ซึ่งจะดึงข้อมูลฟีดและแสดงผลเป็น InputStream
  3. ใช้ StackOverflowXmlParser เพื่อแยกวิเคราะห์ InputStream StackOverflowXmlParser เติมข้อมูล List ของ entries ที่มีข้อมูลจากฟีด
  4. ประมวลผล List ของ entries และรวมข้อมูลฟีดกับมาร์กอัป HTML
  5. แสดงสตริง HTML ที่แสดงในกิจกรรมหลัก UI

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