XML Abfragen

Artikel Bild
Es gibt viele Möglichkeiten, um XML-Dateien auszulesen und abzufragen. Hier findet ihr eine davon.

Das ist die Art von Artikeln, die ich sehr gerne schreibe. Oft entsteht die Idee dazu, weil mir nichts Besseres einfällt. Dann greife ich gerne auf eigene Herausforderungen oder Anwendungsfälle zurück. Eigentlich wollte ich die nächste Podcastfolge mit Joël über die Alpha 5 des COSMIC-Desktops aufnehmen. Doch dann fiel mir auf, dass wir bereits im Juli 2024 in CIW095 über COSMIC gesprochen haben. Wir haben uns dann schnell auf ein anderes Thema geeinigt; ihr werdet es am Mittwoch in CIW121 hören.

Bei den vielen Artikeln und Podcastfolgen wird es immer schwerer, den Überblick zu behalten. Über was haben wir schon geschrieben? Worüber haben wir bereits gesprochen? Und so ist die Idee für diesen Artikel entstanden. Ich brauche eine Liste aller Podcastfolgen, in der ich schnell nachschauen kann, was wir wann behandelt haben. Während ich diese Einleitung schreibe, habe ich noch keine Ahnung, wie die Lösung aussehen wird.

Wie könnte es gehen?

Zuerst wollte ich ein kleines Python-Skript schreiben, welches sich per FTP mit unserem Server verbindet und dort das Artikelverzeichnis ausliest. Daraus hätte ich die Podcastfolgen extrahiert und die gewünschten Daten aus den Shownote-Artikeln geparst. Unser CMS (Bludit) legt für jeden Artikel ein Verzeichnis an, in dem sich eine Datei mit dem Inhalt befindet.

Dann kam mir in den Sinn, dass wir XML-Dateien für die RSS-Feeder haben. Einen für alle Artikel und einen anderen für den Podcast. Auf unserer Webseite findet man beide ganz unten auf der Seite beim RSS- und dem Mikrofon-Symbol. Bei der Verwendung des Podcast-XMLs würde ich mir das Filtern auf Podcastartikel sparen. Also habe ich diese Datei lokal heruntergeladen; einen Ausschnitt seht ihr im Titelbild.

Mit XML umgehen

Nun stellt sich die Frage, welche Inhalte der XML-Datei ich für meine Liste brauche. Ein Podcast-Eintrag aus der Datei sieht so aus:

<item> <title>CIW119 - Ditana</title> <link>https://gnulinux.ch/ciw119-podcast</link> <description><![CDATA[Wir werfen einen Blick auf die neue Linux Distribution Ditana]]></description> <content:encoded><![CDATA[<h2>CIW - Folge 119 - 15.01.2025 - Ditana</h2><ul><li>Wir begrüssen alle Distro-Hopper zur Folge 119 von ...</li></ul>]]></content:encoded> <author>GNU/Linux.ch</author> <pubDate>Wed, 15 Jan 2025 11:45:35 +0000</pubDate> <guid>https://gnulinux.ch/ciw119-podcast</guid> <enclosure url="https://gnulinux.ch/podcast/CIW119.mp3" length="97219675" type="audio/mpeg" /> <itunes:summary>Wir werfen einen Blick auf die neue Linux Distribution Ditana</itunes:summary> <itunes:author>GNU/Linux.ch</itunes:author> <itunes:duration>4856</itunes:duration> <itunes:explicit>no</itunes:explicit> <itunes:title>CIW119 - Ditana</itunes:title> </item>

Die Tags <title>, <pubDate> und <itunes:summary> würden mir genügen. Doch wie geht das? Nach kurzer Suche im Internet bin ich auf das CLI-Werkzeug xmllint gestossen. Dabei handelt es sich um einen XML-Linter. Ein Linter ist ein Werkzeug zur statischen Code-Analyse. Xmllint kann XML formatieren, validieren und via Xpath abfragen. So wie ich das einschätze, ist xmllint bei den meisten Distributionen vorinstalliert.

Schritt für Schritt

Im ersten Schritt habe ich den Titel aus der RSS-XML-Datei abgefragt. Das geht so:

xmllint --xpath '//item/title' gnulinux_newscast_rss.xml <title>CIW120 - Asocial Media Flucht</title> <title>CIW119 - Ditana</title> <title>CIW118 - Happy GNU Year</title> <title>CIW117 - Steuererklärung</title> ...

Den Xpath kann man absolut (/rss/channel/item/title) oder relativ (//item/title) angeben, was nichts am Ergebnis ändert. Dummerweise ist der Text vom Title-Tag umrandet. Zwar gibt es bei xmllint dieses Kommando, welches die Tags entfernt:

xmllint --xpath 'string(//item/title)' xml-Datei

… was leider nur die erste Instanz zurückgibt. Daher habe ich die Ausgabe in eine Datei geschrieben:

xmllint --xpath '//item/title' gnulinux_newscast_rss.xml > title.txt

Um die Titel-Klammer zu entfernen, kam der Stream-Editor sed zum Einsatz; einmal für das vordere Tag und noch einmal für den hinteren Teil. Beim vorderen <title> geht das so:

sed -i 's/<title>//g' title.txt

Der Parameter -i sorgt dafür, dass das Ersetzen direkt in der angegebenen Datei title.txt stattfindet. Mit dem s sagt man, dass etwas ersetzt werden soll. Die Slashes sind Trennzeichen zwischen den Befehlsparametern. Dann gibt man an, was ersetzt werden soll (<title>) und wodurch es ersetzt werden soll (//), also gar nichts. Zum Schluss sagt man mit dem g, dass sich die Ersetzung auf alle Vorkommnisse beziehen soll (global). Danach sieht die Liste so aus:

CIW120 - Asocial Media Flucht</title> CIW119 - Ditana</title> CIW118 - Happy GNU Year</title> ...

Der vordere Tag wurde entfernt. Nun könnte man denken, dass sich der hintere Tag genauso einfach entfernen lässt. Theoretisch ja, praktisch steht jedoch der Slash als Trennzeichen im Weg. Der Befehl:

sed -i 's/</title>//g' title.txt

… funktioniert nicht, weil der Slash vor title zur Verwirrung bei sed führt. Ist das jetzt ein Trennzeichen, oder soll das ersetzt werden? Um das zu lösen, gibt es verschiedene Möglichkeiten, mit denen ich euch nicht langweilen möchte. Am einfachsten sagt ihr sed, dass ein anderes Trennzeichen verwendet werden soll, z. B. ein Semikolon:

sed -i 's;</title>;;g' title.txt

Nun sieht das Ergebnis so aus:

CIW120 - Asocial Media Flucht CIW119 - Ditana CIW118 - Happy GNU Year ...

… und das ist, was ich haben wollte. Um das Datum herauszufischen, mache ich das Gleiche in Grün:

xmllint --xpath '//item/pubDate' gnulinux_newscast_rss.xml > date.txt sed -i 's/<pubDate>//g' date.txt sed -i 's;</pubDate>;;g' date.txt

Doch beim Auslesen der Summary ergeben sich unerwartete Schwierigkeiten:

xmllint --xpath '//item/itunes:summary' gnulinux_newscast_rss.xml > summary.txt XPath error : Undefined namespace prefix XPath evaluation failure

Da bockt xmllint wegen des Namespaces in itunes:summary. Man kann in xmllint den Namespace angeben; leider habe ich nicht herausgefunden, wie man das macht. Das ist eine Frage an die Kommentatoren. Daher belasse ich es bei den beiden Feldern title und pubDate. Diese Listen stehen jetzt in den beiden Dateien title.txt und date.txt.

Zusammenführen

Jetzt stellt sich die Frage, wie man diese beiden Dateien zeilenweise zusammenführt. Dafür gibt es eine naheliegende Antwort: Ich kopiere beide Dateien als Spalten in LibreOffice Calc:

Doch geht das auch im Terminal? Nichts einfacher als das:

paste date.txt title.txt > ciw_folgen.txt Wed, 22 Jan 2025 11:34:04 +0000 CIW120 - Asocial Media Flucht Wed, 15 Jan 2025 11:45:35 +0000 CIW119 - Ditana Wed, 08 Jan 2025 11:33:57 +0000 CIW118 - Happy GNU Year Wed, 18 Dec 2024 11:31:36 +0000 CIW117 - Steuererklärung Wed, 11 Dec 2024 11:32:34 +0000 CIW116 - Aufmerksamkeitsökonomie

Das Datum könnte man noch auf 22 Jan 2025 reduzieren. Auch das geht mit einem Befehl im Terminal:

cut -b 6-16 date.txt > date_cut.txt

Mit cut schneide ich den Text von Position 6 bis 16 aus und schreibe das Ergebnis in die Datei date_cut.txt.

Zusammenfassung

Was ich hier mit vielen Worten beschrieben habe, lässt sich in einem Shell-Skript vereinen. Das manuelle Herunterladen der XML-Datei entfällt; das Skript erledigt das mit dem wget Befehl.

#!/bin/bash # Create a list of podcast episodes with date and title wget -q https://gnulinux.ch/podcast/gnulinux_newscast_rss.xml -O rss.xml xmllint --xpath '//item/title' rss.xml > title.txt sed -i 's/<title>//g' title.txt sed -i 's;</title>;;g' title.txt xmllint --xpath '//item/pubDate' rss.xml > date.txt sed -i 's/<pubDate>//g' date.txt sed -i 's;</pubDate>;;g' date.txt cut -b 6-16 date.txt > date_cut.txt paste date_cut.txt title.txt > ciw_folgen.txt echo 'Suche in: ciw_folgen.txt'

Das Ergebnis in der Datei ciw_folgen.txt sieht so aus:

22 Jan 2025 CIW120 - Asocial Media Flucht 15 Jan 2025 CIW119 - Ditana 08 Jan 2025 CIW118 - Happy GNU Year 18 Dec 2024 CIW117 - Steuererklärung ...

Es geht noch einfacher

Falls ihr euch erinnert, war die Frage, ob ein Thema (z. B.: COSMIC) in einer Podcastfolge bereits behandelt wurde. Obwohl die Ausgabe nicht schön ist, beantwortet dieser Einzeiler die Frage ebenfalls:

curl -s https://gnulinux.ch/podcast/gnulinux_newscast_rss.xml | grep cosmic

Statt dem Gebastel mit xmllint, sed, cut und paste, holt curl die XML-Datei im Silent-Modus und lässt die Suche mit grep darauf los. Vorteil: die vollständigen Shownotes werden durchsucht. Nachteil: das Ergebnis ist unübersichtlich.

Fazit

Es hält sich der hartnäckige Mythos, dass man für Linux die Kommandozeile bedienen und beherrschen muss. Dieser Mythos ist negativ belegt. Fakt ist, dass niemand die Kommandozeile bedienen muss, wenn er oder sie es nicht möchte.

Die Wahrheit ist: Wer das Terminal für sich entdeckt, hat Freude daran und erledigt viele Aufgaben in Null-Komma-Nichts. Traut euch, die Kommandozeile für euch zu entdecken. Die Community hilft gerne dabei.

Die oben beschriebene Aufgabenstellung ist ein anschauliches Beispiel dafür. Nun kann ich eine aktuelle Podcast-Liste in ein paar Sekunden erstellen und weiss immer, welche Themen wir schon besprochen haben. Ohne das Terminal würde ich viel länger brauchen, um diese Liste zu erzeugen.

Titelbild: selbst erstellt

Quellen: stehen im Text


GNU/Linux.ch ist ein Community-Projekt. Bei uns kannst du nicht nur mitlesen, sondern auch selbst aktiv werden. Wir freuen uns, wenn du mit uns über die Artikel in unseren Chat-Gruppen oder im Fediverse diskutierst. Auch du selbst kannst Autor werden. Reiche uns deinen Artikelvorschlag über das Formular auf unserer Webseite ein.