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