Data::Stag(3pm)

๐Ÿ“ฆ
version 0.14 ยท 5 versions
, in debian bookworm, debian trixie, ubuntu noble, ubuntu questing

okay bestie, welcome to the Data::Stag(3pm) manual page from the User Contributed Perl Documentation ๐Ÿ’–. This is the structured tags moment, and honestly? It's giving iconic. No cap. Let's get into it, fr fr. ๐ŸŽ€

โœจ NAME โœจ

Data::Stag โ€” Structured Tags datastructures. That's the whole name, that's the whole vibe. ๐Ÿฆ‹

๐Ÿฆ‹ SYNOPSIS ๐Ÿฆ‹

okay so first up, procedural usage โ€” you call subroutines and they do the thing:

         # PROCEDURAL USAGE
         use Data::Stag qw(:all);
         $doc = stag_parse($file);
         @persons = stag_find($doc, "person");
         foreach $p (@persons) {
           printf "%s, %s phone: %s\n",
             stag_sget($p, "family_name"),
             stag_sget($p, "given_name"),
             stag_sget($p, "phone_no"),
           ;
         }

and then object-oriented usage โ€” same energy, but with methods and a lil more main character energy:

         # OBJECT-ORIENTED USAGE
         use Data::Stag;
         $doc = Data::Stag->parse($file);
         @persons = $doc->find("person");
         foreach $p (@person) {
           printf "%s, %s phone:%s\n",
             $p->sget("family_name"),
             $p->sget("given_name"),
             $p->sget("phone_no"),
           ;
         }

๐Ÿ’– DESCRIPTION ๐Ÿ’–

This module is for manipulating data as hierarchical tag/value pairs (Structured TAGs or Simple Tree AGgreggates). These datastructures can be represented as nested arrays, which have the advantage of being native to perl. A simple example is shown below:

         [ person=> [  [ family_name => $family_name ],
                       [ given_name  => $given_name  ],
                       [ phone_no    => $phone_no    ] ] ],

Data::Stag uses a subset of XML for import and export. This means the module can also be used as a general XML parser/writer (with certain caveats). ๐Ÿ‘€

The above set of structured tags can be represented in XML as

         <person>
           <family_name>...</family_name>
           <given_name>...</given_name>
           <phone_no>...</phone_no>
         </person>

This datastructure can be examined, manipulated and exported using Stag functions or methods:

         $document = Data::Stag->parse($file);
         @persons = $document->find('person');
         foreach my $person (@person) {
           $person->set('full_name',
                        $person->sget('given_name') . ' ' .
                        $person->sget('family_name'));
         }

Advanced querying is performed by passing functions, for example:

         # get all people in dataset with name starting 'A'
         @persons =
           $document->where('person',
                            sub {shift->sget('family_name') =~ /^A/});

One of the things that marks this module out against other XML modules is this emphasis on a functional approach as an obect-oriented or procedural approach. (yes, the typo is staying, it's part of the lore ๐Ÿ™ˆ)

For full information on the stag project, see <http://stag.sourceforge.net> ๐Ÿ’…

๐ŸŽ€ PROCEDURAL VS OBJECT-ORIENTED USAGE ๐ŸŽ€

Depending on your preference, this module can be used a set of procedural subroutine calls, or as method calls upon Data::Stag objects, or both. Both is valid, bestie.

In procedural mode, all the subroutine calls are prefixed stag_ to avoid namespace clashes. The following three calls are equivalent:

         $person = stag_find($doc, "person");
         $person = $doc->find("person");
         $person = $doc->find_person;

In object mode, you can treat any tree element as if it is an object with automatically defined methods for getting/setting the tag values. It's giving glow-up. โœจ

๐ŸŒˆ USE OF XML ๐ŸŒˆ

Nested arrays can be imported and exported as XML, as well as other formats. XML can be slurped into memory all at once (using less memory than an equivalent DOM tree), or a simplified SAX style event handling model can be used. Similarly, data can be exported all at once, or as a series of events.

Although this module can be used as a general XML tool, it is intended primarily as a tool for manipulating hierarchical data using nested tag/value pairs.

This module is more suited to dealing with data-oriented documents than text-oriented documents. iykyk ๐Ÿ“„

By using a simpler subset of XML equivalent to a basic data tree structure, we can write simpler, cleaner code.

This module is ideally suited to element-only XML (that is, XML without attributes or mixed elements).

If you are using attributes or mixed elements, it is useful to know what is going on under the hood. ๐Ÿ‘€

All attributes are turned into elements; they are nested inside an element with name '@'.

For example, the following piece of XML

         <foo id="x">
           <bar>ugh</bar>
         </foo>

Gets represented internally as

         <foo>
           <@>
             <id>x</id>
           </@>
           <bar>ugh</bar>
         </foo>

Of course, this is not valid XML. However, it is just an internal representation - when exporting back to XML it will look like normal XML with attributes again. slay ๐Ÿ’…

Mixed content cannot be represented in a simple tree format, so this is also expanded.

The following piece of XML

         <paragraph id="1" color="green">
           example of <bold>mixed</bold>content
         </paragraph>

gets parsed as if it were actually:

         <paragraph>
           <@>
             <id>1</id>
             <color>green</color>
           </@>
           <.>example of</.>
           <bold>mixed</bold>
           <.>content</.>
         </paragraph>

When using stag with attribute or mixed attribute xml, you can treat '@' and '.' as normal elements

๐ŸŒธ SAX ๐ŸŒธ

This module can also be used as part of a SAX-style event generation / handling framework - see Data::Stag::BaseHandler

๐Ÿ“ PERL REPRESENTATION ๐Ÿ“

Because nested arrays are native to perl, we can specify an XML datastructure directly in perl without going through multiple object calls.

For example, instead of using XML::Writer for the lengthy

         $obj->startTag("record");
         $obj->startTag("field1");
         $obj->characters("foo");
         $obj->endTag("field1");
         $obj->startTag("field2");
         $obj->characters("bar");
         $obj->endTag("field2");
         $obj->end("record");

We can instead write

         $struct = [ record => [
                     [ field1 => 'foo'],
                     [ field2 => 'bar']]];

๐Ÿงš PARSING ๐Ÿงš

The following example is for parsing out subsections of a tree and changing sub-elements

         use Data::Stag qw(:all);
         my $tree = stag_parse($xmlfile);
         my ($subtree) = stag_findnode($tree, $element);
         stag_set($element, $sub_element, $new_val);
         print stag_xml($subtree);

๐Ÿ‘‘ OBJECT ORIENTED ๐Ÿ‘‘

The same can be done in a more OO fashion

         use Data::Stag qw(:all);
         my $tree = Data::Stag->parse($xmlfile);
         my ($subtree) = $tree->findnode($element);
         $element->set($sub_element, $new_val);
         print $subtree->xml;

๐Ÿฆ‹ IN A STREAM ๐Ÿฆ‹

Rather than parsing in a whole file into memory all at once (which may not be suitable for very large files), you can take an event handling approach. The easiest way to do this to register which nodes in the file you are interested in using the makehandler method. The parser will sweep through the file, building objects as it goes, and handing the object to a subroutine that you specify.

For example:

         use Data::Stag;
         # catch the end of 'person' elements
         my $h = Data::Stag->makehandler( person=> sub {
                                                      my ($self, $person) = @_;
                                                      printf "name:%s phone:%s\n",
                                                        $person->get_name,
                                                        $person->get_phone;
                                                      return;   # clear node
                                                       });
         Data::Stag->parse(-handler=>$h,
                           -file=>$f);

see Data::Stag::BaseHandler for writing handlers

See the Stag website at <http://stag.sourceforge.net> for more examples.

๐Ÿงš STRUCTURED TAGS TREE DATA STRUCTURE ๐Ÿงš

A tree of structured tags is represented as a recursively nested array, the elements of the array represent nodes in the tree.

A node is a name/data pair, that can represent tags and values. A node is represented using a reference to an array, where the first element of the array is the tagname, or element, and the second element is the data

This can be visualised as a box:

         +-----------+
         |Name | Data|
         +-----------+

In perl, we represent this pair as a reference to an array

         [ Name => $Data ]

The Data can either be a list of child nodes (subtrees), or a data value.

The terminal nodes (leafs of the tree) contain data values; this is represented in perl using primitive scalars.

For example:

         [ Name => 'Fred' ]

For non-terminal nodes, the Data is a reference to an array, where each element of the the array is a new node.

         +-----------+
         |Name | Data|
         +-----------+
                 |||   +-----------+
                 ||+-->|Name | Data|
                 ||    +-----------+
                 ||
                 ||    +-----------+
                 |+--->|Name | Data|
                 |     +-----------+
                 |
                 |     +-----------+
                 +---->|Name | Data|
                       +-----------+

In perl this would be:

         [ Name => [
                     [Name1 => $Data1],
                     [Name2 => $Data2],
                     [Name3 => $Data3],
                   ]
         ];

The extra level of nesting is required to be able to store any node in the tree using a single variable. This representation has lots of advantages over others, eg hashes and mixed hash/array structures.

๐Ÿ’œ MANIPULATION AND QUERYING ๐Ÿ’œ

The following example is taken from biology; we have a list of species (mouse, human, fly) and a list of genes found in that species. These are cross-referenced by an identifier called tax_id. We can do a relational-style inner join on this identifier, as follows -

         use Data::Stag qw(:all);
         my $tree =
         Data::Stag->new(
           'db' => [
           [ 'species_set' => [
             [ 'species' => [
               [ 'common_name' => 'house mouse' ],
               [ 'binomial' => 'Mus musculus' ],
               [ 'tax_id' => '10090' ]]],
             [ 'species' => [
               [ 'common_name' => 'fruit fly' ],
               [ 'binomial' => 'Drosophila melanogaster' ],
               [ 'tax_id' => '7227' ]]],
             [ 'species' => [
               [ 'common_name' => 'human' ],
               [ 'binomial' => 'Homo sapiens' ],
               [ 'tax_id' => '9606' ]]]]],
           [ 'gene_set' => [
             [ 'gene' => [
               [ 'symbol' => 'HGNC' ],
               [ 'tax_id' => '9606' ],
               [ 'phenotype' => 'Hemochromatosis' ],
               [ 'phenotype' => 'Porphyria variegata' ],
               [ 'GO_term' => 'iron homeostasis' ],
               [ 'map' => '6p21.3' ]]],
             [ 'gene' => [
               [ 'symbol' => 'Hfe' ],
               [ 'synonym' => 'MR2' ],
               [ 'tax_id' => '10090' ],
               [ 'GO_term' => 'integral membrane protein' ],
               [ 'map' => '13 A2-A4' ]]]]]]
          );
# inner join of species and gene parts of tree,
# based on 'tax_id' element
my $gene_set = $tree->find("gene_set");       # get <gene_set> element
my $species_set = $tree->find("species_set"); # get <species_set> element
$gene_set->ijoin("gene", "tax_id", $species_set);   # INNER JOIN

print "Reorganised data:\n";
print $gene_set->xml;

# find all genes starting with letter 'H' in where species/common_name=human
my @genes =
  $gene_set->where('gene',
                   sub { my $g = shift;
                         $g->get_symbol =~ /^H/ &&
                         $g->findval("common_name") eq ('human')});

print "Human genes beginning 'H'\n";
print $_->xml foreach @genes;

like, that's the whole join-and-query serve in one go ๐Ÿ’… no cap.


๐ŸŒธ S-Expression (Lisp) representation ๐ŸŒธ

okay bestie, so the data you're holding with this module can also be written out as Lisp-style S-Expressions ๐Ÿงš

See Data::Stag::SxprParser and Data::Stag::SxprWriter.

If we execute this code on the XML from the example above

$stag = Data::Stag->parse($xmlfile);
print $stag->sxpr;

The following S-Expression will be printed:

'(db
  (species_set
    (species
      (common_name "house mouse")
      (binomial "Mus musculus")
      (tax_id "10090"))
    (species
      (common_name "fruit fly")
      (binomial "Drosophila melanogaster")
      (tax_id "7227"))
    (species
      (common_name "human")
      (binomial "Homo sapiens")
      (tax_id "9606")))
  (gene_set
    (gene
      (symbol "HGNC")
      (tax_id "9606")
      (phenotype "Hemochromatosis")
      (phenotype "Porphyria variegata")
      (GO_term "iron homeostasis")
      (map
        (cytological
          (chromosome "6")
          (band "p21.3"))))
    (gene
      (symbol "Hfe")
      (synonym "MR2")
      (tax_id "10090")
      (GO_term "integral membrane protein")))
  (similarity_set
    (pair
      (symbol "HGNC")
      (symbol "Hfe"))
    (pair
      (symbol "WNT3A")
      (symbol "Wnt3a"))))

๐Ÿงš TIPS FOR EMACS USERS AND LISP PROGRAMMERS ๐Ÿงš

If you use emacs, you can save this as a file with the .el suffix and get syntax highlighting for editing this file. Quotes around the terminal node data items are optional.

If you know emacs lisp or any other lisp, this also turns out to be a very nice language for manipulating these datastructures. Try copying and pasting the above s-expression to the emacs scratch buffer and playing with it in lisp ๐Ÿ‘‘.

๐ŸŽ€ INDENTED TEXT REPRESENTATION ๐ŸŽ€

Data::Stag has its own text format for writing data trees. Again, this is only possible because we are working with a subset of XML (no attributes, no mixed elements). The data structure above can be written as follows -

db:
  species_set:
    species:
      common_name: house mouse
      binomial: Mus musculus
      tax_id: 10090
    species:
      common_name: fruit fly
      binomial: Drosophila melanogaster
      tax_id: 7227
    species:
      common_name: human
      binomial: Homo sapiens
      tax_id: 9606
  gene_set:
    gene:
      symbol: HGNC
      tax_id: 9606
      phenotype: Hemochromatosis
      phenotype: Porphyria variegata
      GO_term: iron homeostasis
      map: 6p21.3
    gene:
      symbol: Hfe
      synonym: MR2
      tax_id: 10090
      GO_term: integral membrane protein
      map: 13 A2-A4
  similarity_set:
    pair:
      symbol: HGNC
      symbol: Hfe
    pair:
      symbol: WNT3A
      symbol: Wnt3a

See Data::Stag::ITextParser and Data::Stag::ITextWriter.

๐Ÿฆ‹ NESTED ARRAY SPECIFICATION II ๐Ÿฆ‹

To avoid excessive square bracket usage, you can specify a structure like this:

use Data::Stag qw(:all);

*N = \&stag_new;
my $tree =
  N(top=>[
          N('personset'=>[
                          N('person'=>[
                                       N('name'=>'davey'),
                                       N('address'=>'here'),
                                       N('description'=>[
                                                         N('hair'=>'green'),
                                                         N('eyes'=>'two'),
                                                         N('teeth'=>5),
                                                        ]
                                        ),
                                       N('pets'=>[
                                                  N('petname'=>'igor'),
                                                  N('petname'=>'ginger'),
                                                 ]
                                        ),

                                      ],
                           ),
                          N('person'=>[
                                       N('name'=>'shuggy'),
                                       N('address'=>'there'),
                                       N('description'=>[
                                                         N('hair'=>'red'),
                                                         N('eyes'=>'three'),
                                                         N('teeth'=>1),
                                                        ]
                                        ),
                                       N('pets'=>[
                                                  N('petname'=>'thud'),
                                                  N('petname'=>'spud'),
                                                 ]
                                        ),
                                      ]
                           ),
                         ]
           ),
          N('animalset'=>[
                          N('animal'=>[
                                       N('name'=>'igor'),
                                       N('class'=>'rat'),
                                       N('description'=>[
                                                         N('fur'=>'white'),
                                                         N('eyes'=>'red'),
                                                         N('teeth'=>50),
                                                        ],
                                        ),
                                      ],
                           ),
                         ]
           ),

         ]
   );

# find all people
my @persons = stag_find($tree, 'person');

# write xml for all red haired people
foreach my $p (@persons) {
  print stag_xml($p)
    if stag_tmatch($p, "hair", "red");
} ;

# find all people that have name == shuggy
my @p =
  stag_qmatch($tree,
              "person",
              "name",
              "shuggy");

๐Ÿ’œ NODES AS DATA OBJECTS ๐Ÿ’œ

As well as the methods listed below, a node can be treated as if it is a data object of a class determined by the element.

For example, the following are equivalent.

$node->get_name;
$node->get('name');

$node->set_name('fred');
$node->set('name', 'fred');

This is really just syntactic sugar. The autoloaded methods are not checked against any schema, although this may be added in future.

โญ INDEXING STAG TREES โญ

A stag tree can be indexed as a hash for direct retrieval; see Data::Stag::HashDB

This index can be made persistent as a DB file; see Data::Stag::StagDB

If you wish to use Stag in conjunction with a relational database, you should install DBIx::DBStag


๐Ÿ‘‘ STAG METHODS ๐Ÿ‘‘

All method calls are also available as procedural subroutine calls; unless otherwise noted, the subroutine call is the same as the method call, but with the string stag_ prefixed to the method name. The first argument should be a Data::Stag datastructure.

To import all subroutines into the current namespace, use this idiom:

use Data::Stag qw(:all);
$doc = stag_parse($file);
@persons = stag_find($doc, 'person');

If you wish to use this module procedurally, and you are too lazy to prefix all calls with stag_, use this idiom:

use Data::Stag qw(:lazy);
$doc = parse($file);
@persons = find($doc, 'person');
But beware of clashes! ๐Ÿ˜ญ

Most method calls also have a handy short mnemonic. Use of these is optional. Software engineering types prefer longer names, in the belief that this leads to clearer code. Hacker types prefer shorter names, as this requires less keystrokes, and leads to a more compact representation of the code. It is expected that if you do use this module, then its usage will be fairly ubiquitous within your code, and the mnemonics will become familiar, much like the qw and s/ operators in perl. As always with perl, the decision is yours ๐Ÿ’….

Some methods take a single parameter or list of parameters; some have large lists of parameters that can be passed in any order. If the documentation states:

Args: [x str], [y int], [z ANY]

Then the method can be called like this:

$stag->foo("this is x", 55, $ref);

or like this:

$stag->foo(-z=>$ref, -x=>"this is x", -y=>55);

๐ŸŒฑ INITIALIZATION METHODS ๐ŸŒฑ

โœจ new โœจ

  • Title: new
  • Args: element str, data STAG-DATA
  • Returns: Data::Stag node
  • Example: $node = stag_new();
  • Example: $node = Data::Stag->new;
  • Example: $node = Data::Stag->new(person => [[name=>$n], [phone=>$p]]);

creates a new instance of a Data::Stag node ๐ŸŽ€

โœจ stagify (nodify) โœจ

  • Title: stagify
  • Synonym: nodify
  • Args: data ARRAY-REF
  • Returns: Data::Stag node
  • Example: $node = stag_stagify([person => [[name=>$n], [phone=>$p]]]);

turns a perl array reference into a Data::Stag node.

similar to new

โœจ parse โœจ

  • Title: parse
  • Args: [file str], [format str], [handler obj], [fh FileHandle]
  • Returns: Data::Stag node
  • Example: $node = stag_parse($fn);
  • Example: $node = stag_parse(-fh=>$fh, -handler=>$h, -errhandler=>$eh);
  • Example: $node = Data::Stag->parse(-file=>$fn, -handler=>$myhandler);

slurps a file or string into a Data::Stag node structure. Will guess the format (xml, sxpr, itext, indent) from the suffix if it is not given.

The format can also be the name of a parsing module, or an actual parser object;

Okay so the handler is literally any object that can take those nested Stag events (start_event, end_event, evbody) that get generated from the parse. And if you just... don't pass a handler? Then every single event gets cached and the resulting tree gets handed right back to you. No cap. ๐Ÿซถ

See Data::Stag::BaseHandler for writing your own handlers, bestie โ€” that's where you go to build your own.

See Data::Stag::BaseGenerator for the deets on parser classes and error handling. โœจ


๐ŸŒธ parsestr ๐ŸŒธ

  • Title: parsestr
  • Args: [str str], [format str], [handler obj]
  • Returns: Data::Stag node
  • Example: $node = stag_parsestr('(a (b (c "1")))');
  • Example: $node = Data::Stag->parsestr(-str=>$str, -handler=>$myhandler);

Lowkey identical to parse(), except the very first argument is a string instead of... whatever else you'd feed it. It's giving "same energy, different input." ๐Ÿ’…

๐ŸŒˆ from ๐ŸŒˆ

  • Title: from
  • Args: format str, source str
  • Returns: Data::Stag node
  • Example: $node = stag_from('xml', $fn);
  • Example: $node = stag_from('xmlstr', q[<top><x>1</x></top>]);
  • Example: $node = Data::Stag->from($parser, $fn);

Basically the same vibe as parse. It slurps a file or a string straight into a Data::Stag node structure. Obsessed with the convenience, fr fr. ๐ŸŽ€

And plot twist: the format can also just be the name of a parsing module, or an actual parser object. Flexible queen behavior.

๐Ÿฆ‹ unflatten ๐Ÿฆ‹

  • Title: unflatten
  • Args: data array
  • Returns: Data::Stag node
  • Example: $node = stag_unflatten(person=>[name=>$n, phone=>$p, address=>[street=>$s, city=>$c]]);

This one creates a node structure from a semi-flattened representation โ€” meaning the children of a node are written as a flat list of data instead of a list of array references. So much easier on the eyes, ngl.

So like, a structure can be specified as:

person=>[name=>$n,
         phone=>$p,
         address=>[street=>$s,
                   city=>$c]]

Instead of... this absolute jump-scare:

[person=>[ [name=>$n],
           [phone=>$p],
           [address=>[ [street=>$s],
                       [city=>$c] ] ]
         ]
]

And the former gets converted into the latter for the internal representation. The way that unflatten just does that for you?? Iconic. โœจ

๐Ÿ’– makehandler ๐Ÿ’–

  • Title: makehandler
  • Args: hash of CODEREFs keyed by element name โ€” OR a string containing the name of a module
  • Returns: Data::Stag::BaseHandler
  • Example: $h = Data::Stag->makehandler(%subs);
  • Example: $h = Data::Stag->makehandler("My::FooHandler");
  • Example: $h = Data::Stag->makehandler('xml');

This creates a Stag event handler, bestie. The argument is a hash of subroutines keyed by element/node name. After each node gets fired by the parser/generator, the subroutine is called, passing the handler object and the stag node as arguments. And whatever the subroutine returns gets placed right back into the tree. Main character subroutines only. ๐Ÿ‘‘

For example, for a parser/generator that fires events with the following tree form:

<person>
  <name>foo</name>
  ...
</person>

we can create a handler that writes person/name like this:

$h = Data::Stag->makehandler(
                             person => sub { my ($self,$stag) = @_;
                                             print $stag->name;
                                             return $stag; # don't change tree
                                           });
$stag = Data::Stag->parse(-str=>"(...)", -handler=>$h)

See Data::Stag::BaseHandler for the full tea on handlers. ๐Ÿ“

๐Ÿงš getformathandler ๐Ÿงš

  • Title: getformathandler
  • Args: format str OR Data::Stag::BaseHandler
  • Returns: Data::Stag::BaseHandler
  • Example:
$h = Data::Stag->getformathandler('xml');
$h->file("my.xml");
Data::Stag->parse(-fn=>$fn, -handler=>$h);

Creates a Stag event handler โ€” and this handler can be passed straight to an event generator / parser. The built-in handlers include:

  • xml โ€” Generates xml tags from events
  • sxpr โ€” Generates S-Expressions from events
  • itext โ€” Generates itext format from events
  • indent โ€” Generates indent format from events

All the above are kinds of Data::Stag::Writer. Every single one, no skipping. ๐Ÿ’œ

๐Ÿ’… chainhandler ๐Ÿ’…

  • Title: chainhandler
  • Args: blocked events โ€” str or str[]; initial handler โ€” handler object; final handler โ€” handler object
  • Returns: (nothing listed, bestie)
  • Example: $h = Data::Stag->chainhandler('foo', $processor, 'xml');

This chains handlers together โ€” like, you might wanna make transforms on an event stream, and then pass that event stream to another handler. Say, an xml handler. Chaining = teamwork, and teamwork makes the dream work. ๐ŸŒˆ

$processor = Data::Stag->makehandler(
                                     a => sub { my ($self,$stag) = @_;
                                                $stag->set_foo("bar");
                                                return $stag
                                              },
                                     b => sub { my ($self,$stag) = @_;
                                                $stag->set_blah("eek");
                                                return $stag
                                              },
                                     );
$chainh = Data::Stag->chainhandler(['a', 'b'], $processor, 'xml');
$stag = Data::Stag->parse(-str=>"(...)", -handler=>$chainh)

And if the inner handler has a method CONSUMES(), that method will determine the blocked events if none are specified. Sneaky but slay.

Also see the script stag-handle.pl. ๐Ÿ‘€


๐Ÿ” RECURSIVE SEARCHING ๐Ÿ”

find (f)

  • Title: find
  • Synonym: f
  • Args: element str
  • Returns: node[] or ANY
  • Example: @persons = stag_find($struct, 'person');
  • Example: @persons = $struct->find('person');

Recursively searches the tree for all elements of the given type, and returns all nodes or data elements found. It's giving detective energy. ๐Ÿ•ต๏ธโ€โ™€๏ธ

  • if the element found is a non-terminal node, will return the node
  • if the element found is a terminal (leaf) node, will return the data value

And the element argument can be a path:

@names = $struct->find('department/person/name');

which will find name in the nested structure below:

(department
 (person
  (name "foo")))

Path traversal? We love to see it. โœจ

findnode (fn)

  • Title: findnode
  • Synonym: fn
  • Args: element str
  • Returns: node[]
  • Example: @persons = stag_findnode($struct, 'person');
  • Example: @persons = $struct->findnode('person');

Recursively searches the tree for all elements of the given type, and returns all nodes found. Paths can also be used (see find). Same energy, node-flavored. ๐ŸŽ€

findval (fv)

  • Title: findval
  • Synonym: fv
  • Args: element str
  • Returns: ANY[] or ANY
  • Example: @names = stag_findval($struct, 'name');
  • Example: @names = $struct->findval('name');
  • Example: $firstname = $struct->findval('name');

Recursively searches the tree for all elements of the given type, and returns all data values found. Those data values could be primitive scalars or nodes โ€” surprise, surprise. ๐Ÿ˜Œ

Paths can also be used (see find).

sfindval (sfv)

  • Title: sfindval
  • Synonym: sfv
  • Args: element str
  • Returns: ANY
  • Example: $name = stag_sfindval($struct, 'name');
  • Example: $name = $struct->sfindval('name');

Same as findval, but it returns the first value found. Just the one. One singular value, because sometimes less is more. ๐Ÿ’…

Paths can also be used (see find).

findvallist (fvl)

  • Title: findvallist
  • Synonym: fvl
  • Args: element str[]
  • Returns: ANY[]
  • Example: ($name, $phone) = stag_findvallist($personstruct, 'name', 'phone');
  • Example: ($name, $phone) = $personstruct->findvallist('name', 'phone');

Recursively searches the tree for all elements in the list. Efficient, honestly.

DEPRECATED โ€” yes bestie, this one's deprecated. It still lives here in the docs, but heads up: it's deprecated. ๐Ÿ˜ญ

๐ŸŽ€ DATA ACCESSOR METHODS ๐ŸŽ€

These allow getting and setting of elements directly underneath the current one. Direct children only โ€” no wandering off into the descendents, okay? Keep it local. ๐Ÿฆ‹

get (g)

  • Title: get
  • Synonym: g
  • Args: element str
  • Return: node[] or ANY
  • Example: $name = $person->get('name');
  • Example: @phone_nos = $person->get('phone_no');

Gets the value of the named sub-element. Simple, clean, effective.

  • if the sub-element is a non-terminal, will return a node(s)
  • if the sub-element is a terminal (leaf), it will return the data value(s)

The examples above would work on a data structure like this:

[person => [ [name => 'fred'],
             [phone_no => '1-800-111-2222'],
             [phone_no => '1-415-555-5555']]]

Will return an array or a single value depending on the context. Context-sensitive king. ๐Ÿ‘‘

[equivalent to findval(), except that only direct children (as opposed to all descendents) are checked]

Paths can also be used, like this:

@phones_nos = $struct->get('person/phone_no')

sget (sg)

  • Title: sget
  • Synonym: sg
  • Args: element str
  • Return: ANY
  • Example: $name = $person->sget('name');
  • Example: $phone = $person->sget('phone_no');
  • Example: $phone = $person->sget('department/person/name');

Same as get but always returns a single value. No arrays, no ambiguity, just one thing. Periodt. ๐Ÿ’…

[equivalent to sfindval(), except that only direct children (as opposed to all descendents) are checked]

getl (gl, getlist)

  • Title: gl
  • Synonym: getl
  • Synonym: getlist
  • Args: element str[]
  • Return: node[] or ANY[]
  • Example: ($name, @phone) = $person->getl('name', 'phone_no');

Returns the data values for a list of sub-elements of a node. Bulk request, bulk response. ๐ŸŒธ

[equivalent to findvallist(), except that only direct children (as opposed to all descendents) are checked]

getn (gn, getnode)

  • Title: getn
  • Synonym: gn
  • Synonym: getnode
  • Args: element str
  • Return: node[]
  • Example: $namestruct = $person->getn('name');
  • Example: @pstructs = $person->getn('phone_no');

Same as get but returns the whole node rather than just the data value. Full package, not just the highlight reel. โœจ

[equivalent to findnode(), except that only direct children (as opposed to all descendents) are checked]

sgetmap (sgm)

  • Title: sgetmap
  • Synonym: sgm
  • Args: hash
  • Return: hash
  • Example:
%h = $person->sgetmap('social-security-no'=>'id',
                      'name'              =>'label',
                      'job'               =>0,
                      'address'           =>'location');

Returns a hash of key/val pairs based on the values of the data values of the subnodes in the current element. Keys are mapped according to the hash you passed in โ€” and a value of '' or 0 will map an identical key/val. Mapping queen. ๐Ÿ—บ๏ธ

No multivalued data elements are allowed. Just... none. Don't try it. ๐Ÿ™ˆ

set (s)

  • Title: set
  • Synonym: s
  • Args: element str, datavalue ANY (list)
  • Return: ANY
  • Example: $person->set('name', 'fred'); # single val
  • Example: $person->set('phone_no', $cellphone, $homephone);

Sets the data value of an element for any node. And if the element is multivalued, all the old values get replaced with the new ones you specified. Out with the old, in with the new, that's the vibe. ๐Ÿ’–

ordering is preserved bestie, unless the element you named doesn't exist yet โ€” in that case the new tag/value pair just gets yeeted to the end. ๐Ÿ’…

say we've got a stag node $person like this:

person:
  name: shuggy
  job:  bus driver

and we do this:

$person->set('name', ());

we end up with:

person:
  job:  bus driver

and then if we do this:

$person->set('name', 'shuggy');

the name node comes back as the last attribute, no cap:

person:
  job:  bus driver
  name: shuggy

you can also use magic methods, like:

$person->set_name('shuggy');
$person->set_job('bus driver', 'poet');
print $person->itext;

which prints:

person:
  name: shuggy
  job:  bus driver
  job:  poet
heads up bestie ๐Ÿซถ โ€” if the datavalue is a non-terminal node and not a primitive value, you gotta do it like this instead:
$people  = Data::Stag->new(people=>[
                                    [person=>[[name=>'Sherlock Holmes']]],
                                    [person=>[[name=>'Moriarty']]],
                                   ]);
$address = Data::Stag->new(address=>[
                                     [address_line=>"221B Baker Street"],
                                     [city=>"London"],
                                     [country=>"Great Britain"]]);
($person) = $people->qmatch('person', (name => "Sherlock Holmes"));
$person->set("address", $address->data);

and if you're working with XML data, you set attributes like this, periodt:

$person->set('@'=>[[id=>$id],[foo=>$foo]]);

unset (u)

  • Title: unset
  • Synonym: u
  • Args: element str, datavalue ANY
  • Return: ANY
  • Example: $person->unset('name');
  • Example: $person->unset('phone_no');

prunes all nodes of the specified element from the current node. gone. deleted. not invited to the function anymore. ๐Ÿ’€

magic methods work here too, like:

$person->unset_name;
$person->unset_phone_no;

free

  • Title: free
  • Synonym: u
  • Args:
  • Return:
  • Example: $person->free;

removes all data from a node. and if that node is a subnode of another node? it gets removed altogether. poof. ๐Ÿฆ‹

for instance, if we had the data below:

<person>
  <name>fred</name>
  <address>
  ..
  </address>
</person>

and called:

$person->get_address->free

then the person node would look like this:

<person>
  <name>fred</name>
</person>

add (a)

  • Title: add
  • Synonym: a
  • Args: element str, datavalues ANY[]
  • Args: OR
  • Args: Data::Stag
  • Return: ANY
  • Example: $person->add('phone_no', $cellphone, $homephone);
  • Example: $person->add_phone_no('1-555-555-5555');
  • Example: $dataset->add($person)

adds a datavalue or a whole list of datavalues. if it's already there it appends; if it's not already there it creates new element/value pairs. slay.

if the argument is a stag node, it adds that node under the current one. nesting hours โœจ

for example, if we have the following node in $dataset:

<dataset>
  <person>
    <name>jim</name>
  </person>
</dataset>

and then we add data to it:

($person) = $dataset->qmatch('person', name=>'jim');
$person->add('phone_no', '555-1111', '555-2222');

we'll be left with:

<dataset>
  <person>
    <name>jim</name>
    <phone_no>555-1111</phone_no>
    <phone_no>555-2222</phone_no>
  </person>
</dataset>

and the above call is equivalent to:

$person->add_phone_no('555-1111', '555-2222');

as well as adding data values, we can add whole nodes:

$dataset->add(person=>[[name=>"fred"],
                       [phone_no=>"555-3333"]]);

which is equivalent to:

$dataset->add_person([[name=>"fred"],
                      [phone_no=>"555-3333"]]);
remember bestie, the value has to be specified as an array reference of nodes. and in general you should use the addkid() method to add nodes and use add() to add values. iykyk ๐Ÿ’–

element (e name)

  • Title: element
  • Synonym: e
  • Synonym: name
  • Args:
  • Return: element str
  • Example: $element = $struct->element

returns the element name of the current node. main character identification, honestly. ๐Ÿ‘‘

this is illustrated in the different representation formats below:

sxpr

(element "data")

or

(element
 (sub_element "..."))

xml

<element>data</element>

or

<element>
  <sub_element>...</sub_element>
</element>

perl

[element => $data ]

or

[element => [
              [sub_element => "..." ]]]

itext

element: data

or

element:
  sub_element: ...

indent

element "data"

or

element
  sub_element "..."

kids (k children)

  • Title: kids
  • Synonym: k
  • Synonym: children
  • Args:
  • Return: ANY or ANY[]
  • Example: @nodes = $person->kids
  • Example: $name = $namestruct->kids

returns the data value(s) of the current node. if it's a terminal node, you get a single value which is the data. if it's non-terminal, you get an array of nodes. two moods, one method. ๐ŸŒธ

addkid (ak addchild)

  • Title: addkid
  • Synonym: ak
  • Synonym: addchild
  • Args: kid node
  • Return: ANY
  • Example: $person->addkid($job);

adds a new child node to a non-terminal node, placed after all the existing child nodes. end of the line energy, respectfully. ๐Ÿ’…

you can use this method/procedure to add XML attribute data to a node:

$person->addkid(['@'=>[[id=>$id]]]);

subnodes

  • Title: subnodes
  • Args:
  • Return: ANY[]
  • Example: @nodes = $person->subnodes

returns the child nodes; returns an empty list if this is a terminal node.

ntnodes

  • Title: ntnodes
  • Args:
  • Return: ANY[]
  • Example: @nodes = $person->ntnodes

returns all non-terminal children of the current node.

tnodes

  • Title: tnodes
  • Args:
  • Return: ANY[]
  • Example: @nodes = $person->tnodes

returns all terminal children of the current node.


๐Ÿ” QUERYING AND ADVANCED DATA MANIPULATION ๐Ÿ”

ijoin (j)

  • Title: ijoin
  • Synonym: j
  • Synonym: ij
  • Args: element str, key str, data Node
  • Return: undef

does a relational style inner join โ€” see the previous example in this doc. it's giving very SQL of it. โœจ

key can either be a single node name that must be shared (analogous to SQL INNER JOIN .. USING), or a key1=key2 equivalence relation (analogous to SQL INNER JOIN ... ON).

qmatch (qm)

  • Title: qmatch
  • Synonym: qm
  • Args: return-element str, match-element str, match-value str
  • Return: node[]
  • Example: @persons = $s->qmatch('person', 'name', 'fred');
  • Example: @persons = $s->qmatch('person', (job=>'bus driver'));

queries the node tree for all elements that satisfy the specified key=val match โ€” see the previous example in this doc. ๐Ÿ”Ž

for those inclined to thinking relationally, this can be thought of as a query that returns a stag object:

SELECT <return-element> FROM <stag-node> WHERE <match-element> = <match-value>
this always returns an array bestie, which means calling it in scalar context returns the number of elements. for example:
$n = $s->qmatch('person', (name=>'fred'));

the value of $n will be equal to the number of persons called fred. iconic counting behaviour, no notes. ๐ŸŽ€

tmatch (tm)

  • Title: tmatch
  • Synonym: tm
  • Args: element str, value str
  • Return: bool
  • Example: @persons = grep {$_->tmatch('name', 'fred')} @persons

returns true if the value of the specified element matches โ€” see the previous example in this doc. it's a vibe check but for values. โœ…

tmatchhash (tmh)

  • Title: tmatchhash
  • Synonym: tmh
  • Args: match hashref
  • Return: bool
  • Example: @persons = grep {$_->tmatchhash({name=>'fred', hair_colour=>'green'})} @persons

returns true if the node matches a set of constraints, specified as a hash.

tmatchnode (tmn)

  • Title: tmatchnode
  • Synonym: tmn
  • Args: match node
  • Return: bool
  • Example: @persons = grep {$_->tmatchnode([person=>[[name=>'fred'], [hair_colour=>'green']]])} @persons

returns true if the node matches a set of constraints, specified as a node.

cmatch (cm)

  • Title: cmatch
  • Synonym: cm
  • Args: element str, value str
  • Return: bool
  • Example: $n_freds = $personset->cmatch('name', 'fred');

counts the number of matches. it's the "how many of y'all" method fr fr. ๐Ÿงฎ

where (w)

  • Title: where
  • Synonym: w
  • Args: element str, test CODE
  • Return: Node[]
  • Example: @rich_persons = $data->where('person', sub {shift->get_salary > 100000});

the tree is queried for all elements of the specified type that satisfy the coderef (and the coderef must return a boolean, okay bestie?).

my @rich_dog_or_cat_owners =
  $data->where('person',
               sub {my $p = shift;
                    $p->get_salary > 100000 &&
                    $p->where('pet',
                              sub {shift->get_type =~ /(dog|cat)/})});

iterate (i)

  • Title: iterate
  • Synonym: i
  • Args: CODE
  • Return: Node[]
  • Example:
$data->iterate(sub {
                   my $stag = shift;
                   my $parent = shift;
                   if ($stag->element eq 'pet') {
                       $parent->set_pet_name($stag->get_name);
                   }
               });

iterates through the whole tree calling the specified subroutine. ๐Ÿฆ‹

the first arg passed to the subroutine is the stag node representing the tree at that point; the second arg is for the parent.

for instance, the example code above would turn this:

(person
 (name "jim")
 (pet
  (name "fluffy")))

into this:

(person
 (name "jim")
 (pet_name "fluffy")
 (pet
  (name "fluffy")))

maptree

  • Title: maptree
  • Args: CODE
  • Return: Node[]
  • Example:
$data->maptree(sub {
                   my $stag = shift;
                   my $parent = shift;
                   if ($stag->element eq 'pet') {
                       [pet=>$stag->sget_foo]
                   }
                   else {
                       $stag
                   }
               });

โœจ MISCELLANEOUS METHODS โœจ

duplicate (d)

  • Title: duplicate
  • Synonym: d
  • Args: โ€”
  • Return: Node
  • Example:
$node2 = $node->duplicate;

does a deep copy of a stag structure โ€” it's giving full clone card energy for your whole node tree, no cap. ๐Ÿ’–

isanode

  • Title: isanode
  • Args: โ€”
  • Return: bool
  • Example:
if (stag_isanode($node)) { ... }

lowkey the vibe check for โ€œis this actually a stag node?โ€ ๐Ÿ‘‘

hash

  • Title: hash
  • Args: โ€”
  • Return: hash
  • Example:
$h = $node->hash;

turns a tree into a hash. all data values will be arrayrefs. bestie, every value is wrapped in an arrayref, periodt. ๐ŸŽ€

pairs

  • Title: pairs
  • Args: โ€”
  • Return: โ€”
  • Example: โ€”

turns a tree into a hash. all data values will be scalar (IMPORTANT: this means duplicate values will be lost). not me warning you that duplicates get ghosted ๐Ÿ˜ญ

write

  • Title: write
  • Args: filename str, format str[optional]
  • Return: โ€”
  • Example:
$node->write("myfile.xml");
$node->write("myfile", "itext");

will try and guess the format from the extension if not specified. slay, it does the detective work for you. ๐Ÿ•ต๏ธโ€โ™€๏ธโœจ

xml

  • Title: xml
  • Args: filename str, format str[optional]
  • Return: โ€”
  • Example:
$node->write("myfile.xml");
$node->write("myfile", "itext");
  • Args: โ€”
  • Return: xml str
  • Example:
print $node->xml;

the way this method has two whole forms โ€” one writes, one prints the xml string. iconic, honestly. ๐Ÿ’…


๐Ÿฆ‹ XML METHODS ๐Ÿฆ‹

xslt

  • Title: xslt
  • Args: xslt_file str
  • Return: Node
  • Example:
$new_stag = $stag->xslt('mytransform.xsl');

transforms a stag tree using XSLT. it's giving glow-up via stylesheet. โœจ

xsltstr

  • Title: xsltstr
  • Args: xslt_file str
  • Return: str
  • Example:
print $stag->xsltstr('mytransform.xsl');

As above, but returns the string of the resulting transform, rather than a stag tree. same energy, different return type. ๐Ÿซถ

sax

  • Title: sax
  • Args: saxhandler SAX-CLASS
  • Return: โ€”
  • Example:
$node->sax($mysaxhandler);

turns a tree into a series of SAX events. it's literally streaming the whole vibe. ๐ŸŒŠ

xpath (xp, tree2xpath)

  • Title: xpath
  • Synonym: xp
  • Synonym: tree2xpath
  • Args: โ€”
  • Return: xpath object
  • Example:
$xp = $node->xpath; $q = $xp->find($xpathquerystr);

get you an xpath object so you can query like a main character. ๐Ÿ”ฅ

xpquery (xpq, xpathquery)

  • Title: xpquery
  • Synonym: xpq
  • Synonym: xpathquery
  • Args: xpathquery str
  • Return: Node[]
  • Example:
@nodes = $node->xqp($xpathquerystr);

yes bestie, the example says xqp โ€” copy it exactly, no autocorrect ๐Ÿ’…


๐ŸŽ€ STAG SCRIPTS ๐ŸŽ€

The following scripts come with the stag module

  • stag-autoschema.pl โ€” writes the implicit stag-schema for a stag file
  • stag-db.pl โ€” persistent storage and retrieval for stag data (xml, sxpr, itext)
  • stag-diff.pl โ€” finds the difference between two stag files
  • stag-drawtree.pl โ€” draws a stag file (xml, itext, sxpr) as a PNG diagram
  • stag-filter.pl โ€” filters a stag file (xml, itext, sxpr) for nodes of interest
  • stag-findsubtree.pl โ€” finds nodes in a stag file
  • stag-flatten.pl โ€” turns stag data into a flat table
  • stag-grep.pl โ€” filters a stag file (xml, itext, sxpr) for nodes of interest
  • stag-handle.pl โ€” streams a stag file through a handler into a writer
  • stag-join.pl โ€” joins two stag files together based around common key
  • stag-mogrify.pl โ€” mangle stag files
  • stag-parse.pl โ€” parses a file and fires events (e.g. sxpr to xml)
  • stag-query.pl โ€” aggregare queries
  • stag-split.pl โ€” splits a stag file (xml, itext, sxpr) into multiple files
  • stag-splitter.pl โ€” splits a stag file into multiple files
  • stag-view.pl โ€” draws an expandable Tk tree diagram showing stag data

To get more documentation, type

stag_<script> -h

๐Ÿ’€ BUGS ๐Ÿ’€

none known so far, possibly quite a few undocumented features! not me manifesting a bug-free era ๐Ÿ™ˆ

Not a bug, but the underlying default datastructure of nested arrays is more heavyweight than it needs to be. More lightweight implementations are possible. Some time I will write a C implementation. the way future-you is gonna optimize this โ€” we love to see it. โญ

๐ŸŒˆ WEBSITE ๐ŸŒˆ

<http://stag.sourceforge.net>

๐Ÿ‘‘ AUTHOR ๐Ÿ‘‘

Chris Mungall <cjm AT fruitfly DOT org>

Copyright (c) 2004 Chris Mungall

This module is free software. You may distribute this module under the same terms as perl itself


that's the whole page, bestie! ๐Ÿ’… this was Data::Stag(3pm), from perl v5.34.0, dated 2022-06-12. now go be the main character of your own tree ๐ŸŒณโœจ

๐Ÿ“ฆ where this page comes from