File Coverage

blib/lib/Data/Stag/StagImpl.pm
Criterion Covered Total %
statement 727 1316 55.2
branch 260 538 48.3
condition 94 224 41.9
subroutine 75 124 60.4
pod 0 100 0.0
total 1156 2302 50.2


line stmt bran cond sub pod time code
1             # $Id: StagImpl.pm,v 1.67 2008/06/05 06:17:51 cmungall Exp $
2             #
3             # Author: Chris Mungall
4             #
5             # You may distribute this module under the same terms as perl itself
6              
7             package Data::Stag::StagImpl;
8              
9             =head1 NAME
10              
11             Data::Stag::StagImpl - default implementation for Data::Stag
12              
13             =head1 SYNOPSIS
14              
15             use Data::Stag qw(:all);
16              
17             =head1 DESCRIPTION
18              
19             This is the default implementation for Data::Stag - please see L
20              
21             =cut
22              
23 20     20   17133 use FileHandle;
  20         280195  
  20         127  
24 20     20   30958 use IO::String;
  20         1174875  
  20         629  
25 20     20   178 use Carp;
  20         80  
  20         1618  
26 20     20   117 use strict;
  20         40  
  20         689  
27 20     20   102 use vars qw($AUTOLOAD $DEBUG);
  20         41  
  20         1949  
28 20     20   119 use Data::Stag::Base;
  20         43  
  20         2579  
29 20     20   12434 use Data::Stag::Util qw(rearrange);
  20         379  
  20         1247  
30 20     20   112 use base qw(Data::Stag::StagI);
  20         35  
  20         11218  
31              
32 20     20   115 use vars qw($VERSION);
  20         38  
  20         868936  
33             $VERSION="0.14";
34              
35              
36             sub new {
37 180     180 0 265 my $proto = shift;
38 180   33     795 my $class = ref($proto) || $proto;
39 180 50 66     602 if (@_ == 2 || @_ == 0) {
40 180         1057 return bless [@_], $class;
41             }
42             else {
43 0         0 confess @_;
44             }
45             }
46              
47             sub unflatten {
48 11     11 0 18 my $proto = shift;
49 11   33     53 my $class = ref($proto) || $proto;
50 11         16 my ($name, $flist) = @_;
51 11         18 my @uflist = ();
52 11 50       33 if (!ref($flist)) {
53 0         0 return $class->new($name=>$flist);
54             }
55 11 100       36 if (ref($flist) eq 'HASH') {
56             # unpack hash into array
57 9         35 $flist = [%$flist];
58             }
59 11 50       37 if (ref($flist) ne 'ARRAY') {
60 0         0 confess("$name => $flist not array");
61             }
62 11         28 while (@$flist) {
63 22         34 my $k = shift @$flist;
64 22         31 my $v = shift @$flist;
65 22 100       48 if (ref($v)) {
66 7         44 push(@uflist,
67             $class->unflatten($k=>$v));
68             }
69             else {
70 15         55 push(@uflist,
71             [$k=>$v]);
72             }
73             }
74 11         67 return $class->new($name=>[@uflist]);
75             }
76              
77             sub unhash {
78 14     14 0 22 my $proto = shift;
79 14   33     56 my $class = ref($proto) || $proto;
80 14         49 my %hash = @_;
81              
82 14         25 my @tags = ();
83 14         33 foreach my $k (keys %hash) {
84 46         63 my $v = $hash{$k};
85 46 100       76 if (ref($v)) {
86 14 100       46 if (ref($v) eq 'ARRAY') {
    50          
87 4         30 push(@tags, [$k=>$_]) foreach @$v;
88             }
89             elsif (ref($v) eq 'HASH') {
90 10         41 my $stag = unhash($class, %$v);
91 10         35 push(@tags, [$k=>$stag->data]);
92             }
93             else {
94 0         0 confess("cannot unhash $v");
95             }
96             }
97             else {
98 32         97 push(@tags, [$k=>$v]);
99             }
100             }
101 14         111 return $class->new(stag=>[@tags]);
102             }
103              
104             sub unstone {
105 0     0 0 0 my $tree = shift;
106 0         0 my $stone = shift;
107 0         0 my $xml = $stone->asXML;
108 0         0 from($tree, xmlstr=>$xml);
109             }
110              
111             sub stone {
112 0     0 0 0 my $tree = shift;
113 0         0 my %h = hash($tree);
114 0         0 load_module("Stone");
115 0         0 Stone->new(%h);
116             }
117              
118             sub load_module {
119              
120 89     89 0 185 my $classname = shift;
121 89 50       231 confess("no class") unless $classname;
122 89         181 my $mod = $classname;
123 89         443 $mod =~ s/::/\//g;
124              
125 89 50       297 if (!$mod) {
126 0         0 confess("must supply as mod as argument");
127             }
128              
129 89 100       393 if ($main::{"_<$mod.pm"}) {
130             }
131             else {
132 87         748029 require "$mod.pm";
133             }
134             }
135              
136             # -------------------------------------
137             # ---- INPUT/OUTPUT FUNCTIONS -----
138             # -------------------------------------
139              
140              
141             sub parser {
142 17     17 0 36 my $tree = shift;
143 17         146 my ($fn, $fmt, $h, $eh, $str, $fh) =
144             rearrange([qw(file format handler errhandler str fh)], @_);
145              
146 17 50 66     137 if ($fn && $fn eq '-') {
147 0         0 $fn = '';
148 0         0 $fh = \*STDIN;
149             }
150             # GUESS FORMAT BASED ON FILENAME
151 17 100 100     153 if (!$fmt && $fn) {
152 13 50       248 if ($fn =~ /\.xml$/) {
    50          
    50          
    50          
    100          
    100          
    50          
    0          
    0          
153 0         0 $fmt = "xml";
154             }
155             elsif ($fn =~ /\.indent$/) {
156 0         0 $fmt = "indent";
157             }
158             elsif ($fn =~ /\.ind$/) {
159 0         0 $fmt = "indent";
160             }
161             elsif ($fn =~ /\.xtc$/) {
162 0         0 $fmt = "indent";
163             }
164             elsif ($fn =~ /\.ite?xt$/) {
165 10         30 $fmt = "itext";
166             }
167             elsif ($fn =~ /\.se?xpr$/) {
168 1         2 $fmt = "sxpr";
169             }
170             elsif ($fn =~ /\.el$/) {
171 2         6 $fmt = "sxpr";
172             }
173             elsif ($fn =~ /\.pl$/) {
174 0         0 $fmt = "perl";
175             }
176             elsif ($fn =~ /\.perl$/) {
177 0         0 $fmt = "perl";
178             }
179             else {
180             # default to xml
181 0 0 0     0 if (!$str && $fn) {
182 0   0     0 my $fh = FileHandle->new($fn) ||
183             confess("no such file $fn");
184             # get the first line
185 0         0 $str = <$fh>;
186 0         0 chomp $str;
187 0         0 $fh->close;
188             }
189             else {
190 0         0 $fmt = "xml";
191             }
192             }
193             }
194              
195             # GUESS FORMAT BASED ON STR
196 17 100 66     78 if (!$fmt && $str) {
197 1 50       10 if ($str =~ /^\s*\'/) {
    50          
    0          
    0          
    0          
    0          
    0          
198 0         0 $fmt = "sxpr";
199             }
200             elsif ($str =~ /^\s*\(/) {
201 1         2 $fmt = "sxpr";
202             }
203             elsif ($str =~ /^\s*\;/) {
204 0         0 $fmt = "sxpr";
205             }
206             elsif ($str =~ /^\s*\
207 0         0 $fmt = "xml";
208             }
209             elsif ($str =~ /^\s*\w+\:\s/) {
210 0         0 $fmt = "itext";
211             }
212             elsif ($str =~ /^\s*\#/) {
213 0         0 $fmt = "indent";
214             }
215             elsif ($str =~ /^\w+/) {
216 0         0 $fmt = "indent";
217             }
218             else {
219             }
220             }
221              
222 17         36 my $parser;
223 17 50 33     149 if (!ref($fmt) && $fmt =~ /::/) {
224 0         0 load_module($fmt);
225 0         0 $fmt = $fmt->new;
226             }
227              
228 17 50       204 if (ref($fmt)) {
    50          
    50          
    100          
    50          
    50          
229 0         0 $parser = $fmt;
230             }
231             elsif ($fmt eq "xml") {
232 0         0 $parser = "Data::Stag::XMLParser";
233             }
234             elsif ($fmt eq "indent") {
235 0         0 $parser = "Data::Stag::IndentParser";
236             }
237             elsif ($fmt eq "itext") {
238 10         23 $parser = "Data::Stag::ITextParser";
239             }
240             elsif ($fmt eq "perl") {
241 0         0 $parser = "Data::Stag::PerlParser";
242             }
243             elsif ($fmt eq "sxpr") {
244 7         15 $parser = "Data::Stag::SxprParser";
245             }
246             else {
247 0 0       0 confess("cannot guess parser from fmt=\"$fmt\" @_") unless $parser;
248             }
249 17 50       318136 unless (ref($parser)) {
250 17         79 load_module($parser);
251 17         220 $parser = $parser->new;
252             }
253 17 100       132 $parser->file($fn) if $fn;
254             # $parser->fh($fh) if $fh;
255 17         63 return $parser;
256             }
257              
258             sub parse {
259 17     17 0 84 my $tree = shift;
260 17         138 my ($fn, $fmt, $h, $eh, $str, $fh) =
261             rearrange([qw(file format handler errhandler str fh)], @_);
262              
263 17 100 33     175 if (!$tree || !ref($tree)) {
264 9         24 $tree = [];
265             }
266              
267 17         98 my $p = parser($tree, @_);
268 17 100       531 $h = Data::Stag::Base->new unless $h;
269 17 50       69 if (!$eh) {
270 17         101 $eh = getformathandler($tree, 'xml');
271 17         75 $eh->fh(\*STDERR);
272             }
273 17         147 $p->handler($h);
274 17         60 $p->errhandler($eh);
275 17         165 $p->parse(
276             -file=>$fn,
277             -str=>$str,
278             -fh=>$fh,
279             );
280 17 50       210 my $htree = $h->can("tree") ? $h->tree : [];
281 17         100 Nodify($tree);
282 17 50       30 @$tree = @{$htree || []};
  17         88  
283 17         581 return $tree;
284             }
285             *parseFile = \&parse;
286              
287             sub parsestr {
288 1     1 0 2 my $tree = shift;
289 1         9 my ($str, $fmt, $h, $eh) =
290             rearrange([qw(str format handler errhandler)], @_);
291             return
292 1         20 $tree->parse(-str=>$str,
293             -format=>$fmt,
294             -handler=>$h,
295             -errhandler=>$eh);
296            
297             }
298             *parseStr = \&parsestr;
299              
300             sub from {
301 3     3 0 7 my $class = shift;
302 3         23 my ($fmt, $file, $str) =
303             rearrange([qw(fmt file str)], @_);
304 3 50       30 if ($fmt eq 'xmlstr') {
    50          
    0          
305 0         0 return xmlstr2tree($file);
306             }
307             elsif ($fmt =~ /(.*)str/) {
308 3         12 $fmt = $1;
309 3         21 return parse([],
310             -str=>$file,
311             -format=>$fmt);
312            
313             }
314             elsif ($fmt eq 'xml') {
315 0         0 return xml2tree($file);
316             }
317             else {
318 0         0 return parse([],
319             -file=>$file,
320             -format=>$fmt);
321             }
322             }
323              
324             sub _gethandlerobj {
325 57   50 57   214 my $tree = shift || [];
326 57         433 my ($fn, $fmt, $fh) =
327             rearrange([qw(file fmt fh)], @_);
328 57 50       220 if (!$fmt) {
329 0 0       0 if (!$fn) {
    0          
    0          
    0          
330 0         0 $fmt = "xml";
331             }
332             elsif ($fn =~ /\.xml$/) {
333 0         0 $fmt = "xml";
334             }
335             elsif ($fn =~ /\.ite?xt$/) {
336 0         0 $fmt = "itext";
337             }
338             elsif ($fn =~ /\.ind/) {
339 0         0 $fmt = "indent";
340             }
341             else {
342             }
343             }
344 57         94 my $writer;
345 57 50       470 if (ref($fmt)) {
    100          
    100          
    50          
    50          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
346 0         0 return $fmt;
347             }
348             elsif ($fmt =~ /xml/i) {
349 45         92 $writer = "Data::Stag::XMLWriter";
350             }
351             elsif ($fmt =~ /itext/i) {
352 1         2 $writer = "Data::Stag::ITextWriter";
353             }
354             elsif ($fmt =~ /indent/i) {
355 0         0 $writer = "Data::Stag::IndentWriter";
356             }
357             elsif ($fmt =~ /sxpr/i) {
358 11         31 $writer = "Data::Stag::SxprWriter";
359             }
360             elsif ($fmt =~ /yaml/i) {
361 0         0 $writer = "Data::Stag::YAMLWriter";
362             }
363             elsif ($fmt =~ /perl/i) {
364 0         0 $writer = "Data::Stag::PerlWriter";
365             }
366             elsif ($fmt =~ /json/i) {
367 0         0 $writer = "Data::Stag::JSONWriter";
368             }
369             elsif ($fmt =~ /dtd/i) {
370 0         0 $writer = "Data::Stag::DTDWriter";
371             }
372             elsif ($fmt =~ /simple/i) {
373 0         0 $writer = "Data::Stag::Simple";
374             }
375             elsif ($fmt =~ /xslt\/(.+)/i) {
376 0         0 my $xslt_file = $1;
377 0         0 $writer = "Data::Stag::XSLTHandler";
378 0         0 load_module($writer);
379 0         0 my $w = $writer->new(-file=>$fn, -fh=>$fh);
380 0         0 $w->xslt_file($xslt_file);
381 0         0 return $w;
382             }
383             elsif ($fmt =~ /::/) {
384 0         0 $writer = $fmt;
385             }
386             elsif (!$fmt) {
387 0         0 confess("no format/writer!");
388             }
389             else {
390 0         0 confess("unrecognised:$fmt");
391             }
392 57         201 load_module($writer);
393              
394 57         778 my $w = $writer->new(-file=>$fn, -fh=>$fh);
395 57         185 return $w;
396             }
397             *findhandler = \&_gethandlerobj;
398              
399             sub getformathandler {
400 18     18 0 40 my $tree = shift;
401 18         37 my $fmt = shift;
402 18         118 return findhandler($tree, -fmt=>$fmt);
403             }
404              
405             sub generate {
406 38   50 38 0 146 my $tree = shift || [];
407 38         138 my $w = _gethandlerobj($tree, @_);
408 38         213 $w->is_buffered(1);
409 38         248 $w->event(@$tree);
410 38   50     259 return $w->popbuffer || '';
411             }
412             *gen = \&generate;
413              
414             sub write {
415 1   50 1 0 544 my $tree = shift || [];
416 1         5 my $w = _gethandlerobj($tree, @_);
417              
418 1         9 $w->is_buffered(0);
419 1         7 $w->event(@$tree);
420 1         12 $w->close_fh;
421 1         156 return;
422             }
423            
424             sub makexslhandler {
425 0     0 0 0 my $tree = shift;
426 0         0 my $xslt_file = shift;
427 0         0 load_module("Data::Stag::XSLTHandler");
428 0         0 my $handler = Data::Stag::XSLTHandler->new;
429 0         0 $handler->xslt_file($xslt_file);
430 0         0 return $handler;
431             }
432              
433             sub makehandler {
434 5     5 0 11 my $tree = shift;
435 5         13 my $handler;
436 5 50       24 if (@_ == 1) {
437 0         0 my $module = shift;
438 0 0       0 if ($module =~ /\.xsl/) {
439 0         0 $handler = makexslhandler($tree, $module);
440             }
441             else {
442 0         0 load_module($module);
443 0         0 $handler = $module->new;
444             }
445             }
446             else {
447 5         19 my %trap_h = @_;
448 5         11 my %opt_h = ();
449             %trap_h =
450             map {
451 5 100       20 if ($_ =~ /^-(.*)/) {
  12         34  
452 1         5 $opt_h{lc($1)} = $trap_h{$_};
453 1         4 ();
454             }
455             else {
456 11         46 ($_ => $trap_h{$_})
457             }
458             } keys %trap_h;
459 5         26 load_module("Data::Stag::BaseHandler");
460 5         44 $handler = Data::Stag::BaseHandler->new;
461 5         26 $handler->trap_h(\%trap_h);
462            
463 5 100       21 if ($opt_h{notree}) {
464 1         3 load_module("Data::Stag::null");
465 1         8 my $null = Data::Stag::null->new;
466 1         17 my $ch = Data::Stag->chainhandlers([keys %trap_h],
467             $handler,
468             $null);
469 1         7 return $ch;
470             }
471             }
472 4         21 return $handler;
473             }
474             *mh = \&makehandler;
475              
476             sub chainhandlers {
477 2     2 0 5 my $tree = shift;
478 2         4 my $block = shift;
479 2         6 my @sh = @_;
480              
481 2         5 load_module("Data::Stag::ChainHandler");
482 2         27 my $handler = Data::Stag::ChainHandler->new;
483             $handler->subhandlers([
484             map {
485 2 50       8 if (ref($_)) {
  4 0       17  
486 4 50       15 if (ref($_) eq 'HASH') {
487             # make a new handler
488 0         0 makehandler($tree, %$_);
489             }
490             else {
491             # assume it is an object
492 4         17 $_;
493             }
494             }
495             elsif (!$_) {
496             ()
497 0         0 }
498             else {
499             # assume it is string specifying format
500 0         0 _gethandlerobj($tree, -fmt=>$_)
501             }
502             } @sh
503             ]);
504 2         10 $handler->blocked_event($block);
505              
506             # if no explicit blocked events set, then introspect
507             # the subhandlers to see if they declare what they emit
508 2 50 33     20 if (ref($block) && !@$block) {
509 0         0 my @emits = map {$_->CONSUMES} @{$handler->subhandlers};
  0         0  
  0         0  
510 0         0 $handler->blocked_event(\@emits);
511             }
512 2         10 return $handler;
513             }
514              
515             sub transform {
516 0     0 0 0 my $tree = shift;
517 0         0 my @T = @_;
518 0         0 my %trap_h =
519             map {
520 0         0 my ($from, $to) = @$_;
521             $from=> sub {
522 0     0   0 my $self = shift;
523 0         0 my $stag = shift;
524             # print STDERR "Transforming $from => $to\n";
525             # print STDERR $stag->sxpr;
526 0         0 my $data = $stag->data;
527 0         0 my @path = splitpath($to);
528 0         0 my $node = [];
529 0         0 my $p = $node;
530 0         0 while (@path) {
531 0         0 my $elt = shift @path;
532 0         0 $p->[0] = $elt;
533 0 0       0 if (@path) {
534 0         0 my $newpath = [];
535 0         0 $p->[1] = [$newpath];
536 0         0 $p = $newpath;
537             }
538             else {
539 0         0 $p->[1] = $data;
540             }
541             }
542             # @$stag = @$node;
543             # print STDERR $stag->sxpr;
544 0         0 return $node;
545             # return 0;
546             }
547 0         0 } @T;
548 0         0 load_module("Data::Stag::BaseHandler");
549 0         0 my $handler = Data::Stag::BaseHandler->new;
550 0         0 $handler->trap_h(\%trap_h);
551 0         0 $tree->events($handler);
552 0         0 my $nu = $handler->stag;
553 0         0 @$tree = @$nu;
554 0         0 return;
555             }
556             *t = \&transform;
557              
558             # transform stag into hash datastruct;
559             # stag keys become hash keys (unordered)
560             # single valued keys map to single value (itself a hash or primitive)
561             # multivalued map to arrayrefs
562             sub hash {
563 7     7 0 41 my $tree = shift;
564 7         13 my ($ev, $subtree) = @$tree;
565              
566             # make sure we have non-terminal
567 7 50       23 if (ref($subtree)) {
568             # make hash using stag keys
569 7         13 my %h = ();
570 7         17 foreach my $subnode (@$subtree) {
571 26         43 my $k = $subnode->[0];
572 26         29 my $v;
573            
574             # terminals map to data value; non-terminals
575             # get recursively mapped to hashes
576 26 100       51 if (isterminal($subnode)) {
577 21         37 $v = $subnode->[1];
578             }
579             else {
580 5         27 $v = {hash($subnode)};
581             }
582              
583             # determine if it is single-valued or multi-valued hash
584 26         46 my $curr = $h{$k};
585 26 100       43 if ($curr) {
586 3 100 66     24 if (ref($curr) && ref($curr) eq 'ARRAY') {
587 1         5 push(@$curr, $v);
588             }
589             else {
590 2         10 $h{$k} = [$curr, $v];
591             }
592             }
593             else {
594             # if there is only one value, don't use array -- yet
595 23         61 $h{$k} = $v;
596             }
597             }
598 7         64 return %h;
599             }
600             else {
601 0         0 warn("can't make a hash from a terminal");
602 0         0 return ();
603             }
604             }
605             *tree2hash = \&hash;
606              
607             # this FLATTENS everything
608             # any non terminal node is flattened and lost
609             # does not check for duplicates!
610             sub pairs {
611 0     0 0 0 return @{(_pairs(@_))[1]};
  0         0  
612             }
613             *tree2pairs = \&pairs;
614             sub _pairs {
615 0     0   0 my $tree = shift;
616 0         0 my ($ev, $subtree) = @$tree;
617 0 0       0 if (ref($subtree)) {
618 0         0 my @pairs = map { _pairs($_) } @$subtree;
  0         0  
619 0         0 return $ev=>[@pairs];
620             }
621             else {
622 0         0 return $ev=>$subtree;
623             }
624             }
625              
626             # PRIVATE
627             sub tab {
628 0     0 0 0 my $t=shift;
629 0         0 return " " x $t;
630             }
631              
632             sub dlist {
633 0     0 0 0 my $tree = shift;
634 0   0     0 my $del = shift || '/';
635 0         0 _dlist($tree, $del, $del, 1);
636             }
637              
638             sub _dlist {
639 0     0   0 my $tree = shift;
640 0   0     0 my $del = shift || '/';
641 0         0 my $root = shift;
642 0         0 my $nextid = shift;
643 0         0 my @kids = $tree->kids;
644 0 0       0 if (isterminal($tree)) {
645 0         0 my $data = $tree->data;
646 0         0 $data =~ s/$del/\\$del/g;
647             # my $stem = $root . $tree->element . "[$nextid]";
648             # $root = $stem . $del;
649             # return ($root, $root . $del . $data);
650              
651 0         0 return ($root . $tree->element . ': ' . $data . "[$nextid]");
652             }
653             else {
654 0         0 my $id = 1;
655 0         0 my $stem = $root . $tree->element . "[$nextid]";
656 0         0 $root = $stem . $del;
657 0         0 my @dlist =
658             map {
659 0         0 _dlist($_, $del, $root, $id++);
660             } @kids;
661 0         0 return ($stem, @dlist);
662             }
663             # return (@dlist);
664             }
665              
666             sub xml {
667 28     28 0 977 my $tree = shift;
668 28         45 my $fn = shift;
669 28         116 generate($tree, $fn, 'xml', @_);
670             }
671             *tree2xml = \&xml;
672              
673             sub sxpr {
674 9     9 0 115 my $tree = shift;
675 9         18 my $fn = shift;
676 9         40 generate($tree, $fn, 'sxpr', @_);
677             }
678              
679             sub itext {
680 1     1 0 305 my $tree = shift;
681 1         2 my $fn = shift;
682 1         5 generate($tree, $fn, 'itext', @_);
683             }
684              
685             sub indent {
686 0     0 0 0 my $tree = shift;
687 0         0 my $fn = shift;
688 0         0 generate($tree, $fn, 'indent', @_);
689             }
690              
691             sub as {
692 0     0 0 0 my $tree = shift;
693 0         0 my $fmt = shift;
694 0         0 my $fn = shift;
695 0         0 generate($tree, $fn, $fmt, @_);
696             }
697              
698             sub perldump {
699 0     0 0 0 my $tree = shift;
700             return
701 0         0 _tree2perldump($tree, 1) . ";\n";
702             }
703             *tree2perldump = \&perldump;
704              
705             sub _tree2perldump {
706 0     0   0 my $tree = shift;
707 0   0     0 my $indent = shift || 0;
708 0         0 my ($ev, $subtree) = @$tree;
709 0 0       0 if (ref($subtree)) {
710 0         0 $indent++;
711             return
712 0         0 sprintf("%s[ '$ev' => [\n%s%s]]",
713             # tab($indent++),
714             "",
715             join(",\n", map {
716 0         0 tab($indent) .
717             _tree2perldump($_, $indent)
718             } @$subtree),
719             # tab($indent-1),
720             "",
721             );
722             }
723             else {
724             return
725 0   0     0 sprintf("%s[ '$ev' => %s ]",
726             # tab($indent),
727             "",
728             perlesc($subtree) || "");
729             }
730             }
731              
732             sub perlesc {
733 0     0 0 0 my $val = shift;
734 0 0       0 return "undef" if !defined $val;
735 0         0 $val =~ s/\'/\\\'/g;
736 0         0 return "'$val'";
737             }
738              
739             sub sax {
740 1     1 0 381 my $tree = shift;
741 1         3 my $saxhandler = shift;
742 1         10 $saxhandler->start_document;
743 1         4 _tree2sax($tree, $saxhandler);
744 1         11 $saxhandler->end_document;
745             }
746             *tree2sax = \&sax;
747              
748             sub _tree2sax {
749 37     37   44 my $tree = shift;
750 37         39 my $saxhandler = shift;
751 37         87 my ($ev, $subtree) = @$tree;
752 37         131 $saxhandler->start_element({Name => $ev});
753 37 100       83 if (ref($subtree)) {
754 13         20 map { _tree2sax($_, $saxhandler) } @$subtree;
  36         74  
755             }
756             else {
757 24         87 $saxhandler->characters({Data => $subtree});
758            
759             }
760 37         150 $saxhandler->end_element({Name => $ev});
761             }
762              
763             sub xslt {
764 0     0 0 0 my $tree = shift;
765 0         0 my $xsltstr = xsltstr($tree,@_);
766 0         0 return parsestr($tree,
767             -str=>$xsltstr,
768             -format=>'xml');
769             }
770              
771             sub xsltstr {
772 0     0 0 0 my $stag = shift;
773 0         0 my $xslt_file = shift;
774            
775 0         0 load_module("XML::LibXML");
776 0         0 load_module("XML::LibXSLT");
777 0         0 my $parser = XML::LibXML->new();
778 0         0 my $source = $parser->parse_string($stag->xml);
779            
780 0         0 my $xslt = XML::LibXSLT->new();
781 0         0 my $styledoc = $parser->parse_file($xslt_file);
782 0         0 my $stylesheet = $xslt->parse_stylesheet($styledoc);
783              
784 0         0 my $results = $stylesheet->transform($source);
785 0         0 return $results->toString;
786             }
787              
788              
789             sub events {
790 0     0 0 0 my $tree = shift;
791 0         0 my $handler = shift;
792 0         0 $handler->event(@$tree);
793             }
794              
795             sub xmlesc {
796 0     0 0 0 my $word = shift;
797 0 0       0 return '' unless defined $word;
798 0         0 $word =~ s/\&/\&/g;
799 0         0 $word =~ s/\
800 0         0 $word =~ s/\>/\>/g;
801 0         0 return $word;
802            
803             }
804              
805             sub xml2tree {
806 0     0 0 0 my $file = shift;
807 0         0 my $handler = Data::Stag::Base->new;
808 0         0 load_module("XML::Parser::PerlSAX");
809 0         0 my $parser = XML::Parser::PerlSAX->new();
810 0         0 my %parser_args = (Source => {SystemId => $file},
811             Handler => $handler);
812 0         0 $parser->parse(%parser_args);
813 0         0 return Node(@{$handler->tree});
  0         0  
814             }
815              
816             sub xmlstr2tree {
817 0     0 0 0 my $str = shift;
818 0         0 my $handler = Data::Stag::Base->new;
819 0         0 load_module("XML::Parser::PerlSAX");
820 0         0 my $parser = XML::Parser::PerlSAX->new();
821 0         0 my %parser_args = (Source => {String => $str},
822             Handler => $handler);
823 0         0 $parser->parse(%parser_args);
824 0         0 return Node(@{$handler->tree});
  0         0  
825             }
826              
827             # WANTARRAY
828             sub sxpr2tree {
829 0     0 0 0 my $sxpr = shift;
830 0   0     0 my $indent = shift || 0;
831 0         0 my $i=0;
832 0         0 my @args = ();
833 0         0 while ($i < length($sxpr)) {
834 0         0 my $c = substr($sxpr, $i, 1);
835             ################################# print STDERR "c;$c i=$i\n";
836 0         0 $i++;
837 0 0       0 if ($c eq ')') {
838 0         0 my $funcnode = shift @args;
839             # print STDERR "f = $funcnode->[1]\n";
840 0         0 map {print xml($_)} @args;
  0         0  
841 0         0 return [$funcnode->[1] =>[@args]], $i;
842             }
843 0 0       0 if ($c =~ /\s/) {
844 0         0 next;
845             }
846 0 0       0 if ($c eq '(') {
847 0         0 my ($tree, $extra) = sxpr2tree(substr($sxpr, $i), $indent+1);
848 0         0 push(@args, $tree);
849 0         0 $i += $extra;
850             # printf STDERR "tail: %s\n", substr($sxpr, $i);
851             }
852             else {
853             # look ahead
854 0         0 my $v = "$c";
855 0         0 my $p=0;
856 0         0 while ($i+$p < length($sxpr)) {
857 0         0 my $c = substr($sxpr, $i+$p, 1);
858 0 0       0 if ($c =~ /\s/) {
859 0         0 last;
860             }
861 0 0       0 if ($c eq '(') {
862 0         0 last;
863             }
864 0 0       0 if ($c eq ')') {
865 0         0 last;
866             }
867 0         0 $p++;
868 0         0 $v.= $c;
869             }
870 0         0 $i+=$p;
871 0         0 push(@args, [arg=>$v]);
872             }
873             }
874             # map {print xml($_)} @args;
875 0         0 map {Nodify($_)} @args;
  0         0  
876 0 0       0 if (wantarray) {
877 0         0 return @args;
878             }
879            
880 0         0 return $args[0];
881             }
882              
883             sub addkid {
884 82     82 0 106 my $tree = shift;
885 82         87 my $newtree = shift;
886 82 50 33     546 confess("problem: $tree not arr") unless ref($tree) && ref($tree) eq "ARRAY" || isastag($tree);
      33        
887 82         162 my ($ev, $subtree) = @$tree;
888 82 50 33     337 if (ref($newtree) && $newtree->[0] eq '@') {
889 0         0 unshift(@$subtree, $newtree);
890             }
891             else {
892 82         171 push(@$subtree, $newtree);
893             }
894 82         220 $newtree;
895             }
896             *addChildTree = \&addkid;
897             *addchild = \&addkid;
898             *ak = \&addkid;
899              
900             #sub findChildren {
901             # my $tree = shift;
902             # confess("problem: $tree not arr") unless ref($tree) && ref($tree) eq "ARRAY" || isaNode($tree);
903             # my ($ev, $subtree) = @$tree;
904             # return @$subtree;
905             #}
906              
907             sub findnode {
908 568     568 0 689 my $tree = shift;
909 568         904 my ($node, @path) = splitpath(shift);
910              
911 568         717 my $replace = shift;
912 568 50 66     6738 confess("problem: $tree not arr") unless ref($tree) && ref($tree) eq "ARRAY" || isastag($tree);
      66        
913              
914 568 50       1181 if (@path) {
915 0         0 my @r = map { $_->findnode(\@path, $replace) } findnode($tree, $node);
  0         0  
916 0         0 return @r;
917             }
918              
919 568         951 my ($ev, $subtree) = @$tree;
920 568         741 my @r = ();
921 568 50       1077 if ($DEBUG) {
922 0         0 print STDERR "$ev, $subtree;; replace = $replace\n";
923             }
924 568 100       987 if (test_eq($ev, $node)) {
925 46 50       96 if ($DEBUG) {
926 0         0 print STDERR " MATCH\n";
927             }
928 46 50       94 if (defined $replace) {
929 0         0 my @old = @$tree;
930 0         0 @$tree = @$replace;
931 0         0 return Nodify([@old]);
932             }
933             # return [$ev=>$subtree] ;
934             # return Nodify($tree);
935 46         107 @r = (Nodify($tree));
936             }
937 568         890 my @nextlevel = ();
938 568 100       1066 if ( ref($subtree)) {
939 56         101 @nextlevel =
940             map {
941 201         302 map {
942 541         8673 Nodify($_)
943             } findnode($_, $node, $replace);
944            
945             } @$subtree;
946             # get rid of empty nodes
947             # (can be caused by replacing)
948 201 50 33     296 @$subtree = map { ref($_) && !scalar(@$_) ? () : $_ } @$subtree;
  541         2493  
949             }
950             # if (wantarray) {
951             # return $nextlevel[0];
952             # }
953 568         1286 return (@r, @nextlevel);
954             }
955             *fn = \&findnode;
956             *findSubTree = \&findnode;
957             *fst = \&findnode;
958              
959             sub remove {
960 0     0 0 0 my $tree = shift;
961 0         0 my ($node, @path) = splitpath(shift);
962              
963 0         0 my $replace = shift;
964 0 0 0     0 confess("problem: $tree not arr") unless ref($tree) && ref($tree) eq "ARRAY" || isastag($tree);
      0        
965              
966 0 0       0 if (@path) {
967 0         0 $_->remove(\@path) foreach findnode($tree, $node);
968 0         0 return;
969             }
970              
971 0         0 my ($ev, $subtree) = @$tree;
972 0 0       0 return unless ref $subtree;
973 0         0 my @subnodes = @$subtree;
974 0         0 @subnodes =
975             grep {
976 0         0 $_->[0] ne $node;
977             } @subnodes;
978 0         0 @$subtree = @subnodes;
979 0         0 remove($_, $node) foreach @subnodes;
980 0         0 return;
981             }
982              
983             sub set {
984 12   33 12 0 57 my $tree = shift || confess;
985 12         33 my ($node, @path) = splitpath(shift);
986 12         30 my @replace = @_;
987              
988 12 50       44 if (@path) {
989 0         0 my $last = pop @path;
990 0         0 my @nodes = getnode($tree, [$node, @path]);
991 0         0 foreach (@nodes) {
992 0         0 set($_, $last, @replace);
993             }
994 0         0 return @replace;
995             }
996              
997 12 50 33     131 confess("problem: $tree not arr") unless ref($tree) && ref($tree) eq "ARRAY" || isastag($tree);
      33        
998 12         30 my ($ev, $subtree) = @$tree;
999 12 50       42 confess("$subtree not arr [$ev IS A TERMINAL NODE!!]") unless ref($subtree);
1000 12         20 my $is_set;
1001 12         21 my @nu = ();
1002 12         31 foreach my $st (@$subtree) {
1003 24         40 push(@nu, $st);
1004 24         43 my ($ev, $subtree) = @$st;
1005 24 50       54 if (test_eq($ev, $node)) {
1006 0 0       0 if (!$is_set) {
1007 0         0 pop @nu;
1008 0         0 push(@nu,
1009             map {
1010 0         0 [$node => $_]
1011             } @replace);
1012 0         0 $is_set = 1;
1013             }
1014             else {
1015 0         0 pop @nu;
1016             }
1017             }
1018             }
1019            
1020             # place at the end if not already present
1021 12 50       28 if (!$is_set) {
1022 12         60 map {
1023 12         22 addkid($tree, [$node=>$_]);
1024             } @replace;
1025             }
1026             else {
1027 0         0 @$tree = ($ev, \@nu);
1028             }
1029 12         160 return @replace;
1030             }
1031             *s = \&set;
1032             *setSubTreeVal = \&set;
1033              
1034             sub setl {
1035 0   0 0 0 0 my $tree = shift || confess;
1036 0         0 my @args = @_;
1037 0         0 while (@args) {
1038 0         0 set($tree, splice(@args, 0, 2));
1039             }
1040 0         0 return;
1041             }
1042             *sl = \&setl;
1043             *setlist = \&setl;
1044              
1045             sub setnode {
1046 2     2 0 5 my $tree = shift;
1047 2         4 my $elt = shift;
1048 2         3 my $nunode = shift;
1049 2         6 set($tree, $elt, data($nunode));
1050             }
1051             *sn = \&setnode;
1052             *setn = \&setnode;
1053             *settree = \&setnode;
1054              
1055             # EXPERIMENTAL
1056             sub free {
1057 7     7 0 22 my $tree = shift;
1058 7         35 @$tree = ();
1059             }
1060              
1061             sub add {
1062 115   33 115 0 269 my $tree = shift || confess;
1063 115         129 my $node = shift;
1064             # usage1: stag_add($tree, $name, @nodes)
1065 115         183 my @v = @_; # nodes to be added
1066 115 50       234 if (ref($node)) {
1067             # usage2: stag_add($tree, $node)
1068 0 0       0 if ($node->isnull) {
1069 0         0 confess("cannot add null node");
1070             }
1071             # split node into name and data
1072 0         0 ($node, @v) = ($node->[0], [$node->[1]]);
1073             }
1074 115 100 100     690 if (ref($v[0]) && !ref($v[0]->[0])) {
1075 2         2 @v = map { $_->[1] } @v;
  2         8  
1076             }
1077 115 50 33     582 confess("problem: $tree not arr") unless ref($tree) && ref($tree) eq "ARRAY" || isastag($tree);
      33        
1078 115         248 my ($ev, $subtree) = @$tree;
1079              
1080 115         179 my @nu_subtree = ();
1081 115         126 my $has_been_set = 0;
1082 115         272 for (my $i=0; $i<@$subtree; $i++) {
1083 319         453 my $st = $subtree->[$i];
1084 319         405 my $next_st = $subtree->[$i+1];
1085 319         438 my ($ev, $subtree) = @$st;
1086 319         331 push(@nu_subtree, $st);
1087 319 100 100     911 if (!$has_been_set &&
      100        
      66        
1088             test_eq($ev, $node) &&
1089             (!$next_st ||
1090             $next_st->[0] ne $ev)) {
1091 45         153 push(@nu_subtree,
1092 45         64 map { [$ev=>$_] } @v);
1093 45         145 $has_been_set = 1;
1094             }
1095             }
1096 115 100       184 if (!$has_been_set) {
1097 70         243 addkid($tree, [$node=>$_]) foreach @v;
1098             }
1099             else {
1100 45         153 @$subtree = @nu_subtree;
1101             }
1102 115         335 return;
1103             }
1104             *a = \&add;
1105             *addSubTreeVal = \&add;
1106              
1107             sub addnode {
1108 0     0 0 0 my $tree = shift;
1109 0         0 my $elt = shift;
1110 0         0 my $node = shift;
1111 0         0 my $nodename = $node->name;
1112 0 0       0 if ($nodename ne $elt) {
1113 0         0 confess("$nodename ne $elt");
1114             }
1115 0         0 add($tree, $elt, $node->data);
1116             }
1117             *an = \&addnode;
1118             *addn = \&addnode;
1119              
1120             sub unset {
1121 4   33 4 0 17 my $tree = shift || confess;
1122 4         11 my ($node, @path) = splitpath(shift);
1123 4 50 33     32 confess("problem: $tree not arr") unless ref($tree) && ref($tree) eq "ARRAY" || isastag($tree);
      33        
1124              
1125 4 100       11 if (@path) {
1126 1         5 $_->unset(\@path) foreach findnode($tree, $node);
1127 1         4 return;
1128             }
1129              
1130 3         7 my ($ev, $subtree) = @$tree;
1131 3 50       10 return unless ref $subtree;
1132 3         4 my @nu_tree = ();
1133 3         5 foreach my $st (@$subtree) {
1134 9         15 my ($ev, $subtree) = @$st;
1135 9 100       214 if ($ev ne $node) {
1136 6         10 push(@nu_tree, $st);
1137             }
1138             }
1139 3         6 $tree->[1] = \@nu_tree;
1140 3         15 return;
1141             }
1142             *unsetSubTreeVal = \&unset;
1143             *u = \&unset;
1144              
1145              
1146             # WANTARRAY
1147             sub find {
1148 176   33 176 0 969 my $tree = shift || confess;
1149 176         299 my ($node, @path) = splitpath(shift);
1150 176         361 my $replace = shift;
1151              
1152 176 50 66     1114 confess("problem: $tree not arr") unless (ref($tree) && ref($tree) eq "ARRAY") || isastag($tree);
      66        
1153              
1154 176         256 my @r = ();
1155 176 100       303 if (@path) {
1156 2         6 @r = map { $_->find(\@path, $replace) } findnode($tree, $node)
  2         21  
1157             }
1158             else {
1159 174         290 my ($ev, $subtree) = @$tree;
1160 174 100       295 if (test_eq($ev, $node)) {
1161 23         43 my $is_nt = ref($subtree);
1162 23 50       78 if (defined $replace) {
1163 0 0       0 if ($is_nt) {
1164 0 0       0 confess("use findval") unless ref($replace);
1165 0         0 @$tree = @$replace;
1166             }
1167             else {
1168 0         0 $tree->[1] = $replace;
1169             }
1170             }
1171 23 100       89 return $is_nt ? Nodify($tree) : $subtree;
1172             # @r = ($is_nt ? Nodify($tree) : $subtree);
1173             }
1174 151 100       496 return unless ref($subtree);
1175 64         99 push(@r, map { find($_, $node, $replace) } @$subtree);
  164         414  
1176             }
1177 66 100       246 if (wantarray) {
1178 62         186 return @r;
1179             }
1180 4         29 $r[0];
1181             }
1182             *f = \&find;
1183              
1184             sub findval {
1185 135   33 135 0 286 my $tree = shift || confess;
1186 135         221 my ($node, @path) = splitpath(shift);
1187 135         178 my $replace = shift;
1188              
1189 135 50 66     671 confess("problem: $tree not arr") unless ref($tree) && ref($tree) eq "ARRAY" || isastag($tree);
      66        
1190              
1191 135         204 my @r = ();
1192 135 50       234 if (@path) {
1193 0         0 @r = map { $_->findval(\@path, $replace) } findnode($tree, $node)
  0         0  
1194             }
1195             else {
1196 135         204 my ($ev, $subtree) = @$tree;
1197 135 100       251 if (test_eq($ev, $node)) {
1198 12         24 my $dataref = \$tree->[1];
1199 12 50       27 if (ref($subtree)) {
1200             # check if it is the data node of
1201             # an element with attributes
1202 0         0 my @kids = grep {$_->[0] eq '.'} @$subtree;
  0         0  
1203 0 0       0 if (@kids == 1) {
1204 0         0 $dataref = \$kids[0]->[1];
1205             }
1206             }
1207 12 50       24 if (defined $replace) {
1208 0         0 $$dataref = $replace;
1209             }
1210 12         38 return $$dataref;
1211             }
1212 123 100       387 return unless ref($subtree);
1213 36         55 @r = map { findval($_, $node, $replace) } @$subtree;
  123         232  
1214             }
1215 36 100       78 if (wantarray) {
1216 32         80 return @r;
1217             }
1218 4         20 $r[0];
1219             }
1220             *fv = \&findval;
1221             *finddata = \&findval;
1222             *fd = \&findval;
1223             *findSubTreeVal = \&findval;
1224              
1225             # WANTARRAY
1226             sub getdata {
1227 2   33 2 0 8 my $tree = shift || confess;
1228 2         6 my ($node, @path) = splitpath(shift);
1229 2         4 my $replace = shift;
1230              
1231 2 50 33     16 confess("problem: $tree not arr") unless ref($tree) && ref($tree) eq "ARRAY" || isastag($tree);
      33        
1232              
1233 2         5 my @v = ();
1234 2 100       6 if (@path) {
1235 1         4 @v = map { $_->getdata(\@path, $replace) } getnode($tree, $node)
  1         12  
1236             }
1237             else {
1238              
1239 1         3 my ($top_ev, $children) = @$tree;
1240 1         2 @v = ();
1241 1         3 foreach my $child (@$children) {
1242 3 50       7 confess unless ref $child;
1243 3         7 my ($ev, $subtree) = @$child;
1244 3 50       6 if (test_eq($ev, $node)) {
1245 3 50       9 if (defined $replace) {
1246 0         0 $child->[1] = $replace;
1247             }
1248 3         9 push(@v, $subtree);
1249             }
1250             }
1251             }
1252 2 50       6 if (wantarray) {
1253 2         9 return @v;
1254             }
1255 0         0 $v[0];
1256             }
1257             *gd = \&getdata;
1258              
1259             sub get {
1260 825   33 825 0 3435 my $tree = shift || confess;
1261 825         1352 my ($node, @path) = splitpath(shift);
1262 825         1331 my $replace = shift;
1263             # confess("problem: $tree not arr") unless ref($tree) && (ref($tree) eq "ARRAY" || isastag($tree));
1264 825 50       1920 if (!ref($tree->[1])) {
1265             # terminal node - always returns undef
1266 0         0 return;
1267             }
1268 825         1251 my @v = ();
1269 825 100       1332 if (@path) {
1270 21         64 @v = map { $_->get(\@path, $replace) } getnode($tree, $node)
  26         147  
1271             }
1272             else {
1273              
1274 804         1200 my ($top_ev, $children) = @$tree;
1275 804         962 @v = ();
1276 804 50       1694 if (!ref($children)) {
1277 0         0 confess("problem with $node/$top_ev => $children; cannot call get on terminal node");
1278             }
1279 804         1201 foreach my $child (@$children) {
1280 1782 50       3268 confess unless ref $child;
1281 1782         9115 my ($ev, $subtree) = @$child;
1282 1782 100       3322 if (test_eq($ev, $node)) {
1283 813         898 my $is_nt = 0;
1284 813 100       1525 if (ref($subtree)) {
1285 14         26 $is_nt = 1;
1286             }
1287 813 100       1461 if (defined $replace) {
1288             # $tree->[1] = $replace;
1289 6 100       13 if ($is_nt) {
1290 3 50       10 if (!ref($replace)) {
1291 0         0 confess("use getdata instead");
1292             }
1293 3         9 @$child = @$replace;
1294             }
1295             else {
1296 3         5 $child->[1] = $replace;
1297             }
1298             }
1299 813 100       1168 if ($is_nt) {
1300 14         28 push(@v, Nodify($child));
1301             }
1302             else {
1303 799         2372 push(@v, $subtree);
1304             }
1305             }
1306             }
1307             }
1308 825 100       1710 if (wantarray) {
1309 98         403 return @v;
1310             }
1311 727         2434 $v[0];
1312             }
1313             *g = \&get;
1314              
1315             # WANTARRAY
1316             sub getnode {
1317 40   33 40 0 475 my $tree = shift || confess;
1318 40         83 my ($node, @path) = splitpath(shift);
1319 40         63 my $replace = shift;
1320              
1321 40 50 33     293 confess("problem: $tree not arr") unless ref($tree) && ref($tree) eq "ARRAY" || isastag($tree);
      33        
1322              
1323 40         69 my @v = ();
1324 40 100       105 if (@path) {
1325 3         16 @v = map { $_->getnode(\@path, $replace) } getnode($tree, $node)
  4         11  
1326             }
1327             else {
1328              
1329 37         73 my ($top_ev, $children) = @$tree;
1330 37 50       101 if (!ref($children)) {
1331 0         0 confess("problem: $top_ev => \"$children\" not a tree");
1332             }
1333 37         76 foreach my $child (@$children) {
1334 128         207 my ($ev, $subtree) = @$child;
1335 128 100       213 if (test_eq($ev, $node)) {
1336 61 50       127 if (defined $replace) {
1337 0         0 $tree->[1] = $replace;
1338             }
1339 61         126 push(@v, Nodify($child));
1340             }
1341             }
1342             }
1343              
1344 40 50       109 if (wantarray) {
1345 40         131 return @v;
1346             }
1347 0         0 $v[0];
1348             }
1349             *getn = \&getnode;
1350             *gn = \&getnode;
1351             *gettree = \&getnode;
1352              
1353             sub sgetnode {
1354 6     6 0 9 my $tree = shift;
1355 6         17 my @v = getnode($tree, @_);
1356 6         38 return $v[0];
1357             }
1358             *sgetn = \&sgetnode;
1359             *sgn = \&sgetnode;
1360             *sgettree = \&sgetnode;
1361              
1362              
1363             sub getl {
1364 0   0 0 0 0 my $tree = shift || confess;
1365 0         0 my @elts = @_;
1366 0         0 my %elth = map{$_=>1} @elts;
  0         0  
1367 0         0 my %valh = ();
1368 0 0 0     0 confess("problem: $tree not arr") unless ref($tree) && ref($tree) eq "ARRAY" || isastag($tree);
      0        
1369 0         0 my ($top_ev, $children) = @$tree;
1370 0         0 my @v = ();
1371 0         0 foreach my $child (@$children) {
1372 0         0 my ($ev, $subtree) = @$child;
1373 0         0 my $is_nt = ref($subtree);
1374 0 0       0 if ($elth{$ev}) {
1375             # warn if dupl?
1376 0 0       0 $valh{$ev} = $is_nt ? $child : $subtree;
1377             }
1378             }
1379 0         0 return map {$valh{$_}} @elts;
  0         0  
1380             }
1381             *getlist = \&getl;
1382             *gl = \&getl;
1383              
1384             sub sget {
1385 5     5 0 8 my $tree = shift;
1386 5         20 my @v = get($tree, @_);
1387             # warn if multivalued?
1388 5         32 return $v[0];
1389             }
1390             *sg = \&sget;
1391              
1392             sub sgetdata {
1393 0     0 0 0 my $tree = shift;
1394 0         0 my @v = getdata($tree, @_);
1395             # warn if multivalued?
1396 0         0 return $v[0];
1397             }
1398             *sgd = \&sgetdata;
1399              
1400             sub mapv {
1401 0     0 0 0 my $tree = shift;
1402 0         0 my %maph = @_;
1403 0         0 foreach my $oldkey (keys %maph) {
1404 0         0 my $newkey = $maph{$oldkey};
1405 0         0 my @currv = get($tree, $newkey);
1406 0 0       0 next if @currv;
1407 0         0 my @v = get($tree, $oldkey);
1408 0         0 set($tree, $newkey, @v);
1409 0         0 unset($tree, $oldkey);
1410             }
1411 0         0 return;
1412             }
1413              
1414             sub sgetmap {
1415 0     0 0 0 my $tree = shift;
1416 0         0 my %maph = @_;
1417 0         0 my %vh = ();
1418 0         0 foreach my $oldkey (keys %maph) {
1419             # if 0 is supplied, use oldkey
1420 0   0     0 my $newkey = $maph{$oldkey} || $oldkey;
1421 0         0 my @v = get($tree, $oldkey);
1422 0 0       0 if (@v > 1) {
1423 0         0 $tree->throw("multivalued key $oldkey");
1424             }
1425 0         0 $vh{$newkey} = $v[0];
1426             }
1427 0         0 return %vh;
1428             }
1429             *sgm = \&sgetmap;
1430              
1431             sub sfindval {
1432 0     0 0 0 my $tree = shift;
1433 0         0 my @v = findval($tree, @_);
1434             # warn if multivalued?
1435 0         0 return $v[0];
1436             }
1437             *sfv = \&sfindval;
1438             *singlevalFindSubTreeVal = \&sfindval;
1439              
1440             # private
1441             sub indexOn {
1442 4     4 0 6 my $tree = shift;
1443 4         7 my $key = shift;
1444              
1445 4         7 my %h = ();
1446 4         7 my ($evParent, $stParent) = @$tree;
1447 4         8 foreach my $subtree (@$stParent) {
1448 10         22 my @vl = get($subtree, $key);
1449 10         17 foreach my $v (@vl) {
1450 10 50       23 if (!$h{$v}) { $h{$v} = [] }
  10         25  
1451 10         15 push(@{$h{$v}}, $subtree);
  10         40  
1452             }
1453             }
1454 4         12 return \%h;
1455             }
1456              
1457             # does a relational style join
1458             sub ijoin {
1459 2     2 0 15 my $tree = shift;
1460 2         3 my $element = shift; # name of element to join
1461 2         3 my $key = shift; # name of join element
1462 2         4 my $searchstruct = shift; # structure
1463 2         9 my @elts = $tree->fst($element);
1464             paste($_, $key, $searchstruct)
1465 2         10 foreach @elts;
1466            
1467 2         8 return;
1468             }
1469             *ij = \&ijoin;
1470             *j = \&ijoin;
1471             *nj = \&ijoin;
1472             *njoin = \&ijoin;
1473              
1474             sub paste {
1475 4     4 0 8 my $tree = shift;
1476 4         5 my $key = shift;
1477 4         7 my $searchstruct = shift;
1478             # use indexing?
1479 4         8 my ($key1, $key2) = ($key, $key);
1480 4 100       21 if ($key =~ /(.*)=(.*)/) {
1481 2         9 ($key1, $key2) = ($1, $2);
1482             }
1483 4         8 my $ssidx = indexOn($searchstruct, $key2);
1484              
1485 4         10 my ($evParent, $stParent) = @$tree;
1486 4         6 my @children = ();
1487 4         9 foreach my $subtree (@$stParent) {
1488 14         21 my @nu = ($subtree);
1489 14         23 my ($ev, $st) = @$subtree;
1490 14 100       27 if ($ev eq $key1) {
1491 6 50       15 $tree->throw("can't join on $ev - $st is not primitive")
1492             if ref $st;
1493 6   100     21 my $replace = $ssidx->{$st} || [];
1494 6         13 @nu = @$replace;
1495             }
1496 14         32 push(@children, @nu);
1497             }
1498 4         13 @$tree = ($evParent, \@children);
1499 4         29 return;
1500             }
1501              
1502             #
1503             sub maptree {
1504 0     0 0 0 my $tree = shift;
1505 0         0 my $code = shift;
1506 0         0 my $parent = shift;
1507 0         0 my $next = $code->($tree, $parent);
1508 0 0       0 if ($next) {
1509 0 0 0     0 if (ref($next) && ref($next) eq 'ARRAY') {
1510 0         0 return @$next;
1511             }
1512 0         0 my $elt = $next->element;
1513 0         0 my @subnodes = subnodes($next);
1514 0 0       0 if (!@subnodes) {
1515 0         0 return $next;
1516             }
1517 0         0 my @new_subnodes = ();
1518 0         0 foreach (@subnodes) {
1519 0         0 my @nu = maptree($_, $code, $tree);
1520 0         0 push(@new_subnodes,@nu);
1521             }
1522 0         0 return Data::Stag->new($elt=>[@new_subnodes]);
1523             }
1524             else {
1525 0         0 return ();
1526             }
1527             }
1528              
1529             # iterate depth first through tree executing code
1530             sub iterate {
1531 396     396 0 962 my $tree = shift;
1532 396         406 my $code = shift;
1533 396         410 my $parent = shift;
1534 396         668 $code->($tree, $parent);
1535 396         786 my @subnodes = subnodes($tree);
1536 396         680 foreach (@subnodes) {
1537 379         645 iterate($_, $code, $tree);
1538             }
1539 396         1648 return;
1540             }
1541             *i = \&iterate;
1542              
1543             # takes a denormalized flat table of rows/columns
1544             # and turns it back into its original tree structure;
1545             # useful for querying databases
1546             # DEPRECATED
1547             sub normalize {
1548 3   50 3 0 15 my $tree = shift || [];
1549 3         34 my ($schema,
1550             $rows,
1551             $top,
1552             $cols,
1553             $constraints,
1554             $path) =
1555             rearrange([qw(schema
1556             rows
1557             top
1558             cols
1559             constraints
1560             path)], @_);
1561 3 100       17 if (!$schema) {
1562 2         14 $schema = $tree->new(schema=>[]);
1563             }
1564 3 100       18 if (!isastag($schema)) {
1565 1 50       10 if (!ref($schema)) {
1566             # it's a string - parse it
1567             # (assume sxpr)
1568             }
1569 1         354 $schema = $tree->from('sxprstr', $schema);
1570             }
1571             # TOP - this is the element name
1572             # to group the structs under.
1573             # [override if specified explicitly]
1574 3 100       11 if ($top) {
1575 2         24 $schema->set_top($top);
1576             }
1577 3   50     8512 $top = $schema->get_top || "set";
1578 3         34 my $topstruct = $tree->new($top, []);
1579              
1580             # COLS - this is the columns (attribute names)
1581             # in the order they appear
1582             # [override if specified explicitly]
1583 3 100       11 if ($cols) {
1584             my @ncols =
1585             map {
1586 2 50       5 if (ref($_)) {
  11         21  
1587 0         0 $_
1588             }
1589             else {
1590             # presume it's a string
1591             # format = GROUP.ATTRIBUTENAME
1592 11 50       48 if (/(\w+)\.(\w+)/) {
1593 11         81 $tree->new(col=>[
1594             [group=>$1],
1595             [name=>$2]]);
1596             }
1597             else {
1598 0         0 confess $_;
1599             }
1600             }
1601             } @$cols;
1602 2         18 $schema->set_cols([@ncols]);
1603             }
1604              
1605              
1606             # PATH - this is the tree structure in
1607             # which the groups are structured
1608             # [override if specified explicitly]
1609 3 100       14 if ($path) {
1610 2 50       6 if (ref($path)) {
1611             }
1612             else {
1613 2         20 $path = $tree->from('sxprstr', $path);
1614             }
1615 2         20 $schema->set_path([$path]);
1616             }
1617 3         27 $path = $schema->sgetnode_path;
1618 3 50       11 if (!$path) {
1619 0         0 confess("no path!");
1620             }
1621            
1622             # column headings
1623 3         73 my @cols = $schema->sgetnode_cols->getnode_col();
1624              
1625             # set the primary key for each group;
1626             # the default is all the columns in that group
1627 3         12 my %pkey_by_groupname = ();
1628 3         6 my %cols_by_groupname = ();
1629 3         6 foreach my $col (@cols) {
1630 19         88 my $groupname = $col->get_group;
1631 19         84 my $colname = $col->get_name;
1632 19 100       64 $pkey_by_groupname{$groupname} = []
1633             unless $pkey_by_groupname{$groupname};
1634 19         23 push(@{$pkey_by_groupname{$groupname}},
  19         41  
1635             $colname);
1636 19 100       49 $cols_by_groupname{$groupname} = []
1637             unless $cols_by_groupname{$groupname};
1638 19         19 push(@{$cols_by_groupname{$groupname}},
  19         44  
1639             $colname);
1640             }
1641 3         24 my @groupnames = keys %pkey_by_groupname;
1642              
1643             # override PK if set as a constraint
1644 3         20 my @pks = $schema->findnode("primarykey");
1645 3         12 foreach my $pk (@pks) {
1646 2         14 my $groupname = $pk->get_group;
1647 2         20 my @cols = $pk->get_col;
1648 2         12 $pkey_by_groupname{$groupname} = [@cols];
1649             }
1650              
1651             # ------------------
1652             #
1653             # loop through denormalised rows,
1654             # grouping the columns into their
1655             # respecive groups
1656             #
1657             # eg
1658             #
1659             # <----- a -----> <-- b -->
1660             # a.1 a.2 a.3 b.1 b.2
1661             #
1662             # algorithm:
1663             # use path/tree to walk through
1664             #
1665             # ------------------
1666              
1667             # keep a hash of all groups by their primary key vals
1668             # outer key = groupname
1669             # inner key = pkval
1670             # hash val = group structure
1671 3         9 my %all_group_hh = ();
1672 3         6 foreach my $groupname (@groupnames) {
1673 10         25 $all_group_hh{$groupname} = {};
1674             }
1675              
1676             # keep an array of all groups
1677             # outer key = groupname
1678             # inner array = ordered list of groups
1679             # my %all_group_ah = ();
1680             # foreach my $groupname (keys %pkey_by_groupname) {
1681             # $all_group_ah{$groupname} = [];
1682             # }
1683              
1684 3         16 my ($first_in_path) = $path->subnodes;
1685 3         18 my $top_record_h =
1686             {
1687             child_h=>{
1688             $first_in_path->name=>{}
1689             },
1690             struct=>$topstruct
1691             };
1692             # loop through rows
1693 3         10 foreach my $row (@$rows) {
1694 47         215 my @colvals = @$row;
1695              
1696             # keep a record of all groups in
1697             # this row
1698 47         63 my %current_group_h = ();
1699 47         159 for (my $i=0; $i<@cols; $i++) {
1700 321         419 my $colval = $colvals[$i];
1701 321         350 my $col = $cols[$i];
1702 321         1368 my $groupname = $col->get_group;
1703 321         1326 my $colname = $col->get_name;
1704 321         696 my $group = $current_group_h{$groupname};
1705 321 100       628 if (!$group) {
1706 173         401 $group = {};
1707 173         291 $current_group_h{$groupname} = $group;
1708             }
1709 321         1262 $group->{$colname} = $colval;
1710             }
1711              
1712             # we now have a hash of hashes -
1713             # outer keyed by group id
1714             # inner keyed by group attribute name
1715            
1716             # traverse depth first down path;
1717             # add new nodes as children of the parent
1718             sub make_a_tree {
1719 155     155 0 196 my $class = shift;
1720 155         194 my $parent_rec_h = shift;
1721 155         143 my $node = shift;
1722 155 50       141 my %current_group_h= %{shift ||{}};
  155         774  
1723 155 50       210 my %pkey_by_groupname = %{shift ||{}};
  155         595  
1724 155 50       196 my %cols_by_groupname = %{shift ||{}};
  155         681  
1725 155         402 my $groupname = $node->name;
1726 155         279 my $grouprec = $current_group_h{$groupname};
1727 155         182 my $pkcols = $pkey_by_groupname{$groupname};
1728 267   50     724 my $pkval =
1729             CORE::join("\t",
1730             map {
1731 155         214 esctab($grouprec->{$_} || '')
1732             } @$pkcols);
1733 155         345 my $rec = $parent_rec_h->{child_h}->{$groupname}->{$pkval};
1734 155 100       300 if (!$rec) {
1735 110         182 my $groupcols = $cols_by_groupname{$groupname};
1736 207         948 my $groupstruct =
1737             $class->new($groupname=>[
1738             map {
1739 110         155 [$_ => $grouprec->{$_}]
1740             } @$groupcols
1741             ]);
1742 110         191 my $parent_groupstruct = $parent_rec_h->{struct};
1743 110 50       214 if (!$parent_groupstruct) {
1744 0         0 confess("no parent for $groupname");
1745             }
1746 110         216 add($parent_groupstruct,
1747             $groupstruct->name,
1748             $groupstruct->data);
1749 110         411 $rec =
1750             {struct=>$groupstruct,
1751             child_h=>{}};
1752 110         248 foreach ($node->subnodes) {
1753             # keep index of children by PK
1754 64         177 $rec->{child_h}->{$_->name} = {};
1755             }
1756 110         401 $parent_rec_h->{child_h}->{$groupname}->{$pkval} = $rec;
1757             }
1758 155         366 foreach ($node->subnodes) {
1759 108         246 make_a_tree($class,
1760             $rec, $_, \%current_group_h,
1761             \%pkey_by_groupname, \%cols_by_groupname);
1762             }
1763             }
1764 47         164 make_a_tree($tree,
1765             $top_record_h, $first_in_path, \%current_group_h,
1766             \%pkey_by_groupname, \%cols_by_groupname);
1767             }
1768 3         220 return $topstruct;
1769             }
1770             *norm = \&normalize;
1771             *normalise = \&normalize;
1772              
1773              
1774             sub esctab {
1775 267     267 0 341 my $w=shift;
1776 267         336 $w =~ s/\t/__MAGICTAB__/g;
1777 267         752 $w;
1778             }
1779              
1780             sub findvallist {
1781 0   0 0 0 0 my $tree = shift || confess;
1782 0         0 my @nodes = @_;
1783 0         0 my @vals =
1784             map {
1785 0         0 my @v = findval($tree, $_);
1786 0 0       0 if (@v > 1) {
1787 0         0 confess(">1 val for $_: @v");
1788             }
1789 0         0 $v[0];
1790             } @nodes;
1791 0         0 return @vals;
1792             }
1793             *findSubTreeValList = \&findvallist;
1794             *fvl = \&findvallist;
1795              
1796             sub findChildVal {
1797 0     0 0 0 confess("deprecated - use get");
1798 0         0 my $tree = shift;
1799 0         0 my $node = shift;
1800 0         0 my $replace = shift;
1801 0 0       0 confess unless ref($tree);
1802 0         0 my ($ev, $subtree) = @$tree;
1803 0 0       0 return $subtree if test_eq($ev, $node);
1804 0 0       0 return unless ref($subtree);
1805 0         0 my @children = grep { $_->[0] eq $node } @$subtree;
  0         0  
1806             # print "@children\n";
1807 0 0       0 if (defined $replace) {
1808             return
1809 0         0 map { $_->[1] = $replace } @children;
  0         0  
1810             }
1811 0         0 my @r = map { $_->[1] } @children;
  0         0  
1812 0 0       0 if (wantarray) {
1813 0         0 return @r;
1814             }
1815 0         0 $r[0];
1816             }
1817              
1818             sub qmatch {
1819 13     13 0 517 my $tree = shift;
1820 13         21 my $elt = shift;
1821 13         19 my $matchkey = shift;
1822 13         22 my $matchval = shift;
1823 13         15 my $replace = shift;
1824              
1825 13         38 my @st = findnode($tree, $elt);
1826 26         85 my @match =
1827             grep {
1828             # tmatch($_, $matchkey, $matchval);
1829 13         44 grep {$_ eq $matchval} $_->get($matchkey);
  28         79  
1830             } @st;
1831 13 50       36 if ($replace) {
1832 0         0 map {
1833 0         0 @$_ = @$replace;
1834             } @match;
1835             }
1836 13         98 return @match;
1837             }
1838             *qm = \&qmatch;
1839             *findSubTreeMatch = \&qmatch;
1840              
1841              
1842             sub tmatch {
1843 8     8 0 724 my $tree = shift;
1844 8         14 my $elt = shift;
1845 8         11 my $matchval = shift;
1846 8         22 my @vals = findval($tree, $elt);
1847 8         13 return grep {$_ eq $matchval} @vals;
  8         52  
1848             }
1849             *testSubTreeMatch = \&tmatch;
1850             *tm = \&tmatch;
1851              
1852              
1853             sub tmatchhash {
1854 0     0 0 0 my $tree = shift;
1855 0         0 my $match = shift;
1856 0         0 my @mkeys = keys %$match;
1857 0         0 my @mvals = map {$match->{$_}} @mkeys;
  0         0  
1858              
1859 0         0 my @rvals = findvallist($tree, @mkeys);
1860 0         0 my $pass = 1;
1861 0         0 for (my $i=0; $i<@mvals; $i++) {
1862 0 0       0 $pass = 0 if $mvals[$i] ne $rvals[$i];
1863             }
1864             # print "CHECK @mvals eq @rvals [$pass]\n";
1865 0         0 return $pass;
1866             }
1867             *tmh = \&tmatchhash;
1868             *testSubTreeMatchHash = \&tmatchhash;
1869              
1870             sub tmatchnode {
1871 0     0 0 0 my $tree = shift;
1872 0         0 my $matchtree = shift;
1873 0         0 my ($node, $subtree) = @$tree;
1874 0 0       0 confess unless ref $matchtree;
1875 0         0 my ($mnode, $msubtree) = @$matchtree;
1876 0 0       0 if ($node ne $mnode) {
1877 0 0       0 return unless ref $subtree;
1878             return
1879 0         0 grep {
1880 0         0 testSubTreeMatchTree($_,
1881             $matchtree)
1882             } @$subtree;
1883             }
1884 0 0 0     0 if (!ref($subtree) && !ref($msubtree)) {
1885 0         0 return $subtree eq $msubtree;
1886             }
1887 0 0 0     0 if (ref($subtree) && ref($msubtree)) {
1888 0         0 my @got = ();
1889 0         0 for (my $i=0; $i<@$msubtree; $i++) {
1890 0         0 my $n = $msubtree->[$i]->[0];
1891 0         0 my ($x) = grep {$_->[0] eq $n} @$subtree;
  0         0  
1892 0 0       0 return unless $x;
1893 0         0 my $ok =
1894             testSubTreeMatchTree($x,
1895             $msubtree->[$i]);
1896 0 0       0 return unless $ok;
1897            
1898             }
1899 0         0 return 1;
1900             }
1901              
1902             }
1903             *tmn = \&tmatchnode;
1904             *testSubTreeMatchTree = \&tmatchnode;
1905              
1906             sub cmatch {
1907 0     0 0 0 my $tree = shift;
1908 0         0 my $node = shift;
1909 0         0 my $matchval = shift;
1910 0         0 my @vals = findval($tree, $node);
1911 0         0 return scalar(grep {$_ eq $matchval} @vals);
  0         0  
1912             }
1913             *cm = \&cmatch;
1914             *countSubTreeMatch = \&cmatch;
1915              
1916              
1917             sub OLDwhere {
1918 0     0 0 0 my $tree = shift;
1919 0         0 my $node = shift;
1920 0         0 my $testcode = shift;
1921 0         0 my $replace = shift;
1922 0         0 my @subtrees = findnode($tree, $node);
1923 0         0 my @match =
1924             grep {
1925 0         0 $testcode->($_);
1926             } @subtrees;
1927 0 0       0 if (defined $replace) {
1928 0         0 map {
1929 0         0 @$_ = @$replace;
1930             } @match;
1931             }
1932 0         0 return @match;
1933             }
1934             sub where {
1935 12     12 0 1190 my $tree = shift;
1936 12         23 my $node = shift;
1937 12         18 my $testcode = shift;
1938 12         16 my $replace = shift;
1939              
1940 12         23 my @match = ();
1941             iterate($tree,
1942             sub {
1943 282     282   294 my $stag = shift;
1944 282 100       398 if (name($stag) eq $node) {
1945 24 100       57 if ($testcode->($stag)) {
1946 12         142 push(@match, $stag);
1947             }
1948             }
1949 12         67 });
1950              
1951 12 50       64 if (defined $replace) {
1952 0         0 map {
1953 0         0 @$_ = @$replace;
1954             } @match;
1955             }
1956 12         55 return @match;
1957             }
1958             *w = \&where;
1959             *findSubTreeWhere = \&where;
1960              
1961              
1962             #sub findSubTreeWhere {
1963             # my $tree = shift;
1964             # my $node = shift;
1965             # my $testcode = shift;
1966             # my $replace = shift;
1967             ## use Data::Dumper;
1968             ## print Dumper($expr);
1969             # my $call = $expr->name;
1970             # my @p = $expr->findChildVal('arg');
1971             ## print Dumper(\@p);
1972             # my @subtrees = findSubTree($tree, $node);
1973             # no strict 'refs';
1974             # my @match =
1975             # grep {
1976             # &$call($_, @p);
1977             # } @subtrees;
1978             # if (defined $replace) {
1979             # map {
1980             # print "WAS:";
1981             # print xml($_);
1982             # print "NOW:";
1983             # print xml($replace);
1984             # @$_ = @$replace;
1985             # } @match;
1986             # }
1987             # return @match;
1988             #}
1989              
1990             sub run {
1991 0     0 0 0 my $tree = shift;
1992 0         0 my $code = shift;
1993 0         0 my $func = $code->name;
1994 0         0 my @p = $code->children();
1995 0         0 my @args = ();
1996 0         0 foreach my $p (@p) {
1997 0 0       0 if ($p->name eq 'arg') {
1998 0 0       0 if (isastag($p->children)) {
1999 0         0 die;
2000 0         0 push(@args,
2001             evalTree($tree,
2002             $p->children));
2003            
2004             }
2005             else {
2006 0         0 push(@args, $p->children);
2007             }
2008             }
2009             else {
2010             # print "rcall $tree $p\n";
2011 0         0 push(@args,
2012             evalTree($tree,
2013             $p));
2014             }
2015             }
2016 20     20   306 no strict 'refs';
  20         50  
  20         16955  
2017 0         0 my @r = &$func($tree, @p);
2018 0 0       0 return @r if wantarray;
2019 0         0 return shift @r;
2020             }
2021             *evalTree = \&run;
2022              
2023              
2024             # ------------------------
2025             # collapseElement($tree, $elt)
2026             #
2027             # eg if we have
2028             #
2029             # +personset
2030             # +person
2031             # +name jim
2032             # +job sparky
2033             # +person
2034             # +name jim
2035             # +hair green
2036             # +person
2037             # +name eck
2038             # +hair blue
2039             #
2040             # then execute
2041             # collapseElement($tree, 'person', 'name')
2042             #
2043             # we end up with
2044             #
2045             # +personset
2046             # +person
2047             # +name jim
2048             # +job sparky
2049             # +hair green
2050             # +person
2051             # +name eck
2052             # +hair blue
2053             #
2054             # OR if we have
2055             #
2056             # +personset
2057             # +person
2058             # +name jim
2059             # +job sparky
2060             # +petname bubbles
2061             # +pettype chimp
2062             # +person
2063             # +name jim
2064             # +job sparky
2065             # +petname flossie
2066             # +pettype sheep
2067             # +person
2068             # +name eck
2069             # +job bus driver
2070             # +petname gnasher
2071             # +pettype dug
2072             #
2073             #
2074             # then execute
2075             # collapseElement($tree, 'name', 'job')
2076             #
2077             # we end up with
2078             #
2079             # +personset
2080             # +person
2081             # +name jim
2082             # +job sparky
2083             # +pet
2084             # +petname bubbles
2085             # +pettype chimp
2086             # +pet
2087             # +petname flossie
2088             # +pettype sheep
2089             # +person
2090             # +name eck
2091             # +job bus driver
2092             # +pet
2093             # +petname gnasher
2094             # +pettype dug
2095             #
2096             #
2097             # warning: element should be unique
2098             # todo: allow person/name
2099             #
2100             # ------------------------
2101             sub collapse {
2102 0     0 0 0 my $tree = shift;
2103 0         0 my $elt = shift;
2104 0         0 my $merge_elt = shift;
2105 0         0 my @subtrees = findnode($tree, $elt);
2106 0         0 my %treeh = ();
2107 0         0 my @elt_order = (); # preserve ordering
2108 0         0 map {
2109 0         0 my @v = findval($_, $merge_elt);
2110 0 0       0 die unless scalar(@v) == 1;
2111 0         0 my $val = shift @v;
2112 0 0       0 push(@elt_order, $val) unless $treeh{$val};
2113 0 0       0 $treeh{$val} = [] unless $treeh{$val};
2114 0         0 push(@{$treeh{$val}}, $_);
  0         0  
2115             } @subtrees;
2116 0         0 my @new_subtrees =
2117             map {
2118 0         0 my $trees = $treeh{$_};
2119 0         0 my ($v) = findval($trees->[0], $merge_elt);
2120             [
2121 0         0 $elt=>[
2122             [$merge_elt=>$v],
2123             map {
2124 0         0 my ($ev, $subtree) = @$_;
2125 0         0 grep {
2126 0         0 $_->[0] ne $merge_elt
2127             } @$subtree;
2128             } @$trees,
2129             ]
2130             ]
2131             } @elt_order;
2132              
2133 0         0 findnode($tree, $elt, []);
2134 0         0 push(@{$tree->[1]}, @new_subtrees);
  0         0  
2135 0         0 return $tree;
2136             }
2137             *collapseElement = \&collapse;
2138              
2139             sub merge {
2140 0     0 0 0 my $tree = shift;
2141 0 0       0 my @elts = @{shift || []};
  0         0  
2142 0         0 my $merge_key = shift;
2143              
2144 0 0       0 return unless @elts;
2145 0         0 my $e1 = $elts[0];
2146 0         0 my ($type, $v) = @$e1;
2147 0         0 my @cur_elts = findnode($tree, $type);
2148 0         0 foreach my $elt (@elts) {
2149 0         0 my ($v) = findval($elt, $merge_key);
2150 0         0 foreach my $cur_elt (@cur_elts) {
2151 0         0 my ($cv) = findval($cur_elt, $merge_key);
2152 0 0       0 if ($cv eq $v) {
2153             # merge
2154 0         0 my $cur_children = $cur_elt->[1];
2155 0         0 my $children = $elt->[1];
2156 0         0 push(@$cur_children,
2157             grep {
2158 0         0 $_->[0] ne $merge_key
2159             } @$children);
2160             }
2161             }0
2162 0         0 }
2163 0         0 return $tree;
2164             }
2165             *mergeElements = \&merge;
2166              
2167             sub makeattrsnodes {
2168 0     0 0 0 my $tree = shift;
2169 0 0       0 return if isterminal($tree);
2170 0         0 my @attrs = get($tree,'@');
2171 0 0       0 if (@attrs) {
2172 0         0 my @nu = ();
2173 0         0 foreach (@attrs) {
2174 0         0 push(@nu, kids($_));
2175             }
2176 0         0 unset($tree, '@');
2177 0         0 unshift(@{$tree->[1]},@nu);
  0         0  
2178             }
2179 0         0 my @subnodes = subnodes($tree);
2180 0         0 makeattrsnodes($_) foreach @subnodes;
2181 0         0 return;
2182             }
2183              
2184             sub duplicate {
2185 7     7 0 43 my $tree = shift;
2186 7         22 load_module('Data::Dumper');
2187 20     20   25704 use Data::Dumper;
  20         288748  
  20         70107  
2188 7         10 my $nu;
2189 7         53 my $d = Data::Dumper->new( [$tree], [qw($nu)] );
2190 7         247 my $dump = $d->Dump;
2191 7         1279 eval $dump;
2192 7 50       32 if ($@) {
2193 0         0 confess $@;
2194             }
2195 7         24 return stagify($nu);
2196             }
2197             *d = \&duplicate;
2198             *clone = \&duplicate;
2199              
2200             sub isastag {
2201 995     995 0 1299 my $node = shift;
2202 995   66     6633 return UNIVERSAL::isa($node, "Data::Stag::StagI") ||
2203             UNIVERSAL::isa($node, "Node");
2204             }
2205             *isanode = \&isastag;
2206             *isa_node = \&isanode;
2207             *isaNode = \&isanode;
2208              
2209             sub isnull {
2210 2     2 0 4 my $node = shift;
2211 2 50       6 if (@$node) {
2212 0         0 return 0;
2213             }
2214 2         29 return 1;
2215             }
2216              
2217             sub node {
2218 0     0 0 0 return Data::Stag::StagImpl->new(@_);
2219             }
2220             *Node = \&node;
2221              
2222             sub stagify {
2223 809     809 0 980 my $tree = shift;
2224 809 50       1599 if (!ref($tree)) {
2225             # allow static or nonstatic usage
2226 0         0 $tree = shift;
2227             }
2228 809 50       1432 confess unless ref $tree;
2229 809         919 my $class = "Data::Stag::StagImpl";
2230 809         2907 bless $tree, $class
2231             }
2232             *nodify = \&stagify;
2233             *Nodify = \&stagify;
2234              
2235             sub xpath {
2236 0     0 0 0 my $tree = shift;
2237 0         0 load_module("XML::XPath");
2238 0         0 my $xp = XML::XPath->new(xml=>xml($tree));
2239 0         0 return $xp;
2240             }
2241             *xp = \&xpath;
2242             *getxpath = \&xpath;
2243             *tree2xpath = \&xpath;
2244              
2245              
2246             sub xpquery {
2247 0     0 0 0 my $tree = shift;
2248 0         0 my @args = @_;
2249 0         0 load_module("XML::XPath::XMLParser");
2250 0         0 my $xp = $tree->getXPath;
2251 0         0 my $nodeset = $xp->find(@args);
2252 0         0 my @nodes =
2253             map {
2254 0         0 xmlstr2tree(XML::XPath::XMLParser::as_string($_));
2255             } $nodeset->get_nodelist;
2256 0         0 return @nodes;
2257             }
2258             *xpq = \&xpquery;
2259             *xpathquery = \&xpquery;
2260             *xpfind = \&xpquery;
2261             *xpFind = \&xpquery;
2262              
2263             sub grammarparser {
2264 0     0 0 0 my $tree = shift;
2265 0         0 my $grammar = shift;
2266 0         0 load_module("Parse::RecDescent");
2267 0         0 $::RD_AUTOACTION = q
2268             { use Data::Stag;
2269             $#item == 1 && !ref $item[1] ? $item[1] : Data::Stag->new(shift @item, [map {if(ref($_)) {$_} else {[arg=>$_]}} @item ]);
2270             };
2271 0 0       0 my $parser = Parse::RecDescent->new($grammar) or confess "Bad grammar!\n";
2272 0         0 return $parser;
2273             }
2274              
2275             #use overload
2276             # '.' => sub {my @r=findnodeVal($_[0],$_[1]);$r[0]},
2277             # '-' => sub {my @r=findnodeVal($_[0],$_[1]);$r[0]},
2278             # '+' => sub {my @r=$_[0]->findnode($_[1]);return $r[0]},
2279             # '/' => sub {[findnodeVal($_[0],$_[1])]},
2280             # '*' => sub {[$_[0]->findnode($_[1])]},
2281             # '<' => sub {Data::Stag::testSubTreeMatchTree($_[0], $_[1])},
2282             # qw("" stringify);
2283              
2284             #sub stringify {
2285             # $_[0];
2286             #}
2287              
2288              
2289             sub kids {
2290 679     679 0 694 my $self = shift;
2291 679 50       1477 if (@_) {
2292 0         0 @$self = ($self->[0], [map {Nodify($_)} @_]);
  0         0  
2293             }
2294 679         1066 my ($name, $kids) = @$self;
2295 679 100       1463 if (!ref($kids)) {
2296 395         797 return $kids;
2297             }
2298 284         365 return map {Nodify($_)} @$kids;
  591         998  
2299             }
2300             *k = \&kids;
2301             *children = \&kids;
2302             *getChildren = \&kids;
2303             *getKids = \&kids;
2304              
2305             sub subnodes {
2306 677     677 0 819 my $self = shift;
2307 677         1181 return grep {ref($_)} kids($self);
  984         2991  
2308             }
2309              
2310             # non-terminal nodes
2311             sub ntnodes {
2312 0     0 0 0 my $self = shift;
2313 0         0 my @subnodes = $self->subnodes;
2314 0         0 return grep {!$_->isterminal} @subnodes;
  0         0  
2315             }
2316             *nonterminalnodes = \&ntnodes;
2317             *nonterminals = \&ntnodes;
2318              
2319             # terminal nodes
2320             sub tnodes {
2321 0     0 0 0 my $self = shift;
2322 0         0 my @subnodes = $self->subnodes;
2323 0         0 return grep {$_->isterminal} @subnodes;
  0         0  
2324             }
2325             *terminalnodes = \&tnodes;
2326             *terminals = \&tnodes;
2327              
2328             sub element {
2329 859     859 0 7309 my $self = shift;
2330 859 100       1615 if (@_) {
2331 11         29 $self->[0] = shift;
2332             }
2333 859         11021 return $self->[0];
2334             }
2335             *e = \&element;
2336             *name = \&element;
2337             *tagname = \&element;
2338              
2339             sub data {
2340 169     169 0 211 my $self = shift;
2341 169 100       471 if (@_) {
2342 20         94 $self->[1] = shift;
2343             }
2344 169         422 return $self->[1];
2345             }
2346              
2347             sub rename {
2348 0     0 0 0 my $tree = shift;
2349 0         0 my $from = shift;
2350 0         0 my $to = shift;
2351 0         0 foreach (kids($tree)) {
2352 0 0       0 if ($_->[0] eq $from) {
2353 0         0 $_->[0] = $to;
2354             }
2355             }
2356 0         0 return;
2357             }
2358              
2359             sub isterminal {
2360 134     134 0 160 my $self = shift;
2361 134         400 return !ref($self->[1]);
2362             }
2363              
2364             sub _min {
2365 31     31   37 my $x = shift;
2366 31         43 my $y = shift;
2367 31 100       99 return $x if !defined($y);
2368 12 50       20 return $y if !defined($x);
2369 12 100       36 $x < $y ? $x : $y;
2370             }
2371             sub _max {
2372 31   100 31   72 my $x = shift || 0;
2373 31   100     122 my $y = shift || 0;
2374 31 100       124 $x > $y ? $x : $y;
2375             }
2376              
2377             # automatically deduces schema
2378             # FORMAT MAY CHANGE!!!!!
2379             sub autoschema {
2380 1     1 0 7 my $tree = shift;
2381 1         4 my $schema = _autoschema($tree);
2382             # use Data::Dumper;
2383             # print Dumper $schema;
2384 1         5 my $nu = genschema($tree, undef, $tree->element, $schema);
2385             $nu->iterate(sub{
2386 20     20   24 my $node = shift;
2387 20 100       42 if ($node->name =~ /(\S+)\.(\S+)/) {
2388 11         27 $node->name($2); }
2389 1         11 });
2390 1         25 return $nu;
2391             }
2392              
2393             sub dtd {
2394 0     0 0 0 my $tree = shift;
2395 0         0 my ($name, $subtree) = @$tree;
2396 0   0     0 my $done_h = shift || {};
2397 0         0 $name =~ s/[\+\?\*]$//;
2398 0 0       0 return '' if $done_h->{$name};
2399 0         0 my $is_nt = ref($subtree);
2400 0         0 my $s;
2401 0 0       0 if ($is_nt) {
2402 0         0 my $s2 = join('', map {dtd($_,$done_h)} @$subtree);
  0         0  
2403 0         0 my @subnames = map {$_->[0]} @$subtree;
  0         0  
2404 0         0 my $S = join('|',@subnames);
2405 0 0       0 if (@subnames < 2) {
2406 0         0 $S = "@subnames";
2407 0 0       0 if (!@subnames) {
2408 0         0 $S = 'EMPTY';
2409             }
2410             }
2411              
2412 0         0 $s = "\n\n\n$s2";
2413             }
2414             else {
2415 0         0 $s = "\n\n\n";
2416             }
2417 0         0 $done_h->{$name} = 1;
2418 0         0 $s;
2419             }
2420              
2421             sub genschema {
2422 20     20 0 29 my $tree = shift;
2423 20         26 my $parent = shift;
2424 20         24 my $root = shift;
2425 20         24 my $schema = shift;
2426 20   100     47 my $path = shift || [];
2427              
2428 20         28 my $cycle = 0;
2429 20 50       32 if (grep {$_ eq $root} @$path) {
  190         334  
2430 0         0 $cycle = 1;
2431 0         0 warn "cycle detected: @$path $root";
2432 0         0 return (Data::Stag->new($root=>[]));
2433             }
2434 20         40 push(@$path, $root);
2435              
2436 20         31 my $data = $schema->{data};
2437 20         30 my $childh = $schema->{childh};
2438              
2439 20         28 my $card = "";
2440 20 100       41 if ($parent) {
2441 19         44 my $link = "$parent $root";
2442 19         31 my $min = $schema->{mincard}->{$link};
2443 19         33 my $max = $schema->{maxcard}->{$link};
2444 19 50       38 die if !$max;
2445 19 100       34 if ($min == 0) {
2446 2 100       6 if ($max == 1) {
2447 1         2 $card = '?';
2448             }
2449             else {
2450 1         3 $card = '*';
2451             }
2452             }
2453             else {
2454             # $min >= 1
2455 17 100       30 if ($max == 1) {
2456 13         24 $card = '';
2457             }
2458             else {
2459 4         10 $card = '+';
2460             }
2461             }
2462             }
2463 20         96 my $ss = Data::Stag->new($root.$card=>[]);
2464 20 100       92 if ($data->{$root}) {
2465 11         28 $ss->data($data->{$root});
2466             }
2467             else {
2468 9         14 my $c = $childh->{$root};
2469 19         52 my @sn =
2470             map {
2471 9         17 genschema($tree, $root, $_, $schema, $path);
2472             } @$c;
2473 9         29 $ss->data([@sn]);
2474             }
2475 20         65 return $ss;
2476             }
2477              
2478             # automatically deduces schema
2479             sub _autoschema {
2480 13     13   17 my $tree = shift;
2481 13   100     43 my $schema = shift || {data=>{},
2482             childh=>{},
2483             mincard=>{},
2484             maxcard=>{},
2485             };
2486 13         22 my $data = $schema->{data};
2487 13         17 my $childh = $schema->{childh};
2488 13         19 my $mincard = $schema->{mincard};
2489 13         17 my $maxcard = $schema->{maxcard};
2490              
2491 13         32 my $elt = $tree->element;
2492 13         30 my @sn = $tree->subnodes;
2493             # CARD: default(blank) : 1
2494             # + : 1 or more
2495             # * : 0 or more
2496             # ? : 0 or one
2497 13         25 my %lcard = (); # local cardinality
2498 13         23 foreach (@sn) {
2499             # nonterminal nodes are uniquely defined
2500             # by the node name
2501 36         57 my $se = element($_);
2502 36 100       62 if (isterminal($_)) {
2503             # a terminal node is uniquely defined
2504             # by parent.node
2505 24         46 $se = "$elt.$se";
2506             }
2507 19         51 push(@{$childh->{$elt}}, $se)
  67         186  
2508             unless $childh->{$elt} &&
2509 36 100 100     110 grep { $_ eq $se } @{$childh->{$elt}};
  27         50  
2510 36 100       113 $lcard{$se} = 0 unless $lcard{$se};
2511 36         74 $lcard{$se}++;
2512             }
2513             # foreach (keys %lcard) {
2514 13         20 foreach (@{$childh->{$elt}}) {
  13         29  
2515 31         62 my $link = "$elt $_";
2516             # print "$link :: $lcard{$_}\n";
2517 31   100     121 $mincard->{$link} =
2518             _min($lcard{$_} || 0,
2519             $mincard->{$link});
2520 31         100 $maxcard->{$link} =
2521             _max($lcard{$_},
2522             $maxcard->{$link});
2523             }
2524 13         24 foreach (grep {isterminal($_)} @sn) {
  36         51  
2525 24         48 my $elt = $elt.'.'.element($_);
2526             # push(@{$data->{$elt}}, $_->data);
2527 24         55 my $in = $_->data;
2528 24   100     133 my $d = $data->{$elt} || 'INT';
2529 24 50       118 if (!$in) {
    100          
    50          
2530             }
2531             elsif ($in =~ /^\-?\d+$/) { # LOOKS LIKE INT
2532             # CHANGE SIZE IF IT IS
2533 6 50       16 if ($d eq 'INT') {
2534 6         7 $d = 'INT';
2535 6         8 my $lin = length($in);
2536 6 50       19 if ($lin > 10) {
2537             # too big for an int
2538             # TODO: largeint?
2539 0         0 $d = _mkvarchar($lin);
2540             }
2541             }
2542             }
2543             elsif ($in =~ /^\-?\d*\.\d+$/) { # LOOKS LIKE FLOAT
2544             # PROMOTE TO FLOAT IF INT
2545 0 0       0 if ($d eq 'INT') {
2546 0         0 $d = 'FLOAT';
2547             }
2548             }
2549             else {
2550 18   50     43 my $lin = length($in) || 0;
2551 18 100       61 if ($d =~ /VARCHAR\((\d+)\)/) {
    50          
2552 10 50       34 if ($lin > $1) {
2553 0         0 $d = _mkvarchar($lin);
2554             }
2555             else {
2556             }
2557             }
2558             elsif ($d =~ /TEXT/) {
2559             }
2560             else {
2561 8         17 $d = _mkvarchar($lin);
2562             }
2563             }
2564 24         70 $data->{$elt} = $d;
2565             }
2566 13         20 foreach (grep {!isterminal($_)} @sn) {
  36         52  
2567 12         41 _autoschema($_, $schema);
2568             }
2569 13         43 return $schema;
2570             }
2571              
2572             sub _mkvarchar {
2573 8   50 8   17 my $size = shift || 1;
2574             # round up to log2
2575 8         33 my $s2 = 2**(int(log($size) / log(2))+2) -1;
2576 8 50       20 if ($s2 > 255) {
2577 0         0 return 'TEXT';
2578             }
2579 8         25 return "VARCHAR($s2)";
2580             }
2581              
2582             sub AUTOLOAD {
2583 765     765   4114 my $self = shift;
2584 765         1038 my @args = @_;
2585              
2586 765         887 my $name = $AUTOLOAD;
2587 765         2793 $name =~ s/.*://; # strip fully-qualified portion
2588              
2589 765 50       1722 if ($name eq "DESTROY") {
2590             # we dont want to propagate this!!
2591 0         0 return;
2592             }
2593              
2594 765 50       2570 if ($name =~ /^([a-zA-Z]+)_(\w+)/) {
2595 765 50       2961 if ($self->can($1)) {
2596 765         1971 return $self->$1($2, @args);
2597             }
2598             }
2599 0         0 confess("no such method:$name");
2600             }
2601              
2602             # --MISC--
2603              
2604             sub splitpath {
2605 1762     1762 0 2516 my $node = shift;
2606 1762 100 66     6078 if (ref($node) && ref($node) eq 'ARRAY') {
    100          
2607 36         94 @$node;
2608             }
2609             elsif ($node =~ /\//) {
2610 21         108 (split(/\//, $node));
2611             }
2612             else {
2613 1705         3886 ($node);
2614             }
2615             }
2616              
2617             sub test_eq {
2618 3132     3132 0 4289 my ($ev, $node) = @_;
2619 3132 50       5418 $ev = '' unless defined $ev;
2620 3132 50       5637 $node = '' unless defined $node;
2621 3132   66     15934 return $ev eq $node || $node eq '*';
2622             }
2623              
2624             1;
2625