File Coverage

xfdxpath.yp
Criterion Covered Total %
statement 64 164 39.0
branch 18 70 25.7
condition 6 13 46.1
subroutine 12 59 20.3
pod 0 4 0.0
total 100 310 32.2


line stmt bran cond sub pod time code
1             ## Grammar by James Clark. Funky Perl by yours truely.
2             #
3             # Grammar copied from a message on the xsl-list:
4             # Subject: Re: New XSLT draft
5             # From: James Clark
6             # Date: Mon, 12 Jul 1999 09:03:55 +0700
7              
8              
9             %{
10 1     1   4 use Carp;
  1         2  
  1         52  
11 1     1   914 use UNIVERSAL;
  1         13  
  1         5  
12 1     1   1426 use XML::Filter::Dispatcher::Ops;
  1         2  
  1         14  
13              
14             sub _no {
15 0     0   0 my $p = shift;
16             # push @{$p->{USER}->{NONONO}}, join(
17             die join(
18             "",
19             "XPath construct not supported: ",
20             join( " ", map
21             defined $_
22             ? ref $_
23 0 0       0 ? do {
    0          
24 0         0 my $f = ref $_;
25 0         0 $f =~ s/^XFD:://;
26 0         0 $f;
27             }
28             : $_
29             : "" ,
30             @_
31             ),
32             " (grammar rule at ",
33             (caller)[1],
34             ", line ",
35             (caller)[2],
36             ")"
37             );
38              
39 0         0 return ();
40             }
41              
42             sub _step {
43 5     5   1253 my @ops = grep $_, @_;
44              
45 5         190 for ( 0..$#ops-1 ) {
46 5         391 $ops[$_]->set_next( $ops[$_+1] );
47             }
48            
49 5         196 return $ops[0];
50             }
51             %}
52              
53             %token QNAME
54 5     5 0 11 %token NAME_COLON_STAR
55 5 50       13 %token DOT
56             %token DOT_DOT
57             %token AT
58             %token AXIS_NAME
59             %token FUNCTION_NAME
60             %token COMMENT
61             %token PI
62             %token TEXT
63             %token NODE
64             %token STAR
65             %token LPAR
66             %token RPAR
67             %token LSQB
68             %token RSQB
69             %token LITERAL
70             %token NUMBER
71             %token COLON_COLON
72             %token DOLLAR_QNAME
73             %token SLASH
74             %token SLASH_SLASH
75             %token VBAR
76             %token COMMA
77             %token PLUS
78             %token MINUS
79             %token EQUALS
80             %token GT
81             %token LT
82             %token GTE
83             %token LTE
84             %token MULTIPLY
85             %token AND
86             %token OR
87             %token MOD
88             %token DIV
89             # %token QUO
90              
91             ## We also catch some Perl tokens so we can give useful advice
92             %token EQUALS_EQUALS
93             %token VBAR_VBAR
94             %token AMP_AMP
95              
96             %%
97              
98             ## NOTE: I use the paren-less format for Perl subcalls here so that
99             ## perl will warn me if I don't have one defined.
100              
101             expr :
102             or_expr
103             ;
104              
105             or_expr :
106             and_expr
107 0     0   0 | or_expr OR and_expr { XFD::Operator::or->new( @_[1,3] ) }
108             | or_expr VBAR_VBAR and_expr {
109 0     0   0 die "XPath uses 'or' instead of Perl's '||'\n";
110             }
111             ;
112              
113             and_expr :
114             equality_expr
115 0     0   0 | and_expr AND equality_expr { XFD::Operator::and->new( @_[1,3] ) }
116             | and_expr AMP_AMP equality_expr {
117 0     0   0 die "XPath uses 'and' instead of Perl's '&&'\n";
118             }
119             | and_expr AMP equality_expr {
120 0     0   0 die "XPath uses 'and' instead of Perl's '&'\n";
121             }
122             ;
123              
124             equality_expr :
125             relational_expr
126 0     0   0 | equality_expr EQUALS relational_expr { XFD::relational_op equals => @_[1,3] }
127 0     0   0 | equality_expr BANG_EQUALS relational_expr { XFD::relational_op not_equals => @_[1,3] }
128             | equality_expr EQUALS_EQUALS relational_expr {
129 0     0   0 die "XPath uses '=' instead of Perl's '=='\n";
130             }
131             ;
132              
133             relational_expr :
134             additive_expr
135 0     0   0 | relational_expr LT additive_expr { XFD::relational_op lt => @_[1,3] }
136 0     0   0 | relational_expr GT additive_expr { XFD::relational_op gt => @_[1,3] }
137 0     0   0 | relational_expr LTE additive_expr { XFD::relational_op lte => @_[1,3] }
138 0     0   0 | relational_expr GTE additive_expr { XFD::relational_op gte => @_[1,3] }
139             ;
140              
141             additive_expr :
142             multiplicative_expr
143 0     0   0 | additive_expr PLUS multiplicative_expr { XFD::math_op addition => @_[1,3] }
144 0     0   0 | additive_expr MINUS multiplicative_expr { XFD::math_op subtraction => @_[1,3] }
145             ;
146              
147             multiplicative_expr :
148             unary_expr
149 0     0   0 | multiplicative_expr MULTIPLY unary_expr { XFD::math_op multiplication => @_[1,3] }
150 0     0   0 | multiplicative_expr DIV unary_expr { XFD::math_op division => @_[1,3] }
151 0     0   0 | multiplicative_expr MOD unary_expr { XFD::math_op modulus => @_[1,3] }
152             ;
153              
154             unary_expr :
155             union_expr
156 0     0   0 | MINUS unary_expr { XFD::Negation->new( $_[2] ) }
157             ;
158              
159             union_expr :
160             path_expr
161             | union_expr VBAR path_expr {
162 0     0   0 for ( $_[1], $_[3] ) {
163 0 0       0 next if $_->can( "set_next" );
164 0         0 $_ = ref $_;
165 0         0 s/^XFD:://;
166 0         0 die "Can't use a $_ in a union, perhaps you want || instead of |\n";
167             }
168              
169 0         0 my $union;
170 0 0       0 if ( $_[1]->isa( "XFD::union" ) ) {
171 0         0 $_[1]->add( $_[3] );
172 0         0 $union = $_[1];
173             }
174             else {
175 0         0 $union = XFD::union->new( @_[1,3] )
176             }
177 0         0 $union;
178             }
179             ;
180              
181             path_expr :
182             location_path
183             | primary_expr predicates segment {
184 0 0 0 0   0 return $_[1] unless defined $_[2] || defined $_[3];
185              
186 0         0 my $expr = $_[1];
187 0 0       0 $expr = $expr->[0] if $expr->isa( "XFD::Parens" );
188              
189             ## TODO: Cope with nodesets passed in vars or
190             ## returned from functions.
191 0 0       0 die "node-set is required before a predicate or '/' (variables and functions returning nodesets are not (yet) supported)"
192             unless $expr->isa( "XFD::PathTest" );
193              
194 0 0       0 $expr->set_next( $_[2] ) if defined $_[2];
195 0 0       0 $expr->set_next( $_[3] ) if defined $_[3];
196 0         0 $expr;
197             }
198             ;
199              
200             segment :
201             /* empty */
202 0     0   0 | SLASH relative_location_path { $_[2] }
203             | SLASH_SLASH relative_location_path {
204 0     0   0 my $op = XFD::Axis::descendant_or_self->new;
205 0         0 $op->set_next( $_[2] );
206 0         0 $op;
207             };
208              
209             location_path :
210             relative_location_path
211             | absolute_location_path
212             ;
213              
214             absolute_location_path :
215 0     0   0 SLASH { XFD::doc_node->new }
216             | SLASH relative_location_path {
217 0     0   0 my $op = XFD::doc_node->new;
218 0         0 $op->set_next( $_[2] );
219 0         0 $op;
220             }
221             | SLASH_SLASH relative_location_path {
222             ## /descendant-or-self::node()/relative_location_path
223 0     0   0 my $op = XFD::doc_node->new;
224 0         0 my $step = _step(
225             XFD::Axis::descendant_or_self->new,
226             XFD::EventType::node->new,
227             );
228 0         0 $op->set_next( $step );
229 0         0 $step->set_next( $_[2] );
230 0         0 $op;
231             };
232              
233             relative_location_path :
234             step
235 0     0   0 | relative_location_path SLASH step { $_[1]->set_next( $_[3] ) ; $_[1] }
  0         0  
236             ## This next rule means that the grammar does not like "////" :(.
237             ## TODO: add a rule for successive "//" paths
238             | relative_location_path SLASH_SLASH step {
239 0     0   0 my $step = _step(
240             XFD::Axis::descendant_or_self->new,
241             XFD::EventType::node->new,
242             );
243 0         0 $_[1]->set_next( $step );
244 0         0 $step->set_next( $_[3] );
245 0         0 $_[1];
246             }
247             ;
248              
249             step :
250 5     5   183 axis node_test predicates { _step( @_[1..$#_] ) }
251 0     0   0 | DOT { XFD::self_node->new }
252 0     0   0 | DOT_DOT { _no @_; }
253             ;
254              
255             axis:
256 5     5   34 /* empty */ { XFD::Axis::child->new }
257 0     0   0 | AXIS_NAME COLON_COLON { XFD::axis( $_[1] ) }
258 0     0   0 | AT { XFD::Axis::attribute->new }
259             ;
260              
261             predicates :
262             /* empty */
263             | predicates LSQB expr RSQB {
264 0     0   0 my $p = XFD::predicate->new( $_[3] );
265 0 0       0 if ( defined $_[1] ) {
266 0         0 $_[1]->set_next( $p );
267 0         0 return $_[1];
268             }
269 0         0 return $p;
270             }
271             ;
272              
273             primary_expr :
274 0     0   0 DOLLAR_QNAME { XFD::VariableReference->new( $_[1] ) }
275 0     0   0 | LPAR expr RPAR { XFD::Parens->new( $_[2] ) }
276             | LITERAL
277             | NUMBER
278 0     0   0 | FUNCTION_NAME LPAR opt_args RPAR { XFD::function( @_[1,3] ) }
279             ;
280              
281             opt_args :
282 0     0   0 /* empty */ { [] }
283             | args ## pass thru
284             ;
285              
286             args :
287 0     0   0 expr { [ $_[1] ] }
288             | args COMMA expr {
289 0     0   0 push @{$_[1]}, $_[3];
  0         0  
290 0         0 $_[1];
291             }
292             ;
293            
294             node_test :
295             QNAME {
296             $XFD::dispatcher->{Namespaces}
297 5 50   5   42 ? do {
298 0         0 my ( $ns_uri, $local_name ) =
299             XFD::PathTest->_parse_ns_uri_and_localname( $_[1] );
300              
301 0         0 my $op = XFD::namespace_test->new( $ns_uri );
302 0         0 $op->set_next(
303             XFD::node_local_name->new( $local_name )
304             );
305 0         0 $op;
306             }
307             : XFD::node_name->new( $_[1] );
308             }
309 0     0   0 | STAR { XFD::EventType::principal_event_type->new; }
310             | NAME_COLON_STAR {
311 0     0   0 my ( $ns_uri ) =
312             XFD::PathTest->_parse_ns_uri_and_localname( $_[1] );
313 0         0 XFD::namespace_test->new( $ns_uri )
314             }
315 0     0   0 | PI LPAR opt_literal RPAR { XFD::EventType::processing_instruction
316             ->new( $_[2] ) }
317 0     0   0 | COMMENT LPAR RPAR { XFD::EventType::comment ->new }
318 0     0   0 | TEXT LPAR RPAR { XFD::EventType::text ->new }
319 0     0   0 | NODE LPAR RPAR { XFD::EventType::node ->new }
320             ;
321              
322             opt_literal :
323             /* empty */
324 0     0   0 | LITERAL { _no @_; }
325 5         2858 ;
326              
327             %%
328 5         80  
329             =head1
330              
331             XML::Filter::Dispatcher::Parser - Parses the XPath subset used by ...::Dispatcher
332              
333             =head1 SYNOPSIS
334              
335             use XML::Filter::Dispatcher::Parser;
336              
337             my $result = XML::Filter::Dispatcher::Parser->parse( $xpath );
338              
339             =head1 DESCRIPTION
340              
341             Some notes on the parsing and evaluation:
342              
343             =over
344              
345             =item *
346              
347             Result Objects
348              
349             The result expressions alway return true or false. For XPath
350             expressions that would normally return a node-set, the result is true if
351             the current SAX event would build a node that would be in the node set.
352             No floating point or string return objects are supported (this may
353             change).
354              
355             =item *
356              
357             Context
358              
359             The XPath context node is the document root (theoretically; in reality
360             there is none). The variables are the Dispatcher's data members, and
361             the function library is XXX.
362              
363             Not sure what to do about the context position, but the context size is
364             of necessity undefined.
365              
366             The namespace mapping will be added in when I grok the NamespaceHelper.
367              
368             =back
369              
370             =cut
371              
372 1     1   4713 use Carp;
  1         3  
  1         1666  
373              
374             my %tokens = (qw(
375             . DOT
376             .. DOT_DOT
377             @ AT
378             * STAR
379             ( LPAR
380             ) RPAR
381             [ LSQB
382             ] RSQB
383             :: COLON_COLON
384             / SLASH
385             // SLASH_SLASH
386             | VBAR
387             + PLUS
388             - MINUS
389             = EQUALS
390             != BANG_EQUALS
391             > GT
392             < LT
393             >= GTE
394             <= LTE
395              
396             == EQUALS_EQUALS
397             || VBAR_VBAR
398             && AMP_AMP
399             & AMP
400             ),
401             "," => "COMMA"
402             );
403              
404             my $simple_tokens =
405             join "|",
406             map
407             quotemeta,
408             reverse
409             sort {
410             length $a <=> length $b
411             } keys %tokens;
412              
413             my $NCName = "(?:[a-zA-Z_][a-zA-Z0-9_.-]*)"; ## TODO: comb. chars & Extenders
414              
415             my %EventType = qw(
416             node NODE
417             text TEXT
418             comment COMMENT
419             processing-instruction PI
420             );
421              
422             my $EventType = "(?:" .
423             join( "|", map quotemeta, sort {length $a <=> length $b} keys %EventType ) .
424             ")";
425              
426             my $AxisName = "(?:" . join( "|", split /\n+/, <
427             ancestor
428             ancestor-or-self
429             attribute
430             child
431             descendant
432             descendant-or-self
433             following
434             following-sibling
435             namespace
436             parent
437             preceding
438             preceding-sibling
439             self
440             end
441             AXIS_LIST_END
442              
443             my %preceding_tokens = map { ( $_ => undef ) } ( qw(
444             @ :: [
445             and or mod div
446             *
447             / // | + - = != < <= > >=
448              
449             == & && ||
450             ),
451             "(", ","
452             ) ;
453              
454              
455             sub debugging () { 0 }
456              
457              
458             sub lex {
459 10     10 0 21 my ( $p ) = @_;
460              
461             ## Optimization notes: we aren't parsing War and Peace here, so
462             ## readability over performance.
463              
464 10         18 my $d = $p->{USER};
465 10         40 my $input = \$d->{Input};
466              
467             ## This needs to be more contextual, only recognizing axis/function-name
468 10 100 100     52 if ( ( pos( $$input ) || 0 ) == length $$input ) {
469 5         8 $d->{LastToken} = undef;
470 5         23 return ( '', undef );
471             }
472              
473 5         6 my ( $token, $val ) ;
474             ## First do the disambiguation rules:
475              
476             ## If there is a preceding token and the preceding token is not
477             ## one of "@", "::", "(", "[", "," or an Operator,
478 5 50 33     20 if ( defined $d->{LastToken}
479             && ! exists $preceding_tokens{$d->{LastToken}}
480             ) {
481             ## a * must be recognized as a MultiplyOperator
482 0 0       0 if ( $$input =~ /\G\s*\*/gc ) {
    0          
483 0         0 ( $token, $val ) = ( MULTIPLY => "*" );
484             }
485             ## an NCName must be recognized as an OperatorName.
486             elsif ( $$input =~ /\G\s*($NCName)/gc ) {
487 0 0       0 die "Expected and, or, mod or div, got '$1'\n"
488             unless 0 <= index "and|or|mod|div", $1;
489 0         0 ( $token, $val ) = ( uc $1, $1 );
490             }
491             }
492              
493             ## NOTE: \s is only an approximation for ExprWhitespace
494 5 50       13 unless ( defined $token ) {
495 5         434 $$input =~ m{\G\s*(?:
496             ## If the character following an NCName (possibly after
497             ## intervening ExprWhitespace) is (, then the token must be
498             ## recognized as a EventType or a FunctionName.
499             ($NCName)\s*(?=\()
500              
501             ## If the two characters following an NCName (possibly after
502             ## intervening ExprWhitespace) are ::, then the token must be
503             ## recognized as an AxisName
504             |($NCName)\s*(?=::)
505              
506             ## Otherwise, it's just a normal lexer.
507             |($NCName:\*) #NAME_COLON_STAR
508             |((?:$NCName:)?$NCName) #QNAME
509             |('[^']*'|"[^"]*") #LITERAL
510             |(-?\d+(?:\.\d+)?|\.\d+) #NUMBER
511             |\$((?:$NCName:)?$NCName) #DOLLAR_QNAME
512             |($simple_tokens)
513             )\s*}gcx;
514              
515             ( $token, $val ) =
516             defined $1 ? (
517             exists $EventType{$1}
518             ? ( $EventType{$1}, $1 )
519             : ( FUNCTION_NAME => $1 )
520             ) :
521            
522             defined $2 ? ( AXIS_NAME => $2 ) :
523             defined $3 ? ( NAME_COLON_STAR => $3 ) :
524             defined $4 ? ( QNAME => $4 ) :
525 5 0       79 defined $5 ? ( LITERAL => do {
    0          
    0          
    0          
    0          
    50          
    50          
    50          
    50          
526 0         0 my $s = substr( $5, 1, -1 );
527 0         0 $s =~ s/([\\'])/\\$1/g;
528 0         0 XFD::StringConstant->new( $s );
529             }
530             ) :
531             defined $6 ? ( NUMBER =>
532             XFD::NumericConstant->new( "$6")
533             ) :
534             defined $7 ? ( DOLLAR_QNAME => $7 ) :
535             defined $8 ? ( $tokens{$8} => $8 ) :
536             die "Failed to parse '$$input' at ",
537             pos $$input,
538             "\n";
539             }
540              
541 5         16 $d->{LastToken} = $val;
542              
543 5         6 if ( debugging ) {
544             warn
545             "'",
546             $$input,
547             "' (",
548             pos $$input,
549             "):",
550             join( " => ", map defined $_ ? $_ : "", $token, $val ),
551             "\n";
552             }
553              
554 5         29 return ( $token, $val );
555             }
556              
557              
558             sub error {
559 0     0 0 0 my ( $p ) = @_;
560 0         0 print "Couldn't parse '$p->{USER}->{Input}' at position ", pos $p->{USER}->{Input}, "\n";
561             }
562              
563             ## _parse is an internal, reentrant entry point; it's used to parse rules
564             ## and subrules.
565             sub _parse {
566 5     5   10 my $self = shift;
567 5         8 my ( $expr, $action_code ) = @_;
568              
569 5         7 my $options = $XFD::dispatcher;
570              
571 5 50       23 warn "Parsing '$expr'\n" if $options->{Debug};
572              
573 5 50 50     48 my $p = XML::Filter::Dispatcher::Parser->new(
574             yylex => \&lex,
575             yyerror => \&error,
576             ( $options->{Debug} || 0 ) > 5
577             ? ( yydebug => 0x1D )
578             : (),
579             );
580              
581 5 50       96 %{$p->{USER}} = %$options if $options;
  5         55  
582 5         20 $p->{USER}->{Input} = $expr;
583 5         18 local $XFD::dispatcher->{ParseNestingDepth}
584             = $XFD::dispatcher->{ParseNestingDepth} + 1;
585              
586 5         10 my $op_tree = eval {
587 5         27 $p->YYParse; ## <== the actual parse
588             };
589              
590 5 50       11 die $@ if $@;
591              
592 5 50       18 die map "$_\n", @{$p->{USER}->{NONONO}}
  0         0  
593             if $p->{USER}->{NONONO} ;
594              
595 5 50       13 return undef unless defined $op_tree;
596              
597 5 50       15 die "grammar returned '$op_tree', needed a ref\n"
598             unless ref $op_tree;
599              
600             ## TODO: figure a way to allow a limited subset
601             ## of EventPath patterns, kinda like allowing
602             ## a pattern match against the generated Op tree,
603             ## or alternate grammar files. The former could
604             ## give more helpful error messages, the latter
605             ## could be more flexible because it would allow
606             ## non-standard grammars.
607 5 50       30 $op_tree = XFD::ExprEval->new( $op_tree )
608             unless $op_tree->isa( "XFD::PathTest" );
609              
610 5         30 $op_tree->set_next( XFD::action( $action_code ) );
611              
612 5         936 return $op_tree;
613             }
614              
615              
616             sub parse {
617 5     5 0 10 my $self = shift;
618 5         8 local $XFD::dispatcher = shift;
619 5         8 my ( $expr, $context ) = @_;
620              
621 5         14 $XFD::dispatcher->{ParseNestingDepth} = 0;
622 5   66     35 $XFD::dispatcher->{OpTree} ||= XFD::union->new;
623              
624 5         20 my $op_tree = $self->_parse( @_ );
625              
626 5         35 my $rule = XFD::Rule->new( $expr );
627 5         20 $rule->set_next( $op_tree );
628 5         24 $XFD::dispatcher->{OpTree}->add( $rule );
629             }
630              
631             1 ;