home *** CD-ROM | disk | FTP | other *** search
/ PC Welt 2006 November (DVD) / PCWELT_11_2006.ISO / casper / filesystem.squashfs / usr / share / system-tools-backends-2.0 / scripts / Utils / XML.pm < prev   
Encoding:
Perl POD Document  |  2006-08-14  |  17.5 KB  |  1,002 lines

  1. #!/usr/bin/env perl
  2. #-*- Mode: perl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  3.  
  4. # XML printing, scanning and parsing.
  5. #
  6. # Copyright (C) 2000-2001 Ximian, Inc.
  7. #
  8. # Authors: Hans Petter Jansson <hpj@ximian.com>
  9. #          Arturo Espinosa <arturo@ximian.com>
  10. #          Kenneth Christiansen <kenneth@gnu.org>
  11. #
  12. # This program is free software; you can redistribute it and/or modify
  13. # it under the terms of the GNU Library General Public License as published
  14. # by the Free Software Foundation; either version 2 of the License, or
  15. # (at your option) any later version.
  16. #
  17. # This program is distributed in the hope that it will be useful,
  18. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  20. # GNU Library General Public License for more details.
  21. #
  22. # You should have received a copy of the GNU Library General Public License
  23. # along with this program; if not, write to the Free Software
  24. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
  25.  
  26. package Utils::XML;
  27.  
  28. use Utils::Util;
  29. use Utils::Backend;
  30. use Text::ParseWords;
  31.  
  32. $has_encode = 0;
  33. if (eval "require Encode") {
  34.   Encode->import ();
  35.   $has_encode = 1;
  36. }
  37.  
  38. # --- XML print formatting  --- #
  39.  
  40.  
  41. # &gst_xml_enter: Call after entering a block. Increases indent level.
  42. # &gst_xml_leave: Call before leaving a block. Decreases indent level.
  43. # &gst_xml_print_indent: Call before printing a line. Indents to current level. 
  44. # &gst_xml_print_vspace: Ensures there is a vertical space of one and only one line.
  45. # &gst_xml_print: Indent, then print all arguments. Just for sugar.
  46.  
  47.  
  48. my $gst_indent_level = 0;
  49. my $gst_have_vspace = 0;
  50.  
  51. my @gst_xml_stack;
  52.  
  53. sub print_comment # (comment text)
  54. {
  55.   my ($comment) = @_;
  56.  
  57.   &print_line ("<!-- $comment -->") if $comment;
  58. }
  59.  
  60.  
  61. sub print_begin
  62. {
  63.   my ($name) = @_;
  64.  
  65.   $name = "response" if !$name;
  66.  
  67.   &print_string ("<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n");
  68.   &print_string ("<!DOCTYPE $name []>\n\n");
  69.   &print_string ("<$name>\n");
  70.  
  71.   &enter ();
  72.  
  73.   &print_vspace ();
  74. }
  75.  
  76.  
  77. # The frontend expects exactly this string. Don not alter.
  78. sub print_request_end
  79. {
  80.   print "\n<!-- GST: end of request -->\n";
  81. }
  82.  
  83.  
  84. sub print_end
  85. {
  86.   my ($name) = @_;
  87.  
  88.   $name = "response" if !$name;
  89.   
  90.   &leave ();
  91.  
  92.   &print_vspace ();
  93.   &print_string ("</$name>\n");
  94. }
  95.  
  96.  
  97. sub enter
  98. {
  99.   $gst_indent_level += 2;
  100. }
  101.  
  102.  
  103. sub leave
  104. {
  105.   $gst_indent_level -= 2;
  106. }
  107.  
  108.  
  109. sub print_string
  110. {
  111.   if ($has_encode)
  112.   {
  113.     if (&decode_utf8 ($_[0]) eq undef)
  114.     {
  115.       # we first decode the string, if it's not
  116.       # utf-8 (returns undef), then encode it
  117.       print "" . &encode_utf8 ($_[0]);
  118.       return;
  119.     }
  120.   }
  121.  
  122.   # It could not encode the string, write it as is
  123.   print $_[0];
  124. }
  125.  
  126. sub format_indent
  127. {
  128.   $gst_have_vspace = 0;
  129.   return " " x $gst_indent_level;
  130. }
  131.  
  132. sub print_indent
  133. {
  134.   &print_string(&format_indent ());
  135. }
  136.  
  137. sub print_vspace
  138. {
  139.   if (not $gst_have_vspace)
  140.   {
  141.     &print_string ("\n");
  142.     $gst_have_vspace = 1;
  143.   }
  144. }
  145.  
  146.  
  147. sub print_line
  148. {
  149.   my $line;
  150.   $line = join ("", @_);
  151.   $line =~ tr/\n//d;
  152.  
  153.   &print_indent ();
  154.   &print_string ($line . "\n");
  155. }
  156.  
  157. sub format_pcdata # (name, pcdata)
  158. {
  159.   my ($name, $pcdata) = @_;
  160.   return "<$name>$pcdata</$name>" if defined ($name) || defined ($pcdata);
  161. }
  162.  
  163. sub print_pcdata # (name, pcdata)
  164. {
  165.   my ($name, $pcdata) = @_;
  166.   my $line = &format_pcdata ($name, $pcdata);
  167.  
  168.   &print_line ($line) if $line;
  169. }
  170.  
  171. sub format_state_tag
  172. {
  173.   my ($name, $state) = @_;
  174.   my $boolean = &Utils::Util::print_boolean_truefalse ($state);
  175.  
  176.   return "<$name state='$boolean'/>";
  177. }
  178.  
  179. sub print_state_tag
  180. {
  181.   my ($name, $state) = @_;
  182.   my $state_tag = &format_state_tag ($name, $state);
  183.  
  184.   &print_line ($state_tag); 
  185. }
  186.  
  187. # Pass a hash and the keys whose items are scalars. Print <key>val</key>.
  188. sub print_scalars
  189. {
  190.   my ($h, @scalar_keys) = @_;
  191.   my ($i, $val);
  192.  
  193.   @scalar_keys = sort @scalar_keys;
  194.  
  195.   while ($i = shift @scalar_keys)
  196.   {
  197.     $val = "e ($$h{$i});
  198.     &print_line ("<$i>$val</$i>\n") if exists $$h{$i};
  199.   }
  200.   
  201. }
  202.  
  203. # Print the @$array using <$tag>val</$tag> foreach val in the array.
  204. # Actually lets print_structure do that now. Just print sequentially
  205. # the given elements, using as $tag as the surrounding tags.
  206. sub print_array
  207. {
  208.   my ($array, $tag) = @_;
  209.   my ($i, $val);
  210.  
  211.   return if (scalar @$array <= 0);
  212.  
  213.   &print_vspace ();
  214.   foreach $i (@$array)
  215.   {
  216.     &print_structure ($i, $tag);
  217.   }
  218. }
  219.  
  220. # Pass a hash and the keys whose items are arrays. Print <key>val</key> foreach val
  221. # in the array at hash{key}
  222. sub print_arrays
  223. {
  224.   my ($h, @array_keys) = @_;
  225.   my ($i, $j, $val);
  226.   
  227.   foreach $i (sort @array_keys)
  228.   {
  229.     &print_array ($$h{$i}, $i) if (exists $$h{$i})
  230.   }
  231. }
  232.  
  233. # Pass a hash, create a parent tag $tag and print <key>val</key> for every
  234. # value pair in the hash. If structure refs are found, these are recursively
  235. # printed with print_structure.
  236. sub print_hash
  237. {
  238.   my ($hash, $tag) = @_;
  239.   my ($j, $val);
  240.   
  241.   &print_vspace ();
  242.   if (defined $tag) {
  243.       &print_line ("<$tag>\n");
  244.       &enter ();
  245.   }
  246.   
  247.   foreach $j (sort keys (%$hash))
  248.   {
  249.     &print_structure ($$hash{$j}, $j);
  250.   }
  251.  
  252.   if (defined $tag) {
  253.       &leave ();
  254.       &print_line ("</$tag>\n");
  255.   }
  256. }
  257.  
  258. # Call the corresponding function depending on the reference
  259. # type of $x. If just a scalar, print <$tag>$x</$tag>.
  260. sub print_structure
  261. {
  262.   my ($x, $tag) = @_;
  263.  
  264.   if (ref $x eq "ARRAY") { &print_array ($x, $tag); }
  265.   elsif (ref $x eq "HASH")  { &print_hash  ($x, $tag); }
  266.   else
  267.   {
  268.     &print_line ("<$tag>" . "e ($x) . "</$tag>\n");
  269.   }
  270. }
  271.  
  272. # Treats hash as an array: doesn't use the keys as tags for its
  273. # elements, but the given tag.
  274. sub print_hash_hash
  275. {
  276.   my ($h, $tag) = @_;
  277.   my $i;
  278.   
  279.   foreach $i (sort keys %$h)
  280.   {
  281.     &print_hash ($$h{$i}, $tag);
  282.   } 
  283. }
  284.  
  285.  
  286. sub container_enter # (name)
  287. {
  288.   my ($container) = @_;
  289.  
  290.   ## gst_xml_stack is not my, as it is defined at top,
  291.   ## so it is global
  292.   push @gst_xml_stack, $container;
  293.  
  294.   &print_line ("<$container>");
  295.   &enter();
  296. }
  297.  
  298.  
  299. sub container_leave
  300. {
  301.   ## checks if there is a start tag
  302.   if ($#gst_xml_stack >= 0)
  303.   { 
  304.     my $current_container = pop @gst_xml_stack;
  305.  
  306.     &leave ();
  307.     &print_line ("</$current_container>");
  308.   }
  309. }
  310.  
  311.  
  312. sub print_container # (name, @strings)
  313. {
  314.   my ($name, @strings) = @_;
  315.  
  316.   if (@strings) {
  317.     &container_enter ($name);
  318.     foreach $tag (@strings) {
  319.       &print_line ("$tag");
  320.     }
  321.     &container_leave ();
  322.   }
  323.  
  324. # --- XML printing from in-memory model --- #
  325.  
  326. sub model_print_attributes
  327. {
  328.   my ($tree) = @_;
  329.   my ($attrs, $string);
  330.  
  331.   $attrs = @$tree [0];
  332.  
  333.   for $attr (keys %$attrs)
  334.   {
  335.     $string .= " " . $attr . "=\"" . $$attrs{$attr} . "\"";
  336.   }
  337.  
  338.   return $string;
  339. }
  340.  
  341. sub model_print_recurse
  342. {
  343.   my ($tree, $indent) = @_;
  344.   my ($string);
  345.  
  346.   my @children = @$tree;
  347.   shift @children;  # Attributes
  348.  
  349.   while (@children)
  350.   {
  351.     my $branch = $children [1];
  352.  
  353.     if ($children [0] eq "__unparsed__")
  354.     {
  355.       $string .= "<" . $children [1] . ">";
  356.     }
  357.     elsif ($children [0] eq "0")
  358.     {
  359.       $string .= $children [1];
  360.     }
  361.     elsif (@$branch == 1)  # Empty tag.
  362.     {
  363.       $string .= "<" . $children [0] . &model_print_attributes ($branch) . "/>";
  364.     }
  365.     else
  366.     {
  367.       $string .= "<" . $children [0] . &model_print_attributes ($branch) . ">";
  368.       $string .= &model_print_recurse ($branch);
  369.       $string .= "</" . $children [0] . ">";
  370.     }
  371.  
  372.     shift @children;
  373.     shift @children;
  374.   }
  375.  
  376. #  if ($branch)
  377. #  {
  378. #    return &get_attribute ($branch, $property) if $property ne "";
  379. #    return &get_pcdata ($branch);
  380. #  }
  381.  
  382.   return $string;
  383. }
  384.  
  385. sub model_print
  386. {
  387.   my ($tree) = @_;
  388.   my ($string);
  389.  
  390.   $string = &model_print_recurse ($tree);
  391.   chomp $string;
  392.   $string .= "\n";
  393.  
  394.   return $string;
  395. }
  396.  
  397. # --- XML scanning --- #
  398.  
  399.  
  400. # This code tries to replace XML::Parser scanning from stdin in tree mode.
  401.  
  402. sub scan_make_kid_array
  403. {
  404.   my $line = shift;
  405.   my (%hash, @sublist, @attr, @list);
  406.  
  407.   # Remove < and />
  408.   $line =~ s/^[ \t]*<//;
  409.   $line =~ s/[\/]?>[ \t]*$//;
  410.  
  411.   my @list = "ewords ('[ \t]+', 1, $line);
  412.   shift @list; # push tag name.
  413.  
  414.   foreach (@list)
  415.   {
  416.     my @tmp = split '[ \t]*=[ \t]*';
  417.     if (scalar @tmp == 2 && $tmp[1] =~ s/[\"\']//g)
  418.     {
  419.       push @attr, @tmp;
  420.     }
  421.   }
  422.  
  423.   %hash = @attr;
  424.  
  425.   push (@sublist, \%hash);
  426.   return \@sublist;
  427. }
  428.  
  429.  
  430. sub scan_recurse
  431. {
  432.   my ($gst_xml_scan_list, $list_arg) = @_;
  433.   my @list;
  434.   if ($list_arg ne undef) { @list = $$list_arg[0]; }
  435.   
  436.   while (@$gst_xml_scan_list)
  437.   {
  438.     $el = shift @$gst_xml_scan_list;
  439.  
  440.     if (($el eq "") || $el =~ /^\<[!?].*\>$/s) { next; }  # Empty strings, PI and DTD must go.
  441.     if ($el =~ /^\<.*\/\>$/s)  # Empty.
  442.     {
  443.       $el =~ /^\<([a-zA-Z0-9_-]+).*\/\>$/s;
  444.       push (@list, $1);
  445.       push (@list, &scan_make_kid_array ($el));
  446.     }
  447.     elsif ($el =~ /^\<\/.*\>$/s)  # End.
  448.     {
  449.       last;
  450.     }
  451.     elsif ($el =~ /^\<.*\>$/s)  # Start.
  452.     {
  453.       $el =~ /^\<([a-zA-Z0-9_-]+).*\>$/s;
  454.       push (@list, $1);
  455.       $sublist = &scan_make_kid_array ($el);
  456.       push (@list, &scan_recurse ($gst_xml_scan_list, $sublist));
  457.       next;
  458.     }
  459.     elsif ($el ne "")  # PCDATA.
  460.     {
  461.       push (@list, 0);
  462.       push (@list, "$el");
  463.     }
  464.   }
  465.  
  466.   return \@list;
  467. }
  468.  
  469.  
  470. sub read_file
  471. {
  472.   my ($file) = @_;
  473.   my ($doc, $i);
  474.   local *INPUT_FILE;
  475.   
  476.   open INPUT_FILE, $file;
  477.   $doc .= $i while ($i = <INPUT_FILE>);
  478.   close INPUT_FILE;
  479.  
  480.   return $doc;
  481. }
  482.  
  483.  
  484. sub read_compressed_file
  485. {
  486.   my ($file) = @_;
  487.   my ($doc, $i, $fd);
  488.  
  489.   $fd = &Utils::File::run_pipe_read ("gunzip -c $file");
  490.   return undef if $fd eq undef;
  491.   $doc .= $i while ($i = <$fd>);
  492.   &Utils::File::close_file ($fd);
  493.  
  494.   if (length ($doc) < 4)  # Allow for one blank line from gzip, '\n\r'.
  495.   {
  496.     $doc = undef;
  497.   }
  498.  
  499.   return $doc;
  500. }
  501.  
  502.  
  503. sub read_stdin
  504. {
  505.   my ($i, $doc);
  506.   
  507.   do {
  508.     $i = <STDIN>;
  509.  
  510.     if ($i ne undef)
  511.     {
  512.       $doc .=$i;
  513.     }
  514.   } while (! ($i =~ /^<!-- GST: end of request -->$/));
  515.  
  516.   return $doc;
  517. }
  518.  
  519. # (file, tool) If no file specified, reads stdin.
  520. # file could also contain xml document.
  521. # If tool is an gst_tool, stores the read buffer in
  522. # $$tool{"xml_doc"}.
  523. sub scan
  524. {
  525.   my ($file, $tool) = @_;
  526.   my ($doc, @tree, @gst_xml_scan_list);
  527.  
  528.   $file = $gst_input_file unless $file;
  529.  
  530.   if ($file && stat ($file))
  531.   {
  532.     $doc = &read_file ($file);
  533.   }
  534.   elsif ($file)
  535.   {
  536.     $doc = $file;
  537.   }
  538.   else
  539.   {
  540.     $doc = &read_stdin ();
  541.   }
  542.  
  543.   # remove any blank or carriage return at the beginning of the xml
  544.   $doc =~ s/^[ \n]*//;
  545.  
  546.   $$tool{"xml_doc"} = $doc if (&Utils::Backend::is_backend ($tool));
  547.  
  548.   @gst_xml_scan_list = ($doc =~ /([^\<]*)(\<[^\>]*\>)[ \t\n\r]*/mg); # pcdata, tag, pcdata, tag, ...
  549.   $tree = &scan_recurse (\@gst_xml_scan_list);
  550.  
  551.   return $tree;
  552. }
  553.  
  554. # XML scanning that preserves more exact attributes of the scanned XML.
  555.  
  556. sub model_scan_recurse
  557. {
  558.   my @list;
  559.   if (@_) { @list = $_[0]->[0]; }
  560.   
  561.   while (@gst_xml_scan_list)
  562.   {
  563.     $el = $gst_xml_scan_list[0]; shift @gst_xml_scan_list;
  564.  
  565.     if ($el eq "")  # Empty strings.
  566.     {
  567.       next;
  568.     }
  569.     elsif ($el =~ /^\<[!?].*\>$/s)  # PI and DTD.
  570.     {
  571.       $el =~ /^\<([^\>]+)\>$/s;
  572.       push (@list, "__unparsed__");
  573.       push (@list, $1);
  574.     }
  575.     elsif ($el =~ /^\<.*\/\>$/s)  # Empty.
  576.     {
  577.       $el =~ /^\<([a-zA-Z0-9_-]+).*\/\>$/s;
  578.       push (@list, $1);
  579.       push (@list, &scan_make_kid_array ($el));
  580.     }
  581.     elsif ($el =~ /^\<\/.*\>$/s)  # End.
  582.     {
  583.       last;
  584.     }
  585.     elsif ($el =~ /^\<.*\>$/s)  # Start.
  586.     {
  587.       $el =~ /^\<([a-zA-Z0-9_-]+).*\>$/s;
  588.       push (@list, $1);
  589.       $sublist = &scan_make_kid_array ($el);
  590.       push (@list, &model_scan_recurse ($sublist));
  591.       next;
  592.     }
  593.     elsif ($el ne "")  # PCDATA.
  594.     {
  595.       push (@list, 0);
  596.       push (@list, "$el");
  597.     }
  598.   }
  599.  
  600.   return \@list;
  601. }
  602.  
  603. sub model_scan  # (file) If no file specified, reads stdin.
  604. {
  605.   my ($file) = @_;
  606.   my ($doc, $tree, $compressed);
  607.  
  608.   $file = $gst_input_file if $file eq undef;
  609.  
  610.   if ($file)
  611.   {
  612.     $doc = &read_compressed_file ($file);
  613.     if (!$doc)
  614.     {
  615.       $doc = &read_file ($file);
  616.       $compressed = 0;
  617.     }
  618.     else
  619.     {
  620.       $compressed = 1;
  621.     }
  622.   }
  623.   else
  624.   {
  625.     return undef, 0;
  626.   }
  627.  
  628.   @gst_xml_scan_list = ($doc =~ /([^\<]*)(\<[^\>]*\>)/mg); # pcdata, tag, pcdata, tag, ...
  629.   $tree = &model_scan_recurse;
  630.  
  631.   return $tree, $compressed;
  632. }
  633.  
  634. sub model_save
  635. {
  636.   my ($model, $file, $compressed) = @_;
  637.   my $fd;
  638.  
  639.   if ($compressed == 1)
  640.   {
  641.     $fd = &Utils::File::open_write_compressed ($file);
  642.   }
  643.   else
  644.   {
  645.     $fd = &Utils::File::open_write_from_names ($file);
  646.   }
  647.  
  648.   if ($fd == -1) { return -1; }
  649.  
  650.   print $fd &model_print ($model);
  651.   &Utils::File::close_file ($fd);
  652.  
  653.   return 0;
  654. }
  655.  
  656. # Quote/unquote.
  657.  
  658. @gst_xml_entities = ( "<", '<', ">", '>', "'", '\'', """, '"', "&", '&' );
  659.  
  660.  
  661. sub quote
  662. {
  663.   my $in = $_[0];
  664.   my $out = "";
  665.   my @xe;
  666.   my $joined = 0;
  667.   
  668.   my @clist = split (//, $in);
  669.   
  670.   while (@clist)
  671.   {
  672.     # Find character and join its entity equivalent.
  673.     # If none found, simply join the character.
  674.     
  675.     $joined = 0;        # Cumbersome.
  676.     
  677.     for (@xe = @gst_xml_entities; @xe && !$joined; )
  678.     {
  679.       if ($xe [1] eq $clist [0]) { $out = join ('', $out, $xe [0]); $joined = 1; }
  680.       shift @xe; shift @xe;
  681.     }
  682.     
  683.     if (!$joined) { $out = join ('', $out, $clist [0]); }
  684.     shift @clist;
  685.   }
  686.   
  687.   return $out;
  688. }
  689.  
  690.  
  691. sub unquote
  692. {
  693.   my $ret = $_[0];
  694.   my $i;
  695.  
  696.   #print STDERR "INI U: $ret\n";
  697.   
  698.   for ($i = 0; $gst_xml_entities[$i] ne undef; $i += 2)
  699.   {
  700.     $ret =~ s/$gst_xml_entities[$i]/$gst_xml_entities[$i + 1]/g;
  701.   }
  702.  
  703.   while ($ret =~ /&#([0-9]+);/)
  704.   {
  705.     $num = $1;
  706.     $c = chr ($num);
  707.     $ret =~ s/&#$num;/$c/g;
  708.   }
  709.  
  710.   #print STDERR "END U: $ret\n";
  711.   
  712.   return $ret;
  713. }
  714.  
  715.  
  716. # --- XML parsing --- #
  717.  
  718.  
  719. sub get_pcdata
  720. {
  721.   my $tree = $_[0];
  722.   my $retval;
  723.   
  724.   shift @$tree;  # Skip attributes.
  725.   
  726.   while (@$tree)
  727.   {
  728.     if ($$tree[0] == 0)
  729.     {
  730.       $retval = &unquote ($$tree[1]);
  731.       return ($retval);
  732.     }
  733.     
  734.     shift @$tree;
  735.     shift @$tree;
  736.   }
  737.  
  738.   return "";
  739. }
  740.  
  741. # Compresses node into a word and returns it.
  742.  
  743. sub get_word
  744. {
  745.   my $tree = $_[0];
  746.   my $retval;
  747.  
  748.   $retval = &get_pcdata ($tree);
  749.   $retval =~ tr/ \n\r\t\f//d;
  750.   return $retval;
  751. }
  752.  
  753.  
  754. # Compresses node into a size and returns it.
  755.  
  756. sub get_size
  757. {
  758.   my $tree = $_[0];
  759.   my $retval;
  760.  
  761.   $retval = &get_word ($tree);
  762.   if ($retval =~ /Mb$/)
  763.   {
  764.     $retval =~ tr/ Mb//d; 
  765.     $retval *= 1024;
  766.   }
  767.  
  768.   return $retval;
  769. }
  770.  
  771.  
  772. # Replaces misc. whitespace with spaces and returns text.
  773.  
  774. sub get_text
  775. {
  776.   my $tree = $_[0];
  777.   my $retval;
  778.  
  779.   $retval = &get_pcdata ($tree);
  780.   my $type = ref ($retval);
  781.  
  782.   if (!$type) { $retval =~ tr/\n\r\t\f/    /; }  
  783.  
  784.   return $retval;
  785. }
  786.  
  787. sub get_attribute
  788. {
  789.   my ($tree, $attr) = @_;
  790.  
  791.   return $$tree[0]->{$attr};
  792. }
  793.  
  794. sub get_state
  795. {
  796.   my ($tree) = @_;
  797.  
  798.   # Check attribute; 'yes', 'true', 'no', 'false'.
  799.   return &Utils::Util::read_boolean ($$tree[0]->{state});
  800. }
  801.  
  802. # XML model operations.
  803.  
  804. # Locate a node from the branch leading up to it.
  805. sub model_find
  806. {
  807.   my ($model, $varpath) = @_;
  808.   my ($branch, @path);
  809.  
  810.   $branch = $model;
  811.   @path   = split /\//, $varpath;
  812.  
  813.   for $elem (@path)
  814.   {
  815.     next if ($elem eq "");
  816.     my @children = @$branch;
  817.     shift @children;  # Attributes
  818.     $branch = undef;
  819.  
  820.     while (@children)
  821.     {
  822.       if ($children [0] eq $elem)
  823.       {
  824.         shift @children;
  825.         $branch = shift @children;
  826.         last;
  827.       }
  828.  
  829.       shift @children;
  830.       shift @children;
  831.     }
  832.  
  833.     last if ($branch == undef);
  834.   }
  835.  
  836.   return $branch;
  837. }
  838.  
  839. # Add a branch to another branch. Allows duplicates.
  840. sub model_add
  841. {
  842.   my ($model, $varpath, $addpath) = @_;
  843.   my ($branch, @path);
  844.  
  845.   @path = split /\//, $addpath;
  846.   $branch = &model_find ($model, $varpath);
  847.   if ($branch == undef)
  848.   {
  849.     return -1;
  850.   }
  851.  
  852.   for $elem (@path)
  853.   {
  854.     my %hash;
  855.     my @list = ();
  856.  
  857.     push @list, \%hash;
  858.  
  859.     push @$branch, $elem;
  860.     push @$branch, \@list;
  861.  
  862.     $branch = \@list;
  863.   }
  864.  
  865.   return 0;
  866. }
  867.  
  868. # Ensure a branch exists, by extending the branch with given elements, if needed.
  869. sub model_ensure
  870. {
  871.   my ($model, $varpath) = @_;
  872.   my ($branch, @path);
  873.  
  874.   $branch = $model;
  875.   @path   = split /\//, $varpath;
  876.  
  877.   for $elem (@path)
  878.   {
  879.     next if ($elem eq "");
  880.  
  881.     my @children = @$branch;
  882.     my $parent_branch = $branch;
  883.  
  884.     shift @children;  # Attributes
  885.     $branch = undef;
  886.  
  887.     while (@children)
  888.     {
  889.       if ($children [0] eq $elem)
  890.       {
  891.         shift @children;
  892.         $branch = shift @children;
  893.         last;
  894.       }
  895.  
  896.       shift @children;
  897.       shift @children;
  898.     }
  899.  
  900.     if ($branch == undef)
  901.     {
  902.       my %hash;
  903.       my @list = ();
  904.  
  905.       $branch = \@list;
  906.  
  907.       push @list, \%hash;
  908.  
  909.       push @$parent_branch, $elem;
  910.       push @$parent_branch, $branch;
  911.     }
  912.   }
  913.  
  914.   return $branch;
  915. }
  916.  
  917. sub model_remove
  918. {
  919.   my ($model, $varpath, $tag) = @_;
  920.   my ($branch, $i);
  921.  
  922.   @path = split /\//, $addpath;
  923.   $branch = &model_find ($model, $varpath);
  924.   if ($branch == undef)
  925.   {
  926.     return -1;
  927.   }
  928.  
  929.   for ($i = 1; $i < @$branch; $i += 2)
  930.   {
  931.     if (@$branch [$i] eq $tag)
  932.     {
  933.       @$branch = (@$branch [0 .. $i - 1], @$branch [$i + 2 .. @$branch - 1]);
  934.       return 0;
  935.     }
  936.   }
  937.  
  938.   return -1;
  939. }
  940.  
  941. sub model_get_children
  942. {
  943.   my ($branch) = @_;
  944.   my (@children);
  945.  
  946.   if (!$branch) { return \@children; }
  947.  
  948.   for ($i = 1; $i < @$branch; $i += 2)
  949.   {
  950.     if (@$branch [$i] ne "__unparsed__" && @$branch [$i] ne "0")
  951.     {
  952.       push @children, @$branch [$i + 1];
  953.     }
  954.   }
  955.  
  956.   return \@children;
  957. }
  958.  
  959. sub model_get_pcdata
  960. {
  961.   my ($branch) = @_;
  962.   my ($i);
  963.  
  964.   for ($i = 1; $i < @$branch; $i += 2)
  965.   {
  966.     if ($$branch [$i] == 0)
  967.     {
  968.       my $retval = &unquote ($$branch [$i + 1]);
  969.       return ($retval);
  970.     }
  971.   }
  972.  
  973.   return "";
  974. }
  975.  
  976. sub model_set_pcdata
  977. {
  978.   my ($branch, $pcdata) = @_;
  979.  
  980.   @$branch = (@$branch [0]);
  981.  
  982.   $$branch [1] = 0;
  983.   $$branch [2] = "e ($pcdata);
  984. }
  985.  
  986. sub model_get_attribute
  987. {
  988.   my ($branch, $attr) = @_;
  989.  
  990.   return $$branch[0]->{$attr};
  991. }
  992.  
  993. sub model_set_attribute
  994. {
  995.   my ($branch, $attr, $value) = @_;
  996.  
  997.   return $$branch[0]->{$attr} = $value;
  998. }
  999.  
  1000. 1;
  1001.