| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
# ex: set tabstop=4: |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
=head1 NAME |
|
4
|
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
XML::TinyXML::Selector::XPath - XPath-compliant selector for XML::TinyXML |
|
6
|
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
=over 4 |
|
10
|
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
use XML::TinyXML; |
|
12
|
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
# first obtain an xml context: |
|
14
|
|
|
|
|
|
|
$xml = XML::TinyXML->new("rootnode", param => "somevalue", attrs => { attr1 => v1, attr2 => v2 }); |
|
15
|
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
$selector = XML::TinyXML::Selector->new($xml, "XPath"); |
|
17
|
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
##### |
|
19
|
|
|
|
|
|
|
Assuming the following xml data : |
|
20
|
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
world |
|
23
|
|
|
|
|
|
|
|
|
24
|
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
SECOND |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
##### |
|
36
|
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
@res = $selector->select('//parent'); |
|
38
|
|
|
|
|
|
|
@res = $selector->select('//child*'); |
|
39
|
|
|
|
|
|
|
@res = $selector->select('/parent[2]/blah/..'); |
|
40
|
|
|
|
|
|
|
@res = $selector->select('//blah/..'); |
|
41
|
|
|
|
|
|
|
@res = $selector->select('//parent[1]/..'); |
|
42
|
|
|
|
|
|
|
@res = $selector->select('//parent[1]/.'); |
|
43
|
|
|
|
|
|
|
@res = $selector->select('//blah/.'); |
|
44
|
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
# or using the unabbreviated syntax: |
|
46
|
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
@res = $selector->select('/descendant-or-self::node()/child::parent'); |
|
48
|
|
|
|
|
|
|
@res = $selector->select('/descendant-or-self::node()/child::child*'); |
|
49
|
|
|
|
|
|
|
@res = $selector->select('/child::parent[2]/child::blah/parent::node()'); |
|
50
|
|
|
|
|
|
|
@res = $selector->select('/descendant-or-self::node()/child::blah/parent::node()'); |
|
51
|
|
|
|
|
|
|
@res = $selector->select('/descendant-or-self::node()/child::parent[1]/parent::node()'); |
|
52
|
|
|
|
|
|
|
@res = $selector->select('/descendant-or-self::node()/child::parent[1]/self::node()'); |
|
53
|
|
|
|
|
|
|
@res = $selector->select('/descendant-or-self::node()/child::blah/self::node()'); |
|
54
|
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
|
|
56
|
|
|
|
|
|
|
# refer to XPath documentation for further examples and details on the supported syntax: |
|
57
|
|
|
|
|
|
|
# ( http://www.w3.org/TR/xpath ) |
|
58
|
|
|
|
|
|
|
|
|
59
|
|
|
|
|
|
|
=back |
|
60
|
|
|
|
|
|
|
|
|
61
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
62
|
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
XPath-compliant selector for XML::TinyXML |
|
64
|
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
=head1 INSTANCE VARIABLES |
|
66
|
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
=over 4 |
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
=back |
|
70
|
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
=head1 METHODS |
|
72
|
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=over 4 |
|
74
|
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
=cut |
|
76
|
|
|
|
|
|
|
package XML::TinyXML::Selector::XPath; |
|
77
|
|
|
|
|
|
|
|
|
78
|
4
|
|
|
4
|
|
716
|
use strict; |
|
|
4
|
|
|
|
|
8
|
|
|
|
4
|
|
|
|
|
141
|
|
|
79
|
4
|
|
|
4
|
|
22
|
use warnings; |
|
|
4
|
|
|
|
|
6
|
|
|
|
4
|
|
|
|
|
238
|
|
|
80
|
4
|
|
|
4
|
|
21
|
use base qw(XML::TinyXML::Selector); |
|
|
4
|
|
|
|
|
6
|
|
|
|
4
|
|
|
|
|
420
|
|
|
81
|
4
|
|
|
4
|
|
5974
|
use XML::TinyXML::Selector::XPath::Context; |
|
|
4
|
|
|
|
|
10
|
|
|
|
4
|
|
|
|
|
341
|
|
|
82
|
4
|
|
|
4
|
|
2481
|
use XML::TinyXML::Selector::XPath::Functions; |
|
|
4
|
|
|
|
|
10
|
|
|
|
4
|
|
|
|
|
134
|
|
|
83
|
4
|
|
|
4
|
|
3350
|
use XML::TinyXML::Selector::XPath::Axes; |
|
|
4
|
|
|
|
|
12
|
|
|
|
4
|
|
|
|
|
18097
|
|
|
84
|
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
our $VERSION = '0.34'; |
|
86
|
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
#our @ExprTokens = ('(', ')', '[', ']', '.', '..', '@', ',', '::'); |
|
88
|
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
my @NODE_FUNCTIONS = qw( |
|
90
|
|
|
|
|
|
|
last |
|
91
|
|
|
|
|
|
|
position |
|
92
|
|
|
|
|
|
|
count |
|
93
|
|
|
|
|
|
|
id |
|
94
|
|
|
|
|
|
|
local-name |
|
95
|
|
|
|
|
|
|
namespace-uri |
|
96
|
|
|
|
|
|
|
name |
|
97
|
|
|
|
|
|
|
); |
|
98
|
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
my @STRING_FUNCTIONS = qw( |
|
100
|
|
|
|
|
|
|
string |
|
101
|
|
|
|
|
|
|
concat |
|
102
|
|
|
|
|
|
|
starts-with |
|
103
|
|
|
|
|
|
|
contains |
|
104
|
|
|
|
|
|
|
substring-before |
|
105
|
|
|
|
|
|
|
substring-after |
|
106
|
|
|
|
|
|
|
substring |
|
107
|
|
|
|
|
|
|
string-length |
|
108
|
|
|
|
|
|
|
normalize-space |
|
109
|
|
|
|
|
|
|
translate |
|
110
|
|
|
|
|
|
|
boolean |
|
111
|
|
|
|
|
|
|
not |
|
112
|
|
|
|
|
|
|
true |
|
113
|
|
|
|
|
|
|
false |
|
114
|
|
|
|
|
|
|
lang |
|
115
|
|
|
|
|
|
|
); |
|
116
|
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
my @NUMBER_FUNCTIONS = qw( |
|
118
|
|
|
|
|
|
|
number |
|
119
|
|
|
|
|
|
|
sum |
|
120
|
|
|
|
|
|
|
floor |
|
121
|
|
|
|
|
|
|
ceiling |
|
122
|
|
|
|
|
|
|
round |
|
123
|
|
|
|
|
|
|
); |
|
124
|
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
our @AllFunctions = (@NODE_FUNCTIONS, @STRING_FUNCTIONS, @NUMBER_FUNCTIONS); |
|
126
|
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
our @Axes = qw( |
|
128
|
|
|
|
|
|
|
child |
|
129
|
|
|
|
|
|
|
descendant |
|
130
|
|
|
|
|
|
|
parent |
|
131
|
|
|
|
|
|
|
ancestor |
|
132
|
|
|
|
|
|
|
following-sibling |
|
133
|
|
|
|
|
|
|
preceding-sibling |
|
134
|
|
|
|
|
|
|
following |
|
135
|
|
|
|
|
|
|
preceding |
|
136
|
|
|
|
|
|
|
attribute |
|
137
|
|
|
|
|
|
|
namespace |
|
138
|
|
|
|
|
|
|
self |
|
139
|
|
|
|
|
|
|
descendant-or-self |
|
140
|
|
|
|
|
|
|
ancestor-or-self |
|
141
|
|
|
|
|
|
|
); |
|
142
|
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
=item * init () |
|
144
|
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
=cut |
|
146
|
|
|
|
|
|
|
sub init { |
|
147
|
4
|
|
|
4
|
1
|
13
|
my ($self, %args) = @_; |
|
148
|
4
|
|
|
|
|
42
|
$self->{context} = XML::TinyXML::Selector::XPath::Context->new($self->{_xml}); |
|
149
|
4
|
|
|
|
|
24
|
return $self; |
|
150
|
|
|
|
|
|
|
} |
|
151
|
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
=item * select ($expr, [ $cnode ]) |
|
153
|
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
=cut |
|
155
|
|
|
|
|
|
|
sub select { |
|
156
|
42
|
|
|
42
|
1
|
3321
|
my ($self, $expr) = @_; |
|
157
|
42
|
|
|
|
|
94
|
my $expanded_expr = $self->_expand_abbreviated($expr); |
|
158
|
42
|
|
|
|
|
104
|
my $set = $self->_select_unabbreviated($expanded_expr); |
|
159
|
42
|
50
|
|
|
|
138
|
if ($set) { |
|
160
|
|
|
|
|
|
|
return wantarray |
|
161
|
42
|
0
|
|
|
|
226
|
? @$set |
|
|
|
50
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
: (scalar(@$set > 1) |
|
163
|
|
|
|
|
|
|
? $set |
|
164
|
|
|
|
|
|
|
: @$set[0]) |
|
165
|
|
|
|
|
|
|
} |
|
166
|
|
|
|
|
|
|
} |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
sub context { |
|
169
|
657
|
|
|
657
|
0
|
816
|
my $self = shift; |
|
170
|
657
|
|
|
|
|
2237
|
return $self->{context}; |
|
171
|
|
|
|
|
|
|
} |
|
172
|
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
sub functions { |
|
174
|
20
|
|
|
20
|
0
|
1180
|
my $self = shift; |
|
175
|
20
|
100
|
|
|
|
100
|
return wantarray?@AllFunctions:__PACKAGE__."::Functions"; |
|
176
|
|
|
|
|
|
|
} |
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
sub resetContext { |
|
179
|
19
|
|
|
19
|
0
|
4885
|
my $self = shift; |
|
180
|
19
|
|
|
|
|
80
|
$self->{context} = XML::TinyXML::Selector::XPath::Context->new($self->{_xml}); |
|
181
|
|
|
|
|
|
|
} |
|
182
|
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
###### PRIVATE METHODS ###### |
|
184
|
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
sub _expand_abbreviated { |
|
186
|
51
|
|
|
51
|
|
228
|
my ($self, $expr) = @_; |
|
187
|
|
|
|
|
|
|
|
|
188
|
51
|
|
|
|
|
162
|
$expr =~ s/\/\//\/descendant-or-self::node()\//g; |
|
189
|
51
|
|
|
|
|
182
|
my @tokens = split('/', $expr); |
|
190
|
|
|
|
|
|
|
|
|
191
|
51
|
|
|
|
|
137
|
foreach my $i (0..$#tokens) { |
|
192
|
109
|
|
|
|
|
169
|
my $t = $tokens[$i]; |
|
193
|
109
|
100
|
|
|
|
232
|
next unless ($t); |
|
194
|
87
|
100
|
|
|
|
268
|
if($t !~ /::/) { |
|
195
|
43
|
100
|
|
|
|
129
|
$t = "child::$tokens[$i]" if ($t !~ /\./); |
|
196
|
43
|
|
|
|
|
73
|
$t =~ s/\@/attribute::/g; |
|
197
|
43
|
|
|
|
|
104
|
$t =~ s/\.\./parent::node()/g; |
|
198
|
43
|
|
|
|
|
69
|
$t =~ s/\./self::node()/g; |
|
199
|
43
|
|
|
|
|
110
|
$tokens[$i] = $t; |
|
200
|
|
|
|
|
|
|
} |
|
201
|
|
|
|
|
|
|
} |
|
202
|
51
|
|
|
|
|
209
|
join('/', @tokens); |
|
203
|
|
|
|
|
|
|
} |
|
204
|
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
sub _exec_function { |
|
206
|
8
|
|
|
8
|
|
15
|
my ($self, $fun, @args) = @_; |
|
207
|
8
|
50
|
|
|
|
249
|
unless(grep(/^$fun$/, @AllFunctions)) { |
|
208
|
0
|
|
|
|
|
0
|
warn "Unsupported Function: '$fun'"; |
|
209
|
0
|
|
|
|
|
0
|
return undef; |
|
210
|
|
|
|
|
|
|
} |
|
211
|
8
|
|
|
|
|
26
|
$fun =~ s/-/_/g; |
|
212
|
8
|
|
|
|
|
43
|
return XML::TinyXML::Selector::XPath::Functions->$fun($self->{context}, @args); |
|
213
|
|
|
|
|
|
|
} |
|
214
|
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
# Priveate method |
|
216
|
|
|
|
|
|
|
sub _expand_axis { |
|
217
|
92
|
|
|
92
|
|
122
|
my ($self, $axis) = @_; |
|
218
|
92
|
50
|
|
|
|
366
|
if ($axis =~ /(\S+)\s+(\S+)\s+(\S+)/) { |
|
219
|
0
|
|
|
|
|
0
|
my $a1 = $1; |
|
220
|
0
|
|
|
|
|
0
|
my $op = $2; |
|
221
|
0
|
|
|
|
|
0
|
my $a2 = $3; |
|
222
|
|
|
|
|
|
|
|
|
223
|
0
|
|
|
|
|
0
|
my $i1 = $self->_expand_axis($a1); |
|
224
|
0
|
|
|
|
|
0
|
my $i2 = $self->_expand_axis($a2); |
|
225
|
0
|
|
|
|
|
0
|
return $self->context->operators->{$op}->($i1, $i2); |
|
226
|
|
|
|
|
|
|
} else { |
|
227
|
92
|
50
|
|
|
|
2073
|
unless(grep(/^$axis$/, @Axes)) { |
|
228
|
0
|
|
|
|
|
0
|
warn "Unsupported Axis: '$axis'"; |
|
229
|
0
|
|
|
|
|
0
|
return undef; |
|
230
|
|
|
|
|
|
|
} |
|
231
|
92
|
|
|
|
|
156
|
$axis =~ s/-/_/g; |
|
232
|
92
|
|
|
|
|
409
|
return XML::TinyXML::Selector::XPath::Axes->$axis($self->{context}); |
|
233
|
|
|
|
|
|
|
} |
|
234
|
|
|
|
|
|
|
} |
|
235
|
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
sub _unescape { |
|
237
|
14
|
|
|
14
|
|
24
|
my ($self, $string) = @_; |
|
238
|
|
|
|
|
|
|
|
|
239
|
14
|
50
|
|
|
|
43
|
$string = substr($string, 1, length($string)-2) |
|
240
|
|
|
|
|
|
|
if ($string =~ /^([\"'])(?:\\\1|.)*?\1$/); |
|
241
|
14
|
|
|
|
|
24
|
$string =~ s/"/"/g; |
|
242
|
14
|
|
|
|
|
21
|
$string =~ s/'/'/g; |
|
243
|
14
|
|
|
|
|
14
|
$string =~ s/&/&/g; |
|
244
|
14
|
|
|
|
|
17
|
$string =~ s/>/>/g; |
|
245
|
14
|
|
|
|
|
22
|
$string =~ s/</
|
|
246
|
|
|
|
|
|
|
|
|
247
|
14
|
|
|
|
|
48
|
return $string; |
|
248
|
|
|
|
|
|
|
} |
|
249
|
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
# Priveate method |
|
251
|
|
|
|
|
|
|
sub _parse_predicate { |
|
252
|
12
|
|
|
12
|
|
22
|
my ($self, $predicate) = @_; |
|
253
|
12
|
|
|
|
|
16
|
my ($attr, $child, $value); |
|
254
|
0
|
|
|
|
|
0
|
my %res; |
|
255
|
12
|
100
|
|
|
|
130
|
if ($predicate =~ /^([0-9]+)$/) { |
|
|
|
50
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
256
|
6
|
|
|
|
|
23
|
$res{idx} = $1; |
|
257
|
|
|
|
|
|
|
} elsif (($attr, $value) = $predicate =~ /^\@(\S+)\s*=\s*(.*)\s*$/) { |
|
258
|
0
|
|
|
|
|
0
|
$res{attr} = $attr; |
|
259
|
0
|
|
|
|
|
0
|
$res{attr_value} = $self->_unescape($value); |
|
260
|
|
|
|
|
|
|
} elsif (($child, $value) = $predicate =~ /^(\S+)\s*=\s*(.*)\s*$/) { |
|
261
|
6
|
|
|
|
|
14
|
$res{child} = $child; |
|
262
|
6
|
|
|
|
|
15
|
$res{child_value} = $self->_unescape($value); |
|
263
|
|
|
|
|
|
|
} elsif (($attr) = $predicate =~ /^\@(\S+)$/) { |
|
264
|
0
|
|
|
|
|
0
|
$res{attr} = $attr; |
|
265
|
|
|
|
|
|
|
} |
|
266
|
|
|
|
|
|
|
# TODO - support all predicates |
|
267
|
12
|
50
|
|
|
|
41
|
return wantarray?%res:\%res; |
|
268
|
|
|
|
|
|
|
} |
|
269
|
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
sub _select_unabbreviated { |
|
271
|
114
|
|
|
114
|
|
175
|
my ($self, $expr, $recurse) = @_; |
|
272
|
114
|
|
|
|
|
293
|
my @tokens = split('/', $expr); |
|
273
|
114
|
|
|
|
|
139
|
my @set; |
|
274
|
114
|
100
|
66
|
|
|
359
|
if ($expr =~ /^\// and !$recurse) { # absolute path has been requested |
|
275
|
20
|
|
|
|
|
70
|
$self->context->{items} = [$self->{_xml}->rootNodes()]; |
|
276
|
|
|
|
|
|
|
} |
|
277
|
|
|
|
|
|
|
# XPath works only in single-root mode |
|
278
|
|
|
|
|
|
|
# (which is the only allowed mode by the xml spec anyway) |
|
279
|
114
|
|
|
|
|
356
|
my $state = $self->{_xml}->allowMultipleRootNodes(0); |
|
280
|
|
|
|
|
|
|
#shift(@tokens) |
|
281
|
|
|
|
|
|
|
# if (!$tokens[0] and $recurse); |
|
282
|
114
|
|
|
|
|
956
|
my $token = shift @tokens; |
|
283
|
114
|
100
|
100
|
|
|
862
|
if ($token and $token =~ /([\w-]+)::([\w\(\)\=]+|\*)(\[.*?\])*$/) { |
|
284
|
92
|
|
|
|
|
183
|
my $step = $1; |
|
285
|
92
|
|
|
|
|
141
|
my $nodetest = $2; |
|
286
|
92
|
|
|
|
|
134
|
my $full_predicate = $3; |
|
287
|
92
|
|
|
|
|
200
|
@set = $self->_expand_axis($step); |
|
288
|
92
|
100
|
|
|
|
211
|
if ($nodetest eq '*') { |
|
289
|
10
|
|
|
|
|
23
|
$self->context->{items} = \@set; |
|
290
|
|
|
|
|
|
|
} else { |
|
291
|
82
|
|
|
|
|
193
|
$self->context->{items} = []; |
|
292
|
82
|
|
|
|
|
210
|
foreach my $node (@set) { |
|
293
|
502
|
100
|
|
|
|
1035
|
if ($nodetest =~ /\(\)/) { |
|
294
|
148
|
50
|
|
|
|
260
|
if ($nodetest eq 'node()') { |
|
295
|
148
|
50
|
|
|
|
430
|
push (@{$self->context->{items}}, $node) if ($node->type ne "ATTRIBUTE"); |
|
|
148
|
|
|
|
|
276
|
|
|
296
|
|
|
|
|
|
|
} else { |
|
297
|
0
|
|
|
|
|
0
|
warn "Unknown NodeTest $nodetest"; |
|
298
|
|
|
|
|
|
|
} |
|
299
|
|
|
|
|
|
|
} else { |
|
300
|
354
|
100
|
|
|
|
919
|
push (@{$self->context->{items}}, $node) if ($node->name eq $nodetest); |
|
|
90
|
|
|
|
|
169
|
|
|
301
|
|
|
|
|
|
|
} |
|
302
|
|
|
|
|
|
|
} |
|
303
|
|
|
|
|
|
|
} |
|
304
|
92
|
100
|
66
|
|
|
500
|
if ($full_predicate and $full_predicate =~ s/^\[(.*?)\]$/$1/) { |
|
305
|
30
|
|
|
|
|
64
|
my @predicates = $full_predicate; |
|
306
|
30
|
|
|
|
|
40
|
my $op; |
|
307
|
|
|
|
|
|
|
|
|
308
|
30
|
|
|
|
|
58
|
my $saved_context = $self->context; |
|
309
|
30
|
|
|
|
|
48
|
my %all_sets; |
|
310
|
30
|
|
100
|
|
|
163
|
while ($full_predicate =~ /\(([^()]+)\s+(and|or)\s+([^()]+)\)/ or |
|
311
|
|
|
|
|
|
|
$full_predicate !~ /^(?:__SET\:\S+__)$/) |
|
312
|
|
|
|
|
|
|
{ |
|
313
|
31
|
|
|
|
|
115
|
my $tmpctx2 = XML::TinyXML::Selector::XPath::Context->new($self->{_xml}); |
|
314
|
31
|
|
|
|
|
143
|
$tmpctx2->{items} = $saved_context->items; |
|
315
|
31
|
|
|
|
|
218
|
$self->{context} = $tmpctx2; |
|
316
|
31
|
100
|
66
|
|
|
192
|
my $inner_predicate = ($1 and $2 and $3)?"$1 $2 $3":$full_predicate; |
|
317
|
31
|
|
|
|
|
177
|
$inner_predicate =~ s/(^\(|\)$)//g; |
|
318
|
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
# TODO - implement full support for complex boolean expression |
|
320
|
31
|
100
|
|
|
|
190
|
if ($inner_predicate =~ /^(.*?)\s+(and|or)\s+(.*)$/) { |
|
321
|
4
|
|
|
|
|
15
|
@predicates = ($1, $3); |
|
322
|
4
|
|
|
|
|
7
|
$op = $2; |
|
323
|
|
|
|
|
|
|
} |
|
324
|
31
|
|
|
|
|
33
|
my @itemrefs; |
|
325
|
|
|
|
|
|
|
# save the actual context to ensure sending the correct context to all predicates |
|
326
|
31
|
|
|
|
|
61
|
my $saved_context2 = $self->context; |
|
327
|
31
|
|
|
|
|
58
|
foreach my $predicate_string (@predicates) { |
|
328
|
|
|
|
|
|
|
# using a temporary context while iterating over all predicates |
|
329
|
35
|
|
|
|
|
116
|
my $tmpctx = XML::TinyXML::Selector::XPath::Context->new($self->{_xml}); |
|
330
|
35
|
|
|
|
|
153
|
$tmpctx->{items} = $saved_context2->items; |
|
331
|
35
|
|
|
|
|
97
|
$self->{context} = $tmpctx; |
|
332
|
35
|
100
|
|
|
|
122
|
if ($predicate_string =~ /^__SET:(\S+)__$/) { |
|
|
|
100
|
|
|
|
|
|
|
333
|
1
|
|
|
|
|
2
|
push(@itemrefs, $all_sets{$1}); |
|
334
|
|
|
|
|
|
|
} elsif ($predicate_string =~ /::/) { |
|
335
|
22
|
|
|
|
|
56
|
my ($p, $v) = split('=', $predicate_string); |
|
336
|
22
|
100
|
|
|
|
109
|
$v =~ s/(^['"]|['"]$)//g if ($v); # XXX - unsafe dequoting ... think more to find a better regexp |
|
337
|
22
|
|
|
|
|
30
|
my %uniq; |
|
338
|
|
|
|
|
|
|
my @nodepaths; |
|
339
|
22
|
|
|
|
|
54
|
foreach my $node ($self->_select_unabbreviated($p ,1)) { |
|
340
|
24
|
100
|
|
|
|
75
|
if ($node->type eq "ATTRIBUTE") { |
|
341
|
11
|
|
|
|
|
45
|
my $nodepath = $node->node->path; |
|
342
|
11
|
100
|
100
|
|
|
70
|
next if ($v && $node->value ne $self->_unescape($v)); |
|
343
|
10
|
50
|
|
|
|
32
|
push(@nodepaths, $nodepath) if (!$uniq{$nodepath}); |
|
344
|
10
|
|
|
|
|
29
|
$uniq{$nodepath} = $node->node |
|
345
|
|
|
|
|
|
|
} else { |
|
346
|
13
|
|
|
|
|
31
|
my $parent = $node->parent; |
|
347
|
13
|
50
|
|
|
|
28
|
if ($parent) { |
|
348
|
13
|
100
|
100
|
|
|
39
|
next if ($v && $node->value ne $v); |
|
349
|
10
|
50
|
|
|
|
30
|
push(@nodepaths, $parent->path) if (!$uniq{$parent->path}); |
|
350
|
10
|
|
|
|
|
27
|
$uniq{$parent->path} = $parent |
|
351
|
|
|
|
|
|
|
} else { |
|
352
|
|
|
|
|
|
|
# TODO - Error Messages |
|
353
|
|
|
|
|
|
|
} |
|
354
|
|
|
|
|
|
|
} |
|
355
|
|
|
|
|
|
|
} |
|
356
|
22
|
|
|
|
|
55
|
push (@itemrefs, [ map { $uniq{$_} } @nodepaths ]); |
|
|
20
|
|
|
|
|
79
|
|
|
357
|
|
|
|
|
|
|
} else { |
|
358
|
12
|
|
|
|
|
34
|
my $predicate = $self->_parse_predicate($predicate_string); |
|
359
|
12
|
50
|
|
|
|
53
|
if ($predicate->{attr}) { |
|
|
|
100
|
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
} elsif ($predicate->{child}) { |
|
361
|
6
|
50
|
|
|
|
29
|
if ($predicate->{child} =~ s/\(.*?\)//) { |
|
362
|
6
|
|
|
|
|
8
|
my $func = $predicate->{child}; |
|
363
|
6
|
|
|
|
|
14
|
@set = $self->_exec_function($func); # expand lvalue function |
|
364
|
6
|
50
|
|
|
|
19
|
if ($predicate->{child_value}) { |
|
365
|
78
|
|
|
|
|
244
|
my $op_string = join('|', |
|
366
|
|
|
|
|
|
|
map { |
|
367
|
6
|
|
|
|
|
13
|
$_ =~ s/([\-\|\+\*\<\>=\!])/\\$1/g; |
|
368
|
78
|
|
|
|
|
143
|
$_; |
|
369
|
6
|
|
|
|
|
9
|
} keys(%{$self->context->operators}) |
|
370
|
|
|
|
|
|
|
); |
|
371
|
6
|
|
|
|
|
31
|
my $value = $predicate->{child_value}; |
|
372
|
6
|
100
|
|
|
|
129
|
if ($value =~ s/\(.*?\)(.*)$//) { |
|
|
|
100
|
|
|
|
|
|
|
373
|
2
|
|
|
|
|
3
|
my $extra = $1; |
|
374
|
2
|
|
|
|
|
6
|
$value = $self->_exec_function($value); # expand rvalue function |
|
375
|
2
|
100
|
|
|
|
9
|
if ($extra) { |
|
376
|
1
|
50
|
|
|
|
90
|
if ($extra =~ /($op_string)(.*)$/) { # check if we must perform an extra operation |
|
377
|
1
|
|
|
|
|
3
|
$value = $self->context->operators->{$1}->($value, $2); |
|
378
|
|
|
|
|
|
|
} |
|
379
|
|
|
|
|
|
|
} |
|
380
|
|
|
|
|
|
|
} elsif ($value =~ /^(.*?)($op_string)(.*)$/) { # check if we must perform an extra operation |
|
381
|
2
|
|
|
|
|
6
|
$value = $self->context->operators->{$2}->($1, $3); |
|
382
|
|
|
|
|
|
|
} |
|
383
|
6
|
50
|
|
|
|
23
|
if ($func eq 'position') { |
|
384
|
6
|
|
|
|
|
27
|
my %pos = (@set); |
|
385
|
6
|
|
|
|
|
28
|
push (@itemrefs, [ $pos{$value} ]); |
|
386
|
|
|
|
|
|
|
} |
|
387
|
|
|
|
|
|
|
} |
|
388
|
|
|
|
|
|
|
} else { |
|
389
|
|
|
|
|
|
|
} |
|
390
|
|
|
|
|
|
|
} elsif ($predicate->{idx}) { |
|
391
|
6
|
|
|
|
|
19
|
push (@itemrefs, [ @{$self->context->items}[$predicate->{idx}-1] ]); |
|
|
6
|
|
|
|
|
14
|
|
|
392
|
|
|
|
|
|
|
} |
|
393
|
|
|
|
|
|
|
} |
|
394
|
35
|
|
|
|
|
149
|
$self->{context} = $saved_context2; |
|
395
|
|
|
|
|
|
|
} |
|
396
|
31
|
100
|
|
|
|
61
|
if ($op) { |
|
397
|
4
|
|
|
|
|
11
|
$self->context->{items} = $self->context->operators->{$op}->(@itemrefs); |
|
398
|
|
|
|
|
|
|
} else { |
|
399
|
27
|
|
|
|
|
59
|
$self->context->{items} = $itemrefs[0]; |
|
400
|
|
|
|
|
|
|
} |
|
401
|
31
|
|
|
|
|
60
|
my $id = scalar($self->context->{items}); |
|
402
|
31
|
|
|
|
|
58
|
$all_sets{$id} = $self->context->{items}; |
|
403
|
31
|
100
|
|
|
|
77
|
if ($inner_predicate eq $full_predicate) { |
|
404
|
29
|
|
|
|
|
39
|
$full_predicate = ""; |
|
405
|
29
|
|
|
|
|
71
|
last; |
|
406
|
|
|
|
|
|
|
} |
|
407
|
2
|
50
|
|
|
|
6
|
last unless ($inner_predicate); |
|
408
|
2
|
|
|
|
|
17
|
$inner_predicate =~ s/([()=])/\\$1/g; |
|
409
|
2
|
|
|
|
|
78
|
$full_predicate =~ s/\(?$inner_predicate\)?/__SET\:${id}__/; |
|
410
|
|
|
|
|
|
|
} # while ($full_predicate =~ /\(([^()]+)\)/) |
|
411
|
30
|
100
|
|
|
|
138
|
if ($full_predicate =~ /__SET:(\S+)__/) { |
|
412
|
1
|
|
|
|
|
4
|
$self->context->{items} = $all_sets{$1}; |
|
413
|
|
|
|
|
|
|
} |
|
414
|
|
|
|
|
|
|
} # if ($full_predicate and $full_predicate =~ s/^\[(.*?)\]$/$1/) |
|
415
|
|
|
|
|
|
|
else { |
|
416
|
62
|
50
|
|
|
|
152
|
warn "Bad predicate format : '$full_predicate'" |
|
417
|
|
|
|
|
|
|
if ($full_predicate); |
|
418
|
|
|
|
|
|
|
} |
|
419
|
|
|
|
|
|
|
} else { |
|
420
|
22
|
|
|
|
|
23
|
my @newItems; |
|
421
|
22
|
|
|
|
|
24
|
foreach my $node (@{$self->context->items}) { |
|
|
22
|
|
|
|
|
48
|
|
|
422
|
33
|
100
|
|
|
|
64
|
if ($token) { |
|
423
|
|
|
|
|
|
|
# TODO - handle properly, C api has only partial support for predicates |
|
424
|
12
|
50
|
|
|
|
25
|
if ($token =~ /\[.*?\]/) { |
|
425
|
0
|
|
|
|
|
0
|
my $child = $node->getChildNodeByName($token); |
|
426
|
0
|
0
|
|
|
|
0
|
push (@newItems, $child) if ($child); |
|
427
|
|
|
|
|
|
|
} else { |
|
428
|
12
|
|
|
|
|
33
|
foreach my $child ($node->children) { |
|
429
|
11
|
50
|
|
|
|
26
|
push(@newItems, $child) |
|
430
|
|
|
|
|
|
|
if ($child->name eq $token); |
|
431
|
|
|
|
|
|
|
} |
|
432
|
|
|
|
|
|
|
} |
|
433
|
|
|
|
|
|
|
} else { |
|
434
|
21
|
|
|
|
|
54
|
push(@newItems, $node); |
|
435
|
|
|
|
|
|
|
} |
|
436
|
|
|
|
|
|
|
} |
|
437
|
22
|
|
|
|
|
64
|
$self->context->{items} = \@newItems; |
|
438
|
|
|
|
|
|
|
} |
|
439
|
114
|
100
|
|
|
|
249
|
if (@tokens) { |
|
440
|
50
|
|
|
|
|
279
|
return $self->_select_unabbreviated(join('/', @tokens), 1); # recursion here |
|
441
|
|
|
|
|
|
|
} |
|
442
|
64
|
|
|
|
|
210
|
$self->{_xml}->allowMultipleRootNodes($state); |
|
443
|
64
|
100
|
|
|
|
213
|
return wantarray?@{$self->context->items}:$self->context->items; |
|
|
22
|
|
|
|
|
40
|
|
|
444
|
|
|
|
|
|
|
} |
|
445
|
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
1; |
|
447
|
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
=back |
|
449
|
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
451
|
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
=over 4 |
|
453
|
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
XML::TinyXML XML::TinyXML::Node XML::TinyXML::Selector |
|
455
|
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
=back |
|
457
|
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
=head1 AUTHOR |
|
459
|
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
xant, Exant@cpan.orgE |
|
461
|
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE |
|
463
|
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
Copyright (C) 2009-2010 by xant |
|
465
|
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or modify |
|
467
|
|
|
|
|
|
|
it under the same terms as Perl itself, either Perl version 5.8.8 or, |
|
468
|
|
|
|
|
|
|
at your option, any later version of Perl 5 you may have available. |
|
469
|
|
|
|
|
|
|
|
|
470
|
|
|
|
|
|
|
|
|
471
|
|
|
|
|
|
|
=cut |
|
472
|
|
|
|
|
|
|
|