File Coverage

lib/XML/Parser/Lite/Tree/XPath/Tree.pm
Criterion Covered Total %
statement 345 397 86.9
branch 96 130 73.8
condition 10 12 83.3
subroutine 20 22 90.9
pod 0 20 0.0
total 471 581 81.0


line stmt bran cond sub pod time code
1             package XML::Parser::Lite::Tree::XPath::Tree;
2              
3 30     30   807 use strict;
  30         55  
  30         1283  
4 30     30   166 use XML::Parser::Lite::Tree::XPath::Tokener;
  30         95  
  30         130845  
5              
6             sub new {
7 179     179 0 331 my ($class) = @_;
8 179         703 my $self = bless {}, $class;
9 179         580 $self->{error} = 0;
10 179         8564 return $self;
11             }
12              
13             sub build_tree {
14 179     179 0 297 my ($self, $tokens) = @_;
15              
16 179         324 $self->{error} = 0;
17 179         311 $self->{tokens} = $tokens;
18              
19             #
20             # build a basic tree using the brackets
21             #
22              
23 179 50       683 return 0 unless $self->make_groups();
24 179         519 $self->recurse_before($self, 'del_links');
25              
26              
27             #
28             # simple groupings
29             #
30              
31 179 50       464 return 0 unless $self->recurse_before($self, 'clean_axis_and_abbreviations');
32 179 100       431 return 0 unless $self->recurse_before($self, 'claim_groups');
33 176 50       556 return 0 unless $self->recurse_after($self, 'build_steps');
34 176 50       507 return 0 unless $self->recurse_after($self, 'build_paths');
35              
36              
37             #
38             # get operator oprands
39             #
40              
41 176 50       671 return 0 unless $self->binops(['|'], 'UnionExpr');
42 176 50       577 return 0 unless $self->recurse_before($self, 'unary_minus');
43 176 50       754 return 0 unless $self->binops(['*','div','mod'], 'MultiplicativeExpr');
44 176 50       702 return 0 unless $self->binops(['+','-'], 'AdditiveExpr');
45 176 50       757 return 0 unless $self->binops(['<','<=','>','>='], 'RelationalExpr');
46 176 50       682 return 0 unless $self->binops(['=','!='], 'EqualityExpr');
47 176 50       652 return 0 unless $self->binops(['and'], 'AndExpr');
48 176 50       748 return 0 unless $self->binops(['or'], 'OrExpr');
49              
50             #return 0 unless $self->find_expressions(['UnionExpr', 'MultiplicativeExpr', 'AdditiveExpr', 'RelationalExpr', 'EqualityExpr', 'AndExpr', 'OrExpr']);
51              
52              
53 176         825 return 1;
54             }
55              
56             sub dump_flat {
57 41     41 0 61 my ($self) = @_;
58 41         109 $self->{dump} = '';
59              
60 41         50 for my $token(@{$self->{tokens}}){
  41         100  
61 41         114 $self->dump_flat_go($token);
62             }
63              
64 41         1847 my $dump = $self->{dump};
65 41         84 delete $self->{dump};
66 41         115 return $dump;
67             }
68              
69             sub dump_flat_go {
70 279     279 0 353 my ($self, $node) = @_;
71              
72 279         728 $self->{dump} .= '['.$node->dump();
73              
74 279         340 for my $token(@{$node->{tokens}}){
  279         558  
75              
76 238         459 $self->dump_flat_go($token);
77             }
78              
79 279         655 $self->{dump} .= ']';
80             }
81              
82             sub dump_tree {
83 0     0 0 0 my ($self) = @_;
84 0         0 $self->{dump} = '';
85 0         0 $self->{indent} = [''];
86              
87 0         0 for my $token(@{$self->{tokens}}){
  0         0  
88 0         0 $self->dump_tree_go($token);
89             }
90              
91 0         0 my $dump = $self->{dump};
92 0         0 delete $self->{dump};
93 0         0 delete $self->{indent};
94 0         0 return $dump;
95             }
96              
97             sub dump_tree_go {
98 0     0 0 0 my ($self, $node) = @_;
99              
100 0         0 $self->{dump} .= @{$self->{indent}}[-1].$node->dump()."\n";
  0         0  
101              
102 0         0 push @{$self->{indent}}, @{$self->{indent}}[-1].' - ';
  0         0  
  0         0  
103              
104 0         0 for my $token(@{$node->{tokens}}){
  0         0  
105              
106 0         0 $self->dump_tree_go($token);
107             }
108              
109 0         0 pop @{$self->{indent}};
  0         0  
110             }
111              
112             sub make_groups {
113 179     179 0 250 my ($self) = @_;
114              
115 179         306 my $tokens = $self->{tokens};
116 179         364 $self->{tokens} = [];
117              
118 179         270 my $parent = $self;
119              
120 179         238 for my $token(@{$tokens}){
  179         348  
121              
122 1223 100       2741 if ($token->match('Symbol', '(')){
    100          
    100          
    100          
123              
124 112         332 my $group = XML::Parser::Lite::Tree::XPath::Token->new();
125 112         325 $group->{type} = 'Group()';
126 112         205 $group->{tokens} = [];
127 112         184 $group->{parent} = $parent;
128              
129 112         124 push @{$parent->{tokens}}, $group;
  112         266  
130 112         193 $parent = $group;
131              
132             }elsif ($token->match('Symbol', '[')){
133              
134 50         206 my $group = XML::Parser::Lite::Tree::XPath::Token->new();
135 50         154 $group->{type} = 'Predicate';
136 50         117 $group->{tokens} = [];
137 50         84 $group->{parent} = $parent;
138              
139 50         68 push @{$parent->{tokens}}, $group;
  50         98  
140 50         100 $parent = $group;
141              
142             }elsif ($token->match('Symbol', ')')){
143              
144 112 50       336 if ($parent->{type} ne 'Group()'){
145 0         0 $self->{error} = "Found unexpected closing bracket ')'.";
146 0         0 return 0;
147             }
148              
149 112         258 $parent = $parent->{parent};
150              
151             }elsif ($token->match('Symbol', ']')){
152              
153 50 50       153 if ($parent->{type} ne 'Predicate'){
154 0         0 $self->{error} = "Found unexpected closing bracket ']'.";
155 0         0 return 0;
156             }
157              
158 50         225 $parent = $parent->{parent};
159              
160             }else{
161 899         1548 $token->{parent} = $parent;
162 899         1005 push @{$parent->{tokens}}, $token;
  899         2211  
163             }
164             }
165              
166 179         590 return 1;
167             }
168              
169             sub recurse_before {
170 4300     4300 0 5646 my ($self, $root, $method) = @_;
171              
172 4300 100       9425 return 0 unless $self->$method($root);
173              
174 4118         4439 for my $token(@{$root->{tokens}}){
  4118         7542  
175              
176 3587 50       6989 return 0 unless $self->recurse_before($token, $method);
177             }
178              
179 4118         11199 return 1;
180             }
181              
182             sub recurse_after {
183 12633     12633 0 18184 my ($self, $root, $method) = @_;
184              
185 12633         12434 for my $token(@{$root->{tokens}}){
  12633         21298  
186              
187 11049 50       19868 return 0 unless $self->recurse_after($token, $method);
188             }
189              
190 12633 50       28163 return 0 unless $self->$method($root);
191              
192 12633         34650 return 1;
193             }
194              
195             sub binops {
196 1232     1232 0 1725 my ($self, $ops, $production) = @_;
197 1232         1913 $self->{binops} = $ops;
198 1232         1812 $self->{binop_production} = $production;
199              
200 1232         2239 my $ret = $self->recurse_after($self, 'do_binops');
201              
202 1232         2023 delete $self->{binops};
203 1232         1807 delete $self->{binop_production};
204              
205 1232         3688 return $ret;
206             }
207              
208             sub claim_groups {
209 1443     1443 0 1695 my ($self, $root) = @_;
210              
211 1443         1839 my $tokens = $root->{tokens};
212 1443         2122 $root->{tokens} = [];
213              
214 1443         1562 while(my $token = shift @{$tokens}){
  2802         6464  
215              
216              
217             #
218             # makes claims
219             #
220              
221 1362 100       3088 if ($token->match('NodeType')){
    100          
    50          
222              
223             # node type's claim the follow group node
224              
225 6         6 my $next = shift @{$tokens};
  6         13  
226              
227 6 50       14 if (!$next->match('Group()')){
228 0         0 $self->{error} = "Found NodeType '$token->{content}' without a following '(' (found a following '$next->{type}').";
229 0         0 return 0;
230             }
231              
232 6         8 my $childs = scalar(@{$next->{tokens}});
  6         15  
233              
234 6 100       16 if ($token->{content} eq 'processing-instruction'){
235              
236 4 100       12 if ($childs == 0){
    100          
237              
238             #ok
239              
240             }elsif ($childs == 1){
241              
242 2 100       9 if ($next->{tokens}->[0]->{type} eq 'Literal'){
243              
244 1         4 $token->{argument} = $next->{tokens}->[0]->{content};
245              
246             }else{
247 1         4 $self->{error} = "processing-instruction node has a non-Literal child node (of type '$next->{tokens}->[0]->{type}').";
248 1         7 return 0;
249             }
250             }else{
251 1         3 $self->{error} = "processing-instruction node has more than one child node.";
252 1         10 return 0;
253             }
254              
255             }else{
256 2 100       6 if ($childs > 0){
257 1         6 $self->{error} = "NodeType $token->{content} node has unexpected children.";
258 1         8 return 0;
259             }
260             }
261              
262 3         7 $token->{type} = 'NodeTypeTest';
263 3         5 push @{$root->{tokens}}, $token;
  3         18  
264              
265             }elsif ($token->match('FunctionName')){
266              
267             # FunctionNames's claim the follow group node - it should be an arglist
268              
269 106         136 my $next = shift @{$tokens};
  106         190  
270              
271 106 50       268 if (!$next->match('Group()')){
272 0         0 $self->{error} = "Found FunctionName '$token->{content}' without a following '(' (found a following '$next->{type}').";
273 0         0 return 0;
274             }
275              
276             #
277             # recurse manually - this node will never be scanned by this loop
278             #
279              
280 106 50       327 return 0 unless $self->claim_groups($next);
281              
282              
283             #
284             # organise it into an arg list
285             #
286              
287 106 50       279 return 0 unless $self->make_arg_list($token, $next);
288            
289              
290              
291 106         126 push @{$root->{tokens}}, $token;
  106         398  
292              
293              
294             }elsif ($token->match('Group()')){
295              
296 0         0 $token->{type} = 'PrimaryExpr';
297              
298 0         0 push @{$root->{tokens}}, $token;
  0         0  
299              
300             }else{
301              
302 1250         1351 push @{$root->{tokens}}, $token;
  1250         2947  
303             }
304              
305             }
306              
307 1440         3488 return 1;
308             }
309              
310             sub make_arg_list {
311 106     106 0 158 my ($self, $root, $arg_group) = @_;
312              
313 106         163 $root->{type} = 'FunctionCall';
314 106         711 $root->{tokens} = [];
315              
316             # no need to construct an arg list if there aren't any args
317 106 100       138 return 1 unless scalar @{$arg_group->{tokens}};
  106         324  
318              
319 71         239 my $arg = XML::Parser::Lite::Tree::XPath::Token->new();
320 71         171 $arg->{type} = 'FunctionArg';
321 71         143 $arg->{tokens} = [];
322              
323 71         113 while(my $token = shift @{$arg_group->{tokens}}){
  272         680  
324              
325 201 100       506 if ($token->match('Symbol', ',')){
326              
327 25         42 push @{$root->{tokens}}, $arg;
  25         58  
328              
329 25         83 $arg = XML::Parser::Lite::Tree::XPath::Token->new();
330 25         55 $arg->{type} = 'FunctionArg';
331 25         57 $arg->{tokens} = [];
332              
333             }else{
334              
335 176         277 $token->{parent} = $arg;
336 176         179 push @{$arg->{tokens}}, $token;
  176         369  
337             }
338             }
339              
340 71         110 $arg->{parent} = $root;
341 71         95 push @{$root->{tokens}}, $arg;
  71         131  
342            
343              
344 71         180 return 1;
345             }
346              
347             sub clean_axis_and_abbreviations {
348              
349 1386     1386 0 1604 my ($self, $root) = @_;
350              
351 1386         1774 my $tokens = $root->{tokens};
352 1386         2213 $root->{tokens} = [];
353              
354 1386         1552 while(my $token = shift @{$tokens}){
  2408         6588  
355              
356 1022 100       2404 if ($token->match('AxisName')){
    100          
    100          
    100          
    100          
357              
358 39         55 my $next = shift @{$tokens};
  39         76  
359              
360 39 50       125 unless ($next->match('Symbol', '::')){
361              
362 0         0 $self->{error} = "Found an AxisName '$token->{content}' without a following ::";
363 0         0 return 0;
364             }
365              
366 39         81 $token->{type} = 'AxisSpecifier';
367              
368 39         55 push @{$root->{tokens}}, $token;
  39         127  
369              
370              
371             }elsif ($token->match('Symbol', '@')){
372              
373 15         36 $token->{type} = 'AxisSpecifier';
374 15         28 $token->{content} = 'attribute';
375              
376 15         22 push @{$root->{tokens}}, $token;
  15         99  
377              
378              
379             }elsif ($token->match('Operator', '//')){
380              
381             # // == /descendant-or-self::node()/
382              
383 61         214 $token = XML::Parser::Lite::Tree::XPath::Token->new();
384 61         185 $token->{type} = 'Operator';
385 61         131 $token->{content} = '/';
386 61         96 push @{$root->{tokens}}, $token;
  61         152  
387              
388 61         214 $token = XML::Parser::Lite::Tree::XPath::Token->new();
389 61         160 $token->{type} = 'AxisSpecifier';
390 61         132 $token->{content} = 'descendant-or-self';
391 61         96 push @{$root->{tokens}}, $token;
  61         123  
392              
393 61         223 $token = XML::Parser::Lite::Tree::XPath::Token->new();
394 61         156 $token->{type} = 'NodeTypeTest';
395 61         132 $token->{content} = 'node';
396 61         285 push @{$root->{tokens}}, $token;
  61         125  
397              
398 61         204 $token = XML::Parser::Lite::Tree::XPath::Token->new();
399 61         150 $token->{type} = 'Operator';
400 61         112 $token->{content} = '/';
401 61         90 push @{$root->{tokens}}, $token;
  61         172  
402              
403              
404             }elsif ($token->match('Symbol', '.')){
405              
406 1         5 $token = XML::Parser::Lite::Tree::XPath::Token->new();
407 1         3 $token->{type} = 'AxisSpecifier';
408 1         3 $token->{content} = 'self';
409 1         2 push @{$root->{tokens}}, $token;
  1         3  
410              
411 1         5 $token = XML::Parser::Lite::Tree::XPath::Token->new();
412 1         3 $token->{type} = 'NodeTypeTest';
413 1         2 $token->{content} = 'node';
414 1         2 push @{$root->{tokens}}, $token;
  1         3  
415              
416              
417             }elsif ($token->match('Symbol', '..')){
418              
419 1         4 $token = XML::Parser::Lite::Tree::XPath::Token->new();
420 1         3 $token->{type} = 'AxisSpecifier';
421 1         3 $token->{content} = 'parent';
422 1         2 push @{$root->{tokens}}, $token;
  1         3  
423              
424 1         5 $token = XML::Parser::Lite::Tree::XPath::Token->new();
425 1         3 $token->{type} = 'NodeTypeTest';
426 1         2 $token->{content} = 'node';
427 1         2 push @{$root->{tokens}}, $token;
  1         4  
428              
429              
430             }else{
431              
432 905         1047 push @{$root->{tokens}}, $token;
  905         2266  
433             }
434             }
435              
436 1386         3454 return 1;
437             }
438              
439             sub build_steps {
440 1334     1334 0 1581 my ($self, $root) = @_;
441              
442 1334         6673 my $tokens = $root->{tokens};
443 1334         1916 $root->{tokens} = [];
444              
445 1334         1609 while(my $token = shift @{$tokens}){
  2325         5529  
446              
447 991 100 100     2438 if ($token->match('AxisSpecifier')){
    100          
    50          
448              
449 117         164 my $next = shift @{$tokens};
  117         210  
450              
451 117 50       311 unless (defined $next){
452              
453 0         0 $self->{error} = "AxisSpecifier found without following NodeTest.";
454 0         0 return 0;
455             }
456              
457 117 50 66     439 unless ($next->match('NodeTypeTest') || $next->match('NameTest')){
458              
459 0         0 $self->{error} = "AxisSpecifier found without following NodeTest (NodeTypeTest | NameTest) (found $next->{type} instead).";
460 0         0 return 0;
461             }
462              
463 117         402 my $step = XML::Parser::Lite::Tree::XPath::Token->new();
464 117         317 $step->{type} = 'Step';
465 117         338 $step->{axis} = $token->{content};
466 117         239 $step->{tokens} = [];
467              
468 117         184 push @{$step->{tokens}}, $next;
  117         254  
469              
470              
471 117         176 while(my $token = shift @{$tokens}){
  117         358  
472              
473 73 50       228 if ($token->match('Predicate')){
474              
475 0         0 push @{$step->{tokens}}, $token;
  0         0  
476             }else{
477 73         101 unshift @{$tokens}, $token;
  73         139  
478 73         158 last;
479             }
480             }
481              
482 117         164 push @{$root->{tokens}}, $step;
  117         483  
483              
484              
485             }elsif ($token->match('NodeTypeTest') || $token->match('NameTest')){
486              
487 179         554 my $step = XML::Parser::Lite::Tree::XPath::Token->new();
488 179         447 $step->{type} = 'Step';
489 179         336 $step->{tokens} = [];
490              
491 179         243 push @{$step->{tokens}}, $token;
  179         381  
492              
493              
494 179         246 while(my $token = shift @{$tokens}){
  229         626  
495              
496 134 100       367 if ($token->match('Predicate')){
497              
498 50         70 push @{$step->{tokens}}, $token;
  50         137  
499             }else{
500 84         111 unshift @{$tokens}, $token;
  84         151  
501 84         162 last;
502             }
503             }
504              
505 179         254 push @{$root->{tokens}}, $step;
  179         525  
506              
507              
508             }elsif ($token->match('Predicate')){
509              
510 0         0 $self->{error} = "Predicate found without preceeding NodeTest.";
511 0         0 return 0;
512              
513             }else{
514              
515 695         797 push @{$root->{tokens}}, $token;
  695         1781  
516             }
517             }
518              
519 1334         3294 return 1;
520             }
521              
522             sub build_paths {
523 1513     1513 0 1831 my ($self, $root) = @_;
524              
525 1513         2007 my $tokens = $root->{tokens};
526 1513         2496 $root->{tokens} = [];
527              
528 1513         1622 while(my $token = shift @{$tokens}){
  2439         5647  
529              
530 926 100       2326 if ($token->match('Step')){
    100          
531              
532 25         90 my $path = XML::Parser::Lite::Tree::XPath::Token->new();
533 25         64 $path->{type} = 'LocationPath';
534 25         55 $path->{absolute} = 0;
535 25         62 $path->{tokens} = [$token];
536              
537 25 50       88 return 0 unless $self->slurp_path($path, $tokens);
538              
539 25         38 push @{$root->{tokens}}, $path;
  25         72  
540              
541             }elsif ($token->match('Operator', '/')){
542              
543 131         172 unshift @{$tokens}, $token;
  131         254  
544              
545 131         666 my $path = XML::Parser::Lite::Tree::XPath::Token->new();
546 131         346 $path->{type} = 'LocationPath';
547 131         280 $path->{absolute} = 1;
548 131         272 $path->{tokens} = [];
549              
550 131 50       1288 return 0 unless $self->slurp_path($path, $tokens);
551              
552 131 50       176 unless (scalar @{$path->{tokens}}){
  131         379  
553 0         0 $self->{error} = "Slash found at end of path.";
554 0         0 return 0;
555             }
556              
557 131         181 push @{$root->{tokens}}, $path;
  131         454  
558              
559             }else{
560              
561 770         819 push @{$root->{tokens}}, $token;
  770         1838  
562             }
563             }
564              
565 1513         3873 return 1;
566             }
567              
568             sub slurp_path {
569 156     156 0 248 my ($self, $path, $tokens) = @_;
570              
571 156         202 while(1){
572              
573 427         530 my $t1 = shift @{$tokens};
  427         697  
574              
575 427 100       807 if (defined $t1){
576 288 100       721 if ($t1->match('Operator', '/')){
577              
578 271         302 my $t2 = shift @{$tokens};
  271         441  
579              
580 271 50       642 if (defined $t2){
581 271 50       1038 if ($t2->match('Step')){
582              
583 271         313 push @{$path->{tokens}}, $t2;
  271         890  
584             }else{
585 0         0 $self->{error} = "Non Step token ($t2->{type}) found after slash.";
586 0         0 return 0;
587             }
588             }else{
589 0         0 $self->{error} = "Slash found at end of path.";
590 0         0 return 0;
591             }
592             }else{
593 17         25 unshift @{$tokens}, $t1;
  17         29  
594 17         59 return 1;
595             }
596             }else{
597 139         542 return 1;
598             }
599             }
600             }
601              
602             sub do_binops {
603 9786     9786 0 11484 my ($self, $root) = @_;
604              
605 9786         12923 my $tokens = $root->{tokens};
606 9786         13852 $root->{tokens} = [];
607              
608 9786         10909 while(my $token = shift @{$tokens}){
  18264         41520  
609              
610              
611 8478         8988 for my $op(@{$self->{binops}}){
  8478         13547  
612            
613 16895 100       38032 if ($token->match('Operator', $op)){
614              
615 76 50       92 if (!scalar(@{$root->{tokens}})){
  76         212  
616 0         0 $self->{error} = "Found a binop $token->{content} with no preceeding token";
617 0         0 return 0;
618             }
619              
620 76 50       89 if (!scalar(@{$tokens})){
  76         184  
621 0         0 $self->{error} = "Found a binop $token->{content} with no following token";
622 0         0 return 0;
623             }
624              
625 76         86 my $prev = pop @{$root->{tokens}};
  76         142  
626 76         89 my $next = shift @{$tokens};
  76         155  
627              
628 76         95 push @{$token->{tokens}}, $prev;
  76         175  
629 76         87 push @{$token->{tokens}}, $next;
  76         115  
630 76         136 $token->{type} = $self->{binop_production};
631              
632 76         131 last;
633             }
634             }
635              
636 8478         11327 push @{$root->{tokens}}, $token;
  8478         18017  
637             }
638              
639 9786         24057 return 1;
640             }
641              
642             sub add_links {
643 1398     1398 0 1630 my ($self, $root) = @_;
644              
645 1398         1486 my $prev = undef;
646              
647 1398         1375 for my $token(@{$root->{tokens}}){
  1398         2982  
648              
649 1230         1680 $token->{prev} = $prev;
650 1230 100       2335 $prev->{next} = $token if defined $prev;
651              
652 1230         2560 $prev = $token;
653             }
654             }
655              
656             sub del_links {
657 179     179 0 251 my ($self, $root) = @_;
658              
659 179         249 for my $token(@{$root->{tokens}}){
  179         441  
660              
661 683         907 delete $token->{parent};
662 683         794 delete $token->{prev};
663 683         1357 delete $token->{next};
664             }
665             }
666              
667             sub unary_minus {
668 1398     1398 0 1696 my ($self, $root) = @_;
669              
670 1398         2848 $self->add_links($root);
671              
672 1398         2160 my $tokens = $root->{tokens};
673 1398         2057 $root->{tokens} = [];
674              
675 1398         1565 while(my $token = shift @{$tokens}){
  2620         6260  
676              
677 1222 100       2948 if ($token->match('Operator', '-')){
678              
679 13 100 66     80 if (defined($token->{next}) && defined($token->{prev}) && $token->{prev}->is_expression){
      100        
680              
681             # not unary
682             }else{
683             # unary minus
684              
685 8         13 $token->{type} = 'UnaryExpr';
686 8         9 push @{$token->{tokens}}, shift @{$tokens};
  8         11  
  8         16  
687             }
688             }
689              
690 1222         1445 push @{$root->{tokens}}, $token;
  1222         2654  
691             }
692              
693 1398         3517 return 1;
694             }
695              
696             1;