File Coverage

blib/lib/XML/Twig/XPath.pm
Criterion Covered Total %
statement 7 9 77.7
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 10 12 83.3


line stmt bran cond sub pod time code
1             # $Id: /xmltwig/trunk/Twig/XPath.pm 32 2008-01-18T13:11:52.128782Z mrodrigu $
2             package XML::Twig::XPath;
3 36     36   145595 use strict;
  36         68  
  36         1274  
4 36     36   238 use warnings;
  36         66  
  36         1217  
5 36     36   97020 use XML::Twig;
  0            
  0            
6              
7             my $XPATH; # XPath engine (XML::XPath or XML::XPathEngine);
8             my $XPATH_NUMBER; # <$XPATH>::Number, the XPath number class
9             BEGIN
10             { foreach my $xpath_engine ( qw( XML::XPathEngine XML::XPath) )
11             { if( XML::Twig::_use( $xpath_engine) ) { $XPATH= $xpath_engine; last; } }
12             unless( $XPATH) { die "cannot use XML::Twig::XPath: neither XML::XPathEngine 0.09+ nor XML::XPath are available"; }
13             $XPATH_NUMBER= "${XPATH}::Number";
14             }
15              
16              
17             use vars qw($VERSION);
18             $VERSION="0.02";
19              
20             BEGIN
21             { package # hide from PAUSE
22             XML::XPath::NodeSet;
23             no warnings; # to avoid the "Subroutine sort redefined" message
24             # replace the native sort routine by a Twig'd one
25             sub sort
26             { my $self = CORE::shift;
27             @$self = CORE::sort { $a->node_cmp( $b) } @$self;
28             return $self;
29             }
30              
31             package # hide from PAUSE
32             XML::XPathEngine::NodeSet;
33             no warnings; # to avoid the "Subroutine sort redefined" message
34             # replace the native sort routine by a Twig'd one
35             sub sort
36             { my $self = CORE::shift;
37             @$self = CORE::sort { $a->node_cmp( $b) } @$self;
38             return $self;
39             }
40             }
41              
42             package XML::Twig::XPath;
43              
44             use base 'XML::Twig';
45              
46             my $XP; # the global xp object;
47              
48             sub to_number { return $XPATH_NUMBER->new( $_[0]->root->text); }
49              
50             sub new
51             { my $class= shift;
52             my $t= XML::Twig->new( elt_class => 'XML::Twig::XPath::Elt', @_);
53             $t->{twig_xp}= $XPATH->new();
54             bless $t, $class;
55             return $t;
56             }
57              
58              
59             sub set_namespace { my $t= shift; $t->{twig_xp}->set_namespace( @_); }
60             sub set_strict_namespaces { my $t= shift; $t->{twig_xp}->set_strict_namespaces( @_); }
61              
62             sub node_cmp($$) { return $_[1] == $_[0] ? 0 : -1; } # document is before anything but itself
63              
64             sub isElementNode { 0 }
65             sub isAttributeNode { 0 }
66             sub isTextNode { 0 }
67             sub isProcessingInstructionNode { 0 }
68             sub isPINode { 0 }
69             sub isCommentNode { 0 }
70             sub isNamespaceNode { 0 }
71             sub getAttributes { [] }
72             sub getValue { return $_[0]->root->text; }
73              
74             sub findnodes { my( $t, $path)= @_; return $t->{twig_xp}->findnodes( $path, $t); }
75             sub findnodes_as_string { my( $t, $path)= @_; return $t->{twig_xp}->findnodes_as_string( $path, $t); }
76             sub findvalue { my( $t, $path)= @_; return $t->{twig_xp}->findvalue( $path, $t); }
77             sub exists { my( $t, $path)= @_; return $t->{twig_xp}->exists( $path, $t); }
78             sub find { my( $t, $path)= @_; return $t->{twig_xp}->find( $path, $t); }
79             sub matches { my( $t, $path, $node)= @_; $node ||= $t; return $t->{twig_xp}->matches( $node, $path, $t) || 0; }
80              
81             sub getNamespaces { $_[0]->root->getNamespaces(); }
82              
83             #TODO: it would be nice to be able to pass in any object in this
84             #distribution and cast it to the proper $XPATH class to use as a
85             #variable (via 'nodes' argument or something)
86             sub set_var {
87             my ($t, $name, $value) = @_;
88             if( ! ref $value) { $value= $t->findnodes( qq{"$value"}); }
89             $t->{twig_xp}->set_var($name, $value);
90             }
91              
92             1;
93              
94             # adds the appropriate methods to XML::Twig::Elt so XML::XPath can be used as the XPath engine
95             package XML::Twig::XPath::Elt;
96             use base 'XML::Twig::Elt';
97              
98             *getLocalName= *XML::Twig::Elt::local_name;
99             *getValue = *XML::Twig::Elt::text;
100             sub isAttributeNode { 0 }
101             sub isNamespaceNode { 0 }
102              
103             sub to_number { return $XPATH_NUMBER->new( $_[0]->text); }
104              
105             sub getAttributes
106             { my $elt= shift;
107             my $atts= $elt->atts;
108             # alternate, faster but less clean, way
109             my @atts= map { bless( { name => $_, value => $atts->{$_}, elt => $elt },
110             'XML::Twig::XPath::Attribute')
111             }
112             sort keys %$atts;
113             # my @atts= map { XML::Twig::XPath::Attribute->new( $elt, $_) } sort keys %$atts;
114             return wantarray ? @atts : \@atts;
115             }
116              
117             sub getNamespace
118             { my $elt= shift;
119             my $prefix= shift() || $elt->ns_prefix;
120             if( my $expanded= $elt->namespace( $prefix))
121             { return XML::Twig::XPath::Namespace->new( $prefix, $expanded); }
122             else
123             { return XML::Twig::XPath::Namespace->new( $prefix, ''); }
124             }
125              
126             # returns namespaces declared in the element
127             sub getNamespaces #_get_namespaces
128             { my( $elt)= @_;
129             my @namespaces;
130             foreach my $att ($elt->att_names)
131             { if( $att=~ m{^xmlns(?::(\w+))?$})
132             { my $prefix= $1 || '';
133             my $expanded= $elt->att( $att);
134             push @namespaces, XML::Twig::XPath::Namespace->new( $prefix, $expanded);
135             }
136             }
137             return wantarray() ? @namespaces : \@namespaces;
138             }
139              
140             sub node_cmp($$)
141             { my( $a, $b)= @_;
142             if( UNIVERSAL::isa( $b, 'XML::Twig::XPath::Elt'))
143             { # 2 elts, compare them
144             return $a->cmp( $b);
145             }
146             elsif( UNIVERSAL::isa( $b, 'XML::Twig::XPath::Attribute'))
147             { # elt <=> att, compare the elt to the att->{elt}
148             # if the elt is the att->{elt} (cmp return 0) then -1, elt is before att
149             return ($a->cmp( $b->{elt}) ) || -1 ;
150             }
151             elsif( UNIVERSAL::isa( $b, 'XML::Twig::XPath'))
152             { # elt <=> document, elt is after document
153             return 1;
154             }
155             else
156             { die "unknown node type ", ref( $b); }
157             }
158              
159             sub getParentNode
160             { return $_[0]->_parent
161             || $_[0]->twig;
162             }
163              
164             sub findnodes { my( $elt, $path)= @_; return $elt->twig->{twig_xp}->findnodes( $path, $elt); }
165             sub findnodes_as_string { my( $elt, $path)= @_; return $elt->twig->{twig_xp}->findnodes_as_string( $path, $elt); }
166             sub findvalue { my( $elt, $path)= @_; return $elt->twig->{twig_xp}->findvalue( $path, $elt); }
167             sub exists { my( $elt, $path)= @_; return $elt->twig->{twig_xp}->exists( $path, $elt); }
168             sub find { my( $elt, $path)= @_; return $elt->twig->{twig_xp}->find( $path, $elt); }
169             sub matches { my( $elt, $path)= @_; return $elt->twig->{twig_xp}->matches( $elt, $path, $elt->getParentNode) || 0; }
170              
171              
172             1;
173              
174             # this package is only used to allow XML::XPath as the XPath engine, otherwise
175             # attributes are just attached to their parent element and are not considered objects
176              
177             package XML::Twig::XPath::Attribute;
178              
179             sub new
180             { my( $class, $elt, $att)= @_;
181             return bless { name => $att, value => $elt->att( $att), elt => $elt }, $class;
182             }
183              
184             sub getValue { return $_[0]->{value}; }
185             sub getName { return $_[0]->{name} ; }
186             sub getLocalName { (my $name= $_[0]->{name}) =~ s{^.*:}{}; $name; }
187             sub string_value { return $_[0]->{value}; }
188             sub to_number { return $XPATH_NUMBER->new( $_[0]->{value}); }
189             sub isElementNode { 0 }
190             sub isAttributeNode { 1 }
191             sub isNamespaceNode { 0 }
192             sub isTextNode { 0 }
193             sub isProcessingInstructionNode { 0 }
194             sub isPINode { 0 }
195             sub isCommentNode { 0 }
196             sub toString { return qq{$_[0]->{name}="$_[0]->{value}"}; }
197              
198             sub getNamespace
199             { my $att= shift;
200             my $prefix= shift();
201             if( ! defined( $prefix))
202             { if($att->{name}=~ m{^(.*):}) { $prefix= $1; }
203             else { $prefix=''; }
204             }
205              
206             if( my $expanded= $att->{elt}->namespace( $prefix))
207             { return XML::Twig::XPath::Namespace->new( $prefix, $expanded); }
208             }
209              
210             sub node_cmp($$)
211             { my( $a, $b)= @_;
212             if( UNIVERSAL::isa( $b, 'XML::Twig::XPath::Attribute'))
213             { # 2 attributes, compare their elements, then their name
214             return ($a->{elt}->cmp( $b->{elt}) ) || ($a->{name} cmp $b->{name});
215             }
216             elsif( UNIVERSAL::isa( $b, 'XML::Twig::XPath::Elt'))
217             { # att <=> elt : compare the att->elt and the elt
218             # if att->elt is the elt (cmp returns 0) then 1 (elt is before att)
219             return ($a->{elt}->cmp( $b) ) || 1 ;
220             }
221             elsif( UNIVERSAL::isa( $b, 'XML::Twig::XPath'))
222             { # att <=> document, att is after document
223             return 1;
224             }
225             else
226             { die "unknown node type ", ref( $b); }
227             }
228              
229             *cmp=*node_cmp;
230              
231             1;
232              
233             package XML::Twig::XPath::Namespace;
234              
235             sub new
236             { my( $class, $prefix, $expanded)= @_;
237             bless { prefix => $prefix, expanded => $expanded }, $class;
238             }
239              
240             sub isNamespaceNode { 1; }
241              
242             sub getPrefix { $_[0]->{prefix}; }
243             sub getExpanded { $_[0]->{expanded}; }
244             sub getValue { $_[0]->{expanded}; }
245             sub getData { $_[0]->{expanded}; }
246              
247             sub node_cmp($$)
248             { my( $a, $b)= @_;
249             if( UNIVERSAL::isa( $b, 'XML::Twig::XPath::Namespace'))
250             { # 2 attributes, compare their elements, then their name
251             return $a->{prefix} cmp $b->{prefix};
252             }
253             else
254             { die "unknown node type ", ref( $b); }
255             }
256              
257             *cmp=*node_cmp;
258              
259             1
260