XML Parser (DOM & SAX)
| Drucken |
Heute möchte ich Euch zeigen, wie man einen XML Parser für einen RSS Feed schreibt.
Den kompletten Source Code findet Ihr ganz am Ende der Seite.
Wir fangen mit einem einfachen RSS Feed an. In einem fortgeschrittenen Tutorial werde ich den Parser für Youtube Playlists erweitern und in eine ListView integrieren.
DOM Parser vs SAX Parser
Bei der Entwicklung einer eigenen App, die mit verschiedenen XML-Dateien arbeitet, bekam ich eine OutOfMemory Exception. Nachdem mit Eclipse den heap dump analysiert hatte, fiel mir auf, dass meine Klassen, die XML mit DOM parsen, den meisten Speicher beanspruchen.
Dadurch fand ich auch den Grund dafür. Die XML-Datei, also die komplette Baumstruktur, wird mit dem DOM Parser komplett gespeichert. Ab einer bestimmten Dateigröße (etwas über 80 kb glaube ich), beansprucht dieser so viel Platz, dass der heap überläuft.
Die Lösung hierzu war der SAX Parser. Dieser speichert die XML-Einträge nicht, sondern geht diese Zeile für Zeile durch. Allerdings ist der SAX Parser merklich langsamer als der DOM Parser.
XML Datei
Die XML Datei vom heise.de RSS Feed für einen Eintrag sieht so aus:
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>heise online Top-News</title>
<subtitle>Nachrichten nicht nur aus der Welt der Computer</subtitle>
<link href="http://www.heise.de/newsticker/" />
<link rel="self" href="http://www.heise.de/newsticker/heise-top-atom.xml" />
<updated>2011-01-21T15:28:00+01:00</updated>
<author>
<name>heise online</name>
</author>
<id>http://www.heise.de/newsticker/</id>
<entry>
<title>Duke Nukem verlässt angeblich im Mai die ewige Warteschleife</title>
<link href="http://www.heise.de/newsticker/meldung/Duke-Nukem-verlaesst-angeblich-im-Mai-die-ewige-Warteschleife-1174484.html/from/atom10" />
<id>http://www.heise.de/newsticker/meldung/Duke-Nukem-verlaesst-angeblich-im-Mai-die-ewige-Warteschleife-1174484.html/from/atom10</id>
<published>2011-01-21T15:28:00+01:00</published>
<updated>2011-01-21T16:48:38+01:00</updated>
</entry>
<entry> ... </entry>
</feed>
Uns interessieren nur die <entry> Tags. In unserem Beispiel lesen wir nur den Titel ein. Die anderen Tags funktionieren ähnlich wie der Titel.
DOM Parser
String urlString = "http://www.heise.de/newsticker/heise-top-atom.xml";
InputStream is = getInputStreamFromURL(urlString);
Wir definieren die URL zum RSS Feed (hier von heise.de).
Anschließend benutzen wir unsere Methode, die wir bereits kennen gelernt haben, um die XML Datei herunterzuladen.
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
try {
db = dbf.newDocumentBuilder();
//parse rss feed
Document doc = db.parse(is);
doc.normalize();
if (is != null)
readXML(doc);Wir erzeugen uns ein
DOM Document und parsen den
InputStream mithilfe des
DocumentBuilders. Falls der InputStream ungleich null ist, rufen wir die Methode
readXML auf, mit der wir das DOM Document auslesen.
Der komplette Source-Code, der das Document erzeugt:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
String urlString = "http://www.heise.de/newsticker/heise-top-atom.xml";
InputStream is = getInputStreamFromURL(urlString);
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db;
try {
db = dbf.newDocumentBuilder();
//parse rss feed
Document doc = db.parse(is);
doc.normalize();
if (is != null)
readXML(doc);
} catch (ParserConfigurationException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (NullPointerException e){
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
}
private void readXML(Document doc) throws NullPointerException
heißt unsere Methode, die das Document übergeben bekommt.
//get all elements named "entry"
NodeList nl = doc.getElementsByTagName("entry");
//check if NodeList has child elements
if (nl != null && nl.getLength() > 0) {
//if true, loop through all elements
for (int i = 0 ; i < nl.getLength(); i++) {Aus dem
Document holen wir uns alle Elemente, die den Tag
<entry> enthalten. Diese
NodeList überprüfen wir auf
Kindelemente. Falls Kindelemente vorhanden, gehen wir diese mit einer for-Schleife durch.
//get each entry
Element entry = (Element) nl.item(i);
//read the title of each entry
Element title = (Element) entry.getElementsByTagName("title").item(0);
String title = title.getFirstChild().getNodeValue();In der
for-Schleife holen wir uns den
i-ten Knoten aus der NodeList und holen uns alle Elemente, die den Tag
<title> enthalten. Diesen können wir dann in einem String speichern.
Der komplette Code der readXML Methode:
private void readXML(Document doc) throws NullPointerException {
//get all elements named "entry"
NodeList nl = doc.getElementsByTagName("entry");
//check if NodeList has child elements
if (nl != null && nl.getLength() > 0) {
//if true, loop through all elements
for (int i = 0 ; i < nl.getLength(); i++) {
//get each entry
Element entry = (Element) nl.item(i);
//read the title of each entry
Element title = (Element) entry.getElementsByTagName("title").item(0);
String title = title.getFirstChild().getNodeValue();
}
}
}
Zur besseren Darstellung der RSS Ergebnisse habe im Source Code noch einen ScrollView eingefügt, der ein LinearLayout enthält. Dieses LinearLayout fülle ich dynamisch in der for-Schleife mit TextViews und Platzhaltern zur Trennung der verschiedenen Einträge.
Die angepasste readXML-Methode:
private void readXML(Document doc) throws NullPointerException {
LinearLayout layout = (LinearLayout) findViewById(R.id.layout);
//get all elements named "entry"
NodeList nl = doc.getElementsByTagName("entry");
//check if NodeList has child elements
if (nl != null && nl.getLength() > 0) {
//if true, loop through all elements
for (int i = 0 ; i < nl.getLength(); i++) {
//get each entry
Element entry = (Element) nl.item(i);
//read the title of each entry
Element title = (Element) entry.getElementsByTagName("title").item(0);
//create TextView and set the title as text
TextView tv_title = new TextView(this);
tv_title.setText(title.getFirstChild().getNodeValue());
//create a spacer, which shows a horizontal line between each news
LinearLayout layout_spacer = new LinearLayout(this);
layout_spacer.setBackgroundColor(Color.DKGRAY);
layout_spacer.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, 3));
//add both new TextView and LinearLayout
layout.addView(tv_title);
layout.addView(layout_spacer);
}
}
}
So sieht die fertige App aus:

Source Code
Zum Schluss wie versprochen noch der komplette Source Code: hier downloaden