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

Iterator.pm - liefert Pfade/Werte komplexer Datenstrukturen

1. Kurzbeschreibung

Iterator.pm ist ein objektorientiertes (reines) Perl-Modul zum Durchlaufen von komplexen Datenstrukturen (LoL, LoH, HoL, HoH usf.). Während die eingebauten Perl-Funktionen foreach(), each(), keys() und values() nur eine Ebene einer Struktur bearbeiten können, gräbt Iterator in die Tiefe - und betrachtet eine Struktur quasi als eindimensionalen Hash.

Zu jedem Element einer verschachtelten Struktur werden sukzessive der symbolische Name ("Datenpfad"), der - nicht modifizierte! - Wert sowie einige Zusatzinformationen geliefert.

Damit stellt Iterator eine einheitliche Syntax zur Abarbeitung von Datenquellen unterschiedlichen Typs bereit.

Iterator modifiziert die übergebene Datenstruktur nicht. Allerdings kann der Benutzer Werte explizit via Iterator ändern.

Iterator exportiert keine Variablen oder Funktionen. Zwar lassen sich bekanntlich alle Paket-subs auch via &Paketname::subname () aufrufen, sinnvolle Ergebnisse darf man dann aber nicht zwingend erwarten :-)

Ausnahmen gibts aber auch hier:

Data::Iterator::cfg()

womit sich (auch) modulweite Voreinstellungen lesen/setzen lassen.

$Data::Iterator::VERSION

die Versionsnummer des Moduls.

2. Abhängigkeiten

Iterator benötigt die Module Carp und FileHandle (Bestandteile der Standarddistributionen).

3. Verwendung

4. Methoden

new()

Liefert ein neues Iterator-Datenobjekt als blessed reference auf einen Hash; undef, falls keine Referenz auf die übergebene Quell-Datenstruktur gebildet werden konnte.

Parameter (Referenz auf die darzustellende Datenstruktur):

 (1) \%hash
 (2) \@array
 (3) \&code
 (4) \*glob
 (5) \$scalar   (nicht sehr struktural...)
 (6) '-FILE:Path/to/file.ext'
 (7) $scalar    (ditto nicht sehr struktural...)

Rückgabe:

 - Scalar: Gesegnetet Referenz auf Iterator-Objekt, oder
 - undef:  bei Mißerfolg (Objekt konnte wg. unbekannten Referenztyps nicht erstellt werden)
cfg()

Setzt/liest je nach Aufruf die Konfiguration des respektiven Iterator-Objektes (Aufruf als Objektmethode) oder die modulweite Konfiguration (Aufruf als Klassenmethode Data::Iterator->cfg()). Benannte Einstellungen werden in der Reihenfolge der Übergabe in einem Array zurückgegeben.

Welche Werte gesetzt und/oder gelesen werden, entscheidet sich anhand der übergebenen Parameter:

- Wird der Name einer Einstellung gegeben, gefolgt von einem Wert (== Nicht-Einstellungsname), wird diese Einstellung auf den gegebenen Wert gesetzt. Der alte Wert wird zurückgeliefert:

 my @object_opts = $dobj->cfg    (-opt1 => 'val1', -opt2 => 'val2', ...);
 my @global_opts = Data::Iterator->cfg (-opt1 => 'val1', -opt2 => 'val2', ...);

 setzt -opt1 und -opt2 auf 'val1' bzw. 'val2' und liefert:

 (old_val_of_opt1, old_val_of_opt2, ...)

- Wird nur der Einstellungsname gegeben, liefert cfg() den zugehörigen Wert:

 my @object_opts = $dobj->cfg    ('-opt2', '-opt1', ...);
 my @global_opts = Data::Iterator->cfg ('-opt2', '-opt1', ...);

 liefert:

 (val_of_opt2, val_of_opt1, ...)

- Setzen und Lesen können bei der Parameterübergabe beliebig kombiniert werden:

 my @object_opts = $dobj->cfg    (-opt3, -opt1 => 'new!', -opt2);
 my @global_opts = Data::Iterator->cfg (-opt3, -opt1 => 'new!', -opt2);

 liefert:

 (val_of_opt3, old_val_of_opt1, val_of_-opt2 )

- Wird kein Parameter gegeben, liefert cfg() alle Einstellungen in einem Hash zurück:

 my %object_opts = $dobj->cfg;
 my %global_opts = Data::Iterator->cfg;

Zu den Einstellungen siehe Abschnitt 5. Optionen.

element()

Liefert im Arraykontext Informationen über Elemente der an new() übergebenen Datenstruktur, eine leere Liste, wenn keine weiteren Elemente vorhanden sind.

Liefert im Scalarkontext 1, wenn ein Element gefunden wurde, undef, falls Strukturende erreicht.

element() erzeugt keine Liste und keine Kopie der übergebenen Daten, sondern grast die Struktur elementweise ab und liefert im Listenkontext eine Liste (sic!) mit diversen Informationen über das jeweilige Element - weshalb bspw. eine while()-Schleife hervorragend geeignet ist, den kompletten Baum zu durchforsten.

In einem foreach-Loop hingegen liefert element() nicht zwingend die gewünschten Resultate... (foreach() arbeitet eine Liste ab und stellt selber den Listenkontext her)

 my ($p, $v, $k, $l, $r, $pp, $p) = $obj->element;

wobei:

 [0] $p:  "Datenpfad", ein String im Format {'key'}|[index]{'key'}|[index] usw.
 [1] $v:  Der Wert
 [2] $k:  der Schlüssel/Index des aktuellen Elements
 [3] $l:  Ebene ("level") des aktuellen Elements in der Hierarchie
 [4] $r:  Referenz auf das aktuelle Element
 [5] $pp: "Elternpfad", Name des nächsthöheren Datenelements (Array, Hash usf.)
          Elternpfad.({Schlüssel}|{Index]) ergibt den Datenpfad [0].
 [6] $p:  Elter des aktuellen Elements

Zur sukzessiven Listung einer Datenstruktur verwende man bspw. folgenden Code:

 while (my @elm = $dobj->element) {
   print join ('|', @elm),"\n";
 }

oder:

 while ($dobj->element) {
   print $dobj->{path}.' = '.$dobj->{val}.', at '.$dobj->{vref}."\n";
 }

Soll eine Unterstruktur dargestellt werden, gebe man element() eine Pfadangabe (einen String) mit:

 while (my @elm = $dobj->element('{c}*')) {
   print join ('|', @elm),"\n";
 }

Soll ein einzelner Wert zu einem Datenpfad geliefert werden, spart man sich das Sternchen:

 print join ('|', $dobj->element ('{c}')),"\n";

Tritt ein Fehler auf, findet sich eine entsprechende Meldung in

 $dobj->{'err'}

Nützlich, wenn warn()-ungen abgeschaltet wurden.

Via element() können Werte gesetzt werden. Dazu akzeptiert die Methode einen zweiten Parameter, und liefert den alten Wert:

 print $dobj->element ('{c}{c3}', 'a new value!');
 # druckt 'val_of_c3'
 print ($dobj->element ('{c}{c3}'))[1];
 # druckt 'a new value!'
reset()

Setzt den internen Stack von element() zurück, d.h. nach einem unvollständigen Durchlauf beginnt element() wieder am Anfang der initialen Datenstruktur. Nützlich, wenn eine while($dobj->element()){...}-Schleife vorzeitig verlassen wurde.

reset() arbeitet selektiv. Wird ein Datenpfad übergeben, wird der Stack für die der entsprechende Unterstruktur zurückgesetzt.

keys()

Liefert einen Array mit den Datenpfaden des Objektes, über den sich bspw. mit foreach() iterieren läßt:

 my @keys   = $dobj->keys;
 my @c_keys = $dobj->keys('{c}');

keys() kann ein initialer Datenpfad mitgegeben werden. keys() liefert dann die Datenpfade des Elementes, das an [Datenpfad] gefunden wurde. Ein gegebenfalls angehängtes Sternchen wird ignoriert.

values()

Liefert einen Array mit den Werten des Objektes, über den sich bspw. mit foreach() iterieren läßt:

 my @vals   = $dobj->values;
 my @c_vals = $dobj->values('{c}');

Auch dieser Methode kann ein initialer Datenpfad mitgegeben werden. values() liefert dann die Werte des Elementes, das an [Datenpfad] gefunden wurde. Ein gegebenfalls angehängtes Sternchen wird ignoriert.

5. Optionen

Iterator kennt drei Gruppen von Einstellungen, die verschiedene Bereiche beeinflussen:

(1) die Darstellung:

"-Nodes" Werte: 0|1

Schaltet die Darstellung von Knoten (Elementen, die eine Referenz bspw. auf einen Hash oder Array enthalten) ein (1) bzw. aus (0). Default ist 0.

"-DigLevel" Werte: undef|Integer

Gibt an, ob alle Ebenen der Datenstruktur dargestellt werden (undef) oder nur Elemente bis zur (inklusive) Ebene n. Default ist undef.

(2) die Auflösung des als Datenobjekt übergebenen Wertes bei new():

"-SRefs" Werte: 0|1

Gibt an, ob eine initiale Skalarreferenz bei Initialisierung bis zu ersten Nicht-Skalarreferenz aufgelöst werden soll (1) oder nicht (0). Default ist 1.

Wird hier 0 gegeben, liefert element() lediglich das Argument zurück. Es sei denn, "-DigSRefs" ist auf 1 gesetzt.

"-Files" Werte: 0|1

Gibt an, ob ein Argument im "-File:..."-Format bei Initialisierung die angegebene Datei öffnen soll (1) oder nicht (0). Default ist 1.

Wird hier 0 gegeben, liefern element() und values() lediglich das Argument zurück. Es sei denn, "-DigFiles" ist auf 1 gesetzt.

"-Code" Werte: 0|1

Gibt an, ob ein Coderef-Argument bei Initialisierung ausgeführt wird (1) oder nicht (0). Default ist 1.

Wird hier 0 gegeben, liefern element() und values() lediglich das Argument zurück. Es sei denn, "-DigCode" ist auf 1 gesetzt.

(3) die Auflösung von in der Datenstruktur enthaltenen Referenzen bei element(), keys(), values():

"-DigSRefs" Werte: 0|1

Gibt an, ob Skalarreferenzen aufgelöst werden (1) oder nicht (0). Default ist 1.

Merke: Verkettete Skalarreferenzen werden vollständig aufgelöst.

"-DigFiles" Werte: 0|1

Schaltet die Auflösung von "-File:..."-Elementen (i.e. Öffnen und sukzessives einlesen der Datei) ein (1) bzw. aus (0). Default ist 1.

"-DigCode" Werte: 0|1

Schaltet die Ausführung von Codereferenzen ein (1) bzw. aus (0). Default ist 1.

"-DigGlobs" Werte: 0|1

Schaltet die Verfolgung von Globreferenzen (i.e. Lesen vom referenzierten Handle) ein (1) bzw. aus (0). Default ist 1.

6. Feinheiten

Datentypen

Folgende Datentypen kann Iterator handhaben:

- Skalare: werden mit ihrem Inhalt dargestellt

- Referenzen: werden aufgelöst. Erkannt werden die Perl-üblichen Typen (Scalar, Array, Hash, Code, Glob). Die unspezifische REF-Referenz wird als einfacher Skalar behandelt.

Neben diesen Typen erkennt Iterator ein FileHandle-Objekt, und liest die damit bezogene Datei.

- Weiters kennt Iterator den Verweis auf eine Datei. Dieser wird als String gegeben, und muß im Format

"-File:Pfad/dateiname"

vorliegen. Kann die angegebene Datei nicht zum Lesen geöffnet werden, wird ge-warn()-t.

Zirkuläre Referenzen

Referenzen, die auf einen Elter des aktuellen Datenelementes verweisen, werden nicht aufgelöst. Sie erzeugen einen nicht-tödlichen Fehler nebst Meldung. Der Wert des Elementes wird als undef geliefert.

Dies gilt auch für via "-File:..." bezogene Dateien, die einen Verweis auf sich selbst enthalten.

Wird ein Datenpfad gegeben, kann das bezogene Datenelement getrost auf einen Elter verweisen - es wird gleichwohl aufgelöst.

element()

- Verhalten

Wird eine Struktur via element() komplett durchlaufen, fängt element() in einer späteren Schleife von vorne an.

Wird der Durchlauf abgebrochen, machen spätere element()-Aufrufe da weiter, wo zuvor abgebrochen wurde.

Ist dies nicht gewünscht, sollte zwischenzeitlich reset() aufgerufen werden. Dies setzt element() auf das erste Element zurück.

Diese Verhalten gilt auch für die Abarbeitung von Teilstrukturen, reset() ist dann der entsprechende Datenpfad zu übergeben.

Merke: element()-Aufrufe mit unterschiedlichen Datenpfaden beeinflussen sich wechselseitig nicht.

Gleiches gilt für keys()- bzw. values()-Aufrufe. Diese interferieren in keinem Fall mit element()-Aufrufen.

element() dotiert folgende Datenfelder seines Objektes:

@{$dobj}{'path','val','key','level','vref','ppath','parent','err'}

Objekttheoretisch zwar nicht ganz sauber, kann damit stets auf die letzten Ergebnisse eines element()-Aufrufes zugegriffen werden. Dies gilt nicht, wenn via element() ein Wert gesetzt wurde.

- Dateien

Via "-File:...", \*Glob bzw. FileHandle-Objekt bezogene Dateien/Handles können mit element() nicht beschrieben werden. Siehe dazu Stichwort "Pseudoarrays".

- Autovivification:

Wird element() ein Datenpfad übergeben, dessen letzter Schlüssel/Index auf ein Element mit einem inexistenten Elter verweist, wird erfolgt keine Wunderzeugung des Elters. Dies im Unterschied zum Standardlookup in Perl.

Wird hingegen via element() ein Wert gesetzt, werden bei Bedarf alle nicht vorhandenen Vorfahren gezeugt.

Ebenen

Die jeweils gelieferte Ebene eines Datenelementes gibt die Schachtelungstiefe an, gerechnet von der aktuellen Wurzel. Die Zählung beginnt mit 0.

Will sagen, die Ebene 0 der Stammstruktur ist nicht identisch mit der Ebene 0 einer Teilstruktur, die ihrerseits auf einer beliebigen Ebene der Stammstruktur angesiedelt sein kann.

Entsprechend begrenzt die Einstellung -DigLevel die Datendarstellung stets auf n Ebenen von der aktuellen Wurzel an gerechnet, gleich ob gerade die Stammstruktur (Datenpfad = '' oder undef) oder eine Teilstruktur dargestellt wird.

Datenpfade, Format

element(), keys() und values() sind recht tolerant hinsichtlich der Schreibweise der ggf. übergebenen Pfade zu den Daten.

Die Standardnotation entspricht der Perl-mäßigen Indizierung von Hashes/Arrays:

 my $path = "{'key1'}{'key2'}[2][1]";

Wem das zu umständlich ist, kann zur verkürzten Notation greifen:

 my $path = 'key1.key2.2.1';

Soll ein klammerloser Pfad richtig aufgelöst werden, müssen Hashschlüssel mindestens ein nicht-numerisches Zeichen enthalten. Sonst werden sie für Arrayindices gehalten - und generieren einen nicht-fatalen Fehler nebst Meldung.

Weiters kann ein beliebiger Trenner definiert werden - nützlich, wenn der "." in bezogenen Hashschlüsseln vorkommt:

 my $path = "#key1#key2#2#1";

Merke:

- Ist das erste Zeichen im Pfad nicht-alphanumerisch, wird dieses als Trenner behandelt. Ausnahmen: [ und {

- Ist das erste Zeichen alphanumerisch, wird der . als Trenner angenommen.

- klammerlose und klammerhaltige Schreibweise dürfen gemischt werden: "#key1{key2}[2]#[1]"

- vor dem Backslash \ als Trenner sollte man sich hüten.

- Das quoten von Hashschlüsseln ist nicht zwingend erforderlich.

Behandlung von Coderefs

Coderefs werden derart aufgelöst, daß der referenzierte Code ausgeführt wird. Dies geschieht bereits bei der Initialisierung des ensprechenden Datenelementes.

Vor der Ausführung werden $SIG{__WARN__} und $SIG{__DIE__} lokal auf eine eigene Routine verbogen, Fehler im referenzierten Code führen also nicht zum Tod des aktuellen Skriptes.

Die Ausgabe von im Code ausgelöstem warn bzw. die wird abgefangen und gespeichert.

Die Rückgabewerte des Codes werden in einem Pseudoarray gespeichert und von element() bzw. values() geliefert.

Findet sich im ersten Element des Ergebnisarray ein Array namens '_ERR_', hat der referenzierte Code ge-warn()t oder ge-carp()t oder ist mit die() bzw. croak() abgestorben. Wenn nicht, dann mutmaßlich nicht. Warnungen bzw. Tode können anhand der Präfixe 'WARN : ' bzw. 'FATAL: ' unterschieden werden.

Pseudoarrays

Werden "-FILE:..."- oder Coderef-Elemente aufgelöst, finden sich die Ergebnisse in einem Pseudoarray.

Pseudo deshalb, diese Arrays nicht als solche existieren. Entsprechend kann auf deren Elemente nicht unmittelbar - etwa über einen entsprechenden Datenpfad - zugegriffen werden. Dies deshalb, weil Iterator die ursprüngliche Datenstruktur nicht modifizieren mag und deshalb keinen Handle/Datenpfad kennt bzw. generiert, der einen "normalen" Zugriff erlauben würde.

7. Version

  • 0.021 vom 30.12.2000 (Bugfix-Release)

     - Iteration funktioniert nun auch bei einem Array-Datenobjekt,
       wenn Datenpfad gegeben
     - Einige lästige "Use of uninitialized value..."-Meldungen
       sind nun gegenstandslos
     - Beispielcode in der Dokumentation korrigiert
  • 0.02 vom 10.12.2000 (Erstveröffentlichung)

8. Autor

 Hartmut Camphausen <h.camp@creagen.de>
 Internet: http://www.creagen.de/

9.Copyright

Copyright (c) 2000 by CREAGEN Computerkram Hartmut Camphausen <h.camp@creagen.de>. Alle Rechte vorbehalten.

Dieses Modul ist freie Software. Es kann zu den gleichen Bedingungen genutzt, verändert und weitergegeben werden wie Perl selbst.

1 POD Error

The following errors were encountered while parsing the POD:

Around line 506:

Non-ASCII character seen before =encoding in 'Während'. Assuming CP1252