Um es kurz zu machen: Es gibt derzeit absolut KEIN echtes Cluster-Filesystem (wie z.B. GFS oder OCFS2) für FreeBSD. Auch andere Projekte, welche sich mit verteilten Dateisystemen beschäftigen wie z.B. GlusterFS, PVFS oder DRBD sind entweder nicht nach FreeBSD portiert, oder die Portierung ist sehr alt und läuft häufig nicht unter einem aktuellen FreeBSD.
Da ich jedoch vier Filesysteme auf gleichem Stand halten muß (und zwar muß die Aktualisierung binnen Sekunden nach einem Upload erfolgen), habe ich einen kleinen Workaround entwickelt, der dies über rsync und das FreeBSD audit-System ermöglicht. Die Idee das audit-System hierzu zu nutzen habe ich von Luke Marsden, der die Filesystem-Aktivitäten mit audit_control und einigen Python-Scripts überwacht.
Als erstes muß das audit_system aktiviert und konfiguriert werden. Das event-auditing ist Teil des FreeBSD-Systems selbst und muß im Kernel aktiviert werden.
Folgende Zeile muß in der Kernel-Konfiguration hinzugefügt werden:
options AUDIT
Danach muß der Kernel neu compiliert und installiert werden. Das Vorgehen hierzu ist im FreeBSD Handbook zu finden.
Als nächstes muß die folgende Zeile in der /etc/rc.conf ergänzt werden:
auditd_enable=”YES”
Soweit so gut – nun möchte auch das audit-system selbst eine Konfiguration haben. Hierzu öffnet man die Datei /etc/security/audit_control und ändert die config wie folgt:
dir:/var/audit
flags:fc,fd,fw
minfree:20
naflags:lo
policy:cnt
filesz:0
Das war es schon. Nun kann man das audit-system starten indem man folgenden Befehl ausführt:
/etc/rc.d/auditd start
oder indem man das System via reboot neu startet.
Falls rsync noch nicht auf dem System installiert ist, kann man dies einfach und schnell aus den ports nachholen:
cd /usr/ports/net/rsync
make
make install
Dabei sollte es keine Probleme geben dürfen.
Als nächstes muß nun ein alternativer Pfad zum Daten-Verzeichnis über einen symbolischen Link (“Alias”) erstellt werden; warum dieser benötigt wird werde ich später erklären.
ln -s /path/to/your/data/ /alternative_data_path/
Nun sollte rsync so konfiguriert werden, daß es als daemon läuft. Hierzu erstellen, bzw. verändern wir die Config-Datei /etc/rsyncd.conf wie folgt:
max connections = 5
log file = /var/log/rsync.log
timeout = 30[shareName]
comment = Name of this “Rsync mount”
path = /alternative_data_path/
read only = no
list = yes
uid = validUser
gid = validGroup
hosts allow = ,
hosts deny = *
Um rsync nun zu starten wird der folgende Befehl ausgeführt:
/usr/local/bin/rsync –config=/etc/rsyncd.conf –daemon
Es ist vermutlich eine gute Idee den rsynd mit einem entsprechenden Tool zu überwachen (z.B. mit den daemontools) damit sichergestellt ist, daß der rsync-Service immer verfügbar ist (in diesem Fall muß rsyncd dann mit der Option –no-detach option gestartet werden).
#!/usr/bin/perl ## # This software is published under the Apchae 2.0 licenses. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # # Author: Erik Scholtz # Web: http://blog.elitecoderz.net ### # We are strict, cauz we are elitecoderz! use strict; use threads qw(yield); use threads::shared; use Thread::Semaphore; # No caching $|=1; ################ # Configuration my $debug = 1; # 0/1 to enable logging to the console or disable my $path = '/path/to/your/data/'; # Path to sync my @cmds; # Syncer commands that should be executed $cmds[0] = '/usr/local/bin/rsync -raz --progress --size-only /path/to/symboliclink/data/<!--target--> rsync:///shareName/<!--target-->'; $cmds[1] = '/usr/local/bin/rsync -raz --progress --size-only /path/to/symboliclink/data/<!--target--> rsync:///shareName/<!--target-->'; $cmds[2] = '/usr/local/bin/rsync -raz --progress --size-only /path/to/symboliclink/data/<!--target--> rsync:///shareName/<!--target-->'; ############################################################################### # DO NOT CHANGE ANYTHING BELOW THIS LINE, UNLESS YOU KNOW WHAT YOU ARE DOING! # ############################################################################### ### # Set Threads yield threads->yield(); # SetUp some thread-shared variables my $commands :shared; $commands = &share([]); my $run :shared; $run = &share({}); $run->{'status'} = 1; my $sema :shared; $sema = &share({}); # Local array where all syncer-threads are stored my @threads; # Create a thread for each syncer my $maxid = -1; for (my $i=0;$i<=$#cmds;$i++) { print "Starting syncer $i\n" if $debug; $sema->{$i} = Thread::Semaphore->new(0); my $syncer = threads->create('syncJob',$run,$sema,$i,$commands,$path,$cmds[$i],$debug); push(@threads,$syncer); $maxid = $i; } # Create the Checker thread, which cleanup the jobs and ensures the function of all syncers $sema->{'checker'} = Thread::Semaphore->new(0); my $syncer = threads->create('JobChecker',$run,$sema,$commands,$maxid,$debug); push(@threads,$syncer); # Create the audit thread print "Starting audit\n" if $debug; my $auditthread = threads->create('audit',$run,$sema,$commands,$path,$maxid,$debug); print "Waiting for audi to terminatet\n" if $debug; $auditthread->join(); # If the audit-thread gets joinable, we have to terminate everything # Terminate all threads and cleanup $run->{'status'} = 0; while ($#threads >=0 ) { my $worker = shift(@threads); print "Shutdown of syncer ...\n" if $debug; $worker->join(); } print "Shutdown clean completed\n" if $debug; exit(0); ######################################################################################################################################## ######################################################################################################################################## #################################################################################################### # audit thread sub audit { my $r = shift; my $sp = shift; my $c = shift; my $p = shift; my $m = shift; my $d = shift; print " audit started ...\n" if $d; # open listener on the audit device open(STATUS, "/usr/sbin/praudit /dev/auditpipe |") || die "can't fork: $!"; while (<STATUS>) { my $line = $_; last if ($line eq '' || $r->{'status'}<=0); # Terminate if audit terminated if ($line =~ /path,$p(.+)/) { # Check if the changed file is in the observed path my $file = $1; print "Change detected on file: $file\n" if $d; my $hash :shared; # Create a command for the syncers $hash = &share({}); $hash->{'file'} = $file; $hash->{'status'} = ''; $hash->{'time'} = ''; for (my $j=0;$j<=$m;$j++) { # init job done charta $hash->{$j} = 'no'; } if (1) { lock($c); push(@{$c},$hash); print "Added new job for $file\n" if $d; } for (my $j=0;$j<=$m;$j++) { # wakeup syncers $sp->{$j}->up(); } $sp->{'checker'}->up(); } } close STATUS || die "audit not closed correctly: $! $?"; print " audit terminated ...\n" if $d; return(0); } #################################################################################################### # syncer thread sub syncJob { my $r = shift; my $sp = shift; my $id = shift; my $c = shift; my $p = shift; my $e = shift; my $d = shift; print " syncer $id started ...\n" if $d; while ($r->{'status'}>0) { if ($#{$c}>=0) { # if there are any jobs to be done for (my $j=0; $j<=$#{$c}; $j++) { next if ($c->[$j]->{$id} eq 'ok'); # if my job is already done skip this job and check next my $file = $c->[$j]->{'file'}; if (-e $p.$file) { # check if the file is existing $c->[$j]->{$id} = 'working'; # mark this job as being worked on my $dif = 1; while ($dif>0) { # check if the file is in upload and changes size within 1,5 secs print "Checking Filesize ...\n" if $d; my $ssize = -s $p.$file; sleep(1.5); my $eesize = -s $p.$file; $dif = $eesize - $ssize; print "Checking Filesize $ssize - $eesize = $dif\n" if $d; } my $cm = $e; $cm =~ s/<!--target-->/$file/g; system($cm); # rsync to other server } lock($c); $c->[$j]->{$id} = 'ok'; # mark job as done for me } } $sp->{$id}->down(); } print " syncer $id terminated ...\n" if $d; return(0); } #################################################################################################### # checker thread that checks if all jobs are done sub JobChecker { my $r = shift; my $sp = shift; my $c = shift; my $m = shift; my $d = shift; print " checker started ...\n" if $d; while ($r->{'status'}>0) { while ($#{$c} >= 0 && $r->{'status'}>0) { print " Checker loop ...\n" if $d; my $rem = 0; foreach my $job (@{$c}) { # loop through all jobs my $mem = 'ok'; for (my $j=0;$j<=$m;$j++) { # check job done charta if ($job->{$j} eq 'no') { # job not handled $mem = 'no' if ($mem ne 'working'); # job not handled (may never override a job in progress state) } elsif ($job->{$j} eq 'working') { # job in progress (always overrides not handled) $mem = 'working'; } } # Job not completed if ($mem eq 'no') { if ($job->{'time'} eq '') { # Set timestamp to know, how long this job is already waiting $job->{'time'} = time; } else { # Job already got a timestamp my $watch = time - $job->{'time'}; print "Job age: $watch\n" if $d; if (time - $job->{'time'} > 300) { # Job has waited for more than 5 minutes. terminate program print "TIME FOR JOB EXCEEDED - shutting down syncer"; $r->{'status'} = 0; for (my $j=0;$j<=$m;$j++) { # wakeup syncers $sp->{$j}->up(); $sp->{'checker'}->up(); # wakeup ourself } } } } elsif ($mem eq 'working') { # job in progress - just actualize the timestamp $job->{'time'} = time; } else { $job->{'status'} = 'complete'; # job is completely done and is marked for being removed $rem = 1; } } # Job to remove available if ($rem > 0) { lock($c); # lock the command-queue my @arr; for (my $j=0;$j<=$#{$c};$j++) { # store all not handled jobs / drop completed jobs my $ex = shift(@{$c}); if ($ex->{'status'} ne 'complete') { push(@arr,$ex); } } for (my $j=0;$j<=$#arr;$j++) { # put all stored (not finished) jobs back into the command queue push(@{$c},$arr[$j]); } } print " Checker reloop ...\n" if $d; sleep(1); } print " Checker sleeping (".$#{$c}.")...\n" if $d; $sp->{'checker'}->down(); } print " checker terminated ...\n" if $d; return(0); } |
Dieses Script ist das Herzstück des Ganzen: Über das audit-system hört es auf Veränderungen von Dateien; bei einer Änderung (oder neu anlegen) einer Datei wird die Datei via rsync direkt auf die anderen Systeme kopiert. Und das ist der Punkt, warum der symbolische Link so wichtig ist. Wenn das Script die Datei via rsync auf ein zweites System kopiert, so registriert das audit-system dort die Änderung und informiert das dort installierte Script. Dieses würde dann direkt die Änderung auf den ersten Server zurück kopieren, wodurch das Script auf dem ersten Server wiederum über eine Änderung informiert würde, usw … Ohne den symbolischen Link würde also eine Endlos-Kopier-Schleife entstehen.
Das Script wird auf jedes System kopiert, welches mit den jeweils anderen Systemen synchron gehalten werden soll. Ich empfehle dringend auch dieses Script mit den daemontools zu überwachen. Dann muß das Script wie folgt angepasst werden:
$debug kann entweder 0 (keine Debuging Ausgabe) oder 1 (mit Ausgabe) sein.
$path sollte der physikalische Pfad zu den Daten sein.
Für jedes System welches synchronisiert werden soll muß folgende Zeile ergänzt werden. Bitte unbedingt beachten, daß die Nummer in den eckigen Klammern ($cmds["Nummer"]) um jeweils eins erhöht werden muß:
$cmds[0] = ‘/usr/local/bin/rsync -raz –progress –size-only /path/to/symboliclink/data/ rsync:///shareName/‘;
BEVOR irgendetwas auf dem eigenen System geändert wird, muß unbedingt ein komplettes BackUp erstellt werden. Die Benutzung des Scriptes und des HowTos erfolgt auf eigene Gefahr. Wenn es also aufgrund der Anwendung dieses Scriptes oder Howtos zu irgendwelchen Datenverlusten kommt, dann kann man mich dafür nicht verantwortlich machen.
Um möglichst eine “Echtzeitsynchronisierung” hinzubekommen, startet das Script für jedes System welches synchronisiert werden soll einen eigenen Threas. Deswegen muß die Perl-Installation “thread-enabled” sein.
Mein letzter Artikel beschäftigte sich mit XML::Simple, einem (wie der Name schon sagt) simplen und einfach zu verstehendem Modul für Perl um XML-Strukturen zu parsen. Heute wollen wir uns mit einem weiteren Modul beschäftigen: XML::Mini::Document.
Kurzinformation:
3 von 5 Sternen Rating auf cpan
Letzte Änderung: 05 Feb. 2008
XML::Mini::Document ist ein Modul, welches auf Anhieb eher den Eindruck eines mächtigen XML-Parsers macht, da es nicht nur über die Option verfügt XML-Strukturen in ein Hash zu parsen, bzw. daraus zu generieren. Es verfügt auch über eine API, mit der man die XML-Strukturen selbst beeinflussen kann. Die Dokumentation und die Beispiele sind einfach und verständlich gehalten, sodaß man sich schnell mit dem Modul zurechtfindet.
Zunächst verwenden wir das selbe test-xml, welches wir auch schon für den Test von XML::Simple verwendet haben:
<?xml version="1.0" encoding="iso-8859-1"?> <test debug="0" attr1="1" attr2="2" another="<>"> <info attr1="perl" attr2="xml module" /> <info attr1="perl" attr2="xml module" /> <info attr1="perl" attr2="xml module" /> <info attr1="perl" attr2="xml module"><deepinfo>last text here</deepinfo></info> </test> |
Und wie wollen die selben Operationen im XML ausführen, wie wir das mit XML::Simple davor ebenfalls getan haben. Wenn wir die “toHash()” Methode verwenden, so sieht der Quellcode nur leicht anders aus; das Testprogramm sieht dann wie folgt aus:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | #!/usr/bin/perl # Simple XML module test unit ### # We are strict, cauz we are Elitecoderz! use strict; use XML::Mini::Document; use XML::Mini; ################ # Get / Check Parameter (here we get the xml file we wanna deal with) if ($#ARGV+1 != 1) { print "Error: Wrong number of parameters.\n"; exit(1); } my $input = $ARGV[0]; chomp($input); ################ # Read Inputfile / check content / validate XML structure # Some modules are able to read directly from a file; for easy going, we use this method here. # Direct, dirty, but simple reading of a file open(FILE, "<$input") || die "Error: File not readable.\n"; my @lines = <FILE>; close(FILE); # Put the lines into one string for this parser my $XMLString = join(' ',@lines); #################################################################### # XML::Mini my $xmlDoc = XML::Mini::Document->new(); eval { $xmlDoc->parse($XMLString); }; if ($@) { print "Error: XML parsing error: $@\n"; exit(1); } my $xmlHash = $xmlDoc->toHash(); # Adding an attribute and a text to the first node $xmlHash->{'test'}->{'info'}->[0]->{'addon'} = 'valid text'; $xmlHash->{'test'}->{'info'}->[0]->{'content'} = "Here is an valid xml text\nusing linebreaks"; # Adding an attribute and an unescaped text to the second node $xmlHash->{'test'}->{'info'}->[1]->{'addon'} = 'invalid unescaped text'; $xmlHash->{'test'}->{'info'}->[1]->{'content'} = "Here is an valid xml text\nusing linebreaks\nand unescaped characters like < and >\ndo you see the and?"; # Adding a third node unescaped text to the second node $xmlHash->{'test'}->{'info'}->[2]->{'addon'} = 'valid unescaped text in cdata'; $xmlHash->{'test'}->{'info'}->[2]->{'content'} = "<![CDATA[Here is an valid xml text\nusing linebreaks\nand unescaped characters like < and >\ndo you see the and?]]>"; # my $newDoc = XML::Mini::Document->new(); $xmlDoc->fromHash($xmlHash); open(DATEI, ">output_XMLMini") || die "Datei nicht gefunden"; print DATEI $xmlDoc->toString(); close(DATEI); |
Das resultierende XML sieht wie folgt aus:
<test> <info> <attr2> xml module </attr2> <addon> valid text </addon> <attr1> perl </attr1> <content> Here is an valid xml text using linebreaks </content> </info> <info> <attr2> xml module </attr2> <addon> invalid unescaped text </addon> <attr1> perl </attr1> <content> Here is an valid xml text using linebreaks and unescaped characters like < and > do you see the and? </content> </info> <info> <attr2> xml module </attr2> <addon> valid unescaped text in cdata </addon> <attr1> perl </attr1> <content> <![CDATA[Here is an valid xml text using linebreaks and unescaped characters like < and > do you see the and?]]> </content> </info> <info> <attr2> xml module </attr2> <attr1> perl </attr1> <deepinfo> last text here </deepinfo> </info> <attr2> 2 </attr2> <attr1> 1 </attr1> <another> &lt;&gt; </another> <debug> 0 </debug> </test> <xml> <version> 1.0 </version> <encoding> iso-8859-1 </encoding> </xml> |
*AUTSCH* – was zum Teufel ist mit unserem XML passiert?! Nach dem ersten Schock kann man erkennen, daß alle Attribute in XML-Knoten verwandelt wurden. Das ist zwar ein gültiger XML-Syntax, und alle Informationen sind nach wie vor enthalten, aber das Problem ist, daß jemand anderes ein erwartetes Attribut vermutlich nicht als XML-Knoten suchen wird. Das Gute dieses Mal ist jedoch, daß der “deepinfo”-Knoten in der Hirachie richtig steht.
Ein anderes Problem ist der Umgang mit CDATAs. XML::Mini::Document escaped den CDATA-Knoten und ist offensichtlich nicht in der Lage ein CDATA korrekt zu schreiben (jedenfalls habe ich keine Möglichkeit gefunden).
Das übelste Problem jedoch ist die Behandlung des “XML-Informations”-Knotens; Er wird als XML-Knoten behandelt, aufgebrochen und am Ende der Datei eingefügt!
Erstes Ergebnis:
Die Methoden “fromHash()” and “toHash()” in XML::Mini::Document sind technischer Müll, die bestenfalls in XMLs ohne Attribute und CDATAs zum Einsatz kommen können.
Im Gegensatz zu XML::Simple ist bei XML::Mini::Document das “Hash-Verfahren” nicht die einzige Option wie mit XML-Strukturen umgegangen werden kann. Es kann mit der Struktur über eine Toolbox auch direkt gearbeitet werden. Hierfür schreiben wir unser Testprogramm etwas um:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | #!/usr/bin/perl # Simple XML module test unit ### # We are strict, cauz we are Elitecoderz! use strict; use XML::Mini::Document; use XML::Mini; ################ # Get / Check Parameter (here we get the xml file we wanna deal with) if ($#ARGV+1 != 1) { print "Error: Wrong number of parameters.\n"; exit(1); } my $input = $ARGV[0]; chomp($input); ################ # Read Inputfile / check content / validate XML structure # Some modules are able to read directly from a file; for easy going, we use this method here. # Direct, dirty, but simple reading of a file open(FILE, "<$input") || die "Error: File not readable.\n"; my @lines = <FILE>; close(FILE); # Put the lines into one string for this parser my $XMLString = join(' ',@lines); #################################################################### # XML::Mini my $xmlDoc = XML::Mini::Document->new(); eval { $xmlDoc->parse($XMLString); }; if ($@) { print "Error: XML parsing error: $@\n"; exit(1); } my $xmlRoot = $xmlDoc->getRoot(); my $firstnode = $xmlDoc->getElementByPath('test/info'); $firstnode->attribute('addon', "Here is an valid xml text\nusing linebreaks"); my $secondnode = $xmlDoc->getElementByPath('test/info',1,2); $secondnode->attribute('addon','invalid unescaped text'); $secondnode->text("Here is an valid xml text\nusing linebreaks\nand unescaped characters like < and >\ndo you see the and?"); my $testnode = $xmlDoc->getElementByPath('test'); my $newchild = $testnode->createChild("info"); $newchild->attribute('addon', "unescaped text for a CDATA"); $newchild->cdata("Here is an valid xml text\nusing linebreaks\nand unescaped characters like < and >\ndo you see the and?"); open(DATEI, ">output_XMLMini3") || die "Datei nicht gefunden"; print DATEI $xmlDoc->toString(); close(DATEI); |
Das Ergebnis sieht hier wie folgt aus:
<?xml version="1.0" encoding="iso-8859-1"?> <test another="<>" attr1="1" attr2="2" debug="0"> <info addon="Here is an valid xml text using linebreaks" attr1="perl" attr2="xml module" /> <info addon="invalid unescaped text" attr1="perl" attr2="xml module"> Here is an valid xml text using linebreaks and unescaped characters like < and > do you see the and? </info> <info attr1="perl" attr2="xml module" /> <info attr1="perl" attr2="xml module"> <deepinfo> last text here </deepinfo> </info> <info addon="unescaped text for a CDATA"> <![CDATA[ Here is an valid xml text using linebreaks and unescaped characters like < and > do you see the and? ]]> </info> </test> |
*Wow* – nach dem schlechten Bild zum Start macht dieses Ergbnis einen sehr guten Eindruck! Die Struktur ist komplett, ist inhaltlich korrekt und wurde korrekt escaped. In kurzen Worten: Das XML ist perfekt wie erwartet! Es scheint sich also zu lohnen einen weiteren Blick auf XML::Mini::Document zu werfen. Und genau das werden wir im nächsten Kapitel tun.
Um das testen zu können, passen wir unser Testprogramm nocheinmal etwas an
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | #!/usr/bin/perl # Simple XML module test unit ### # We are strict, cauz we are Elitecoderz! use strict; use XML::Mini::Document; use XML::Mini; ################ # Get / Check Parameter (here we get the xml file we wanna deal with) if ($#ARGV+1 != 1) { print "Error: Wrong number of parameters.\n"; exit(1); } my $input = $ARGV[0]; chomp($input); ################ # Read Inputfile / check content / validate XML structure # Some modules are able to read directly from a file; for easy going, we use this method here. # Direct, dirty, but simple reading of a file open(FILE, "<$input") || die "Error: File not readable.\n"; my @lines = <FILE>; close(FILE); # Put the lines into one string for this parser my $XMLString = join(' ',@lines); #################################################################### # XML::Mini my $xmlDoc = XML::Mini::Document->new(); eval { $xmlDoc->parse($XMLString); }; if ($@) { print "Error: XML parsing error: $@\n"; exit(1); } my $xmlRoot = $xmlDoc->getRoot(); my $firstnode = $xmlDoc->getElementByPath('test/info'); $firstnode->attribute('addon', "Here is an valid xml text\nusing linebreaks"); my $cdatanode = $xmlDoc->getElementByPath('test/info',1,5); print "\n\n--\n".$cdatanode->getValue."\n--\n\n"; my $textnode = $xmlDoc->getElementByPath('test/info',1,2); print "\n\n--\n".$textnode->getValue."\n--\n\n"; open(DATEI, ">output_XMLMini4") || die "Datei nicht gefunden"; print DATEI $xmlDoc->toString(); close(DATEI); |
und nehmen als Test-XML das eben selbst generierte XML-File her. Die Ausgabe die wir erhalten sieht wie folgt aus:
-- Here is an valid xml text using linebreaks and unescaped characters like < and > do you see the and? -- -- Here is an valid xml text using linebreaks and unescaped characters like < and > do you see the and? --
Das Ergebnis des CDATAs sieht perfekt aus, der aus dem zweiten Knoten ausgelesene Text wurde jedoch leider nicht unescaped. Es ist zwar kein große Sache dies selbst zu tun, aber meiner Meinung nach sollte das XML::Mini::Document für mich tun.
Ergebnis:
Die Methode “fromHash()” sollte man tunlichst nicht verwenden, es sei denn man ist darauf aus seine XML-Struktur gründlich zu zerstören. Die “toHash()” Methode kann eventuell ganz nützlich sein um kurz und schnell Informationen aus einem XML auszulesen.Die Toolbox für das direkte bearbeiten von XML-Strukturen hingegen ist sehr praktisch. Besonders gut gefällt mir die Idee und der Syntax um in bestimmten Pfaden zu navigieren (durch anhängen des “node-count”). Geniale Idee – hervorragende Umsetzung!
XML::Mini::Document kann mit allen XML-Strukturen umgehen und escaped Inhalte -wo benötigt- automatisch. Das Unescaping von Texten muß dagegen leider händisch vorgenommen werden – in meinen Augen ein klarer Bug der mich davon abhält dieses Modul produktiv zu nutzen.
XML::Mini::Document ist klar die Beste Wahl, wenn man mit etwas komplexeren XML-Strukturen umgehen muß. Da das unescaping “händisch” selbst gemacht werden muß empfehle ich jedoch dieses Modul nicht bei großen, komplexen und “empfindlichen” XMLs einzusetzen. Ansonsten ist XML::Mini::Document ein einfacher und schneller Weg mit XMLs zu arbeiten.
Im nächsten Artikel werde ich mich mit dem Thema “Kompfortabler Umgang mit XML::LibXML” beschäftigen.
Alle oben durchgeführten Tests hat die XML::LibXML ohne Schwächen oder Fehler bestanden. Dafür gibt es einige andere Probleme, die einem das Leben unter XML::LibXML schwer machen.
Leider ist der Eintrag nur auf English verfügbar.