XML-Daten parsen

Extensible Markup Language (XML) ist ein Regelsatz für die Codierung von Dokumenten in maschinenlesbarer Form. XML ist ein beliebtes Format für die Weitergabe von Daten im Internet.

Websites, die ihre Inhalte häufig aktualisieren, wie Nachrichtenwebsites oder Blogs, stellen häufig einen XML-Feed bereit, damit externe Programme über Inhaltsänderungen auf dem Laufenden bleiben. Das Hochladen und Parsen von XML-Daten ist eine häufige Aufgabe bei mit dem Netzwerk verbundenen Anwendungen. In diesem Thema wird erläutert, wie XML-Dokumente analysiert und ihre Daten verwendet werden.

Weitere Informationen zum Erstellen webbasierter Inhalte in Ihrer Android-App finden Sie unter Webbasierte Inhalte.

Parser auswählen

Wir empfehlen XmlPullParser, eine effiziente und pflegeleichte Möglichkeit zum Parsen von XML unter Android. Android hat zwei Implementierungen dieser Oberfläche:

Beide Optionen sind in Ordnung. Im Beispiel in diesem Abschnitt werden ExpatPullParser und Xml.newPullParser() verwendet.

Feed analysieren

Der erste Schritt beim Parsen eines Feeds besteht darin, zu entscheiden, welche Felder Sie interessieren. Der Parser extrahiert Daten für diese Felder und ignoriert den Rest.

Sehen Sie sich den folgenden Auszug aus einem geparsten Feed in der Beispiel-App an. Jeder Beitrag auf StackOverflow.com wird im Feed als entry-Tag angezeigt, das mehrere verschachtelte Tags enthält:

<?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>

Die Beispiel-App extrahiert Daten für das entry-Tag und die verschachtelten Tags title, link und summary.

Parser instanziieren

Der nächste Schritt beim Parsen eines Feeds besteht darin, einen Parser zu instanziieren und den Parsing-Prozess zu starten. Dieses Snippet initialisiert einen Parser, um keine Namespaces zu verarbeiten und das bereitgestellte InputStream als Eingabe zu verwenden. Der Parsing-Prozess wird mit einem Aufruf von nextTag() gestartet und die Methode readFeed() aufgerufen, die die für die App relevanten Daten extrahiert und verarbeitet:

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 lesen

Die Methode readFeed() übernimmt die eigentliche Verarbeitung des Feeds. Als Ausgangspunkt für die rekursive Verarbeitung des Feeds wird nach Elementen mit dem Tag "entry" gesucht. Wenn ein Tag kein entry-Tag ist, wird es übersprungen. Nachdem der gesamte Feed rekursiv verarbeitet wurde, gibt readFeed() ein List mit den Einträgen (einschließlich verschachtelter Datenelemente) zurück, die aus dem Feed extrahiert wurden. Diese List wird dann vom Parser zurückgegeben.

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 parsen

Zum Parsen eines XML-Feeds sind folgende Schritte erforderlich:

  1. Wie unter Feed analysieren beschrieben, ermitteln Sie die Tags, die Sie in Ihre App aufnehmen möchten. In diesem Beispiel werden Daten für das entry-Tag und die darin enthaltenen Tags title, link und summary extrahiert.
  2. Erstellen Sie die folgenden Methoden:

    • Eine Lesemethode für jedes Tag, das Sie einschließen möchten, z. B. readEntry() und readTitle(). Der Parser liest Tags aus dem Eingabestream. Wenn es auf ein Tag mit dem Namen entry, title, link oder summary stößt, wird die entsprechende Methode für dieses Tag aufgerufen. Andernfalls wird das Tag übersprungen.
    • Methoden zum Extrahieren von Daten für jeden verschiedenen Tag-Typ und zum Fortsetzen des Parsers zum nächsten Tag. In diesem Beispiel sind die folgenden Methoden relevant:
      • Für die Tags title und summary ruft der Parser readText() auf. Diese Methode extrahiert Daten für diese Tags durch Aufrufen von parser.getText().
      • Für das Tag link extrahiert der Parser Daten für Links, indem er zuerst ermittelt, ob der Link die Art hat, an der er interessiert ist. Dann wird parser.getAttributeValue() verwendet, um den Wert des Links zu extrahieren.
      • Für das Tag entry ruft der Parser readEntry() auf. Diese Methode parst die verschachtelten Tags des Eintrags und gibt ein Entry-Objekt mit den Datenelementen title, link und summary zurück.
    • Eine rekursive Hilfsmethode skip(). Weitere Informationen zu diesem Thema finden Sie unter Unwichtige Tags überspringen.

Dieses Snippet zeigt, wie der Parser Einträge, Titel, Links und Zusammenfassungen parst.

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

Unwichtige Tags überspringen

Der Parser muss Tags überspringen, an denen er nicht interessiert ist. Hier ist die skip()-Methode des Parsers:

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

So funktioniert's:

  • Sie gibt eine Ausnahme aus, wenn das aktuelle Ereignis kein START_TAG ist.
  • Er verarbeitet das START_TAG und alle Ereignisse bis einschließlich des übereinstimmenden END_TAG.
  • Er erfasst die Verschachtelungstiefe, um sicherzustellen, dass er bei der richtigen END_TAG und nicht beim ersten Tag nach dem ursprünglichen START_TAG endet.

Wenn also das aktuelle Element verschachtelte Elemente hat, ist der Wert von depth erst 0, wenn der Parser alle Ereignisse zwischen der ursprünglichen START_TAG und der übereinstimmenden END_TAG verarbeitet. Sehen Sie sich beispielsweise an, wie der Parser das <author>-Element überspringt, das die beiden verschachtelten Elemente <name> und <uri> enthält:

  • Beim ersten Mal durch die while-Schleife ist das nächste Tag, das der Parser nach <author> sieht, das START_TAG für <name>. Der Wert für depth erhöht sich auf 2.
  • Das nächste Mal durch die while-Schleife ist das nächste Tag, das der Parser sieht, das END_TAG-</name>. Der Wert für depth verringert sich auf 1.
  • Beim dritten Mal durch die while-Schleife ist das nächste Tag, das der Parser sieht, das START_TAG-<uri>. Der Wert für depth erhöht sich in 2.
  • Beim vierten Mal durch die while-Schleife ist das nächste Tag, das der Parser sieht, das END_TAG-</uri>. Der Wert für depth verringert sich auf 1.
  • Beim fünften und letzten Mal durch die while-Schleife ist das nächste Tag, das der Parser findet, das END_TAG-</author>. Der Wert für depth verringert sich auf 0. Dies zeigt an, dass das Element <author> erfolgreich übersprungen wurde.

XML-Daten verarbeiten

Die Beispielanwendung ruft den XML-Feed asynchron ab und parst ihn. Dadurch wird die Verarbeitung aus dem Haupt-UI-Thread entnommen. Wenn die Verarbeitung abgeschlossen ist, aktualisiert die Anwendung die UI in ihrer Hauptaktivität NetworkActivity.

Im folgenden Auszug führt die Methode loadPage() Folgendes aus:

  • Initialisiert eine Stringvariable mit der URL für den XML-Feed.
  • Ruft die Methode downloadXml(url) auf, wenn die Einstellungen des Nutzers und die Netzwerkverbindung dies zulassen. Diese Methode lädt den Feed herunter, parst ihn und gibt ein Stringergebnis zurück, das auf der UI angezeigt werden soll.

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

Mit der Methode downloadXml werden die folgenden Methoden in Kotlin aufgerufen:

  • lifecycleScope.launch(Dispatchers.IO), das Kotlin-Coroutinen verwendet, um die Methode loadXmlFromNetwork() im E/A-Thread zu starten. Die Feed-URL wird dabei als Parameter übergeben. Der Feed wird mit der Methode loadXmlFromNetwork() abgerufen und verarbeitet. Wenn der Vorgang abgeschlossen ist, wird ein Ergebnisstring zurückgegeben.
  • withContext(Dispatchers.Main) verwendet Kotlin-Koroutinen, um zum Hauptthread zurückzukehren, und zeigt den zurückgegebenen String in der UI an.

In der Programmiersprache Java läuft der Prozess wie folgt ab:

  • Ein Executor führt die Methode loadXmlFromNetwork() in einem Hintergrundthread aus. Die Feed-URL wird dabei als Parameter übergeben. Der Feed wird mit der Methode loadXmlFromNetwork() abgerufen und verarbeitet. Wenn der Vorgang abgeschlossen ist, wird ein Ergebnisstring zurückgegeben.
  • Ein Handler ruft post auf, um zum Hauptthread zurückzukehren, nimmt den zurückgegebenen String an und zeigt ihn in der UI an.

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

Die Methode loadXmlFromNetwork(), die über downloadXml aufgerufen wird, wird im nächsten Snippet gezeigt. Dabei geschieht Folgendes:

  1. Instanziiert ein StackOverflowXmlParser. Außerdem werden Variablen für eine List mit Entry-Objekten (entries) sowie für title, url und summary erstellt, um die aus dem XML-Feed extrahierten Werte für diese Felder aufzunehmen.
  2. Ruft downloadUrl() auf, wodurch der Feed abgerufen und als InputStream zurückgegeben wird.
  3. Verwendet StackOverflowXmlParser, um InputStream zu parsen. StackOverflowXmlParser füllt ein List von entries mit Daten aus dem Feed.
  4. Verarbeitet das entries-List und kombiniert die Feeddaten mit HTML-Markup.
  5. Gibt einen HTML-String zurück, der auf der UI der Hauptaktivität angezeigt wird.

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