Christian Kruse: eigener XML-Parser; Speicheralgorithmus gesucht

Beitrag lesen

Hoi,

ACHTUNG: Dies ist ein langes Posting (schreibwut)

So lang ists doch gar nicht?

Seit gestern programmiere ich an einem eigenen XML-Parser

Der Sinn sei dahingestellt ;-) es gibt auch SAX-Parser fuer Perl

Wie wird das XML-Infoset intern verwaltet:
   Jeder Start-tag bekommt eine eindeutige pathID zugewiesen. In
einem Hash (%pathes{$pathID}) stehen alle Daten zu einem solchen
path (z. B. Name, Wert, Attribute, ...).
   XML-stream:
      <adressen>                        PathID : 1
         <adresse>                      PathID : 2
            <name>Hasenfratz</name>     PathID : 3
         </adresse>
         <adresse>                      PathID : 4
            <name>anderer Name</name>   PathID : 5
         </adresse>
      </adressen>

[...]

Eine rekursive Lösung habe ich bereits erarbeitet, die ist auch
nicht schwer:

[... rekursive Loesung ...]

Nun, bei sehr grossen XML-files wird's wohl etwas kritisch mit
dem Speicher (besonders, wenn viele XML-files auf dem Webserver
geparsed werden müssen).

Seh ich nicht so.

Also möchte ich eine "lineare" Methode anwenden. Habe auch schon
was gebastelt, aber ich bin irgendwie unfähig, dem Programm zu
sagen, dass Tag's auch geschlossen werden sollen.

Das das langsamer ist, weisst du? Nunja, ich hab dir da mal was
geschrieben, allerdings nicht in das Paket verpackt (hatte ich keinen
Nerv zu):

sub saveXML($) {
  my $pathes        = shift;
  my $AlreadyClosed = {};

foreach my $path (sort { $a <=> $b } keys %$pathes) {
    if($pathes->{$path}->{NodeType} == 0) { # really easy
      print '<'.$pathes->{$path}->{NodeName}.attributes($path,$pathes)."/>\n";
    }
    elsif($pathes->{$path}->{NodeType} == 1) { # really easy
      print '<'.$pathes->{$path}->{NodeName}.attributes($path,$pathes).'>'.$pathes->{$path}->{NodeValue}.'</'.$pathes->{$path}->{NodeName}.">\n";
    }
    elsif($pathes->{$path}->{NodeType} == 2) {
      print '<'.$pathes->{$path}->{NodeName}.attributes($path,$pathes).">\n";
    }

if(shallClose($path,$pathes)) {
      print closeString($path,$pathes,$AlreadyClosed);
    }
  }
}

hier werden die end-tags zusammengesetzt

sub closeString($$$) {
  my $path   = shift;
  my $pathes = shift;
  my $ac     = shift;
  my $retval = '';

# sicher ist hier eine shift-push-Loesung besser
  my @keys   = sort { $a <=> $b } keys %$pathes;

for(my $i=$path;$i!=0;$i--) {
    # alle anderen element-typen sind schon geschlossen
    next if $pathes->{$i}->{NodeType} != 2;

# es gibt noch element, die tiefer oder gleich stehen in der Hierarchie
    next if grep { $pathes->{$_}->{ChildOf} >= $i } @keys[$path..$#keys];

# schon geschlossen worden
    next if $ac->{$i};

$retval .= '</'.$pathes->{$i}->{NodeName}.">\n";
    $ac->{$i} = 1;
  }

return $retval;
}

soll der Tag geschlossen werden?

geschlossen werden soll dann, wenn das darauf

folgende element Kind eines Uebergestellten Kindes ist (kleinere

Path-ID) oder kein Kind mehr folgt

sub shallClose($$) {
  my $path   = shift;
  my $pathes = shift;

if(exists $pathes->{$path+1}) {
    return $pathes->{$path+1}->{ChildOf} < $pathes->{$path}->{ChildOf};
  }

return 1;
}

attribut-strings setzen

sub attributes($$) {
  my $path   = shift;
  my $pathes = shift;
  my $retval = '';

$retval .= ' '.$_.'="'.$pathes->{$path}->{Attributes}->{$_}.'"' foreach (keys %{$pathes->{$path}->{Attributes}});

return $retval;
}

Aufgerufen wird das Ganze mit

saveXML($pathes);

Als Testdaten hatte ich:

my $pathes = {
  1 => {
    ChildOf    => 0,
    NodeValue  => '',
    NodeType   => 2,
    NodeName   => 'element1',
    Attributes => {}
  },
  2 => {
    ChildOf    => 1,
    NodeValue  => '',
    NodeType   => 2,
    NodeName   => 'element2',
    Attributes => {x => 'y'}
  },
  3 => {
    ChildOf    => 2,
    NodeValue  => '',
    NodeType   => 2,
    NodeName   => 'element3',
    Attributes => {}
  },
  4 => {
    ChildOf    => 2,
    NodeValue  => '',
    NodeType   => 2,
    NodeName   => 'element4',
    Attributes => {}
  },
  5 => {
    ChildOf    => 4,
    NodeValue  => 'xyz',
    NodeType   => 1,      # NodeType 2 : end-point, enthält also einen Wert
    NodeName   => 'element5',
    Attributes => {}
  },
  6 => {
    ChildOf    => 4,
    NodeValue  => '',
    NodeType   => 0,
    NodeName   => 'element6',
    Attributes => {x => 'y',z => 'a'}
  },
  7 => {
    ChildOf    => 3,
    NodeValue  => 'xxx',
    NodeType   => 1,
    NodeName   => 'element7',
    Attributes => {x => 'z', y => 'a'}
  },
  8 => {
    ChildOf    => 2,
    NodeValue  => 'xxx',
    NodeType   => 1,
    NodeName   => 'element8',
    Attributes => { x => 'y' }
  }
};

Das Problem bei dieser Loesung ist allerdings, dass durch das
dauernde Splice im grep() (sub closeString) das bei groesseren
Dokumenten durchaus sehr langsam werden kann. Da kann man sich eine
shift-push-Methode noch ueberlegen, hatte ich aber keine Lust zu.
Ein bisschen sollst du ja auch noch tun ;-)

Gruesse,
 CK