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
|
|
|
|
|
|
|
|