XML::RSS

(Auszug aus "Perl & XML" von Erik T. Ray & Jason McIntosh)

Unter einem Hilfsmodul verstehen wir im wesentlichen einen XML-Prozessor, wie wir ihn in den vergangenen Abschnitten bereits zuhauf in unseren Werkzeugkasten gelegt haben. Der Unterschied besteht in der klaren Ausrichtung auf eine bestimmte XML-Anwendung, die bei den allgemeinen XML-Prozessoren natürlich fehlt. In einer gewissen Weise sind selbst XML::Parser und Konsorten Hilfsmodule: Sie befreien uns von der Last, für jedes neue Stückchen XML neue Funktionen zu entwickeln, die mit der Dateiein- und -ausgabe und den regulären Ausdrücken von Perl entsprechende Dateien lesen und schreiben. Dasselbe gilt auch für XML::Writer und ähnliche Module, die uns in puncto XML weit über einfache print-Anweisungen hinausheben.

Die nun folgenden XML-Module sind allerdings ganz klar spezialisiert. Eines dieser Module in Ihr Programm einzubauen macht nur dann Sinn, wenn Sie sich für XML entschieden haben, aber nicht etwa für XML im allgemeinen, sondern für einen klar abgegrenzten Teilbereich. Dadurch gewinnen Sie die Möglichkeit, Module anzuwenden (und selbst zu schreiben), die Sie vom rohen XML befreien und nach außen hin eine Schnittstelle bieten, in der XML oft überhaupt nicht mehr spürbar ist.

Als Beispiel wollen wir uns mit XML::RSS beschäftigen – einer netten, kleinen Anwendung von Jonathan Eisenzopf.

Einführung in RSS

RSS (eine Abkürzung für Rich Site Summary oder Really Simple Syndication, das hängt davon ab, wen Sie fragen) ist eine der ersten XML-Anwendungen, die dank des Web schnell populär wurden. RSS ist ursprünglich nicht viel mehr als eine Möglichkeit für Administratoren von Websites, Metainformationen bereitzustellen: eine Übersicht über den Inhalt, Neuigkeiten, ein Logo und derlei Dinge. Es ist nicht weiter schwer, sich dafür ein Dokumentformat auszudenken, der entscheidende Punkt ist die Standardisierung, die durch RSS geschah. Was eine RSS-Anwendung mit der gelesenen Datei anfängt, das ist ihre Sache. Denkbar wäre eine Benachrichtigung per Mail oder eine Zusammenfassung auf einer eigenen, dynamischen HTML-Seite. Ein spezieller Typ von RSS-Programmen sind die Aggregatoren, die verschiedene Sites besuchen, die dortigen RSS-Dokumente lesen, und in neuen RSS-Dokumenten zusammenfassen.

Populäre Aggregatoren sind zum Beispiel Netscape durch dessen konfigurierbares my.netscape.com (tatsächlich handelte es sich dabei um die Geburtsstätte einer der ersten RSS-Versionen) oder Dave Winers "http://www.scripting.com" (dessen Aggregator nichts anderes als ein Frontend für "http://aggregator.userland.com/register" ist). Diese Aggregatoren fassen ihre Ergebnisse in neuem RSS zusammen, so daß man durch Lesen einer einzelnen RSS-Datei Informationen über eine Vielzahl anderer Quellen erhält. Websites, die systematisch Links und Informationen sammeln und präsentieren, benutzen diese Aggregatoren häufig, um sich Informationen über Websites zu verschaffen, die RSS anbieten, und diese dann weiter anzubieten. Ein gutes Beispiel ist der Meerkat-Server des O’Reilly Network ("http://meerkat.oreillynet.com").

Anwendung von XML::RSS

Das Modul XML::RSS ist sowohl beim Lesen fremder RSS-Dokumente als auch beim Verfassen eigener Dokumente sehr hilfreich. Natürlich kann man diese beiden Möglichkeiten auch kombinieren: ein Dokument lesen, modifizieren und dann wieder zurückschreiben. Das Modul stellt ein einfaches und gut dokumentiertes Objektmodell bereit, durch das RSS-Dokumente in den Hauptspeicher abgebildet werden. Damit ähnelt es den baumverarbeitenden Modulen, die wir bereits kennen. Sie können sich diese Art eines XML-Hilfsmoduls ganz gut als geschickte Verpackung eines allgemeineren Werkzeugs vorstellen.

In den folgenden Beispielen arbeiten wir mit einem »Web-Log«, also einer Art Tagebuch, einer häufig geänderten und im Web lesbaren Datei mit persönlichen Informationen, Erlebnissen oder Ansichten. RSS eignet sich hervorragend für solche Web-Logs, speziell für die Zusammenfassung der aktuellsten Einträge in ein abgeleitetes RSS-Dokument.

Nachfolgend finden Sie ein Beispiel eines Web-Log (zugegeben an der unteren Grenze dessen, was man mit RSS anfangen kann, aber es geht uns ja mehr um ein kurzes Beispiel). In unserem Webbrowser könnte das ganze so aussehen:

18. Oktober 2002, 19:07:06

Habe heute den Laboraffen 45-X gefragt, wie er sich nach seinem jüngsten Sieg im Schach gegen Dr. Tscharotschkin fühlt. Als Antwort biß er mir ins Knie. (Der Affe, nicht Dr. Tscharotschkin.) Ich habe den Eindruck, daß dies ein wesentlicher Durchbruch in unserer Kommunikation sein könnte. Davon abgesehen könnte es eine ziemliche Schwellung geben, was die Freude etwas trübt.

27. Oktober 2002, 22:56:11

Nebenbei bemerkt, Dr. Berkemers Forschungen über einen Vergleich zwischen grünen und purpurroten Affen in Bezug auf die trans-soziopolitische Wirkung der Farbe scheinen keine rechten Fortschritte zu machen, zumindest hört man seit einigen Wochen nichts Neues. Heute hat er erfahren, daß sein Assistent farbenblind ist, was er in den Bewerbungsunterlagen zu erwähnen vergaß. Oh, Mann!

Hier sehen wir dasselbe Dokument, aber verpackt in RSS v1.0:

<?xml version="1.0" encoding="ISO-8859-1"?>
<rdf:RDF
    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    xmlns="http://purl.org/rss/1.0/"
    xmlns:dc="http://purl.org/dc/elements/1.1/"
    xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/"
    xmlns:syn="http://purl.org/rss/1.0/modules/syndication/"
    >
    <channel rdf:about="http://www.jmac.org/linklog/">
        <title>Thomas T’s Tagebuch</title>
        <link>http://www.jmac.org/linklog/</link>
        <description>Das Online-FOnline-Forschungsjournalorschungsjournal von Dr. Thomas Thaler</
            description>
            <dc:language>de-DE</dc:language>
            <dc:rights>Copright 2002 Dr. Thomas Thaler</dc:rights>
            <dc:date>2002-10-27 23:59:15 MESZ</dc:date>
            <dc:publisher>tthaler@jmac.org</dc:publisher>
            <dc:creator>tthaler@jmac.org</dc:creator>
            <dc:subject>tthaler</dc:subject>
            <syn:updatePeriod>daily</syn:updatePeriod>
            <syn:updateFrequency>1</syn:updateFrequency>
            <syn:updateBase>2002-03-03 00:00:00 MESZ</syn:updateBase>
            <items>
                <rdf:Seq>
                    <rdf:li rdf:resource="http://www.jmac.org/linklog?2002-10-27#22:56:11" />
                    <rdf:li rdf:resource="http://www.jmac.org/linklog?2002-10-18#19:07:06" />
                </rdf:Seq>
            </items>
    </channel>
    <item rdf:about="http://www.jmac.org/linklog?2002-10-27#22:56:11">
        <title>27. Oktober 2002, 22:56:11</title>
        <link>http://www.jmac.org/linklog?2002-10-27#22:56:11</link>
        <description>
            Habe heute den Laboraffen 45-X gefragt, wie er sich nach seinem jüngsten Sieg
            im Schach gegen Dr. Tscharotschkin fühlt. Als Antwort biß er mir ins Knie. (Der
            Affe, nicht Dr. Tscharotschkin.) Ich habe den Eindruck, daß dies ein wesentlicher
            Durchbruch in unserer Kommunikation sein könnte. Davon abgesehen könnte es eine
            ziemliche Schwellung geben, was die Freude etwas trübt.
        </description>
    </item>
    <item rdf:about="http://www.jmac.org/linklog?2002-10-18#19:07:06">
        <title>18. Oktober 2002, 19:07:06</title>
        <link>http://www.jmac.org/linklog?2002-10-18#19:07:06</link>
        <description>
            Nebenbei bemerkt, Dr. Berkemers Forschungen über einen Vergleich zwischen grünen
            und purpurroten Affen in Bezug auf die trans-soziopolitische Wirkung der Farbe
            scheinen keine rechten Fortschritte zu machen, zumindest hört man seit einigen
            Wochen nichts Neues. Heute hat er erfahren, daß sein Assistent farbenblind ist,
            was er in den Bewerbungsunterlagen zu erwähnen vergaß. Oh, Mann!
        </description>
    </item>
</rdf:RDF>

Beachten Sie, daß RSS 1.0 verschiedenste Namensräume nutzt, um Metadaten über den anschließend folgenden Inhalt bereitzustellen.

Wir achten hier peinlich genau darauf, die RSS-Version anzugeben, da die Versionen 0.9 und 0.91 eine deutlich andere Struktur hatten. Die alte Struktur war einfacher, es gab weder Namensräume noch RDF-verpackte Metadaten. Verwendet wurde statt dessen eine einfache Liste aus <item>-Elementen, eingebettet in ein <rss>-Element. Aus diesem Grund bevorzugen viele Anwender nach wie vor die älteren RSS-Versionen. Freundliche RSS-Software liest und schreibt alle diese Versionen. XML::RSS ist eine solche Software und kann als Nebeneffekt auch für die Konvertierung zwischen beiden Formaten verwendet werden.

Wenn Sie Lust haben, geben Sie doch einmal die angegebenen URIs dieser Namensräume in Ihren Browser ein, da man darunter tatsächlich die zugehörige Dokumentation findet. Bei »dc« handelt es sich um den »Dublin Core«, ein standardisierter Satz von Elementen zur Beschreibung der Quelle eines Dokuments. Dagegen steht »syn« für Synchronisation, eine Handvoll Elemente, die beschreiben, wie lange ein Dokument aktuell ist und wann es erneuert werden sollte. Abschließend wird das eigentliche Dokument in RDF verpackt wiedergegeben.

Parsen

Mit XML::RSS ein existierendes Dokument zu lesen, wird Ihnen vertraut vorkommen. Es ähnelt dem, was wir in den vergangenen Abschnitten schon gemacht haben und ist recht einfach:

use XML::RSS;

# Dateinamen von der Kommandozeile lesen    
my @rss_docs = @ARGV;

# Wir nehmen an, die Dateien stehen alle auf der Festplatte...
foreach my $rss_doc (@rss_docs) { 
  
    # Ein neues RSS-Objekt erzeugen, um das zu lesende Dokument zu repräsentieren
    my $rss = XML::RSS->new;
      
    # Und jetzt lassen wir’s lesen
    $rss->parsefile($rss_doc);
      
    # Das wär’s schon. Hier kann jeder machen, was er will.
}
Vererbung von XML::Parser

Wenn Ihnen die Methode parsefile bekannt vorkam, dann mit gutem Grund: Es handelt sich dabei tatsächlich um die des guten alten Großvaters XML::Parser.

XML::RSS macht sich die Erweiterbarkeit von XML::Parser zunutze, indem es das Modul in sein @ISA-Array einträgt, wenn es geladen wird.

Wenn Sie mit objektorientierter Programmierung unter Perl vertraut sind, wird es Sie nicht überraschen, daß XML:RSS zwar eigene Methoden definiert, davon abgesehen aber nicht viel mehr tut, als SUPER::new aufzurufen. Die Initialisierung von XML::Parser funktioniert also ganz wie gehabt. Schauen wir uns ein Stück Quellcode aus dem Modul an – genauer gesagt, den Konstruktor new, den wir im obigen Beispiel benutzt haben:

sub new {    
    my $class = shift;
    my $self = $class->SUPER::new(Namespaces => 1,
    NoExpand => 1,
    ParseParamEnt => 0,
    Handlers => { Char => \&handle_char,
    XMLDecl => \&handle_dec,
    Start => \&handle_start } );
    bless ($self,$class);
    $self->_initialize(@_);
    return $self;
}

Beachten Sie, wie das Modul die new -Methode seiner Superklasse mit sehr spezifischen Argumenten aufruft. Alle diese Instruktionen sind normale und gut dokumentierte Optionen aus der öffentlichen Schnittstelle von XML::Parser . Die Subklasse XML::RSS unterbindet die Möglichkeit des Benutzers, solche Parameter selbst zu wählen, weil das Modul sehr genaue Erwartungen an das hat, was es bekommen will – in unserem Fall ein Parserobjekt mit der Fähigkeit zur Verarbeitung von Namensräumen, aber ohne Auflösung oder Parsen von Parameterentities. Vor allem aber muß die Subklasse ihre eigenen Handler setzen.

Das Ergebnis des Aufrufs von SUPER::new ist eine Instanz der Klasse XML::Parser , die das Subklassenmodul tunlichst vor seinen Anwendern versteckt – wozu wäre die ganze Abstraktion sonst auch gut! Aus diesem Grund wird ein erneutes bless durchgeführt (um aus $self eine Instanz der eigenen Klasse zu machen), dabei verwenden wir die Perl-itisch korrekte Variante der Methode mit den zwei Argumenten. Das zurückgegebene Objekt behauptet nun, es sei von XML::RSS erzeugt worden und nicht von XML::Parser .

Das Objektmodell

Wir haben gesehen, daß XML::RSS keine besonders originellen Wege geht, um einen Parser zu erzeugen und ein Dokument zu lesen. Wo beginnt nun die eigentliche Domäne dieses Moduls, d. h. der Aufbau eigener Datenstrukturen und die Bereitstellung einer Schnittstelle von Methoden?

Der Code von XML::RSS besteht zum großen Teil aus Accessormethoden, die im wesentlichen nur Daten in die selbst aufgebaute Struktur eintragen bzw. daraus lesen. XML::RSS benutzt dabei nichts, das komplizierter wäre als ein paar verschachtelte Hashreferenzen. Die Schlüssel dieser Hashreferenzen tragen üblicherweise die Element- oder Attributnamen, auf die sie bei der Konvertierung in ein echtes Dokument abgebildet werden. Um die verschiedenen RSS-Versionen unter einen Hut zu bringen, werden Maps benutzt, um eine Abbildung zwischen den verschiedenen Namen durchzuführen. Hier ist zum Beispiel eine solche Map für RSS 0.9:

my %v0_9_ok_fields = (    
    channel => {
        title => '',
        description => '',
        link => '',
        },
    image => {
        title => '',
        url => '',
        link => ''
        },
    textinput => {
        title => '',
        description => '',
        name => '',
        link => ''
        },
    items => [],
    num_items => 0,
    version => '',
    encoding => ''
);

Wir sehen hier, daß es leicht übertrieben war, wenn wir von »nur ein paar Hashreferenzen« gesprochen haben: Das Element items auf der obersten Ebene muß eine Arrayreferenz aufnehmen können. Dagegen haben alle anderen Schlüssel skalare Werte, was wir hier jeweils durch den Leerstring repräsentiert finden. Es gibt eine einzige Ausnahme, nämlich num_items, das von RSS nicht vorgesehen ist. Es enthält die Anzahl der Elemente im Array der Items und ist aus reiner Bequemlichkeit da, um einfach $ref->{num_items} statt @{$ref->{items}} schreiben zu können.

Der Nachteil eines solchen Hilfsschlüssels ist, daß der Programmierer einen aktuellen Stand dieser Variablen sicherstellen muß. Was hier besser ist, ist eher eine Frage des persönlichen Geschmacks und kein Gegenstand dieses Buchs.

Abgesehen davon, daß die Schlüssel eines Hashs auch irgendeinen Wert brauchen ( undef ist in diesem Fall nur eine spezielle Variante von »irgendeinen«), gibt es noch andere gute Gründe für eine Map. Jeder Hash hat die Zusatzaufgabe, einer Funktion des Moduls als Anleitung und Template für die zu generierende bzw. korrekte Datenstruktur zu dienen. Behalten wir das im Gedächtnis und schauen uns an, was passiert, wenn eine XML::Parser-Instanz zum Aufbau eines neuen Dokuments verwendet wird.

Eingabe: Anwender oder Datei

Nachdem wir das XML::RSS-Objekt erzeugt haben, steht es für die Ausgabe eines RSS-Dokuments bereit. Das verdanken wir im wesentlichen dem stolzen Vater, also XML::Parser. Ein Anwender muß lediglich die Methoden parse oder parsefile aufrufen, und das Objekt wird wie von selbst mit Daten gefüllt.

Davon abgesehen mag es manche dieser Objekte geben, die ein langes und produktives Leben führen, ohne jemals in ein vorhandenes XML-Dokument zu beißen. RSS-Anwender bauen oft ein Dokument auch ganz neu auf – oder konvertieren die in einem ganz anderen Format vorliegende Eingabe in RSS. An dieser Stelle werden die Accessormethoden nützlich.

Nehmen wir zum Beispiel an, wir hätten irgendwo eine SQL-Datenbank mit Web-Log-Einträgen herumliegen, die wir gerne in RSS umwandeln wollten. Das leistet dieses kleine Skript:

#!/usr/bin/perl        

# Umwandlung der letzten 15 Einträge von Dr. Thalers Weblog in ein
# RSS-1.0-Dokument, das auf STDOUT ausgegeben wird.

use warnings;
use strict;

use XML::RSS;
use DBIx::Abstract;

my $MAX_ENTRIES = 15;

my ($output_version) = @ARGV;
$output_version ||= '1.0';
unless ($output_version eq '1.0' or $output_version eq '0.9'
                        or $output_version eq '0.91') {
      die "Usage: $0 [version]\n" .
        "Wobei [version] eine gültige RSS-Version sein muß, der das auszugebende\n" .
        "Dokument entsprechen muß: 0.9, 0 .91 oder 1.0 (Vorgabe).\n";
}

my $dbh = DBIx::Abstract->connect({dbname=>'weblog',
                        user=>'thaler',
                        password=>'thomas'})
      or die "Verbindung zur Datenbank konnte nicht hergestellt werden.\n";
my ($date) = $dbh->select('max(date_added)',
                        entry')->fetchrow_array;
my ($time) = $dbh->select('max(time_added)',
                        'entry')->fetchrow_array;
my $time_zone = "MESZ";
my $rss_time = "${date}$time $time_zone";
# Die Ausgangszeit ist der Beginn des Web-Log, die Synchronisationsinformationen
# benötigen diese Angabe.
my $base_time = "2001-03-03 00:00:00 $time_zone";

# Das folgende ist für RSS-Version 1.0 implementiert. Einige Metainformationen gehen
# in 'modules', d.h. sie haben ihre eigenen Namensräume wie 'dc' (Dublin Core) oder
# 'syn' (für RSS Sychronisation), aber glücklicherweise schadet es nicht, wenn das
# Dokument etwas komplizierter wird, wie wir unten noch sehen werden ...

my $rss = XML::RSS->new(version=>'1.0', output=>$output_version);

$rss->channel(
                        title=>'Thomas T’s Tagebuch',
                        link=>'http://www.jmac.org/linklog/',
                        description=>"Das Online-Forschungsjournal von Dr. Thomas Thaler",
                        dc=> {
                                date=>$rss_time,
                                creator=>'llink@jmac.org',
                                rights=>'Copright 2002 Dr. Thomas Thaler',
                                language=>'de-DE',
                                },
                        syn=> {
                                updatePeriod=>'daily',
                                updateFrequency=>1,
                                updateBase=>$base_time,
                        },
);

$dbh->query("select * from entry order by id desc limit $MAX_ENTRIES");
while (my $entry = $dbh->fetchrow_hashref) {
        # Ersetze die für XML kritischen Zeichen durch Entities
        $$entry{entry} =~ s/&/&/g;
        $$entry{entry} =~ s/</&lt;/g;
        $$entry{entry} =~ s/'/&apos;/g;
        $$entry{entry} =~ s/"/&quot;/g;
        $rss->add_item(
                title=>"$$entry{date_added} $$entry{time_added}",
                link=>"http://www.jmac.org/weblog?$$entry{date_added}#$$entry{time_added}",
                description=>$$entry{entry},
                        );
}

# Ergebnis wird einfach nach STDOUT geschrieben.
print $rss->as_string;

Haben Sie in diesem Beispiel etwas von XML bemerkt? Wir nicht. Na gut, wir haben ein bißchen mit regulären Ausdrücken gespielt, um die XML-Sonderzeichen zu ersetzen. Davon abgesehen haben wir aus einer Datenbank gelesen und ein Objekt durch Methodenaufrufe mit Daten gefüllt (genauer gesagt, in einer Schleife die Methode add_item aufgerufen). Diese Aufrufe haben als einziges Argument einen Hash aus ganz gewöhnlichen Strings akzeptiert. Dieses Programm macht sich zwar alles zunutze, was RSS zu bieten hat, aber die Produktion der RSS-Datei hatte vordergründig trotzdem nicht viel mit XML zu tun.

Ausgabe so einfach wie möglich

Wo wir gerade dabei sind: XML::RSS verwendet kein Hilfsmodul wie XML::Writer zur Erzeugung von XML. Es baut einfach einen langen String auf, der mit dem Inhalt der Hash-Maps gefüllt wird, indem es eine Reihe von if-, else- und elsif-Konstrukten durchläuft. Dabei wird der Operator .= für das Anhängen eines Strings reichlich verwendet. Wenn Sie externe Module zum Schreiben von XML für überflüssig halten, ist das durchaus eine Alternative. Man spart eine Menge Aufwand und behält die vollständige Kontrolle über alles, was abläuft. Allerdings bezahlen Sie mit einem gehörigen Maß an Sicherheit: Sie sind selbst dafür verantwortlich, die Wohlgeformtheit sicherzustellen. (Im Fall eines Moduls wie XML::RSS, das sowohl für die Eingabe als auch für die Ausgabe zuständig ist, kann ein Teil dieser Aufgabe wohl auf die sorgfältige Verifizierung der Eingabedaten verlagert werden.)

  

  

<< zurück vor >>

 

 

 

Tipp der data2type-Redaktion:
Zum Thema Perl & XML bieten wir auch folgende Schulungen zur Vertiefung und professionellen Fortbildung an:

Copyright © 2003 O'Reilly Verlag GmbH & Co. KG
Für Ihren privaten Gebrauch dürfen Sie die Online-Version ausdrucken.
Ansonsten unterliegt dieses Kapitel aus dem Buch "Perl & XML" denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

O’Reilly Verlag GmbH & Co. KG, Balthasarstraße 81, 50670 Köln, kommentar(at)oreilly.de