The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME

XML::Reader_de - Lesen von XML-Dateien und Bereitstellung der Pfad information basierend auf einem Pull-Parser.

ÜBERSETZUNG

This document is the German translation from English of the module XML::Reader. In order to get the Perl source code of the module, please see file XML/Reader.pm

Dieses Dokument ist die Deutsche Übersetzung aus dem Englischen des Moduls XML::Reader. Um den Perl Quelltext des Moduls zu lesen, gehen Sie bitte zur Datei XML/Reader.pm

SYNOPSIS

  use XML::Reader;

  my $text = q{<init>n <?test pi?> t<page node="400">m <!-- remark --> r</page></init>};

  my $rdr = XML::Reader->new(\$text) or die "Error: $!";
  while ($rdr->iterate) {
      printf "Path: %-19s, Value: %s\n", $rdr->path, $rdr->value;
  }

Dieses Programm erzeugt folgendes Resultat:

  Path: /init              , Value: n t
  Path: /init/page/@node   , Value: 400
  Path: /init/page         , Value: m r
  Path: /init              , Value:

BESCHREIBUNG

XML::Reader stellt ein einfach zu bedienendes Interface zur Verfügung mit dem man XML-Dateien sequentiell lesen kann (sogenanntes "pull-mode" parsing). Der aktuelle XML-Pfad wird ebenfalls gepflegt.

XML::Reader wurde als eine Hülle über dem bestehenden Modul XML::Parser entwickelt (ausserdem wurden einige Grundfunktionen des Moduls XML::TokeParser mit übernommen). Die bestehenden Module XML::Parser und XML::TokeParser ermöglichen beide das sequentielle Verarbeiten von XML-Dateien, jedoch wird in diesen Modulen der XML-Pfad nicht gepflegt. Ausserdem muss man mit den Modulen XML::Parser und XML::TokeParser die Unterscheidung zwischen Start-Tags, End-Tags und Text machen, was meiner Meinung nach die Sache verkompliziert (obwohl man auch dieselbe Situation in XML::Reader simulieren kann, und zwar durch die Option {filter => 4, mode => 'pyx'}, wenn es das ist was man will).

Es existiert auch ein Modul namens XML::TiePYX, welches ebenfalls das sequentielle Verarbeiten von XML-Dateien erlaubt (siehe http://www.xml.com/pub/a/2000/03/15/feature/index.html für eine Einführung in PYX). Aber dennoch, auch mit XML::TiePYX ist man gezwungen eine Unterscheidung zwischen Start-Tags, End-Tags und Text zu machen und der XML-Pfad wird auch nicht gepflegt.

Im Gegensatz dazu übersetzt XML::Reader die in der XML-Datei bestehenden Start-Tags, End-Tags und Text in XPath-ähnliche Ausdrücke, man erhält also nur einen Pfad und einen Wert, so einfach ist es. (Sollte man jedoch mit XML::Reader PYX-kompatible Ausdrücke erzeugen wollen, dann kann man das auch mit der Option {filter => 4, mode => 'pyx'}, wie zuvor erwähnt, erreichen).

Aber kommen wir zurück zur normalen Benutzung von XML::Reader, dieses hier ist eine Beispiel XML-Datei, kodiert in der Variablen '$line1':

  my $line1 = 
  q{<?xml version="1.0" encoding="iso-8859-1"?>
    <data>
      <item>abc</item>
      <item><!-- c1 -->
        <dummy/>
        fgh
        <inner name="ttt" id="fff">
          ooo <!-- c2 --> ppp
        </inner>
      </item>
    </data>
  };

Diese Beispiel XML-Datei kann man mit XML::Reader lesen, und zwar indem man die Methode iterate verwendet um das jeweils nächste XML-Element zu lesen. Danach kann man dann mit den Methoden path und value den Pfad und den aktuellen Wert lesen.

Man kann ausserdem, wenn man es denn so möchte, die jeweiligen Start- und End-Tags erkennen: Es existiert die Methode is_start, die genau dann 1 zurückgibt, wenn an der aktuellen Positon in der XML-Datei ein Start-Tag existiert, ansonsten gibt die Methode 0 zurück. Es existiert ebenso die zugehörige Methode is_end, die genau dann 1 zurückgibt, wenn nach der aktuellen Positon in der XML-Datei ein End-Tag existiert, ansonsten gibt die Methode 0 zurück.

Es existieren zusätzlich die Methoden tag, attr, type und level. Die Methode tag liefert den aktuellen Tag-Namen, attr liefert den Attribut-Namen, type liefert entweder 'T' für Text oder '@' für Attribute, level liefert die im Moment aktive Verschachtelungstiefe (das ist ein numerischer Wert >= 0)

Hier folgend wird, um das Prinzip zu erklären, ein Beispielprogramm aufgeführt, welches die vorangegangene XML-Datei in '$line1' einliest...

  use XML::Reader;

  my $rdr = XML::Reader->new(\$line1) or die "Error: $!";
  my $i = 0;
  while ($rdr->iterate) { $i++;
      printf "%3d. pat=%-22s, val=%-9s, s=%-1s, e=%-1s, tag=%-6s, atr=%-6s, t=%-1s, lvl=%2d\n", $i,
        $rdr->path, $rdr->value, $rdr->is_start, $rdr->is_end, $rdr->tag, $rdr->attr, $rdr->type, $rdr->level;
  }

...und das hier ist das Resultat:

   1. pat=/data                 , val=         , s=1, e=0, tag=data  , atr=      , t=T, lvl= 1
   2. pat=/data/item            , val=abc      , s=1, e=1, tag=item  , atr=      , t=T, lvl= 2
   3. pat=/data                 , val=         , s=0, e=0, tag=data  , atr=      , t=T, lvl= 1
   4. pat=/data/item            , val=         , s=1, e=0, tag=item  , atr=      , t=T, lvl= 2
   5. pat=/data/item/dummy      , val=         , s=1, e=1, tag=dummy , atr=      , t=T, lvl= 3
   6. pat=/data/item            , val=fgh      , s=0, e=0, tag=item  , atr=      , t=T, lvl= 2
   7. pat=/data/item/inner/@id  , val=fff      , s=0, e=0, tag=@id   , atr=id    , t=@, lvl= 4
   8. pat=/data/item/inner/@name, val=ttt      , s=0, e=0, tag=@name , atr=name  , t=@, lvl= 4
   9. pat=/data/item/inner      , val=ooo ppp  , s=1, e=1, tag=inner , atr=      , t=T, lvl= 3
  10. pat=/data/item            , val=         , s=0, e=1, tag=item  , atr=      , t=T, lvl= 2
  11. pat=/data                 , val=         , s=0, e=1, tag=data  , atr=      , t=T, lvl= 1

INTERFACE

Objekt Erstellung

Um ein Objekt vom Typ XML::Reader zu erstellen, wird folgende Syntax verwendet:

  my $rdr = XML::Reader->new($data,
    {strip => 1, filter => 2, using => ['/path1', '/path2']})
    or die "Error: $!";

Der Parameter $data (welcher immer mit angegeben werden muss) ist entweder der Name einer XML-Datei, oder eine Referenz auf eine Zeichenkette (sodass der Inhalt dieser Zeichenkette als XML verarbeitet werden kann), oder ein zuvor geöffnetes Dateihandle, z.B. \*STDIN, (in diesem Fall wird einfach das Filehandle benutzt um die XML-Daten zu lesen).

Hier ist ein Beispiel um ein Objekt des Typs XML::Reader mit einem einfachen Datei-Namen zu erzeugen:

  my $rdr = XML::Reader->new('input.xml') or die "Error: $!";

Hier ist ein weiteres Beispiel um ein Objekt des Typs XML::Reader mit einer Referenz auf eine Zeichenkette zu erzeugen:

  my $rdr = XML::Reader->new(\'<data>abc</data>') or die "Error: $!";

Hier ist noch ein weiteres Beispiel um ein Objekt des Typs XML::Reader mit einem zuvor geöffneneten Dateihandle zu erzeugen:

  open my $fh, '<', 'input.xml' or die "Error: $!";
  my $rdr = XML::Reader->new($fh);

Hier ist schliesslich ein Beispiel um ein Objekt des Typs XML::Reader mit \*STDIN zu erzeugen:

  my $rdr = XML::Reader->new(\*STDIN);

Eine oder mehrere Optionen können als eine Hash-Referenz hinzugefügt werden:

Option {parse_ct => }

Option {parse_ct => 1} ermöglicht es XML-Kommentare zu lesen, die Voreinstellung ist {parse_ct => 0}

Option {parse_pi => }

Option {parse_pi => 1} ermöglicht es processing-instructions und XML-Declarations zu lesen, die Voreinstellung ist {parse_pi => 0}

Option {using => }

Option {using => } ermöglicht es einen Teil-Baum der XML-Datei zu selektieren.

Die Syntax hierfür lautet: {using => ['/pfad1/pfad2/pfad3', '/pfad4/pfad5/pfad6']}

Option {filter => } und {mode => }

Option {filter => 2} oder {mode => 'attr-bef-start'} zeigt alle XML-Zeilen an, einschliesslich der Attribute.

Option {filter => 3} oder {mode => 'attr-in-hash'} entfernt die Attribut-Zeilen (d.h. alle Zeilen mit $rdr->type eq '@' werden entfernt). Anstelle dessen werden die Attribute in einer Hash-Referenz $rdr->att_hash zurückgeliefert.

Option {filter => 4} oder {mode => 'pyx'} bricht alle Zeilen in individuelle Start-Tags, End-Tags, Attribute, Kommentare und Processing-Instructions auf. Damit wird die Verarbeitung der XML-Datei im PYX-Format ermöglicht.

Option {filter => 5} oder {mode => 'branches'} selektiert nur die Daten für die vorgegebenen Wurzeln ("root"). Die Datenelemente jeder Wurzel werden in einer Array Referenz (wie spezifiziert durch den "branch" Parameter) gesammelt und dann zurückgegeben wenn der "branch" komplett ist. Diese Vorgehensweise liegt auf halbem Weg zwischen der Option "using" (wo alle Elemente einzeln zurückgeliefert werden) und der Funktion "slurp_xml" (wo alle Elemente in einem "branch" gesammelt werden, und alle "branches" dann am Ende in einer grossen Speicherstruktur auf einmal zurückgeliefert werden).

Die Syntax lautet {filter => 2|3|4|5, mode => 'attr-bef-start'|'attr-in-hash'|'pyx'|'branches'}, die Voreinstellung ist {filter => 2, mode => 'attr-bef-start'}

Option {strip => }

Option {strip => 1} entfernt sowohl führende, als auch am Ende befindliche Leerzeichen in Text und in Kommentaren. (Attributen bleiben hiervon unberührt). {strip => 0} lässt Text und Kommentare so wie sie in der XML-Datei existieren, mit alle Leerzeichen.

Die Syntax hierfür lautet {strip => 0|1}, die Voreinstellung ist {strip => 1}

Methoden

Ein erfolgreich erstelltes Objekt vom Typ XML::Reader stellt folgende Methoden zur Verfügung:

iterate

Liest das nächste XML-Element. Die Methode liefert den Wert 1 zurück wenn das Lesen erfolgreich war, oder undef falls das Ende der XML-Datei erreicht wurde.

path

Liefert den gesamten Pfad des aktuellen XML-Elements zurück, Attribute werden mit einem führenden '@'-Zeichen markiert.

value

Liefert den aktuellen Wert zurück (d.h. den Wert des aktuellen Textes oder den des aktuellen Attributes).

Bitte beachten Sie dass wenn {filter => 2 oder 3} selektiert wurde und wenn zusätzlich das aktuelle Element eine XML-Deklaration ist (d.h. $rdr->is_decl == 1), dann ist es ratsam den aktuellen Wert nicht zu berücksichtigen (er ist dann sowieso leer). Ein typisches Beispiel wäre:

  print $rdr->value, "\n" unless $rdr->is_decl;

Dieses oben angegebe Beispiel trifft jedoch nicht zu wenn {filter => 4} aktiv ist. In diesem falle genügt ein einfaches "print $rdr->value;":

  print $rdr->value, "\n";
comment

Liefert den aktuellen Kommentar zurück. Bevor man diesen Wert benutzt, sollte man mit $rdr->is_comment prüfen ob das aktuelle Element wirklich ein Kommentar ist.

type

Liefert den Typ des Wertes zurück: 'T' für Text, '@' für Attribute.

Falls Option {filter => 4} aktiviert ist, dann kann der Typ folgende Werte annehmen: 'T' für Text, '@' für Attribute, 'S' für Start-Tags, 'E' für End-Tags, '#' für Kommentare, 'D' für XML-Declarationen, '?' für Processing-Instructions.

tag

Liefert den aktuellen Tag-Namen zurück.

attr

Liefert den aktuellen Attribut-Namen zurück (liefert eine leere Zeichenkette zurück falls das aktuelle Element kein Attribut ist).

level

Zeigt die aktuelle Verschachtelungstiefe des XPath-Ausdruckes an (das ist ein numerischer Wert > 0)

prefix

Liefert den Präfix zurück der durch die Option {using => ...} entfernt wurde. Falls die Option {using => ...} nicht benutzt wurde, wird eine leere Zeichenkette zurückgegeben

att_hash

Liefert eine Referenz zu einem Hash zurück der die aktuellen Attribute eines Start-Tags enthält (der Hash ist leer falls der aktuelle Tag kein Start-Tag ist)

dec_hash

Liefert eine Referenz zu einem Hash zurück der die aktuellen Attribute einer XML-Declaration enthält (der Hash ist leer falls der aktuelle Tag keine XML-Declaration ist)

proc_tgt

Liefert den Ziel Wert (d.h. den ersten Teil) einer Processing-Instruction zurück (eine leere Zeichenkette wird zurückgegeben falls der aktuelle Tag keine Processing-Instruction ist)

proc_data

Liefert den Daten Wert (d.h. den zweiten Teil) einer Processing-Instruction zurück (eine leere Zeichenkette wird zurückgegeben falls der aktuelle Tag keine Processing-Instruction ist)

pyx

Liefert eine Zeichenkette im PYX-Format des aktuellen Tags zurück.

Das PYX-Format ist eine Zeichenkette deren erstes Zeichen eine spezielle Bedeutung hat. Dieses erste Zeichen einer jeweiligen PYX-Zeichenkette gibt den Typ des Ereignisses an mit dem man es zu tun hat: falls das erste Zeichen ein '(' ist, dann hat man es mit einem Start-Tag zu tun, wenn es ein ')' ist, dann hat man es mit einem End-Tag zu tun, wenn es ein 'A' ist, dann hat man es mit einem Attribut zu tun, wenn es ein '-' ist, dann hat man es mit einem Text zu tun, wenn es ein '?' ist, dann hat man es mit einer Processing-Instruction zu tun. (siehe http://www.xml.com/pub/a/2000/03/15/feature/index.html für eine Einführung in PYX)

Die Methode pyx macht nur Sinn falls die Option {filter => 4} aktiviert wurde, ansonsten wird undef zurückgeliefert.

is_start

Liefert 1 zurück falls die aktuelle Position ein Start-Tag ist, ansonsten wird 0 zurückgeliefert.

is_end

Liefert 1 zurück falls die aktuelle Position ein End-Tag ist, ansonsten wird 0 zurückgeliefert.

is_decl

Liefert 1 zurück falls die aktuelle Position eine XML-Declaration ist, ansonsten wird 0 zurückgeliefert.

is_proc

Liefert 1 zurück falls die aktuelle Position eine Processing-Instruction ist, ansonsten wird 0 zurückgeliefert.

is_comment

Liefert 1 zurück falls die aktuelle Position ein Kommentar ist, ansonsten wird 0 zurückgeliefert.

is_text

Liefert 1 zurück falls die aktuelle Position ein Text ist, ansonsten wird 0 zurückgeliefert.

is_attr

Liefert 1 zurück falls die aktuelle Position ein Attribut ist, ansonsten wird 0 zurückgeliefert.

is_value

Liefert 1 zurück falls die aktuelle Position ein Text oder ein Attribut ist, ansonsten wird 0 zurückgeliefert. Diese Methode ist insbesondere nützlich wenn {filter => 4, mode => 'pyx'} aktiv ist, in diesem Falle kann man damit testen ob es sinnvoll ist die Methode value() aufzurufen.

rx

Das ist der Index des aktuellen Branches (macht nur Sinn wenn {filter => 5, mode => 'branches'} gesetzt wurde).

rvalue

Das ist eine Referenz (auf einen Scalar-Wert oder auf ein Array) für den aktuellen Branch. (macht nur Sinn wenn {filter => 5, mode => 'branches'} gesetzt wurde). rvalue is schneller als, aber nicht so einfach zu benutzen wie, die ähnliche funktion value (mit rvalue muss man die De-Referenzierung selber ausführen.

rstem

Diese Funktion ist identisch mit der bestehenden Funktion "path"

OPTION USING

Mit der Option {using => ...} kann man einen Teil-Baum der XML-Datei selektieren.

So funktioniert das im Detail...

Die Option {using => ['/pfad1/pfad2/pfad3', '/pfad4/pfad5/pfad6']} eliminiert alle Zeilen deren Pfad nicht mit '/pfad1/pfad2/pfad3' (oder nicht mit '/pfad4/pfad5/pfad6') beginnen. Das lässt dann nur noch Zeilen übrig, die dann mit '/pfad1/pfad2/pfad3' oder mit '/pfad4/pfad5/pfad6' beginnen.

Diese Zeilen (die nicht eliminiert wurden) haben dann einen kürzeren Pfad, weil der Präfix '/pfad1/pfad2/pfad3' (oder '/pfad4/pfad5/pfad6') entfernt wurde. Der entfernte Präfix erscheint dann aber in der Methode prefix().

Man kann sagen dass die Pfade '/pfad1/pfad2/pfad3' und '/pfad4/pfad5/pfad6' "absolut" und "komplett" sind. Der Begriff "absolut" bedeutet dass die Pfade mit einem '/' beginnen müssen, der Begriff "komplett" bedeutet dass der Pfad intern mit einem anghängten '/'-Zeichen abgeschlossen wird.

Beispiel mit using

Das folgende Programm liest eine XML-Datei und parst sie mit XML::Reader, die Option 'using' selektiert dabei nur einen Teil des XML-Baumes:

  use XML::Reader;

  my $line2 = q{
  <data>
    <order>
      <database>
        <customer name="aaa" />
        <customer name="bbb" />
        <customer name="ccc" />
        <customer name="ddd" />
      </database>
    </order>
    <dummy value="ttt">test</dummy>
    <supplier>hhh</supplier>
    <supplier>iii</supplier>
    <supplier>jjj</supplier>
  </data>
  };

  my $rdr = XML::Reader->new(\$line2,
    {using => ['/data/order/database/customer', '/data/supplier']});

  my $i = 0;
  while ($rdr->iterate) { $i++;
      printf "%3d. prf=%-29s, pat=%-7s, val=%-3s, tag=%-6s, t=%-1s, lvl=%2d\n",
        $i, $rdr->prefix, $rdr->path, $rdr->value, $rdr->tag, $rdr->type, $rdr->level;
  }

Das ist das Ergebnis dieses Programms:

   1. prf=/data/order/database/customer, pat=/@name , val=aaa, tag=@name , t=@, lvl= 1
   2. prf=/data/order/database/customer, pat=/      , val=   , tag=      , t=T, lvl= 0
   3. prf=/data/order/database/customer, pat=/@name , val=bbb, tag=@name , t=@, lvl= 1
   4. prf=/data/order/database/customer, pat=/      , val=   , tag=      , t=T, lvl= 0
   5. prf=/data/order/database/customer, pat=/@name , val=ccc, tag=@name , t=@, lvl= 1
   6. prf=/data/order/database/customer, pat=/      , val=   , tag=      , t=T, lvl= 0
   7. prf=/data/order/database/customer, pat=/@name , val=ddd, tag=@name , t=@, lvl= 1
   8. prf=/data/order/database/customer, pat=/      , val=   , tag=      , t=T, lvl= 0
   9. prf=/data/supplier               , pat=/      , val=hhh, tag=      , t=T, lvl= 0
  10. prf=/data/supplier               , pat=/      , val=iii, tag=      , t=T, lvl= 0
  11. prf=/data/supplier               , pat=/      , val=jjj, tag=      , t=T, lvl= 0

Beispiel ohne using

Das folgende Programm liest eine XML-Datei und parst sie mit XML::Reader, jedoch ohne Option 'using'.

  use XML::Reader;

  my $rdr = XML::Reader->new(\$line2);
  my $i = 0;
  while ($rdr->iterate) { $i++;
      printf "%3d. prf=%-1s, pat=%-37s, val=%-6s, tag=%-11s, t=%-1s, lvl=%2d\n",
       $i, $rdr->prefix, $rdr->path, $rdr->value, $rdr->tag, $rdr->type, $rdr->level;
  }

Wie man in dem folgenden Resultat sehen kann, werden mehr Ausgabezeilen geschrieben, der Präfix ist leer und der Pfad ist jetzt länger als zuvor.

   1. prf= , pat=/data                                , val=      , tag=data       , t=T, lvl= 1
   2. prf= , pat=/data/order                          , val=      , tag=order      , t=T, lvl= 2
   3. prf= , pat=/data/order/database                 , val=      , tag=database   , t=T, lvl= 3
   4. prf= , pat=/data/order/database/customer/@name  , val=aaa   , tag=@name      , t=@, lvl= 5
   5. prf= , pat=/data/order/database/customer        , val=      , tag=customer   , t=T, lvl= 4
   6. prf= , pat=/data/order/database                 , val=      , tag=database   , t=T, lvl= 3
   7. prf= , pat=/data/order/database/customer/@name  , val=bbb   , tag=@name      , t=@, lvl= 5
   8. prf= , pat=/data/order/database/customer        , val=      , tag=customer   , t=T, lvl= 4
   9. prf= , pat=/data/order/database                 , val=      , tag=database   , t=T, lvl= 3
  10. prf= , pat=/data/order/database/customer/@name  , val=ccc   , tag=@name      , t=@, lvl= 5
  11. prf= , pat=/data/order/database/customer        , val=      , tag=customer   , t=T, lvl= 4
  12. prf= , pat=/data/order/database                 , val=      , tag=database   , t=T, lvl= 3
  13. prf= , pat=/data/order/database/customer/@name  , val=ddd   , tag=@name      , t=@, lvl= 5
  14. prf= , pat=/data/order/database/customer        , val=      , tag=customer   , t=T, lvl= 4
  15. prf= , pat=/data/order/database                 , val=      , tag=database   , t=T, lvl= 3
  16. prf= , pat=/data/order                          , val=      , tag=order      , t=T, lvl= 2
  17. prf= , pat=/data                                , val=      , tag=data       , t=T, lvl= 1
  18. prf= , pat=/data/dummy/@value                   , val=ttt   , tag=@value     , t=@, lvl= 3
  19. prf= , pat=/data/dummy                          , val=test  , tag=dummy      , t=T, lvl= 2
  20. prf= , pat=/data                                , val=      , tag=data       , t=T, lvl= 1
  21. prf= , pat=/data/supplier                       , val=hhh   , tag=supplier   , t=T, lvl= 2
  22. prf= , pat=/data                                , val=      , tag=data       , t=T, lvl= 1
  23. prf= , pat=/data/supplier                       , val=iii   , tag=supplier   , t=T, lvl= 2
  24. prf= , pat=/data                                , val=      , tag=data       , t=T, lvl= 1
  25. prf= , pat=/data/supplier                       , val=jjj   , tag=supplier   , t=T, lvl= 2
  26. prf= , pat=/data                                , val=      , tag=data       , t=T, lvl= 1

OPTION PARSE_CT

Die Option {parse_ct => 1} erlaubt das Lesen von Kommentaren (normalerweise werden Kommentare von XML::Reader ignoriert, d.h. {parse_ct => 0} ist die Voreinstellung).

Hier ist ein Beispiel wo Kommentare, wie voreingestellt, ignoriert werden:

  use XML::Reader;

  my $text = q{<?xml version="1.0"?><dummy>xyz <!-- remark --> stu <?ab cde?> test</dummy>};

  my $rdr = XML::Reader->new(\$text) or die "Error: $!";

  while ($rdr->iterate) {
      if ($rdr->is_decl)    { my %h = %{$rdr->dec_hash};
                              print "Found decl     ",  join('', map{" $_='$h{$_}'"} sort keys %h), "\n"; }
      if ($rdr->is_proc)    { print "Found proc      ", "t=", $rdr->proc_tgt, ", d=", $rdr->proc_data, "\n"; }
      if ($rdr->is_comment) { print "Found comment   ", $rdr->comment, "\n"; }
      print "Text '", $rdr->value, "'\n" unless $rdr->is_decl;
  }

Hier ist das Ergebnis:

  Text 'xyz stu test'

Jetzt ein Beispiel mit den selben XML-Daten und dem selben Algorithmus, ausser dass die Option {parse_ct => 1} jetzt aktiviert ist:

  use XML::Reader;

  my $text = q{<?xml version="1.0"?><dummy>xyz <!-- remark --> stu <?ab cde?> test</dummy>};

  my $rdr = XML::Reader->new(\$text, {parse_ct => 1}) or die "Error: $!";

  while ($rdr->iterate) {
      if ($rdr->is_decl)    { my %h = %{$rdr->dec_hash};
                              print "Found decl     ",  join('', map{" $_='$h{$_}'"} sort keys %h), "\n"; }
      if ($rdr->is_proc)    { print "Found proc      ", "t=", $rdr->proc_tgt, ", d=", $rdr->proc_data, "\n"; }
      if ($rdr->is_comment) { print "Found comment   ", $rdr->comment, "\n"; }
      print "Text '", $rdr->value, "'\n" unless $rdr->is_decl;
  }

Hier ist das Ergebnis:

  Text 'xyz'
  Found comment   remark
  Text 'stu test'

OPTION PARSE_PI

Die Option {parse_pi => 1} erlaubt das Lesen von Processing-Instructions und XML-Declarations (normalerweise werden Processing-Instructions und XML-Declarations von XML::Reader ignoriert, d.h. {parse_pi => 0} ist die Voreinstellung).

Als Beispiel benutzen wir hier die selben XML-Daten und den selben Algorithmus wie zuvor, ausser dass die Option {parse_pi => 1} aktiviert ist (zusammen mit der schon aktivierten Option {parse_ct => 1}):

  use XML::Reader;

  my $text = q{<?xml version="1.0"?><dummy>xyz <!-- remark --> stu <?ab cde?> test</dummy>};

  my $rdr = XML::Reader->new(\$text, {parse_ct => 1, parse_pi => 1}) or die "Error: $!";

  while ($rdr->iterate) {
      if ($rdr->is_decl)    { my %h = %{$rdr->dec_hash};
                              print "Found decl     ",  join('', map{" $_='$h{$_}'"} sort keys %h), "\n"; }
      if ($rdr->is_proc)    { print "Found proc      ", "t=", $rdr->proc_tgt, ", d=", $rdr->proc_data, "\n"; }
      if ($rdr->is_comment) { print "Found comment   ", $rdr->comment, "\n"; }
      print "Text '", $rdr->value, "'\n" unless $rdr->is_decl;
  }

Beachten sie im obigen Programm die Zeile "unless $rdr->is_decl". Diese Zeile existiert damit verhindert wird dass der Wert einer XML-Declaration ausgegeben wird (dieser Wert wäre dann sowieso leer).

Hier ist das Resultat:

  Found decl      version='1.0'
  Text 'xyz'
  Found comment   remark
  Text 'stu'
  Found proc      t=ab, d=cde
  Text 'test'

OPTION FILTER / MODE

Die Option {filter => } oder {mode => } erlaubt verschiedene Grundeinstellungen zum Verarbeiten von XML-Daten.

Option filter 2 mode attr-bef-start

Mit der Option {filter => 2} oder {mode => 'attr-bef-start'}, XML::Reader erzeugt eine Zeile für jedes Text-Event. Falls der vorangehende Tag ein Start-Tag ist, dann wird die Methode is_start auf 1 gesetzt. Falls der folgende Tag ein End-Tag ist, dann wird die Methode is_end auf 1 gesetzt. Falls der vorangehende Tag ein Kommentar ist, dann wird die Methode is_comment auf 1 gesetzt. Falls der vorangehende Tag eine XML-Declaration ist, dann wird die Methode is_decl auf 1 gesetzt. Falls der vorangehende Tag eine Processing-Instruction ist, dann wird die Methode is_proc auf 1 gesetzt.

Zusätzlich, Attribute werden als spezielle Zeilen mit der '/@...' Syntax hinzugefügt.

Option {filter => 2, mode => 'attr-bef-start'} ist die Voreinstellung.

Hier ist ein Beispiel...

  use XML::Reader;

  my $text = q{<root><test param='&lt;&gt;v"'><a><b>"e"<data id="&lt;&gt;z'">'g'&amp;&lt;&gt;</data>}.
             q{f</b></a></test>x <!-- remark --> yz</root>};

  my $rdr = XML::Reader->new(\$text) or die "Error: $!";

  # Die folgenden vier Alternativen sind gleichwertig:
  # --------------------------------------------------
  #   XML::Reader->new(\$text);
  #   XML::Reader->new(\$text, {filter => 2                          });
  #   XML::Reader->new(\$text, {filter => 2, mode => 'attr-bef-start'});
  #   XML::Reader->new(\$text, {             mode => 'attr-bef-start'});

  while ($rdr->iterate) {
      printf "Path: %-24s, Value: %s\n", $rdr->path, $rdr->value;
  }

Dieses Programm (mit der impliziten Option {filter => 2, mode => 'attr-bef-start'} als Voreinstellung) produziert folgendes Resultat:

  Path: /root                   , Value:
  Path: /root/test/@param       , Value: <>v"
  Path: /root/test              , Value:
  Path: /root/test/a            , Value:
  Path: /root/test/a/b          , Value: "e"
  Path: /root/test/a/b/data/@id , Value: <>z'
  Path: /root/test/a/b/data     , Value: 'g'&<>
  Path: /root/test/a/b          , Value: f
  Path: /root/test/a            , Value:
  Path: /root/test              , Value:
  Path: /root                   , Value: x yz

Dieselbe Option {filter => 2, mode => 'attr-bef-start'} erlaubt die Erkennung der XML-Struktur mithilfe der Methoden is_start und is_end. Bitte beachten Sie ebenso in dem obigen Resultat dass die erste Zeile ("Path: /root, Value:") zwar leer ist, jedoch sehr wichtig für die XML-Struktur ist. Daher dürfen wir diese Zeile nicht vergessen.

Betrachten wir jetzt das selbe Beispiel (mit der Option {filter => 2, mode => 'attr-bef-start'}), jedoch mit einem zusätzlichen Algorithmus um die originale XML-Struktur wieder herzustellen. Ausserdem verlangen wir das normale Zeichenketten (also keine Tags und auch keine Attribute) mit den Zeichen "** **" markiert werden.

  use XML::Reader;

  my $text = q{<root><test param='&lt;&gt;v"'><a><b>"e"<data id="&lt;&gt;z'">'g'&amp;&lt;&gt;</data>}.
             q{f</b></a></test>x <!-- remark --> yz</root>};

  my $rdr = XML::Reader->new(\$text) or die "Error: $!";

  # Die folgenden vier Alternativen sind gleichwertig:
  # --------------------------------------------------
  #   XML::Reader->new(\$text);
  #   XML::Reader->new(\$text, {filter => 2                          });
  #   XML::Reader->new(\$text, {filter => 2, mode => 'attr-bef-start'});
  #   XML::Reader->new(\$text, {             mode => 'attr-bef-start'});

  my %at;

  while ($rdr->iterate) {
      my $indentation = '  ' x ($rdr->level - 1);

      if ($rdr->type eq '@')  {
          $at{$rdr->attr} = $rdr->value;
          for ($at{$rdr->attr}) {
              s{&}'&amp;'xmsg;
              s{'}'&apos;'xmsg;
              s{<}'&lt;'xmsg;
              s{>}'&gt;'xmsg;
          }
      }


      if ($rdr->is_start) {
          print $indentation, '<', $rdr->tag, join('', map{" $_='$at{$_}'"} sort keys %at), '>', "\n";
      }

      unless ($rdr->type eq '@') { %at = (); }

      if ($rdr->type eq 'T' and $rdr->value ne '') {
          my $v = $rdr->value;
          for ($v) {
              s{&}'&amp;'xmsg;
              s{<}'&lt;'xmsg;
              s{>}'&gt;'xmsg;
          }
          print $indentation, "  ** $v **\n";
      }

      if ($rdr->is_end) {
          print $indentation, '</', $rdr->tag, '>', "\n";
      }
  }

...hier ist das Resultat:

  <root>
    <test param='&lt;&gt;v"'>
      <a>
        <b>
          ** "e" **
          <data id='&lt;&gt;z&apos;'>
            ** 'g'&amp;&lt;&gt; **
          </data>
          ** f **
        </b>
      </a>
    </test>
    ** x yz **
  </root>

...Dieses Resultat beweist dass die originale XML-Struktur nicht verloren gegangen ist.

Option filter 3 mode attr-in-hash

Die Option {filter => 3, mode => 'attr-in-hash'} funktioniert ähnlich wie {filter => 2, mode => 'attr-bef-start'}.

Der Unterschied jedoch ist dass mit Option {filter => 3, mode => 'attr-in-hash'} alle Attribut-Zeilen eliminiert werden und anstelle dessen die Attribute für ein Start-Tag im hash $rdr->att_hash() erscheinen.

Damit wird die Benutzung einer globalen %at-Variable im oben angegebenen Algorithmus nicht mehr notwendig und kann daher durch die Konstruktion %{$rdr->att_hash} erstzt werden.

Hier ist ein neuer Algorithmus für {filter => 3}, wir brauchen uns nicht mehr explizit um Attribut-Zeilen zu kümmern (d.h. wir brauchen nicht mehr abzufragen ob $rdr->type eq '@') und, wie schon bemerkt, die %at-Variable wird ersetzt durch %{$rdr->att_hash} :

  use XML::Reader;

  my $text = q{<root><test param='&lt;&gt;v"'><a><b>"e"<data id="&lt;&gt;z'">'g'&amp;&lt;&gt;</data>}.
             q{f</b></a></test>x <!-- remark --> yz</root>};

  my $rdr = XML::Reader->new(\$text, {filter => 3}) or die "Error: $!";

  # Die folgenden drei Alternativen sind gleichwertig:
  # --------------------------------------------------
  #   XML::Reader->new(\$text, {filter => 3                        });
  #   XML::Reader->new(\$text, {filter => 3, mode => 'attr-in-hash'});
  #   XML::Reader->new(\$text, {             mode => 'attr-in-hash'});

  while ($rdr->iterate) {
      my $indentation = '  ' x ($rdr->level - 1);

      if ($rdr->is_start) {
          my %h = %{$rdr->att_hash};
          for (values %h) {
              s{&}'&amp;'xmsg;
              s{'}'&apos;'xmsg;
              s{<}'&lt;'xmsg;
              s{>}'&gt;'xmsg;
          }
          print $indentation, '<', $rdr->tag,
            join('', map{" $_='$h{$_}'"} sort keys %h),
            '>', "\n";
      }

      if ($rdr->type eq 'T' and $rdr->value ne '') {
          my $v = $rdr->value;
          for ($v) {
              s{&}'&amp;'xmsg;
              s{<}'&lt;'xmsg;
              s{>}'&gt;'xmsg;
          }
          print $indentation, "  ** $v **\n";
      }

      if ($rdr->is_end) {
          print $indentation, '</', $rdr->tag, '>', "\n";
      }
  }

...das Resultat für {filter => 3, mode => 'attr-in-hash'} ist identisch mit dem Resultat für {filter => 2, mode => 'attr-bef-start'}:

  <root>
    <test param='&lt;&gt;v"'>
      <a>
        <b>
          ** "e" **
          <data id='&lt;&gt;z&apos;'>
            ** 'g'&amp;&lt;&gt; **
          </data>
          ** f **
        </b>
      </a>
    </test>
    ** x yz **
  </root>

Schliesslich können wir (und sollten wir auch) das schreiben von XML einem anderen Modul überlassen. Ich schlage hiermit vor, dass wir das Modul XML::MinWriter benutzen. Hier ist ein Programm welches XML::MinWriter für die Ausgabe von XML benutzt:

  use XML::Reader;
  use XML::MinWriter;

  my $text = q{<root><test param='&lt;&gt;v"'><a><b>"e"<data id="&lt;&gt;z'">'g'&amp;&lt;&gt;</data>}.
             q{f</b></a></test>x <!-- remark --> yz</root>};

  my $rdr = XML::Reader->new(\$text, {filter => 3}) or die "Error: $!";
  my $wrt = XML::MinWriter->new(OUTPUT => \*STDOUT, NEWLINES => 1);

  while ($rdr->iterate) {
      if ($rdr->is_start)                          { $wrt->startTag($rdr->tag, %{$rdr->att_hash}); }
      if ($rdr->type eq 'T' and $rdr->value ne '') { $wrt->characters('** '.$rdr->value.' **'); }
      if ($rdr->is_end)                            { $wrt->endTag($rdr->tag); }
  }

  $wrt->end();

Hier ist das Resultat von XML::MinWriter:

  <root
  ><test param="&lt;&gt;v&quot;"
  ><a
  ><b
  >** "e" **<data id="&lt;&gt;z'"
  >** 'g'&amp;&lt;&gt; **</data
  >** f **</b
  ></a
  ></test
  >** x yz **</root
  >

Das Ausgabeformat von XML::MinWriter ist anfänglich gewöhnungsbedürftig, es ist jedoch korrektes XML Format.

Option filter 4 mode pyx

Obwohl es nicht die Hauptfunktion von XML::Reader darstellt, erlaubt die Option {filter => 4, mode => 'pyx'} die Erzeugung von individuellen Zeilen für jeweils das Start-Tag, das End-Tag, Kommentare, Processing-Instructions und XML-Declarations. Der Sinn ist eine PYX-kompatible Ausgabe-Zeichenkette zur weiteren Verarbeitung zu erzeugen.

Hier ist ein Beispiel:

  use XML::Reader;

  my $text = q{<?xml version="1.0" encoding="iso-8859-1"?>
    <delta>
      <dim alter="511">
        <gamma />
        <beta>
          car <?tt dat?>
        </beta>
      </dim>
      dskjfh <!-- remark --> uuu
    </delta>};

  my $rdr = XML::Reader->new(\$text, {filter => 4, parse_pi => 1}) or die "Error: $!";

  # Die folgenden drei Alternativen sind gleichwertig:
  # --------------------------------------------------
  #   XML::Reader->new(\$text, {filter => 4               , parse_pi => 1});
  #   XML::Reader->new(\$text, {filter => 4, mode => 'pyx', parse_pi => 1});
  #   XML::Reader->new(\$text, {             mode => 'pyx', parse_pi => 1});

  while ($rdr->iterate) {
      printf "Type = %1s, pyx = %s\n", $rdr->type, $rdr->pyx;
  }

und hier ist das Resultat:

  Type = D, pyx = ?xml version='1.0' encoding='iso-8859-1'
  Type = S, pyx = (delta
  Type = S, pyx = (dim
  Type = @, pyx = Aalter 511
  Type = S, pyx = (gamma
  Type = E, pyx = )gamma
  Type = S, pyx = (beta
  Type = T, pyx = -car
  Type = ?, pyx = ?tt dat
  Type = E, pyx = )beta
  Type = E, pyx = )dim
  Type = T, pyx = -dskjfh uuu
  Type = E, pyx = )delta

Bitte berücksichtigen Sie dass, falls {parse_ct => 1} gesetzt ist, Kommentare in der Methode pyx in einem nicht-standardisierten Format erzeugt werden. Die Kommentare werden dann mit einem führenden Doppelkreuz erzugt welches nicht in der PYX-Spezifikation existiert. Das folgende Beispiel demonstriert diesen Fall:

  use XML::Reader;

  my $text = q{
    <delta>
      <!-- remark -->
    </delta>};

  my $rdr = XML::Reader->new(\$text, {filter => 4, parse_ct => 1}) or die "Error: $!";

  # Die folgenden drei Alternativen sind gleichwertig:
  # --------------------------------------------------
  #   XML::Reader->new(\$text, {filter => 4,                parse_ct => 1});
  #   XML::Reader->new(\$text, {filter => 4, mode => 'pyx', parse_ct => 1});
  #   XML::Reader->new(\$text, {             mode => 'pyx', parse_ct => 1});

  while ($rdr->iterate) {
      printf "Type = %1s, pyx = %s\n", $rdr->type, $rdr->pyx;
  }

Hier ist das Ergebnis:

  Type = S, pyx = (delta
  Type = #, pyx = #remark
  Type = E, pyx = )delta

Ausserdem, falls {filter => 4, mode => 'pyx'} gesetzt ist, bleiben folgende Methoden gültig: (value, attr, path, is_start, is_end, is_decl, is_proc, is_comment, is_attr, is_text, is_value, comment, proc_tgt, proc_data, dec_hash und att_hash). Hier ist ein Beispiel:

  use XML::Reader;

  my $text = q{<?xml version="1.0"?>
    <parent abc="def"> <?pt hmf?>
      dskjfh <!-- remark -->
      <child>ghi</child>
    </parent>};

  my $rdr = XML::Reader->new(\$text, {filter => 4, parse_pi => 1, parse_ct => 1}) or die "Error: $!";

  # Die folgenden drei Alternativen sind gleichwertig:
  # --------------------------------------------------
  #   XML::Reader->new(\$text, {filter => 4,                parse_ct => 1, parse_pi => 1});
  #   XML::Reader->new(\$text, {filter => 4, mode => 'pyx', parse_ct => 1, parse_pi => 1});
  #   XML::Reader->new(\$text, {             mode => 'pyx', parse_ct => 1, parse_pi => 1});

  while ($rdr->iterate) {
      printf "Path %-15s v=%s ", $rdr->path, $rdr->is_value;

      if    ($rdr->is_start)   { print "Found start tag ", $rdr->tag, "\n"; }
      elsif ($rdr->is_end)     { print "Found end tag   ", $rdr->tag, "\n"; }
      elsif ($rdr->is_decl)    { my %h = %{$rdr->dec_hash};
                                 print "Found decl     ",  join('', map{" $_='$h{$_}'"} sort keys %h), "\n"; }
      elsif ($rdr->is_proc)    { print "Found proc      ", "t=",    $rdr->proc_tgt, ", d=", $rdr->proc_data, "\n"; }
      elsif ($rdr->is_comment) { print "Found comment   ", $rdr->comment, "\n"; }
      elsif ($rdr->is_attr)    { print "Found attribute ", $rdr->attr, "='", $rdr->value, "'\n"; }
      elsif ($rdr->is_text)    { print "Found text      ", $rdr->value, "\n"; }
  }

Hier ist das Ergebnis:

  Path /               v=0 Found decl      version='1.0'
  Path /parent         v=0 Found start tag parent
  Path /parent/@abc    v=1 Found attribute abc='def'
  Path /parent         v=0 Found proc      t=pt, d=hmf
  Path /parent         v=1 Found text      dskjfh
  Path /parent         v=0 Found comment   remark
  Path /parent/child   v=0 Found start tag child
  Path /parent/child   v=1 Found text      ghi
  Path /parent/child   v=0 Found end tag   child
  Path /parent         v=0 Found end tag   parent

Bitte beachten Sie dass für alle Texte und für alle Attribute v=1 gesetzt ist (d.h. $rdr->is_value == 1).

Option filter 5 mode branches

Mit der Option {filter => 5, mode => 'branches'} spezifiziert man eine (oder mehrere) Wurzeln ("root"), jede "root" hat eine Liste von Ästen ("branch"). Man erhält daraufhin genau einen Record für jedes Auftreten der "root" in der XML Struktur. Eine Wurzel kann mit einem einfachen Schrägstrich (z.B. {root => '/tag1/tag2'}) beginnen, in welchem Falle der Pfad absolut zu betrachten ist, oder sie kann mit einem Doppel-Schrägstrich (z.B. {root => '//tag1/tag2'}) beginnen, in welchem Falle der Pfad relativ zu betrachten ist. Die Wurzel ist ebenso relativ wenn die Wurzel ohne Schrägstrich beginnt (z.B. {root => 'tag1/tag2'}).

Jeder Record enthält genau die Elemente die in der "branch" Struktur definiert wurden. (Als Sonderfall betrachtet man den Ast {branch => '*'}, in welchem Falle die komplette XML-Strujtur des Unterbaumes zurück gegeben wird.

Um die Element zu erhalten die in der "branch" Struktur definiert wurden, kann man die Funktion $rdr->rvalue benutzen.

Am einfachsten lässt sich dieses an einem Beispiel erklären:

  use XML::Reader;

  my $line2 = q{
  <data>
    <supplier>ggg</supplier>
    <customer name="o'rob" id="444">
      <street>pod alley</street>
      <city>no city</city>
    </customer>
    <customer1 name="troy" id="333">
      <street>one way</street>
      <city>any city</city>
    </customer1>
    <tcustomer name="nbc" id="777">
      <street>away</street>
      <city>acity</city>
    </tcustomer>
    <supplier>hhh</supplier>
    <zzz>
      <customer name='"sue"' id="111">
        <street>baker street</street>
        <city>sidney</city>
      </customer>
    </zzz>
    <order>
      <database>
        <customer name="&lt;smith&gt;" id="652">
          <street>high street</street>
          <city>boston</city>
        </customer>
        <customer name="&amp;jones" id="184">
          <street>maple street</street>
          <city>new york</city>
        </customer>
        <customer name="stewart" id="520">
          <street>  ring   road   </street>
          <city>  "'&amp;&lt;&#65;&gt;'"  </city>
        </customer>
      </database>
    </order>
    <dummy value="ttt">test</dummy>
    <supplier>iii</supplier>
    <supplier>jjj</supplier>
    <p>
      <p>b1</p>
      <p>b2</p>
    </p>
    <p>
      b3
    </p>
  </data>
  };

Wir wollen aus der oben angegebenen XML-Datei die Elemente "name", "street" und "city" für den relativen Pfad 'customer' auslesen. Ausserdem wollen wir den Supplier im absoluten Pfad '/data/supplier' lesen. Dann wollen wir für den relativen Pfad '//customer' den XML-Unterbaum erhalten und schliesslich wollen wir den XML-Unterbaum für den relativen Pfad 'p' erhalten.

Die Daten der ersten Wurzel 'customer' erkennt man anhand der Bedingung $rdr->rx == 0, die Daten der zweiten Wurzel '/data/supplier' erkennt man anhand der Bedingung $rdr->rx == 1, die Daten der dritten Wurzel '//customer' erkennt man anhand der Bedingung $rdr->rx == 2, die Daten der vierten Wurzel 'p' erkennt man anhand der Bedingung $rdr->rx == 3 und die Daten der fünften Wurzel '//customer' erkennt man anhand der Bedingung $rdr->rx == 4.

Bitte beachten Sie in dem folgenden Beispielprogramm dass der Parameter {branch => '*'} zur Folge hat dass eine XML Zeichenkette zurückgegeben wird und dass der Parameter {branch => '+'} zur Folge hat dass eine Liste von PYX Instruktionen zurückgegeben wird.

Im folgenden Programm benutzen wir die Funktion $rdr->rvalue um die Daten zu erhalten:

  my $rdr = XML::Reader->new(\$line2, {filter => 5},
    { root => 'customer',       branch => ['/@name', '/street', '/city'] },
    { root => '/data/supplier', branch => ['/']                          },
    { root => '//customer',     branch => '*' },
    { root => 'p',              branch => '*' },
    { root => '//customer',     branch => '+' },
  );

  # Die folgenden drei Alternativen sind gleichwertig:
  # --------------------------------------------------
  #   XML::Reader->new(\$line2, {filter => 5,                   });
  #   XML::Reader->new(\$line2, {filter => 5, mode => 'branches'});
  #   XML::Reader->new(\$line2, {             mode => 'branches'});

  my $root0 = '';
  my $root1 = '';
  my $root2 = '';
  my $root3 = '';
  my $root4 = '';

  my $path0 = '';

  while ($rdr->iterate) {
      if ($rdr->rx == 0) {
          $path0 .= "  ".$rdr->path."\n";
          for ($rdr->rvalue) {
              $root0 .= sprintf "  Cust: Name = %-7s Street = %-12s City = %s\n", $_->[0], $_->[1], $_->[2];
          }
      }
      elsif ($rdr->rx == 1) {
          for ($rdr->rvalue) {
              $root1 .= "  Supp: Name = $_->[0]\n";
          }
      }
      elsif ($rdr->rx == 2) {
          for ($rdr->rvalue) {
              $root2 .= "  Xml: $_\n";
          }
      }
      elsif ($rdr->rx == 3) {
          for ($rdr->rvalue) {
              $root3 .= "  P: $_\n";
          }
      }
      elsif ($rdr->rx == 4) {
          for ($rdr->rvalue) {
              local $" = "', '";
              $root4 .= "  Pyx: '@$_'\n";
          }
      }
  }

  print "root0:\n$root0\n";
  print "path0:\n$path0\n";
  print "root1:\n$root1\n";
  print "root2:\n$root2\n";
  print "root3:\n$root3\n";
  print "root4:\n$root4\n";

Hier ist das Ergebnis:

  root0:
    Cust: Name = o'rob   Street = pod alley    City = no city
    Cust: Name = "sue"   Street = baker street City = sidney
    Cust: Name = <smith> Street = high street  City = boston
    Cust: Name = &jones  Street = maple street City = new york
    Cust: Name = stewart Street = ring road    City = "'&<A>'"

  path0:
    /data/customer
    /data/zzz/customer
    /data/order/database/customer
    /data/order/database/customer
    /data/order/database/customer

  root1:
    Supp: Name = ggg
    Supp: Name = hhh
    Supp: Name = iii
    Supp: Name = jjj

  root2:
    Xml: <customer id='444' name='o&apos;rob'><street>pod alley</street><city>no city</city></customer>
    Xml: <customer id='111' name='"sue"'><street>baker street</street><city>sidney</city></customer>
    Xml: <customer id='652' name='&lt;smith&gt;'><street>high street</street><city>boston</city></customer>
    Xml: <customer id='184' name='&amp;jones'><street>maple street</street><city>new york</city></customer>
    Xml: <customer id='520' name='stewart'><street>ring road</street><city>"'&amp;&lt;A&gt;'"</city></customer>

  root3:
    P: <p><p>b1</p><p>b2</p></p>
    P: <p>b3</p>

  root4:
    Pyx: '(customer', 'Aid 444', 'Aname o'rob', '(street', '-pod alley', ')street', '(city', '-no city', ')city', ')customer'
    Pyx: '(customer', 'Aid 111', 'Aname "sue"', '(street', '-baker street', ')street', '(city', '-sidney', ')city', ')customer'
    Pyx: '(customer', 'Aid 652', 'Aname <smith>', '(street', '-high street', ')street', '(city', '-boston', ')city', ')customer'
    Pyx: '(customer', 'Aid 184', 'Aname &jones', '(street', '-maple street', ')street', '(city', '-new york', ')city', ')customer'
    Pyx: '(customer', 'Aid 520', 'Aname stewart', '(street', '-ring road', ')street', '(city', '-"'&<A>'"', ')city', ')customer'

Wir können ebenso die Funktion $rdr->value benutzen um die gleichen Daten zu erhalten:

  my $rdr = XML::Reader->new(\$line2, {filter => 5},
    { root => 'customer',       branch => ['/@name', '/street', '/city'] },
    { root => 'p',              branch => '*' },
  );

  # Die folgenden drei Alternativen sind gleichwertig:
  # --------------------------------------------------
  #   XML::Reader->new(\$line2, {filter => 5,                   });
  #   XML::Reader->new(\$line2, {filter => 5, mode => 'branches'});
  #   XML::Reader->new(\$line2, {             mode => 'branches'});

  my $out0 = '';
  my $out1 = '';

  while ($rdr->iterate) {
      if ($rdr->rx == 0) {
          my @rv = $rdr->value;
          $out0 .= sprintf "  Cust: Name = %-7s Street = %-12s City = %s\n", $rv[0], $rv[1], $rv[2];
      }
      elsif ($rdr->rx == 1) {
          $out1 .= "  P: ".$rdr->value."\n";
      }
  }

  print "output0:\n$out0\n";
  print "output1:\n$out1\n";

Das is das Ergebnis:

  output0:
    Cust: Name = o'rob   Street = pod alley    City = no city
    Cust: Name = "sue"   Street = baker street City = sidney
    Cust: Name = <smith> Street = high street  City = boston
    Cust: Name = &jones  Street = maple street City = new york
    Cust: Name = stewart Street = ring road    City = "'&<A>'"

  output1:
    P: <p><p>b1</p><p>b2</p></p>
    P: <p>b3</p>

Es ist hier zu Bemerken dass der Fall "root3" / "output1" { root => 'p', branch => '*' } beweist dass immer der grösst möglichste XML-Baum für einen relativen Pfad gewählt wird. Oder anders ausgedrückt: die Ausgabe von "P: <p>b1</p>" and "P: <p>b2</p>" auf separaten Zeilen ist nicht möglich da diese Unterbäme schon in dem grösseren Baum "P: <p><p>b1</p><p>b2</p></p>" enthalten sind.

BEISPIELE

Betrachten wir nun folgende XML-Datei, von der wir die Werte innerhalb der Tags <item> (dabei meine ich nur den ersten Teil 'start...', und nicht den zweiten Teil 'end...') plus die Attribute "p1" und "p3" extrahieren wollen. Der <item>-Tag muss exact im Pfad /start/param/data existieren (und *nicht* im Pfad /start/param/dataz).

  my $text = q{
    <start>
      <param>
        <data>
          <item p1="a" p2="b" p3="c">start1 <inner p1="p">i1</inner> end1</item>
          <item p1="d" p2="e" p3="f">start2 <inner p1="q">i2</inner> end2</item>
          <item p1="g" p2="h" p3="i">start3 <inner p1="r">i3</inner> end3</item>
        </data>
        <dataz>
          <item p1="j" p2="k" p3="l">start9 <inner p1="s">i9</inner> end9</item>
        </dataz>
        <data>
          <item p1="m" p2="n" p3="o">start4 <inner p1="t">i4</inner> end4</item>
        </data>
      </param>
    </start>};

Wir erwarten genau 4 Ergebnis-Zeilen von unserem Parse-Lauf (d.h. wir erwarten keine 'dataz' - 'start9' Werte):

  item = 'start1', p1 = 'a', p3 = 'c'
  item = 'start2', p1 = 'd', p3 = 'f'
  item = 'start3', p1 = 'g', p3 = 'i'
  item = 'start4', p1 = 'm', p3 = 'o'

Beispiel XML filter 2 mode attr-bef-start

Hier ist ein Beispiel-Programm um die XML-Datei mit {filter => 2, mode => 'attr-bef-start'} zu parsen. (Bitte beachten Sie wie der Präfix '/start/param/data/item' in der {using => ...} Option von new verwendet wird). Wir brauchen ausserdem zwei scalar Variablen ('$p1' und '$p3') um die Parameter in '/@p1' und '/@p3' aufzunehmen und sie in den Programmteil $rdr->is_start zu übertragen, wo sie dann ausgegeben werden können.

  my $rdr = XML::Reader->new(\$text,
    {mode => 'attr-bef-start', using => '/start/param/data/item'}) or die "Error: $!";

  my ($p1, $p3);

  while ($rdr->iterate) {
      if    ($rdr->path eq '/@p1') { $p1 = $rdr->value; }
      elsif ($rdr->path eq '/@p3') { $p3 = $rdr->value; }
      elsif ($rdr->path eq '/' and $rdr->is_start) {
          printf "item = '%s', p1 = '%s', p3 = '%s'\n",
            $rdr->value, $p1, $p3;
      }
      unless ($rdr->is_attr) { $p1 = undef; $p3 = undef; }
  }

Beispiel filter 3 mode attr-in-hash

Mit {filter => 3, mode => 'attr-in-hash'} können wir auf die zwei scalar Variablen ('$p1' und '$p3') verzichten, das Programm vereinfacht sich wie folgt:

  my $rdr = XML::Reader->new(\$text,
    {mode => 'attr-in-hash', using => '/start/param/data/item'}) or die "Error: $!";

  while ($rdr->iterate) {
      if ($rdr->path eq '/' and $rdr->is_start) {
          printf "item = '%s', p1 = '%s', p3 = '%s'\n",
            $rdr->value, $rdr->att_hash->{p1}, $rdr->att_hash->{p3};
      }
  }

Beispiel filter 4 mode pyx

Jedoch mit {filter => 4, mode => 'pyx'} wird das Programm wieder komplizierter: Wie schon im Beispiel für {filter => 2, mode => 'attr-bef-start'} gezeigt, benötigen wir hier wieder zwei scalar Variablen ('$p1' und '$p3') um die Parameter in '/@p1' und '/@p3' aufzunehmen und sie dann zu übertragen. Zusätzlich müssen wir hier jedoch auch noch Text-Werte zählen können (siehe scalar Variable '$count'), sodass wir zwischen dem ersten Wert 'start...' (welchen wir ausgeben wollen) und dem zweiten Wert 'end...' (welchen wir nicht ausgeben wollen) unterscheiden können.

  my $rdr = XML::Reader->new(\$text,
    {mode => 'pyx', using => '/start/param/data/item'}) or die "Error: $!";

  my ($count, $p1, $p3);

  while ($rdr->iterate) {
      if    ($rdr->path eq '/@p1') { $p1 = $rdr->value; }
      elsif ($rdr->path eq '/@p3') { $p3 = $rdr->value; }
      elsif ($rdr->path eq '/') {
          if    ($rdr->is_start) { $count = 0; $p1 = undef; $p3 = undef; }
          elsif ($rdr->is_text) {
              $count++;
              if ($count == 1) {
                  printf "item = '%s', p1 = '%s', p3 = '%s'\n",
                    $rdr->value, $p1, $p3;
              }
          }
      }
  }

Beispiel filter 5 mode branches

Sie können {filter => 5, mode => 'branches'} und reguläre Ausdrücke lombinieren:

  my $rdr = XML::Reader->new(\$text, {mode => branches},
    { root => '/start/param/data/item', branch => '*' },
  ) or die "Error: $!";

  while ($rdr->iterate) {
      if ($rdr->value =~ m{\A <item
          (?:\s+ p1='([^']*)')?
          (?:\s+ p2='([^']*)')?
          (?:\s+ p3='([^']*)')?
          \s* > ([^<]*) <}xms) {
          printf "item = '%s', p1 = '%s', p3 = '%s'\n", $4, $1, $3;
      }
  }

FUNKTIONEN

Funktion slurp_xml

Die Funktion slurp_xml liest eine XML Datei und legt die Daten in einer Array-Referenz ab. Hier ist ein Beispiel in dem wir den Namen, die Strasse und die Stadt für alle Kunden im Pfad '/data/order/database/customer' erhalten wollen. Wir wollen ausserdem alle Supplier im Pfad '/data/supplier' erhalten:

  use XML::Reader qw(slurp_xml);

  my $line2 = q{
  <data>
    <supplier>ggg</supplier>
    <supplier>hhh</supplier>
    <order>
      <database>
        <customer name="smith" id="652">
          <street>high street</street>
          <city>boston</city>
        </customer>
        <customer name="jones" id="184">
          <street>maple street</street>
          <city>new york</city>
        </customer>
        <customer name="stewart" id="520">
          <street>ring road</street>
          <city>dallas</city>
        </customer>
      </database>
    </order>
    <dummy value="ttt">test</dummy>
    <supplier>iii</supplier>
    <supplier>jjj</supplier>
  </data>
  };

  my $aref = slurp_xml(\$line2,
    { root => '/data/order/database/customer', branch => ['/@name', '/street', '/city'] },
    { root => '/data/supplier',                branch => '*'                            },
  );

  for (@{$aref->[0]}) {
      printf "Cust: Name = %-7s Street = %-12s City = %s\n", $_->[0], $_->[1], $_->[2];
  }

  print "\n";

  for (@{$aref->[1]}) {
      printf "S: %s\n", $_;
  }

Der erste Parameter in slurp_xml ist entweder der Dateiname (oder eine Skalar Referenz, oder ein offenes Dateihandle) der XML Datei die wir einlesen wollen. In diesem Fall lesen wir von der Skalar Referenz \$line2. Der nächste Parameter ist die Wurzel des Baumes den wir einlesen wollen (in diesem Falle wäre das '/data/order/database/customer') mit einer Liste von Elementen die wir, relativ zur Wurzel, selektieren wollen, in diesem Falle wäre das ['/@name', '/street', '/city']. Der darauffolgende Parameter ist eine zweite Wurzel (root/branch Definition), in diesem Falle ist es root => '/data/supplier' mit branch => ['/'].

Hier ist das Resultat:

  Cust: Name = smith   Street = high street  City = boston
  Cust: Name = jones   Street = maple street City = new york
  Cust: Name = stewart Street = ring road    City = dallas

  S: <supplier>ggg</supplier>
  S: <supplier>hhh</supplier>
  S: <supplier>iii</supplier>
  S: <supplier>jjj</supplier>

slurp_xml funktioniert ähnlich wie XML::Simple, insofern als dass alle benötigten Informationen in einem Rutsch in einer Speicherstruktur abgelegt werden. Der Unterschied jedoch ist dass slurp_xml uns erlaubt spezifische Daten zu selektieren bevor das Einlesen beginnt. Dieses hat zur Folge dass die sich ergebende Speicherstruktur meistens kleiner ist, und damit auch weniger kompliziert.

AUTOR

Klaus Eichner, March 2009

COPYRIGHT UND LIZENZ

Dieses ist der original Text auf Englisch:

Copyright (C) 2009 by Klaus Eichner.

All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the artistic license 2.0, see http://www.opensource.org/licenses/artistic-license-2.0.php

VERWANDTE MODULE

Falls Sie vorhaben XML auch ausgeben zu wollen, dann schlage ich vor eines der zwei Module XML::Writer oder XML::MinWriter zu benutzen. Beide Module bieten ein einfach zu handhabendes Interface zur Ausgabe von XML-Dateien. (Falls sie non-mixed content XML ausgeben, würde ich empfehlen die Optionen DATA_MODE=>1 und DATA_INDENT=>2 zu setzen, das erlaubt die korrekte Formatierung und Einrückung in Ihrer XML Ausgabe-Datei).

REFERENZEN

XML::TokeParser, XML::Simple, XML::Parser, XML::Parser::Expat, XML::TiePYX, XML::Writer, XML::MinWriter.