XML verilerini ayrıştırma

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:

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:

  1. 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 ve summary.
  2. Aşağıdaki yöntemleri oluşturun:

    • Dahil etmek istediğiniz her etiket için bir "okuma" yöntemi (ör. readEntry() ve readTitle()). Ayrıştırıcı, giriş akışındaki etiketleri okur. Bu örnekte entry, title, link veya summary 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 ve summary etiketleri için readText() 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çin parser.getAttributeValue() kullanır.
      • Ayrıştırıcı, entry etiketi için readEntry() yöntemini çağırır. Bu yöntem, girişin iç içe yerleştirilmiş etiketlerini ayrıştırır ve title, link ve summary veri üyelerini içeren bir Entry nesnesi döndürür.
    • Yinelemeli yardımcı skip() yöntemi. Bu konuyla ilgili daha fazla ayrıntı için Önemsiz etiketleri atlama konusuna bakın.

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şen END_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ğru END_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çin START_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 etiket END_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 etiket START_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 etiket END_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ığında loadXmlFromNetwork() 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çin post 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:

  1. StackOverflowXmlParser temsil eder. Ayrıca Entry nesneden (entries) oluşan List için ve title, url ve summary için bu alanların XML feed'inden çıkarılan değerleri tutmak amacıyla değişkenler oluşturur.
  2. downloadUrl() işlevini çağırarak feed'i getirir ve InputStream olarak döndürür.
  3. InputStream ayrıştırmak için StackOverflowXmlParser kullanır. StackOverflowXmlParser, entries değerinin List alanını feed'deki verilerle doldurur.
  4. entries List öğesini işler ve feed verilerini HTML işaretlemesiyle birleştirir.
  5. 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();
}