File Coverage

lib/HTML/Object/XPath.pm
Criterion Covered Total %
statement 354 474 74.6
branch 100 166 60.2
condition 25 31 80.6
subroutine 60 74 81.0
pod 26 26 100.0
total 565 771 73.2


line stmt bran cond sub pod time code
1             ##----------------------------------------------------------------------------
2             ## HTML Object - ~/lib/HTML/Object/XPath.pm
3             ## Version v0.2.0
4             ## Copyright(c) 2021 DEGUEST Pte. Ltd.
5             ## Author: Jacques Deguest <jack@deguest.jp>
6             ## Created 2021/12/04
7             ## Modified 2022/09/18
8             ## All rights reserved
9             ##
10             ##
11             ## This program is free software; you can redistribute it and/or modify it
12             ## under the same terms as Perl itself.
13             ##----------------------------------------------------------------------------
14             package HTML::Object::XPath;
15             BEGIN
16             {
17 8     8   104792 use strict;
  8         33  
  8         273  
18 8     8   61 use warnings;
  8         14  
  8         328  
19 8     8   466 use parent qw( Module::Generic );
  8         316  
  8         73  
20 8         1261 use vars qw(
21             $CACHE $AXES $AXES_KEYS $NC_NAME_RE $QNAME_RE $NC_WILD_RE $QN_WILD_RE
22             $NODE_TYPE_RE $AXIS_NAME_RE $NUMBER_RE $LITERAL_RE $REGEXP_RE $REGEXP_MOD_RE
23             $BASE_CLASS $VERSION
24 8     8   118360 );
  8         25  
25 8     8   4188 use HTML::Object::XPath::Step;
  8         29  
  8         92  
26 8     8   7214 use HTML::Object::XPath::Expr;
  8         26  
  8         96  
27 8     8   6133 use HTML::Object::XPath::Function;
  8         28  
  8         84  
28 8     8   5653 use HTML::Object::XPath::LocationPath;
  8         22  
  8         104  
29 8     8   5436 use HTML::Object::XPath::Variable;
  8         27  
  8         87  
30 8     8   5463 use HTML::Object::XPath::Literal;
  8         33  
  8         68  
31 8     8   5326 use HTML::Object::XPath::Number;
  8         37  
  8         88  
32 8     8   5545 use HTML::Object::XPath::NodeSet;
  8         31  
  8         261  
33 8     8   3369 use HTML::Object::XPath::Root;
  8         18  
  8         122  
34 8     8   5185 our $VERSION = 'v0.2.0';
35 8         21 our $CACHE = {};
36             # Axis name to principal node type mapping
37 8         113 our $AXES =
38             {
39             'ancestor' => 'element',
40             'ancestor-or-self' => 'element',
41             'attribute' => 'attribute',
42             'namespace' => 'namespace',
43             'child' => 'element',
44             'descendant' => 'element',
45             'descendant-or-self' => 'element',
46             'following' => 'element',
47             'following-sibling' => 'element',
48             'parent' => 'element',
49             'preceding' => 'element',
50             'preceding-sibling' => 'element',
51             'self' => 'element',
52             };
53 8         65 my $AXES_KEYS = join( '|', keys( %$AXES ) );
54 8         48 our $NC_NAME_RE = qr/([A-Za-z_][\w\.\-]*)/;
55 8         809 our $QNAME_RE = qr/(${NC_NAME_RE}:)?${NC_NAME_RE}/;
56 8         340 our $NC_WILD_RE = qr/${NC_NAME_RE}:\*/;
57 8         35 our $QN_WILD_RE = qr/\*/;
58 8         20 our $NODE_TYPE_RE = qr/((text|comment|processing-instruction|node)\(\))/;
59 8         707 our $AXIS_NAME_RE = qr/(${AXES_KEYS})::/;
60 8         51 our $NUMBER_RE = qr/\d+(\.\d*)?|\.\d+/;
61 8         30 our $LITERAL_RE = qr/\"[^\"]*\"|\'[^\']*\'/;
62 8         23 our $REGEXP_RE = qr{(?:m?/(?:\\.|[^/])*/)};
63 8         19 our $REGEXP_MOD_RE = qr{(?:[imsx]+)};
64 8         204 our $BASE_CLASS = __PACKAGE__;
65             };
66              
67 8     8   59 use strict;
  8         23  
  8         180  
68 8     8   38 use warnings;
  8         16  
  8         47773  
69              
70             sub init
71             {
72 10     10 1 4054 my $self = shift( @_ );
73 10         512 $self->{_init_strict_use_sub} = 1;
74 10 50       78 $self->SUPER::init( @_ ) || return( $self->pass_error );
75 10         800 $self->{context_pos} = undef; # 1 based position in array context
76 10         55 $self->{context_set} = $self->new_nodeset;
77 10         34 $self->{context_size} = 0; # total size of context
78 10         42 $self->clear_namespaces();
79 10         23 $self->{cache} = {};
80 10         26 $self->{direction} = 'forward';
81 10         28 $self->{namespaces} = {};
82 10         22 $self->{vars} = {};
83 10         32 $self->{_tokpos} = 0;
84 10         30 return( $self );
85             }
86              
87             sub clear_namespaces
88             {
89 10     10 1 22 my $self = shift( @_ );
90 10         38 $self->{uses_namespaces} = 0;
91 10         27 $self->{namespaces} = {};
92 10         19 return( $self );
93             }
94              
95             sub exists
96             {
97 0     0 1 0 my $self = shift( @_ );
98 0         0 my( $path, $context ) = @_;
99 0 0       0 $self = '/' if( !defined( $self ) );
100 0         0 my @nodeset = $self->findnodes( $path, $context );
101 0 0       0 return( scalar( @nodeset ) ? 1 : 0 );
102             }
103              
104             sub find
105             {
106 68     68 1 139 my $self = shift( @_ );
107             # xpath expression and $context is a HTML::Object::Element
108 68         131 my( $path, $context ) = @_;
109             # _parse returns an HTML::Object::XPath::Expr object
110 68         235 my $parsed_path = $self->_parse( $path );
111             # $parsed_path is an HTML:: Object::XPath::Expr object
112             # $results could be a HTML::Object::XPath::NodeSet or something else like HTML::Object::XPath::Number
113 68         2807 my $results = $parsed_path->evaluate( $context );
114             # if( $results->isa( 'HTML::Object::XPath::NodeSet') )
115             # if( $self->isa_nodeset( $results ) )
116 65 100       385 if( $self->_is_a( $results, 'HTML::Object::XPath::NodeSet' ) )
117             {
118 52         2162 return( $results->sort->remove_duplicates );
119             }
120             else
121             {
122 13         563 return( $results );
123             }
124             }
125              
126             sub findnodes
127             {
128 32     32 1 1287 my $self = shift( @_ );
129 32         92 my( $path, $context ) = @_;
130            
131 32         122 my $results = $self->find( $path, $context );
132            
133 32 50       113 if( $self->_is_a( $results => 'HTML::Object::XPath::NodeSet' ) )
134             {
135 32 50       1163 return( wantarray() ? $results->get_nodelist : $results );
136             }
137             # result should be SCALAR
138             else
139             {
140 0 0       0 return( wantarray() ? $self->new_nodeset( $results ) : $results );
141             }
142             #{ return wantarray ? ($results) : $results; } # result should be SCALAR
143             #{ return wantarray ? () : HTML::Object::XPath::NodeSet->new(); }
144             }
145              
146             sub findnodes_as_string
147             {
148 0     0 1 0 my $self = shift( @_ );
149 0         0 my( $path, $context ) = @_;
150            
151 0         0 my $results = $self->find( $path, $context );
152 0 0       0 if( $self->_is_a( $results => 'HTML::Object::XPath::NodeSet' ) )
    0          
    0          
153             {
154 0         0 return( join( '', map{ $_->toString } $results->get_nodelist ) );
  0         0  
155             }
156             elsif( $self->_is_a( $results => 'HTML::Object::XPath::Boolean' ) )
157             {
158             # to behave like XML::LibXML
159 0         0 return( '' );
160             }
161             elsif( $self->_is_a( $results => 'HTML::Object::XPath::Node' ) )
162             {
163 0         0 return( $results->toString );
164             }
165             else
166             {
167 0         0 return( $self->_xml_escape_text( $results->value ) );
168             }
169             }
170              
171             sub findnodes_as_strings
172             {
173 0     0 1 0 my $self = shift( @_ );
174 0         0 my( $path, $context ) = @_;
175 0         0 my $results = $self->find( $path, $context );
176            
177 0 0       0 if( $self->_is_a( $results => 'HTML::Object::XPath::NodeSet' ) )
    0          
    0          
178             {
179 0         0 return( map{ $_->getValue } $results->get_nodelist );
  0         0  
180             }
181             elsif( $self->_is_a( $results => 'HTML::Object::XPath::Boolean' ) )
182             {
183             # to behave like XML::LibXML
184 0         0 return( () );
185             }
186             elsif( $self->_is_a( $results => 'HTML::Object::XPath::Node' ) )
187             {
188 0         0 return( $results->getValue );
189             }
190             else
191             {
192 0         0 return( $self->_xml_escape_text( $results->value ) );
193             }
194             }
195              
196             sub findvalue
197             {
198 31     31 1 17851 my $self = shift( @_ );
199 31         71 my( $path, $context ) = @_;
200 31         91 my $results = $self->find( $path, $context );
201 31 100       132 if( $self->_is_a( $results => 'HTML::Object::XPath::NodeSet' ) )
202             {
203 18         609 return( $results->to_final_value );
204             }
205             #{ return $results->to_literal; }
206 13         370 return( $results->value );
207             }
208              
209             sub findvalues
210             {
211 5     5 1 2233 my $self = shift( @_ );
212 5         11 my( $path, $context ) = @_;
213 5         13 my $results = $self->find( $path, $context );
214 2 50       10 if( $self->_is_a( $results => 'HTML::Object::XPath::NodeSet' ) )
215             {
216 2         58 return( $results->string_values );
217             }
218 0         0 return( $results->string_value );
219             }
220              
221             sub get_namespace
222             {
223 0     0 1 0 my $self = shift( @_ );
224 0         0 my( $prefix, $node ) = @_;
225            
226             my $ns = $node
227             ? $node->getNamespace( $prefix )
228             : $self->{uses_namespaces}
229 0 0       0 ? $self->{namespaces}->{ $prefix }
    0          
230             : $prefix;
231 0         0 return( $ns );
232             }
233              
234             sub get_var
235             {
236 0     0 1 0 my $self = shift( @_ );
237 0         0 my $var = shift( @_ );
238 0         0 $self->{vars}->{ $var };
239             }
240              
241             sub matches
242             {
243 3     3 1 8 my $self = shift( @_ );
244 3         11 my( $node, $path, $context ) = @_;
245 3         12 my @nodes = $self->findnodes( $path, $context );
246 3 100       99 return(1) if( grep{ "$node" eq "$_" } @nodes );
  3         38  
247 2         33 return;
248             }
249              
250 68     68 1 329 sub namespaces { return( shift->_set_get_hash_as_mix_object( 'namespaces', @_ ) ); }
251              
252 255     255 1 554 sub new_expr { return( shift->_class_for( 'Expr' )->new( @_ ) ); }
253              
254 25     25 1 73 sub new_function { return( shift->_class_for( 'Function' )->new( @_ ) ); }
255              
256 29     29 1 88 sub new_literal { return( shift->_class_for( 'Literal' )->new( @_ ) ); }
257              
258 89     89 1 259 sub new_location_path { return( shift->_class_for( 'LocationPath' )->new( @_ ) ); }
259              
260 10     10 1 48 sub new_nodeset { return( shift->_class_for( 'NodeSet' )->new( @_ ) ); }
261              
262 12     12 1 34 sub new_number { return( shift->_class_for( 'Number' )->new( @_ ) ); }
263              
264 48     48 1 164 sub new_root { return( shift->_class_for( 'Root' )->new( @_ ) ); }
265              
266 166     166 1 436 sub new_step { return( shift->_class_for( 'Step' )->new( @_ ) ); }
267              
268 0     0 1 0 sub new_variable { return( shift->_class_for( 'Variable' )->new( @_ ) ); }
269              
270 0     0 1 0 sub parse { return( shift->_parse( @_ ) ); }
271              
272             sub set_namespace
273             {
274 0     0 1 0 my $self = shift( @_ );
275 0         0 my( $prefix, $expanded ) = @_;
276 0         0 $self->{uses_namespaces} = 1;
277 0         0 $self->{namespaces}{ $prefix } = $expanded;
278             }
279              
280             sub set_strict_namespaces
281             {
282 0     0 1 0 my( $self, $strict ) = @_;
283 0         0 $self->{strict_namespaces} = $strict;
284             }
285              
286             sub set_var
287             {
288 0     0 1 0 my $self = shift( @_ );
289 0         0 my $var = shift( @_ );
290 0         0 my $val = shift( @_ );
291 0         0 $self->{vars}->{ $var } = $val;
292             }
293              
294             sub _analyze
295             {
296 56     56   114 my $self = shift( @_ );
297             # Array object
298 56         111 my $tokens = shift( @_ );
299             # lexical analysis
300 56 50       200 if( $self->debug )
301             {
302 0         0 my( $p, $f, $l ) = caller;
303             }
304 56         1537 return( $self->_expr( $tokens ) );
305             }
306              
307             sub _arguments
308             {
309 25     25   69 my( $self, $tokens ) = @_;
310 25         93 my $args = $self->new_array;
311 25 50       520 if( $tokens->[ $self->{_tokpos} ] eq ')' )
312             {
313 0         0 return( $args );
314             }
315            
316 25         144 $args->push( $self->_expr( $tokens ) );
317 25         236 while( $self->_match( $tokens, qr/\,/ ) )
318             {
319 5         19 $args->push( $self->_expr( $tokens ) );
320             }
321 25         133 return( $args );
322             }
323              
324             sub _class_for
325             {
326 807     807   6706 my( $self, $mod ) = @_;
327 807         33994 eval( "require ${BASE_CLASS}\::${mod};" );
328 807 50       3030 die( $@ ) if( $@ );
329             # ${"${BASE_CLASS}\::${mod}\::DEBUG"} = $self->debug;
330 807   50     3132 eval( "\$${BASE_CLASS}\::${mod}\::DEBUG = " . ( $self->debug // 0 ) );
331 807         6651 return( "${BASE_CLASS}::${mod}" );
332             }
333              
334             sub _expr
335             {
336 120     120   301 my( $self, $tokens ) = @_;
337             # $tokens are an array object of xpath expression token
338             # if( $self->debug )
339             # {
340             # my( $p, $f, $l ) = caller;
341             # }
342             # Returning an HTML::Object::XPath::Expr object
343 120         366 return( $self->_expr_or( $tokens ) );
344             }
345              
346             sub _expr_additive
347             {
348 148     148   241 my( $self, $tokens ) = @_;
349            
350 148         350 my $expr = $self->_expr_multiplicative( $tokens );
351 148         535 while( $self->_match( $tokens, qr/[\+\-]/ ) )
352             {
353 0         0 my $add_expr = $self->new_expr( $self );
354 0         0 $add_expr->set_lhs( $expr );
355 0         0 $add_expr->set_op( $self->{_curr_match} );
356            
357 0         0 my $rhs = $self->_expr_multiplicative( $tokens );
358            
359 0         0 $add_expr->set_rhs( $rhs );
360 0         0 $expr = $add_expr;
361             }
362 148         448 return( $expr );
363             }
364              
365             sub _expr_and
366             {
367 121     121   232 my( $self, $tokens ) = @_;
368 121         311 my $expr = $self->_expr_match( $tokens );
369 121         329 while( $self->_match( $tokens, 'and' ) )
370             {
371 2         7 my $and_expr = $self->new_expr( $self );
372 2         14 $and_expr->set_lhs( $expr );
373 2         5 $and_expr->set_op( 'and' );
374 2         11 my $rhs = $self->_expr_match( $tokens );
375 2         10 $and_expr->set_rhs( $rhs );
376 2         4 $expr = $and_expr;
377             }
378 121         277 return( $expr );
379             }
380              
381             sub _expr_equality
382             {
383 126     126   179 my( $self, $tokens ) = @_;
384            
385 126         321 my $expr = $self->_expr_relational( $tokens );
386 126         428 while( $self->_match( $tokens, qr/!?=/ ) )
387             {
388 21         97 my $eq_expr = $self->new_expr( $self );
389 21         199 $eq_expr->set_lhs( $expr );
390 21         79 $eq_expr->set_op( $self->{_curr_match} );
391            
392 21         73 my $rhs = $self->_expr_relational( $tokens );
393            
394 21         127 $eq_expr->set_rhs( $rhs );
395 21         82 $expr = $eq_expr;
396             }
397 126         315 return( $expr );
398             }
399              
400             sub _expr_filter
401             {
402 69     69   144 my( $self, $tokens ) = @_;
403            
404            
405 69         189 my $expr = $self->_expr_primary( $tokens );
406 69         287 while( $self->_match( $tokens, qr/\[/ ) )
407             {
408             # really PredicateExpr...
409 2         13 $expr->push_predicate( $self->_expr( $tokens ) );
410 2         74129 $self->_match( $tokens, qr/\]/, 1 );
411             }
412 69         399 return( $expr );
413             }
414              
415             sub _expr_match
416             {
417 123     123   243 my( $self, $tokens ) = @_;
418            
419 123         293 my $expr = $self->_expr_equality( $tokens );
420              
421 123         435 while( $self->_match( $tokens, qr/[=!]~/ ) )
422             {
423 3         20 my $match_expr = $self->new_expr( $self );
424 3         26 $match_expr->set_lhs( $expr );
425 3         12 $match_expr->set_op( $self->{_curr_match} );
426            
427 3         12 my $rhs = $self->_expr_equality( $tokens );
428            
429 3         14 $match_expr->set_rhs( $rhs );
430 3         12 $expr = $match_expr;
431             }
432 123         293 return( $expr );
433             }
434              
435             sub _expr_multiplicative
436             {
437 148     148   290 my( $self, $tokens ) = @_;
438            
439 148         360 my $expr = $self->_expr_unary( $tokens );
440 148         612 while( $self->_match( $tokens, qr/(\*|div|mod)/ ) )
441             {
442 0         0 my $mult_expr = $self->new_expr( $self );
443 0         0 $mult_expr->set_lhs( $expr );
444 0         0 $mult_expr->set_op( $self->{_curr_match} );
445            
446 0         0 my $rhs = $self->_expr_unary( $tokens );
447            
448 0         0 $mult_expr->set_rhs( $rhs );
449 0         0 $expr = $mult_expr;
450             }
451 148         370 return( $expr );
452             }
453              
454             sub _expr_or
455             {
456 120     120   199 my( $self, $tokens ) = @_;
457            
458 120         348 my $expr = $self->_expr_and( $tokens );
459 120         262 while( $self->_match( $tokens, 'or' ) )
460             {
461 1         5 my $or_expr = $self->new_expr( $self );
462 1         11 $or_expr->set_lhs( $expr );
463 1         4 $or_expr->set_op( 'or' );
464              
465 1         4 my $rhs = $self->_expr_and( $tokens );
466              
467 1         18 $or_expr->set_rhs( $rhs );
468 1         3 $expr = $or_expr;
469             }
470 120 50       389 if( $self->debug )
471             {
472 0         0 my( $p, $f, $l ) = caller;
473             }
474 120         2599 return( $expr );
475             }
476              
477             sub _expr_path
478             {
479 148     148   229 my( $self, $tokens ) = @_;
480             # _expr_path is _location_path | _expr_filter | _expr_filter '//?' _relative_location_path
481             # Since we are being predictive we need to find out which function to call next, then.
482             # LocationPath either starts with "/", "//", ".", ".." or a proper Step.
483 148         328 my $expr = $self->new_expr( $self );
484            
485             # $test is a fragment of the xpath initially provided and broken down into bits by
486             # HTML::Object::XPath::_tokenize
487 148         1113 my $test = $tokens->[ $self->{_tokpos} ];
488            
489             # Test for AbsoluteLocationPath and AbbreviatedRelativeLocationPath
490 148 100       1029 if( $test =~ /^(\/\/?|\.\.?)$/ )
    100          
491             {
492             # LocationPath
493 57         249 $expr->set_lhs( $self->_location_path( $tokens ) );
494             }
495             # Test for AxisName::...
496             elsif( $self->_is_step( $tokens ) )
497             {
498 22         95 $expr->set_lhs( $self->_location_path( $tokens ) );
499             }
500             else
501             {
502             # Not a LocationPath
503             # Use _expr_filter instead:
504 69         224 $expr = $self->_expr_filter( $tokens );
505 69 100       277 if( $self->_match( $tokens, qr/\/\/?/ ) )
506             {
507 10         43 my $loc_path = $self->new_location_path();
508 10         47 $loc_path->push( $expr );
509 10 100       61 if( $self->{_curr_match} eq '//' )
510             {
511 3         19 $loc_path->push( $self->new_step( $self, 'descendant-or-self', $self->_class_for( 'Step' )->TEST_NT_NODE ) );
512             }
513 10         39 $loc_path->push( $self->_relative_location_path( $tokens ) );
514 10         60 my $new_expr = $self->new_expr( $self );
515 10         105 $new_expr->set_lhs( $loc_path );
516 10         33 return( $new_expr );
517             }
518             }
519 138         332 return( $expr );
520             }
521              
522             sub _expr_primary
523             {
524 69     69   122 my( $self, $tokens ) = @_;
525            
526 69         134 my $expr = $self->new_expr( $self );
527            
528 69 100       556 if( $self->_match( $tokens, $LITERAL_RE ) )
    100          
    100          
    100          
    50          
    50          
529             {
530             # new Literal with $self->{_curr_match}...
531 26         177 $self->{_curr_match} =~ m/^(["'])(.*)\1$/;
532 26         97 $expr->set_lhs( $self->new_literal( $2 ) );
533             }
534             elsif( $self->_match( $tokens, qr/${REGEXP_RE}${REGEXP_MOD_RE}?/ ) )
535             {
536             # new Literal with $self->{_curr_match} turned into a regexp...
537 3         67 my( $regexp, $mod)= $self->{_curr_match} =~ m{($REGEXP_RE)($REGEXP_MOD_RE?)};
538 3         21 $regexp =~ s{^m?s*/}{};
539 3         14 $regexp =~ s{/$}{};
540             # move the mods inside the regexp
541 3 50       11 if( $mod )
542             {
543 0         0 $regexp =~ qr/(?$mod:$regexp)/;
544             }
545 3         16 $expr->set_lhs( $self->new_literal( $regexp ) );
546             }
547             elsif( $self->_match( $tokens, $NUMBER_RE ) )
548             {
549             # new Number with $self->{_curr_match}...
550 12         191 $expr->set_lhs( $self->new_number( $self->{_curr_match} ) );
551             }
552             elsif( $self->_match( $tokens, qr/\(/ ) )
553             {
554 3         32 $expr->set_lhs( $self->_expr( $tokens ) );
555 3         400 $self->_match( $tokens, qr/\)/, 1 );
556             }
557             elsif( $self->_match( $tokens, qr/\$$QNAME_RE/ ) )
558             {
559             # new Variable with $self->{_curr_match}...
560 0         0 $self->{_curr_match} =~ /^\$(.*)$/;
561 0         0 $expr->set_lhs( $self->new_variable( $self, $1 ) );
562             }
563             elsif( $self->_match( $tokens, $QNAME_RE ) )
564             {
565             # check match not Node_Type - done in lexer...
566             # new Function
567 25         58 my $func_name = $self->{_curr_match};
568 25         132 $self->_match( $tokens, qr/\(/, 1 );
569 25         193 $expr->set_lhs(
570             $self->new_function(
571             $self,
572             $func_name,
573             $self->_arguments( $tokens )
574             )
575             );
576 25         123 $self->_match( $tokens, qr/\)/, 1 );
577             }
578             else
579             {
580             # die "Not a _expr_primary at ", $tokens->[$self->{_tokpos}], "\n";
581 0         0 return( $self->error( "Not a _expr_primary at ", $tokens->[ $self->{_tokpos} ] ) );
582             }
583 69         270 return( $expr );
584             }
585              
586             sub _expr_relational
587             {
588 147     147   260 my( $self, $tokens ) = @_;
589            
590 147         398 my $expr = $self->_expr_additive( $tokens );
591 147         556 while( $self->_match( $tokens, qr/(<|>|<=|>=)/ ) )
592             {
593 1         5 my $rel_expr = $self->new_expr( $self );
594 1         7 $rel_expr->set_lhs( $expr );
595 1         6 $rel_expr->set_op( $self->{_curr_match} );
596            
597 1         4 my $rhs = $self->_expr_additive( $tokens );
598            
599 1         13 $rel_expr->set_rhs( $rhs );
600 1         9 $expr = $rel_expr;
601             }
602 147         411 return $expr;
603             }
604              
605             sub _expr_unary
606             {
607 148     148   241 my( $self, $tokens ) = @_;
608             # $tokens are an array object of expression tokens
609            
610 148 50       705 if( $self->_match( $tokens, qr/-/ ) )
611             {
612 0         0 my $expr = $self->new_expr( $self );
613 0         0 $expr->set_lhs( $self->new_number(0) );
614 0         0 $expr->set_op( '-' );
615 0         0 $expr->set_rhs( $self->_expr_unary( $tokens ) );
616 0         0 return( $expr );
617             }
618             else
619             {
620 148         512 return( $self->_expr_union( $tokens ) );
621             }
622             }
623              
624             sub _expr_union
625             {
626 148     148   295 my( $self, $tokens ) = @_;
627             # $tokens are an array object of expression tokens
628            
629 148         392 my $expr = $self->_expr_path( $tokens );
630 148         560 while( $self->_match( $tokens, qr/\|/ ) )
631             {
632 0         0 my $un_expr = $self->new_expr( $self );
633 0         0 $un_expr->set_lhs( $expr );
634 0         0 $un_expr->set_op( '|' );
635            
636 0         0 my $rhs = $self->_expr_path( $tokens );
637 0         0 $un_expr->set_rhs( $rhs );
638 0         0 $expr = $un_expr;
639             }
640 148         428 return( $expr );
641             }
642              
643 0     0   0 sub _get_context_node { return( $_[0]->{context_set}->get_node( $_[0]->{context_pos} ) ); }
644              
645 371     371   804 sub _get_context_pos { return( shift->{context_pos} ); }
646              
647 371     371   843 sub _get_context_set { return( shift->{context_set} ); }
648              
649 0     0   0 sub _get_context_size { return( shift->{context_set}->size ); }
650              
651             sub _is_step
652             {
653 96     96   214 my( $self, $tokens ) = @_;
654 96         181 my $token = $tokens->[ $self->{_tokpos} ];
655 96 50       195 return unless( defined( $token ) );
656              
657             # local $^W = 0;
658 96 100 66     3756 if( ( $token eq 'processing-instruction' ) ||
      100        
      100        
      100        
      66        
      100        
659             ( $token =~ /^\@($NC_WILD_RE|$QNAME_RE|$QN_WILD_RE)$/o ) ||
660             ( ( $token =~ /^($NC_WILD_RE|$QNAME_RE|$QN_WILD_RE)$/o ) &&
661             ( ( $tokens->[ $self->{_tokpos} + 1 ] || '' ) ne '(' ) ) ||
662             ( $token =~ /^$NODE_TYPE_RE$/o ) ||
663             ( $token =~ /^$AXIS_NAME_RE($NC_WILD_RE|$QNAME_RE|$QN_WILD_RE|$NODE_TYPE_RE)$/o )
664             )
665             {
666 27         134 return(1);
667             }
668             else
669             {
670 69         253 return;
671             }
672             }
673              
674             sub _location_path
675             {
676 79     79   184 my( $self, $tokens ) = @_;
677 79         221 my $loc_path = $self->new_location_path;
678            
679 79 100       377 if( $self->_match( $tokens, qr/\// ) )
    100          
680             {
681             # root
682             # push @$loc_path, HTML::Object::XPath::Root->new();
683 5         35 $loc_path->push( $self->new_root );
684             # Is it a valid token step?
685 5 50       30 if( $self->_is_step( $tokens ) )
686             {
687             # push @$loc_path, $self->_relative_location_path( $tokens);
688 5         13 $loc_path->push( $self->_relative_location_path( $tokens ) );
689             }
690             }
691             elsif( $self->_match( $tokens, qr/\/\// ) )
692             {
693             # root
694 43         209 $loc_path->push( $self->new_root );
695 43         259 my $optimised = $self->_optimise_descendant_or_self( $tokens );
696 43 100       176 if( !$optimised )
697             {
698 19         57 $loc_path->push(
699             $self->new_step( $self, 'descendant-or-self', $self->_class_for( 'Step' )->TEST_NT_NODE )
700             );
701 19         141 $loc_path->push( $self->_relative_location_path( $tokens ) );
702             }
703             else
704             {
705 24         127 $loc_path->push( $optimised, $self->_relative_location_path( $tokens ) );
706             }
707             }
708             else
709             {
710 31         147 $loc_path->push( $self->_relative_location_path( $tokens ) );
711             }
712 79         613 return( $loc_path );
713             }
714              
715             sub _match
716             {
717 2386     2386   16478 my( $self, $tokens, $match, $fatal ) = @_;
718             # Enabling this debugging section will take a lot more time, because of the
719             # $tokens->length that creates a new Module::Generic::Number every time
720             # and _match gets called a lot
721             # if( $self->debug )
722             # {
723             # my( $p, $f, $l ) = caller;
724             # }
725 2386         3195 $self->{_curr_match} = '';
726 2386 100       5929 return(0) unless( $self->{_tokpos} < scalar( @$tokens ) );
727             # return(0) unless( $self->{_tokpos} < $tokens->length );
728              
729             # local $^W;
730 1825 100       31037 if( $tokens->[ $self->{_tokpos} ] =~ /^$match$/ )
731             {
732 339         1116 $self->{_curr_match} = $tokens->[ $self->{_tokpos} ];
733 339         460 $self->{_tokpos}++;
734 339         1347 return(1);
735             }
736             else
737             {
738 1486 50       2713 if( $fatal )
739             {
740 0         0 die( "Invalid token: ", $tokens->[$self->{_tokpos}], "\n" );
741             # return( $self->error( "Invalid token: ", $tokens->[ $self->{_tokpos} ] ) );
742             }
743             else
744             {
745 1486         6108 return(0);
746             }
747             }
748             }
749              
750             sub _optimise_descendant_or_self
751             {
752 50     50   148 my( $self, $tokens ) = @_;
753            
754 50         139 my $tokpos = $self->{_tokpos};
755            
756             # // must be followed by a Step.
757 50 100 100     473 if( $tokens->[ $tokpos + 1 ] && $tokens->[ $tokpos + 1 ] eq '[' )
    50          
758             {
759             # next token is a predicate
760 18         54 return;
761             }
762             elsif( $tokens->[ $tokpos ] =~ /^\.\.?$/ )
763             {
764             # abbreviatedStep - can't optimise.
765 0         0 return;
766             }
767             else
768             {
769 32         134 my $step = $self->_step( $tokens );
770 32 100       203 if( $step->axis ne 'child' )
771             {
772             # can't optimise axes other than child for now...
773 1         755 $self->{_tokpos} = $tokpos;
774 1         9 return;
775             }
776 31         23878 $step->axis( 'descendant' );
777 31         24622 $step->axis_method( 'axis_descendant' );
778 31         24366 $self->{_tokpos}--;
779 31         171 $tokens->[ $self->{_tokpos} ] = '.';
780 31         100 return( $step );
781             }
782             }
783              
784             sub _parse
785             {
786 68     68   117 my $self = shift( @_ );
787 68         139 my $path = shift( @_ );
788              
789             # $context is something like: //*[@att2="vv"]
790             # my $context = join( '&&', $path, map { "$_=>$self->{namespaces}->{$_}" } sort keys %{$self->{namespaces}});
791 68     0   267 my $context = $self->namespaces->keys->sort->map(sub{ sprintf( '%s=>%s', $_, $self->namespaces->get( $_ ) ); })->prepend( $path )->join( '&&' );
  0         0  
792            
793 68 100       275337 return( $CACHE->{ $context } ) if( $CACHE->{ $context } );
794              
795             # my $tokens = $self->_tokenize( $path ) || return( $self->pass_error );
796 56         3258 my $tokens = $self->_tokenize( $path );
797              
798 56         152 $self->{_tokpos} = 0;
799 56         213 my $tree = $self->_analyze( $tokens );
800            
801 56 50       365 if( $self->{_tokpos} < $tokens->length )
802             {
803             # didn't manage to parse entire expression - throw an exception
804 0         0 die "Parse of expression $path failed - junk after end of expression: $tokens->[$self->{_tokpos}]";
805             # return( $self->error( "Parse of expression $path failed - junk after end of expression: $tokens->[$self->{_tokpos}]" ) );
806             }
807              
808 56         2034942 $tree->{uses_namespaces} = $self->{uses_namespaces};
809 56         7304 $tree->{strict_namespaces} = $self->{strict_namespaces};
810            
811 56         214 $CACHE->{ $context } = $tree;
812            
813 56 50       584 if( $self->debug )
814             {
815 0         0 my( $p, $f, $l ) = caller;
816             }
817 56         1546 return( $tree );
818             }
819              
820             sub _relative_location_path
821             {
822 89     89   229 my( $self, $tokens ) = @_;
823 89         144 my @steps;
824            
825 89         325 push( @steps, $self->_step( $tokens ) );
826 89         731 while( $self->_match( $tokens, qr/\/\/?/ ) )
827             {
828 23 100       174 if( $self->{_curr_match} eq '//' )
829             {
830 7         22 my $optimised = $self->_optimise_descendant_or_self( $tokens);
831 7 50       20 if( !$optimised )
832             {
833 0         0 push( @steps, $self->new_step( $self, 'descendant-or-self', $self->_class_for( 'Step' )->TEST_NT_NODE ) );
834             }
835             else
836             {
837 7         17 push( @steps, $optimised );
838             }
839             }
840 23         74 push( @steps, $self->_step( $tokens ) );
841 23 100 66     339 if( @steps > 1 &&
      66        
842             $steps[-1]->axis eq 'self' &&
843             $steps[-1]->test == $self->_class_for( 'Step' )->TEST_NT_NODE )
844             {
845 7         53 pop( @steps );
846             }
847             }
848 89         479 return( @steps );
849             }
850              
851 1443     1443   4180 sub _set_context_pos { return( shift->_set_get_scalar( 'context_pos', @_ ) ); }
852              
853 936     936   2774 sub _set_context_set { return( shift->_set_get_scalar( 'context_set', @_ ) ); }
854              
855             sub _step
856             {
857 144     144   239 my( $self, $tokens ) = @_;
858 144 100       546 if( $self->_match( $tokens, qr/\./ ) )
    100          
859             {
860             # self::node()
861 40         186 return( $self->new_step( $self, 'self', $self->_class_for( 'Step' )->TEST_NT_NODE ) );
862             }
863             elsif( $self->_match( $tokens, qr/\.\./ ) )
864             {
865             # parent::node()
866 1         28 return( $self->new_step( $self, 'parent', $self->_class_for( 'Step' )->TEST_NT_NODE ) );
867             }
868             else
869             {
870             # AxisSpecifier NodeTest Predicate(s?)
871 103         353 my $token = $tokens->[ $self->{_tokpos} ];
872            
873 103         372 my $step;
874 103 50       3289 if( $token eq 'processing-instruction' )
    100          
    50          
    100          
    100          
    50          
    50          
    50          
    50          
    50          
875             {
876 0         0 $self->{_tokpos}++;
877 0         0 $self->_match( $tokens, qr/\(/, 1 );
878 0         0 $self->_match( $tokens, $LITERAL_RE );
879 0         0 $self->{_curr_match} =~ /^["'](.*)["']$/;
880 0         0 $step = $self->new_step(
881             $self, 'child',
882             $self->_class_for( 'Step' )->TEST_NT_PI,
883             $self->new_literal( $1 )
884             );
885 0         0 $self->_match( $tokens, qr/\)/, 1 );
886             }
887             elsif( $token =~ /^\@($NC_WILD_RE|$QNAME_RE|$QN_WILD_RE)$/o )
888             {
889 32         80 $self->{_tokpos}++;
890 32 100       871 if( $token eq '@*' )
    50          
    50          
891             {
892 3         12 $step = $self->new_step(
893             $self, 'attribute',
894             $self->_class_for( 'Step' )->TEST_ATTR_ANY,
895             '*'
896             );
897             }
898             elsif( $token =~ /^\@($NC_NAME_RE):\*$/o )
899             {
900 0         0 $step = $self->new_step(
901             $self, 'attribute',
902             $self->_class_for( 'Step' )->TEST_ATTR_NCWILD,
903             $1
904             );
905             }
906             elsif( $token =~ /^\@($QNAME_RE)$/o )
907             {
908 29         126 $step = $self->new_step(
909             $self, 'attribute',
910             $self->_class_for( 'Step' )->TEST_ATTR_QNAME,
911             $1
912             );
913             }
914             }
915             # ns:*
916             elsif( $token =~ /^($NC_NAME_RE):\*$/o )
917             {
918 0         0 $self->{_tokpos}++;
919 0         0 $step = $self->new_step(
920             $self, 'child',
921             $self->_class_for( 'Step' )->TEST_NCWILD,
922             $1
923             );
924             }
925             # *
926             elsif( $token =~ /^$QN_WILD_RE$/o )
927             {
928 11         49 $self->{_tokpos}++;
929 11         64 $step = $self->new_step(
930             $self, 'child',
931             $self->_class_for( 'Step' )->TEST_ANY,
932             $token
933             );
934             }
935             # name:name
936             elsif( $token =~ /^$QNAME_RE$/o )
937             {
938 53         159 $self->{_tokpos}++;
939 53         159 $step = $self->new_step(
940             $self, 'child',
941             $self->_class_for( 'Step' )->TEST_QNAME,
942             $token
943             );
944             }
945             elsif( $token eq 'comment()' )
946             {
947 0         0 $self->{_tokpos}++;
948 0         0 $step = $self->new_step(
949             $self, 'child',
950             $self->_class_for( 'Step' )->TEST_NT_COMMENT
951             );
952             }
953             elsif( $token eq 'text()' )
954             {
955 0         0 $self->{_tokpos}++;
956 0         0 $step = $self->new_step(
957             $self, 'child',
958             $self->_class_for( 'Step' )->TEST_NT_TEXT
959             );
960             }
961             elsif( $token eq 'node()' )
962             {
963 0         0 $self->{_tokpos}++;
964 0         0 $step = $self->new_step(
965             $self, 'child',
966             $self->_class_for( 'Step' )->TEST_NT_NODE
967             );
968             }
969             elsif( $token eq 'processing-instruction()' )
970             {
971 0         0 $self->{_tokpos}++;
972 0         0 $step = $self->new_step(
973             $self, 'child',
974             $self->_class_for( 'Step' )->TEST_NT_PI
975             );
976             }
977             elsif( $token =~ /^$AXIS_NAME_RE($NC_WILD_RE|$QNAME_RE|$QN_WILD_RE|$NODE_TYPE_RE)$/o )
978             {
979 7         21 my $axis = $1;
980 7         13 $self->{_tokpos}++;
981 7         13 $token = $2;
982 7 50       223 if( $token eq 'processing-instruction' )
    50          
    100          
    50          
    0          
    0          
    0          
    0          
983             {
984 0         0 $self->_match( $tokens, qr/\(/, 1 );
985 0         0 $self->_match( $tokens, $LITERAL_RE );
986 0         0 $self->{_curr_match} =~ /^["'](.*)["']$/;
987 0         0 $step = $self->new_step(
988             $self, $axis,
989             $self->_class_for( 'Step' )->TEST_NT_PI,
990             HTML::Object::XPath::Literal->new( $1 )
991             );
992 0         0 $self->_match( $tokens, qr/\)/, 1 );
993             }
994             # ns:*
995             elsif( $token =~ /^($NC_NAME_RE):\*$/o )
996             {
997 0 0       0 $step = $self->new_step(
998             $self, $axis,
999             ( ( $axis eq 'attribute' )
1000             ? $self->_class_for( 'Step' )->TEST_ATTR_NCWILD
1001             : $self->_class_for( 'Step' )->TEST_NCWILD
1002             ),
1003             $1
1004             );
1005             }
1006             # *
1007             elsif( $token =~ /^$QN_WILD_RE$/o )
1008             {
1009 1 50       8 $step = $self->new_step(
1010             $self, $axis,
1011             ( ( $axis eq 'attribute' )
1012             ? $self->_class_for( 'Step' )->TEST_ATTR_ANY
1013             : $self->_class_for( 'Step' )->TEST_ANY
1014             ),
1015             $token
1016             );
1017             }
1018             # name:name
1019             elsif( $token =~ /^$QNAME_RE$/o )
1020             {
1021 6 50       28 $step = $self->new_step(
1022             $self, $axis,
1023             ( ( $axis eq 'attribute' )
1024             ? $self->_class_for( 'Step' )->TEST_ATTR_QNAME
1025             : $self->_class_for( 'Step' )->TEST_QNAME
1026             ),
1027             $token
1028             );
1029             }
1030             elsif( $token eq 'comment()' )
1031             {
1032 0         0 $step = $self->new_step(
1033             $self, $axis,
1034             $self->_class_for( 'Step' )->TEST_NT_COMMENT
1035             );
1036             }
1037             elsif( $token eq 'text()' )
1038             {
1039 0         0 $step = $self->new_step(
1040             $self, $axis,
1041             $self->_class_for( 'Step' )->TEST_NT_TEXT
1042             );
1043             }
1044             elsif( $token eq 'node()' )
1045             {
1046 0         0 $step = $self->new_step(
1047             $self, $axis,
1048             $self->_class_for( 'Step' )->TEST_NT_NODE
1049             );
1050             }
1051             elsif( $token eq 'processing-instruction()' )
1052             {
1053 0         0 $step = $self->new_step(
1054             $self, $axis,
1055             $self->_class_for( 'Step' )->TEST_NT_PI
1056             );
1057             }
1058             else
1059             {
1060 0         0 die( "Shouldn't get here" );
1061             }
1062             }
1063             else
1064             {
1065 0         0 die( "token $token does not match format of a 'Step'\n" );
1066             }
1067            
1068 103         1064 while( $self->_match( $tokens, qr/\[/ ) )
1069             {
1070 29         196 push( @{$step->{predicates}}, $self->_expr( $tokens ) );
  29         186  
1071 29         166 $self->_match( $tokens, qr/\]/, 1 );
1072             }
1073 103         463 return( $step );
1074             }
1075             }
1076              
1077             sub _tokenize
1078             {
1079 56     56   139 my $self = shift( @_ );
1080 56         120 my $path = shift( @_ );
1081 56         252 my $tokens = $self->new_array;
1082            
1083            
1084             # Bug: We do not allow "'@' NodeType" which is in the grammar, but I think is just plain stupid.
1085            
1086             # used to desambiguate conflicts (for REs)
1087 56         1013 my $expected = '';
1088              
1089 56         294 while( length( $path ) )
1090             {
1091 522         3103 my $token = '';
1092 522 100 66     7803 if( $expected eq 'RE' && ( $path =~ m{\G\s*($REGEXP_RE $REGEXP_MOD_RE?)\s*}gcxso ) )
    100          
1093             {
1094             # special case: regexp expected after =~ or !~, regular parsing rules do not apply
1095             # ( the / is now the regexp delimiter)
1096 3         10 $token = $1;
1097 3         6 $expected = '';
1098             }
1099             elsif( $path =~ m/\G
1100             \s* # ignore all whitespace
1101             ( # tokens
1102             $LITERAL_RE|
1103             $NUMBER_RE| # digits
1104             \.\.| # parent
1105             \.| # current
1106             ($AXIS_NAME_RE)?$NODE_TYPE_RE| # tests
1107             processing-instruction|
1108             \@($NC_WILD_RE|$QNAME_RE|$QN_WILD_RE)| # attrib
1109             \$$QNAME_RE| # variable reference
1110             ($AXIS_NAME_RE)?($NC_WILD_RE|$QNAME_RE|$QN_WILD_RE)| # NCName,NodeType,Axis::Test
1111             \!=|<=|\-|>=|\/\/|and|or|mod|div| # multi-char seps
1112             =~|\!~| # regexp (not in the XPath spec)
1113             [,\+=\|<>\/\(\[\]\)]| # single char seps
1114             (?<!(\@|\(|\[))\*| # multiply operator rules (see xpath spec)
1115             (?<!::)\*|
1116             $ # end of query
1117             )
1118             \s* # ignore all whitespace
1119             /gcxso )
1120             {
1121 463         870 $token = $1;
1122 463 100       776 $expected = ( $token =~ m{^[=!]~$} ) ? 'RE' : '';
1123             }
1124             else
1125             {
1126 56         131 $token = '';
1127 56         162 last;
1128             }
1129              
1130 466 100       861 if( length( $token ) )
1131             {
1132             # push( @tokens, $token );
1133 410         827 $tokens->push( $token );
1134             }
1135             }
1136            
1137 56 50       184 if( pos( $path ) < length( $path ) )
1138             {
1139 0         0 my $marker = ( '.' x ( pos( $path ) -1 ) );
1140 0         0 $path = substr( $path, 0, pos( $path ) + 8 ) . '...';
1141 0         0 $path =~ s/\n/ /g;
1142 0         0 $path =~ s/\t/ /g;
1143 0         0 die "Query:\n", "$path\n", $marker, "^^^\n", "Invalid query somewhere around here (I think)\n";
1144             # return( $self->error( "Query:\n", "$path\n", $marker, "^^^\n", "Invalid query somewhere around here (I think)" ) );
1145             }
1146             # return( \@tokens );
1147 56         155 return( $tokens );
1148             }
1149              
1150             sub _xml_escape_text
1151             {
1152 0     0     my( $self, $text ) = @_;
1153 0           my $entities = { '&' => '&amp;', '<' => '&lt;', '>' => '&gt;', '"' => '&quote;' };
1154 0           $text =~ s{([&<>])}{$entities->{$1}}g;
1155 0           return( $text );
1156             }
1157              
1158             1;
1159             # NOTE: POD
1160             __END__
1161              
1162             =encoding utf-8
1163              
1164             =head1 NAME
1165              
1166             HTML::Object::XPath - HTML Object XPath Class
1167              
1168             =head1 SYNOPSIS
1169              
1170             use HTML::Object;
1171             use HTML::Object::XQuery;
1172             use HTML::Object::XPath;
1173             my $this = HTML::Object::XPath->new || die( HTML::Object::XPath->error, "\n" );
1174              
1175             my $p = HTML::Object->new;
1176             my $doc = $p->parse_file( $path_to_html_file ) || die( $p->error );
1177             # Returns a list of HTML::Object::Element objects matching the select, which is
1178             # converted into a xpath
1179             my @nodes = $doc->find( 'p' );
1180              
1181             # or directly:
1182             use HTML::Object::XPath;
1183             my $xp = use HTML::Object::XPath->new;
1184             my @nodes = $xp->findnodes( $xpath, $element_object );
1185              
1186             =head1 VERSION
1187              
1188             v0.2.0
1189              
1190             =head1 DESCRIPTION
1191              
1192             This module implements the XPath engine used by L<HTML::Object::XQuery> to provide a jQuery-like interface to query the parsed DOM object.
1193              
1194             =head1 METHODS
1195              
1196             =head2 clear_namespaces
1197              
1198             Clears all previously set namespace mappings.
1199              
1200             =head2 exists
1201              
1202             Provided with a C<path> and a C<context> and this returns true if the given path exists.
1203              
1204             =head2 findnodes
1205              
1206             Provided with a C<path> and a C<context> this returns a list of nodes found by C<path>, optionally in context C<context>.
1207              
1208             In scalar context it returns an HTML::Object::XPath::NodeSet object.
1209              
1210             =head2 findnodes_as_string
1211              
1212             Provided with a C<path> and a C<context> and this returns the nodes found as a single string. The result is not guaranteed to be valid HTML though (it could for example be just text if the query returns attribute values).
1213              
1214             =head2 findnodes_as_strings
1215              
1216             Provided with a C<path> and a C<context> and this returns the nodes found as a list of strings, one per node found.
1217              
1218             =head2 findvalue
1219              
1220             Provided with a C<path> and a C<context> and this returns the result as a string (the concatenation of the values of the result nodes).
1221              
1222             =head2 findvalues
1223              
1224             Provided with a C<path> and a C<context> and this returns the values of the result nodes as a list of strings.
1225              
1226             =head2 matches($node, $path, $context)
1227              
1228             Provided with a C<node> L<object|HTML::Object::Element>, C<path> and a C<context> and this returns true if the node matches the path.
1229              
1230             =head2 find
1231              
1232             Provided with a C<path> and a C<context> and this returns either a L<HTML::Object::XPath::NodeSet> object containing the nodes it found (or empty if no nodes matched the path), or one of L<HTML::Object::XPath::Literal> (a string), L<HTML::Object::XPath::Number>, or L<HTML::Object::XPath::Boolean>. It should always return something - and you can use ->isa() to find out what it returned. If you need to check how many nodes it found you should check $nodeset->size.
1233              
1234             See L<HTML::Object::XPath::NodeSet>.
1235              
1236             =head2 get_namespace ($prefix, $node)
1237              
1238             Provided with a C<prefix> and a C<node> L<object|HTML::Object::Element> and this returns the uri associated to the prefix for the node (mostly for internal usage)
1239              
1240             =head2 get_var
1241              
1242             Provided with a variable name, and this returns the value of the XPath variable (mostly for internal usage)
1243              
1244             =head2 getNodeText
1245              
1246             Provided with a C<path> and this returns the text string for a particular node. It returns a string, or C<undef> if the node does not exist.
1247              
1248             =head2 namespaces
1249              
1250             Sets or gets an hash reference of namespace attributes.
1251              
1252             =head2 new_expr
1253              
1254             Create a new L<HTML::Object::XPath::Expr>, passing it whatever argument was provided, and returns the newly instantiated object, or C<undef> upon L<error|Module::Generic/error>
1255              
1256             =head2 new_function
1257              
1258             Create a new L<HTML::Object::XPath::Function> object, passing it whatever argument was provided, and returns the newly instantiated object, or C<undef> upon L<error|Module::Generic/error>
1259              
1260             =head2 new_literal
1261              
1262             Create a new L<HTML::Object::XPath::Literal> object, passing it whatever argument was provided, and returns the newly instantiated object, or C<undef> upon L<error|Module::Generic/error>
1263              
1264             =head2 new_location_path
1265              
1266             Create a new L<HTML::Object::XPath::LocationPath> object, passing it whatever argument was provided, and returns the newly instantiated object, or C<undef> upon L<error|Module::Generic/error>
1267              
1268             =head2 new_nodeset
1269              
1270             Create a new L<HTML::Object::XPath::NodeSet> object, passing it whatever argument was provided, and returns the newly instantiated object, or C<undef> upon L<error|Module::Generic/error>
1271              
1272             =head2 new_number
1273              
1274             Create a new L<HTML::Object::XPath::Number> object, passing it whatever argument was provided, and returns the newly instantiated object, or C<undef> upon L<error|Module::Generic/error>
1275              
1276             =head2 new_root
1277              
1278             Create a new L<HTML::Object::XPath::Root> object, passing it whatever argument was provided, and returns the newly instantiated object, or C<undef> upon L<error|Module::Generic/error>
1279              
1280             =head2 new_step
1281              
1282             Create a new L<HTML::Object::XPath::Step> object, passing it whatever argument was provided, and returns the newly instantiated object, or C<undef> upon L<error|Module::Generic/error>
1283              
1284             =head2 new_variable
1285              
1286             Create a new L<HTML::Object::XPath::Variable> object, passing it whatever argument was provided, and returns the newly instantiated object, or C<undef> upon L<error|Module::Generic/error>
1287              
1288             =head2 set_namespace
1289              
1290             Provided with a C<prefix> and an C<uri> and this sets the namespace prefix mapping to the uri.
1291              
1292             Normally in L<HTML::Object::XPath> the prefixes in XPath node tests take their context from the current node. This means that foo:bar will always match an element <foo:bar> regardless of the namespace that the prefix foo is mapped to (which might even change within the document, resulting in unexpected results). In order to make prefixes in XPath node tests actually map to a real URI, you need to enable that via a call to the set_namespace method of your HTML::Object::XPath object.
1293              
1294             =head2 parse
1295              
1296             Provided with an XPath expression and this returns a new L<HTML::Object::XPath::Expr> object that can then be used repeatedly.
1297              
1298             You can create an XPath expression from a CSS selector expression using L<HTML::selector::XPath>
1299              
1300             =head2 set_strict_namespaces
1301              
1302             Takes a boolean value.
1303              
1304             By default, for historical as well as convenience reasons, L<HTML::Object::XPath> has a slightly non-standard way of dealing with the default namespace.
1305              
1306             If you search for C<//tag> it will return elements C<tag>. As far as I understand it, if the document has a default namespace, this should not return anything. You would have to first do a C<set_namespace>, and then search using the namespace.
1307              
1308             Passing a true value to C<set_strict_namespaces> will activate this behaviour, passing a false value will return it to its default behaviour.
1309              
1310             =head2 set_var
1311              
1312             Provided with a variable name and its value and this sets an XPath variable (that can be used in queries as C<$var>)
1313              
1314             =head1 NODE STRUCTURE
1315              
1316             All nodes have the same first 2 entries in the array: node_parent and node_pos. The type of the node is determined using the ref() function.
1317              
1318             The node_parent always contains an entry for the parent of the current node - except for the root node which has undef in there. And node_pos is the position of this node in the array that it is in (think: $node == $node->[node_parent]->[node_children]->[$node->[node_pos]] )
1319              
1320             Nodes are structured as follows:
1321              
1322             =head2 Root Node
1323              
1324             The L<root node|HTML::Object::Root> is just an element node with no parent.
1325              
1326             [
1327             undef, # node_parent - check for undef to identify root node
1328             undef, # node_pos
1329             undef, # node_prefix
1330             [ ... ], # node_children (see below)
1331             ]
1332              
1333             =head2 L<Element|HTML::Object::Element> Node
1334              
1335             [
1336             $parent, # node_parent
1337             <position in current array>, # node_pos
1338             'xxx', # node_prefix - namespace prefix on this element
1339             [ ... ], # node_children
1340             'yyy', # node_name - element tag name
1341             [ ... ], # node_attribs - attributes on this element
1342             [ ... ], # node_namespaces - namespaces currently in scope
1343             ]
1344              
1345             =head2 L<Attribute|HTML::Object::Attribute> Node
1346              
1347             [
1348             $parent, # node_parent - the element node
1349             <position in current array>, # node_pos
1350             'xxx', # node_prefix - namespace prefix on this element
1351             'href', # node_key - attribute name
1352             'ftp://ftp.com/', # node_value - value in the node
1353             ]
1354              
1355             =head2 L<Text|HTML::Object::Text> Nodes
1356              
1357             [
1358             $parent,
1359             <pos>,
1360             'This is some text' # node_text - the text in the node
1361             ]
1362              
1363             =head2 L<Comment|HTML::Object::Comment> Nodes
1364              
1365             [
1366             $parent,
1367             <pos>,
1368             'This is a comment' # node_comment
1369             ]
1370              
1371             =head1 AUTHOR
1372              
1373             Jacques Deguest E<lt>F<jack@deguest.jp>E<gt>
1374              
1375             =head1 SEE ALSO
1376              
1377             L<HTML::Object::XPath::Boolean>, L<HTML::Object::XPath::Expr>, L<HTML::Object::XPath::Function>, L<HTML::Object::XPath::Literal>, L<HTML::Object::XPath::LocationPath>, L<HTML::Object::XPath::NodeSet>, L<HTML::Object::XPath::Number>, L<HTML::Object::XPath::Root>, L<HTML::Object::XPath::Step>, L<HTML::Object::XPath::Variable>
1378              
1379             L<Mozilla documentation|https://developer.mozilla.org/en-US/docs/Web/XPath/Introduction_to_using_XPath_in_JavaScript>
1380              
1381             =head1 COPYRIGHT & LICENSE
1382              
1383             Copyright(c) 2021 DEGUEST Pte. Ltd.
1384              
1385             You can use, copy, modify and redistribute this package and associated files under the same terms as Perl itself.
1386              
1387             =cut