XML::DOM

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

Das Modul XML::DOM von Enno Derkson ist ein guter Startpunkt für die Arbeit mit DOM in Perl. Dabei handelt es sich um eine vollständige Implementierung von DOM1 mit einigen wenigen zusätzlichen Methoden, die der Bequemlichkeit dienen. Enthalten ist das Modul XML::DOM::Parser, eine Erweiterung von XML::Parser, die das Lesen einer Instanz von XML::DOM::Document aus einer XML-Datei ermöglicht. Diese Instanz ist das DOM-Dokument mit dem darin enthaltenen Baum. Und erfreulicherweise kann man feststellen, daß dieses DOM-Dokument und seine Objekte dann genau so funktionieren, wie wir das oben beschrieben haben.

Wir zeigen als Beispiel ein Programm, das zur Verarbeitung einer XHTML-Datei DOM einsetzt. Das Programm sucht innerhalb von <p>-Elementen nach dem Wort »Affen«, das dann durch einen Link auf wilhelma.de (Die »Wilhelma«, der Zoologisch-Botanische Garten Stuttgarts) ersetzt wird. Das könnte man auch mit einem gewöhnlichen regulären Ausdruck erledigen, aber unser Beispiel zeigt, wie man nach Knoten sucht, neue erzeugt, Werte liest und setzt – und das alles im typischen Stil von DOM.

Der erste Teil des Programms erzeugt ein Parserobjekt. Das Objekt erhält anschließend den Auftrag, mit parsefile( ) eine Datei zu lesen:

use XML::DOM;
 
&process_file( shift @ARGV );
 
sub process_file {     
     my $infile = shift;
     my $dom_parser = new XML::DOM::Parser; # Erzeugung eines Parserobjekts
     my $doc = $dom_parser->parsefile( $infile ); # Laß den Parser eine Datei lesen
     &add_links( $doc ); # Unsere Änderungen werden durchgeführt
     print "<?xml version=\"1.0\" encoding=\"ISO-8859-1\"?>\n",
          $doc->toString; # Ausgabe des Baums
     $doc->dispose; # Freigabe des Speichers
}

Diese Methode liefert als Ergebnis eine Referenz auf ein XML::DOM::Document-Objekt, das uns den Zugriff auf die enthaltenen Knoten ermöglicht. Wir übergeben diese Referenz an die Funktion add_links( ), die die gesamten Modifikationen erledigt. Abschließend geben wir den Baum durch einen Aufruf von toString( ) aus und geben das Objekt frei. Dieser letzte Schritt ist erforderlich, weil ein DOM-Baum zirkuläre Referenzen enthält, was ohne explizite Freigabe zu einem Speicherleck führen könnte.

Der nächste Teil des Codes klettert den Baum hinauf, um die Absätze zu bearbeiten:

sub add_links {
    my $doc = shift;    
      
    # Finde alle <p>-Elemente
    my $paras = $doc->getElementsByTagName( "p" );
    for( my $i = 0; $i < $paras->getLength; $i++ ) {
        my $para = $paras->item( $i );
          
        # Finde alle im <p>-Element enthaltenen Textknoten und bearbeite sie
        my @children = $para->getChildNodes;
        foreach my $node ( @children ) {
        &fix_text( $node ) if( $node->getNodeType eq TEXT_NODE );
        }
    }
}

Die Funktion add_links( ) beginnt mit einem Aufruf der Methode getElementsByTagName( ) des Dokumentobjekts. Das Ergebnis ist ein Objekt der Klasse XML::DOM::NodeList mit allen im Dokument enthaltenen <p>-Elementen (es ist so leicht, über beliebig viele Hierarchiestufen zu suchen). Die Methode item( ) ermöglicht uns, die einzelnen Elemente zu bearbeiten.

Unser zu suchendes Wort verbirgt sich in einem Textknoten innerhalb eines <p>-Elements. Wir müssen also über die Kindknotenliste des Elements iterieren. Das erledigt der Aufruf getChildNodes( ) für uns, der uns entweder eine ganz normale Perl-Liste (beim Aufruf in einem Arraykontext) oder ein weiteres Objekt vom Typ XML::DOM::NodeList gibt. Wir wählen die erste Variante, um ein bißchen Abwechslung zu haben. Jeder Knoten wird mit Hilfe von getNodeType überprüft, indem das Ergebnis mit der XML::DOM-Konstante TEXT_NODE( ) verglichen wird. Passende Knoten werden an die Funktion übergeben, die die eigentliche Änderung durchführt.

Der letzte Teil des Programms bearbeitet einen solchen Textknoten und ersetzt das Wort »Affen« durch einen Link:

sub fix_text {     
     my $node = shift;
     my $text = $node->getNodeValue;
     if( $text =~ /(affen)/i ) {
  
          # Aufteilung des Textknoten in zwei Textknoten um das Wort Affen herum
          my( $pre, $orig, $post ) = ( $`, $1, $' );
          my $tnode = $node->getOwnerDocument->createTextNode( $pre );
               $node->getParentNode->insertBefore( $tnode, $node );
          $node->setNodeValue( $post );
  
          # Füge ein <a>-Element zwischen den beiden Knoten ein
          my $link = $node->getOwnerDocument->createElement( 'a' );
          $link->setAttribute( 'href', 'http://www.wilhelma.de/' );
          $tnode = $node->getOwnerDocument->createTextNode( $orig );
          $link->appendChild( $tnode );
       
          $node->getParentNode->insertBefore( $link, $node );
          # Rekursion auf dem Rest des Textknotens, falls das
          # Wort mehrfach vorkommt
          fix_text( $node );
     }
}

Die Funktion liest den Textstring mit der Methode getNodeValue( ). DOM spezifiziert den lesenden bzw. schreibenden Zugriff auf diesen Textstring mit zwei redundanten Properties, zum einen durch die generische Node -Methode getNodeValue( ), zum anderen durch die spezifische Methode getData( ) aus der Text-Klasse, die wir genausogut verwenden könnten. Es gibt auch Knotentypen, für die getNodeValue( ) einfach undef liefern würde, zum Beispiel wäre das bei Elementen der Fall.

Als nächstes zerlegen wir diesen Knoten in zwei Textknoten. Das geschieht, indem wir einen neuen Textknoten erzeugen und vor dem alten einfügen. Der erste Textknoten erhält den vor dem Wort »Affen« stehenden Text, der zweite den nachfolgenden Text. Beachten Sie die Verwendung des XML::DOM::Document-Objekts als Factory für die Erzeugung des neuen Knotens. Dieses Feature von DOM kümmert sich ohne zu murren um eine Menge Hausarbeit, die wir sonst selbst erledigen müßten.

Als nächsten Schritt erzeugen wir ein <a>-Element, das zwischen den beiden Textknoten eingefügt wird. Ein ordentlicher Link benötigt eine URL, wir fügen sie mit Hilfe des href-Attributs ein. Ein Link benötigt auch den Text, den man anklicken kann. Wir beschaffen ihn in Form eines weiteren Textknotens mit dem Wort »Affen« und fügen ihn in die Kindknotenliste des neuen Elements ein. Falls das Wort »Affen« mehrfach vorkommt, setzen wir die Bearbeitung des Textknotens rekursiv fort.

Funktioniert das Ganze auch? Probieren Sie’s aus, indem Sie das Programm auf die folgende Datei anwenden:

<?xml version="1.0" encoding="ISO-8859-1"?>
<html>
<head><title>Warum ich Affen mag</title></head>
<body><h1>Warum ich Affen mag</h1>
<h2>Affen sind niedlich</h2>
<p>Affen sind <b>niedlich</b>. Sie sind wie kleine, putzige Versionen
von uns selbst. Sie können unglaubliche Grimassen schneiden und strecken
ihre Zungen heraus.</p>
</body>
</html>

Dies produziert die folgende Ausgabe:

<?xml version="1.0" encoding="ISO-8859-1"?>
<html>
<head><title>Warum ich Affen mag</title></head>
<body><h1>Warum ich Affen mag</h1>
<h2>Affen sind niedlich</h2>
<p><a href="http://www.wilhelma.de/">Affen</a>
sind <b>niedlich</b>. Sie sind wie kleine, putzige Versionen
von uns selbst. Sie können unglaubliche Grimassen schneiden und strecken
ihre Zungen heraus.</p>
</body>
</html>

  

  

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