line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package HTML::Xit; |
2
|
|
|
|
|
|
|
|
3
|
13
|
|
|
13
|
|
301376
|
use strict; |
|
13
|
|
|
|
|
26
|
|
|
13
|
|
|
|
|
427
|
|
4
|
13
|
|
|
13
|
|
51
|
use warnings; |
|
13
|
|
|
|
|
19
|
|
|
13
|
|
|
|
|
286
|
|
5
|
|
|
|
|
|
|
|
6
|
13
|
|
|
13
|
|
638
|
use Data::Dumper; |
|
13
|
|
|
|
|
7244
|
|
|
13
|
|
|
|
|
719
|
|
7
|
13
|
|
|
|
|
803
|
use HTML::Selector::XPath qw( |
8
|
|
|
|
|
|
|
selector_to_xpath |
9
|
13
|
|
|
13
|
|
6097
|
); |
|
13
|
|
|
|
|
26277
|
|
10
|
13
|
|
|
|
|
998
|
use Scalar::Util qw( |
11
|
|
|
|
|
|
|
blessed |
12
|
|
|
|
|
|
|
reftype |
13
|
|
|
|
|
|
|
weaken |
14
|
13
|
|
|
13
|
|
67
|
); |
|
13
|
|
|
|
|
17
|
|
15
|
13
|
|
|
13
|
|
2565
|
use XML::LibXML; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
our $VERSION = '0.05'; |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
# default arguments for XML::LibXML |
20
|
|
|
|
|
|
|
my $default_libxml_args = { |
21
|
|
|
|
|
|
|
recover => 1, |
22
|
|
|
|
|
|
|
suppress_errors => 1, |
23
|
|
|
|
|
|
|
suppress_warnings => 1, |
24
|
|
|
|
|
|
|
}; |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
################### |
27
|
|
|
|
|
|
|
# # |
28
|
|
|
|
|
|
|
# PRIVATE METHODS # |
29
|
|
|
|
|
|
|
# # |
30
|
|
|
|
|
|
|
################### |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
# empty_x |
33
|
|
|
|
|
|
|
# |
34
|
|
|
|
|
|
|
# because methods are chained we need an HTML::Xit object to return |
35
|
|
|
|
|
|
|
# even if a previous selection in the chain failed. |
36
|
|
|
|
|
|
|
my $empty_x = sub { }; |
37
|
|
|
|
|
|
|
bless($empty_x, __PACKAGE__); |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
# new_explicit_args |
40
|
|
|
|
|
|
|
# |
41
|
|
|
|
|
|
|
# create new XML::LibXML instance with args that are passed |
42
|
|
|
|
|
|
|
my $new_explicit_args = sub { |
43
|
|
|
|
|
|
|
my %args = @_; |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
for my $arg (keys %$default_libxml_args) { |
46
|
|
|
|
|
|
|
# set default args values unless the arg is |
47
|
|
|
|
|
|
|
# explicitly set |
48
|
|
|
|
|
|
|
$args{$arg} = $default_libxml_args->{$arg} |
49
|
|
|
|
|
|
|
unless exists $args{$arg}; |
50
|
|
|
|
|
|
|
} |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
return { |
53
|
|
|
|
|
|
|
_xml => XML::LibXML->load_html( %args ) |
54
|
|
|
|
|
|
|
}; |
55
|
|
|
|
|
|
|
}; |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
# new_guess_args |
58
|
|
|
|
|
|
|
# |
59
|
|
|
|
|
|
|
# guess source as one of IO, location, or string based |
60
|
|
|
|
|
|
|
# on the type of scalar that is passed. |
61
|
|
|
|
|
|
|
# |
62
|
|
|
|
|
|
|
# call new_explicit_args once type is guessed. |
63
|
|
|
|
|
|
|
my $new_guess_args = sub { |
64
|
|
|
|
|
|
|
my $arg = shift or die; |
65
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
if (ref $arg) { |
67
|
|
|
|
|
|
|
# file handle |
68
|
|
|
|
|
|
|
if (reftype $arg eq 'GLOB') { |
69
|
|
|
|
|
|
|
return $new_explicit_args->(IO => $arg); |
70
|
|
|
|
|
|
|
} |
71
|
|
|
|
|
|
|
# scalar ref |
72
|
|
|
|
|
|
|
elsif (reftype $arg eq 'SCALAR') { |
73
|
|
|
|
|
|
|
return $new_explicit_args->(string => $arg); |
74
|
|
|
|
|
|
|
} |
75
|
|
|
|
|
|
|
} |
76
|
|
|
|
|
|
|
# url or valid file path |
77
|
|
|
|
|
|
|
elsif ( $arg =~ m{^http} || ($arg !~ m{\n} && -e $arg) ) { |
78
|
|
|
|
|
|
|
return $new_explicit_args->(location => $arg); |
79
|
|
|
|
|
|
|
} |
80
|
|
|
|
|
|
|
# text |
81
|
|
|
|
|
|
|
else { |
82
|
|
|
|
|
|
|
return $new_explicit_args->(string => $arg); |
83
|
|
|
|
|
|
|
} |
84
|
|
|
|
|
|
|
}; |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
# declare this first since we need to call in from closure |
87
|
|
|
|
|
|
|
my $new_X; |
88
|
|
|
|
|
|
|
# new_X |
89
|
|
|
|
|
|
|
# |
90
|
|
|
|
|
|
|
# create a new X instance which is a function reference that |
91
|
|
|
|
|
|
|
# can return a hidden hash ref instance where data is stored |
92
|
|
|
|
|
|
|
$new_X = sub { |
93
|
|
|
|
|
|
|
my($self) = @_; |
94
|
|
|
|
|
|
|
# self must be a hash ref that includes a XML::LibXML object |
95
|
|
|
|
|
|
|
return $empty_x unless eval { $self->{_xml} }; |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
# need to jump through some hoops here to avoid creating |
98
|
|
|
|
|
|
|
# a circular ref. We can't have $X getting captured in its |
99
|
|
|
|
|
|
|
# own closure, but we need to know the scalar value of $X, so |
100
|
|
|
|
|
|
|
# create $Y, then assign the scalar value of $X to $Y after creating |
101
|
|
|
|
|
|
|
# $X |
102
|
|
|
|
|
|
|
my $Y; |
103
|
|
|
|
|
|
|
# HTML::Xit instance |
104
|
|
|
|
|
|
|
my $X = sub { |
105
|
|
|
|
|
|
|
my($select) = @_; |
106
|
|
|
|
|
|
|
# if we are passed a self-reference then |
107
|
|
|
|
|
|
|
# return our hidden instance variable |
108
|
|
|
|
|
|
|
return $self |
109
|
|
|
|
|
|
|
if ref $select |
110
|
|
|
|
|
|
|
and $select eq $Y; |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
my $xml = $self->{_xml}; |
113
|
|
|
|
|
|
|
# else create a new instance |
114
|
|
|
|
|
|
|
my $self = {}; |
115
|
|
|
|
|
|
|
# create new node |
116
|
|
|
|
|
|
|
if ($select =~ m{^<([^>]+)>$}) { |
117
|
|
|
|
|
|
|
my $node_name = $1; |
118
|
|
|
|
|
|
|
# try to get XML::LibXML::Document |
119
|
|
|
|
|
|
|
my $doc = $xml |
120
|
|
|
|
|
|
|
# if the current node supports createElement then use it |
121
|
|
|
|
|
|
|
? $xml->can('createElement') |
122
|
|
|
|
|
|
|
? $xml |
123
|
|
|
|
|
|
|
# if the current belongs to a Document then use that |
124
|
|
|
|
|
|
|
: $xml->can('ownerDocument') |
125
|
|
|
|
|
|
|
? $xml->ownerDocument |
126
|
|
|
|
|
|
|
: XML::LibXML::Document->new |
127
|
|
|
|
|
|
|
# create a new Document to create node from |
128
|
|
|
|
|
|
|
: XML::LibXML::Document->new; |
129
|
|
|
|
|
|
|
# create element under document |
130
|
|
|
|
|
|
|
$xml = $self->{_xml} = $doc->createElement($node_name); |
131
|
|
|
|
|
|
|
} |
132
|
|
|
|
|
|
|
# value is selector |
133
|
|
|
|
|
|
|
else { |
134
|
|
|
|
|
|
|
# generate xpath from CSS selector using HTML::Selector::XPath |
135
|
|
|
|
|
|
|
my $xpath = selector_to_xpath($select) |
136
|
|
|
|
|
|
|
or return $empty_x; |
137
|
|
|
|
|
|
|
# set the current xml context as the result of the xpath selection |
138
|
|
|
|
|
|
|
$self->{_xml} = $xml->find('.'.$xpath); |
139
|
|
|
|
|
|
|
} |
140
|
|
|
|
|
|
|
# return a new HTML::Xit instace with either the selected or created elements |
141
|
|
|
|
|
|
|
return $new_X->($self); |
142
|
|
|
|
|
|
|
}; |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
bless($X, __PACKAGE__); |
145
|
|
|
|
|
|
|
# store scalar value of $X without creating another ref |
146
|
|
|
|
|
|
|
$Y = "$X"; |
147
|
|
|
|
|
|
|
# return blessed sub ref |
148
|
|
|
|
|
|
|
return $X; |
149
|
|
|
|
|
|
|
}; |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
# each |
152
|
|
|
|
|
|
|
# |
153
|
|
|
|
|
|
|
# call callback function for each argument |
154
|
|
|
|
|
|
|
# |
155
|
|
|
|
|
|
|
# unlike the public version of this method the private version does |
156
|
|
|
|
|
|
|
# not create HTML::Xit instances around the returned object. |
157
|
|
|
|
|
|
|
my $each = sub { |
158
|
|
|
|
|
|
|
my($elm, $func) = @_; |
159
|
|
|
|
|
|
|
|
160
|
|
|
|
|
|
|
if (ref $elm && reftype $elm eq 'ARRAY') { |
161
|
|
|
|
|
|
|
$func->($_) for @$elm; |
162
|
|
|
|
|
|
|
} |
163
|
|
|
|
|
|
|
else { |
164
|
|
|
|
|
|
|
$func->($elm); |
165
|
|
|
|
|
|
|
} |
166
|
|
|
|
|
|
|
}; |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
# first |
169
|
|
|
|
|
|
|
# |
170
|
|
|
|
|
|
|
# return the first element from array or return arg if not an array. |
171
|
|
|
|
|
|
|
# |
172
|
|
|
|
|
|
|
# unlike the public version of this method the private version does |
173
|
|
|
|
|
|
|
# not create HTML::Xit instances around the returned object. |
174
|
|
|
|
|
|
|
my $first = sub { |
175
|
|
|
|
|
|
|
my($elm) = @_; |
176
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
return ref $elm && reftype $elm eq 'ARRAY' |
178
|
|
|
|
|
|
|
? $elm->[0] |
179
|
|
|
|
|
|
|
: $elm; |
180
|
|
|
|
|
|
|
}; |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
# arg_to_nodes |
183
|
|
|
|
|
|
|
# |
184
|
|
|
|
|
|
|
# take one or more arguments that may be strings of xml/html |
185
|
|
|
|
|
|
|
# Xit objects referencing XML, or XML objects and return an |
186
|
|
|
|
|
|
|
# XML fragment |
187
|
|
|
|
|
|
|
my $arg_to_nodes = sub |
188
|
|
|
|
|
|
|
{ |
189
|
|
|
|
|
|
|
my $parser = XML::LibXML->new(%$default_libxml_args) |
190
|
|
|
|
|
|
|
or return; |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
my $nodes = []; |
193
|
|
|
|
|
|
|
|
194
|
|
|
|
|
|
|
for my $arg (@_) { |
195
|
|
|
|
|
|
|
if (ref $arg) { |
196
|
|
|
|
|
|
|
if ($arg->isa('HTML::Xit')) { |
197
|
|
|
|
|
|
|
push(@$nodes, @{$arg->get}); |
198
|
|
|
|
|
|
|
} |
199
|
|
|
|
|
|
|
} |
200
|
|
|
|
|
|
|
else { |
201
|
|
|
|
|
|
|
my $xml = $parser->parse_balanced_chunk($arg); |
202
|
|
|
|
|
|
|
push(@$nodes, $xml); |
203
|
|
|
|
|
|
|
} |
204
|
|
|
|
|
|
|
} |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
return $nodes; |
207
|
|
|
|
|
|
|
}; |
208
|
|
|
|
|
|
|
|
209
|
|
|
|
|
|
|
# mod_class |
210
|
|
|
|
|
|
|
# |
211
|
|
|
|
|
|
|
# perform addClass, removeClass, and toggleClass methods |
212
|
|
|
|
|
|
|
my $mod_class = sub { |
213
|
|
|
|
|
|
|
my($action, $X, $value) = @_; |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
return $X unless $value; |
216
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
my $self = $X->($X); |
218
|
|
|
|
|
|
|
my $xml = $self->{_xml} or return $X; |
219
|
|
|
|
|
|
|
# class list may be one or more space seperated class names |
220
|
|
|
|
|
|
|
my @mod_classes = grep {$_ =~ /\w+/} split(/\s+/, $value); |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
return $X unless @mod_classes; |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
$each->($xml, sub { |
225
|
|
|
|
|
|
|
my($sel) = @_; |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
my $class = $sel->getAttribute('class'); |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
my $classes = { |
230
|
|
|
|
|
|
|
map {$_ => 1} grep {$_ =~ /\w+/} split(/\s+/, $class) |
231
|
|
|
|
|
|
|
}; |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
if ($action eq 'add') { |
234
|
|
|
|
|
|
|
$classes->{$_} = 1 for @mod_classes; |
235
|
|
|
|
|
|
|
} |
236
|
|
|
|
|
|
|
elsif ($action eq 'remove') { |
237
|
|
|
|
|
|
|
delete $classes->{$_} for @mod_classes; |
238
|
|
|
|
|
|
|
} |
239
|
|
|
|
|
|
|
elsif ($action eq 'toggle') { |
240
|
|
|
|
|
|
|
for my $mod_class (@mod_classes) { |
241
|
|
|
|
|
|
|
if ($classes->{$mod_class}) { |
242
|
|
|
|
|
|
|
delete $classes->{$mod_class}; |
243
|
|
|
|
|
|
|
} |
244
|
|
|
|
|
|
|
else { |
245
|
|
|
|
|
|
|
$classes->{$mod_class} = 1; |
246
|
|
|
|
|
|
|
} |
247
|
|
|
|
|
|
|
} |
248
|
|
|
|
|
|
|
} |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
$class = join(' ', sort keys %$classes); |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
$sel->setAttribute('class', $class); |
253
|
|
|
|
|
|
|
}); |
254
|
|
|
|
|
|
|
|
255
|
|
|
|
|
|
|
return $X; |
256
|
|
|
|
|
|
|
}; |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
################## |
259
|
|
|
|
|
|
|
# # |
260
|
|
|
|
|
|
|
# PUBLIC METHODS # |
261
|
|
|
|
|
|
|
# # |
262
|
|
|
|
|
|
|
################## |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
# new |
265
|
|
|
|
|
|
|
# |
266
|
|
|
|
|
|
|
# create new HTML::Xit instance which is a function ref |
267
|
|
|
|
|
|
|
sub new |
268
|
|
|
|
|
|
|
{ |
269
|
|
|
|
|
|
|
my $class = shift; |
270
|
|
|
|
|
|
|
# process args which may be in the form of: |
271
|
|
|
|
|
|
|
# new("...") |
272
|
|
|
|
|
|
|
# new("http://www...") |
273
|
|
|
|
|
|
|
# new(FH) |
274
|
|
|
|
|
|
|
# new("/my/file.html") |
275
|
|
|
|
|
|
|
# new(a => 1, b => 2, ...) |
276
|
|
|
|
|
|
|
# new({a => 1, b => 2}) |
277
|
|
|
|
|
|
|
my $self = @_ == 1 |
278
|
|
|
|
|
|
|
? ref $_[0] && ref $_[0] eq 'HASH' |
279
|
|
|
|
|
|
|
# first arg is hash ref, use as args |
280
|
|
|
|
|
|
|
? $new_explicit_args->( %{$_[0]} ) |
281
|
|
|
|
|
|
|
# first arg is not hash ref, guess what it is |
282
|
|
|
|
|
|
|
: $new_guess_args->(@_) |
283
|
|
|
|
|
|
|
# treat multiple args as explicit, which are passed |
284
|
|
|
|
|
|
|
# directly to XML::LibXML |
285
|
|
|
|
|
|
|
: $new_explicit_args->(@_); |
286
|
|
|
|
|
|
|
# need XML::LibXML instance to continue |
287
|
|
|
|
|
|
|
return unless $self and $self->{_xml}; |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
return $new_X->($self); |
290
|
|
|
|
|
|
|
} |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
# addClass |
293
|
|
|
|
|
|
|
# |
294
|
|
|
|
|
|
|
# add class using $mod_class |
295
|
|
|
|
|
|
|
sub addClass { $mod_class->('add', @_) } |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
# append |
298
|
|
|
|
|
|
|
# |
299
|
|
|
|
|
|
|
# add nodes after last child |
300
|
|
|
|
|
|
|
sub append |
301
|
|
|
|
|
|
|
{ |
302
|
|
|
|
|
|
|
my($X) = shift; |
303
|
|
|
|
|
|
|
my $self = $X->($X); |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
my $xml = $self->{_xml} or return $X; |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
my $child_nodes = $arg_to_nodes->(@_) |
308
|
|
|
|
|
|
|
or return $X; |
309
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
$each->($xml, sub { |
311
|
|
|
|
|
|
|
my $sel = shift or return; |
312
|
|
|
|
|
|
|
# must be able to have children |
313
|
|
|
|
|
|
|
return unless $sel->can('appendChild'); |
314
|
|
|
|
|
|
|
# append one or more child nodes |
315
|
|
|
|
|
|
|
$each->($child_nodes, sub { |
316
|
|
|
|
|
|
|
my $node = shift or return; |
317
|
|
|
|
|
|
|
# deep clone child |
318
|
|
|
|
|
|
|
$sel->appendChild( $node->cloneNode(1) ); |
319
|
|
|
|
|
|
|
}); |
320
|
|
|
|
|
|
|
}); |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
return $X; |
323
|
|
|
|
|
|
|
} |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
# attr |
326
|
|
|
|
|
|
|
# |
327
|
|
|
|
|
|
|
# get or set an attribute on selected XML nodes |
328
|
|
|
|
|
|
|
sub attr |
329
|
|
|
|
|
|
|
{ |
330
|
|
|
|
|
|
|
my($X, $name, $value) = @_; |
331
|
|
|
|
|
|
|
my $self = $X->($X); |
332
|
|
|
|
|
|
|
my $xml = $self->{_xml}; |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
if (defined $value) |
335
|
|
|
|
|
|
|
{ |
336
|
|
|
|
|
|
|
return $X unless $xml; |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
$each->($xml, sub { |
339
|
|
|
|
|
|
|
my $sel = shift or return; |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
$sel->setAttribute($name, $value) |
342
|
|
|
|
|
|
|
if $sel->can('setAttribute'); |
343
|
|
|
|
|
|
|
}); |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
return $X; |
346
|
|
|
|
|
|
|
} |
347
|
|
|
|
|
|
|
else |
348
|
|
|
|
|
|
|
{ |
349
|
|
|
|
|
|
|
return undef unless $xml; |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
my $sel = $first->($xml); |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
return $sel->can('getAttribute') |
354
|
|
|
|
|
|
|
? $sel->getAttribute($name) |
355
|
|
|
|
|
|
|
: undef; |
356
|
|
|
|
|
|
|
} |
357
|
|
|
|
|
|
|
} |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
# children |
360
|
|
|
|
|
|
|
# |
361
|
|
|
|
|
|
|
# return child nodes. *does not* process optional selector |
362
|
|
|
|
|
|
|
# at this time. |
363
|
|
|
|
|
|
|
sub children |
364
|
|
|
|
|
|
|
{ |
365
|
|
|
|
|
|
|
my($X) = @_; |
366
|
|
|
|
|
|
|
my $self = $X->($X); |
367
|
|
|
|
|
|
|
|
368
|
|
|
|
|
|
|
my $xml = $self->{_xml} or return $X; |
369
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
# if we are working on a list of elements then return |
371
|
|
|
|
|
|
|
# the children of all of the elements |
372
|
|
|
|
|
|
|
if (reftype $xml eq 'ARRAY') { |
373
|
|
|
|
|
|
|
my @child_nodes; |
374
|
|
|
|
|
|
|
# add child nodes of each element to list |
375
|
|
|
|
|
|
|
push(@child_nodes, $_->childNodes) for grep { |
376
|
|
|
|
|
|
|
$_->can('childNodes') |
377
|
|
|
|
|
|
|
} @$xml; |
378
|
|
|
|
|
|
|
# return empty HTML::Xit unless child nodes were found |
379
|
|
|
|
|
|
|
return $empty_x unless @child_nodes; |
380
|
|
|
|
|
|
|
# return new HTML::Xit with child nodes |
381
|
|
|
|
|
|
|
return $new_X->( {_xml => \@child_nodes} ); |
382
|
|
|
|
|
|
|
} |
383
|
|
|
|
|
|
|
else { |
384
|
|
|
|
|
|
|
# get child nodes or return empty HTML::Xit object |
385
|
|
|
|
|
|
|
return $empty_x unless $xml->can('childNodes') |
386
|
|
|
|
|
|
|
and (my @child_nodes = $xml->childNodes); |
387
|
|
|
|
|
|
|
# create new HTML::Xit object from child nodes |
388
|
|
|
|
|
|
|
return $new_X->( {_xml => \@child_nodes} ); |
389
|
|
|
|
|
|
|
} |
390
|
|
|
|
|
|
|
} |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
# classes |
393
|
|
|
|
|
|
|
# |
394
|
|
|
|
|
|
|
# return a list of the classes assigned to elements |
395
|
|
|
|
|
|
|
sub classes |
396
|
|
|
|
|
|
|
{ |
397
|
|
|
|
|
|
|
my($X) = @_; |
398
|
|
|
|
|
|
|
my $self = $X->($X); |
399
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
my $xml = $self->{_xml} or return; |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
my $classes; |
403
|
|
|
|
|
|
|
|
404
|
|
|
|
|
|
|
# for multiple elements add each element's class attribute |
405
|
|
|
|
|
|
|
# to the list of classes |
406
|
|
|
|
|
|
|
if (reftype $xml eq 'ARRAY') { |
407
|
|
|
|
|
|
|
$classes .= " " . $_->getAttribute('class') for @$xml; |
408
|
|
|
|
|
|
|
} |
409
|
|
|
|
|
|
|
# get class attribute for single element |
410
|
|
|
|
|
|
|
else { |
411
|
|
|
|
|
|
|
$classes = $xml->getAttribute('class'); |
412
|
|
|
|
|
|
|
} |
413
|
|
|
|
|
|
|
# build table of unique class names |
414
|
|
|
|
|
|
|
$classes = { |
415
|
|
|
|
|
|
|
map {$_ => 1} grep {defined $_ && $_ =~ /\w+/} split(/\s+/, $classes) |
416
|
|
|
|
|
|
|
}; |
417
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
return wantarray |
419
|
|
|
|
|
|
|
# return sorted list of classes |
420
|
|
|
|
|
|
|
? sort keys %$classes |
421
|
|
|
|
|
|
|
# return hashref of classes |
422
|
|
|
|
|
|
|
: $classes; |
423
|
|
|
|
|
|
|
} |
424
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
# each |
426
|
|
|
|
|
|
|
# |
427
|
|
|
|
|
|
|
# call callback function for each argument |
428
|
|
|
|
|
|
|
# |
429
|
|
|
|
|
|
|
# unlike the private version, this method creates a new |
430
|
|
|
|
|
|
|
# HTML::Xit instance for each XML element being iterated on |
431
|
|
|
|
|
|
|
sub each |
432
|
|
|
|
|
|
|
{ |
433
|
|
|
|
|
|
|
my($X, $func) = @_; |
434
|
|
|
|
|
|
|
my $self = $X->($X); |
435
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
my $xml = $self->{_xml} or return $X; |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
if (ref $xml && reftype $xml eq 'ARRAY') { |
439
|
|
|
|
|
|
|
# call callback for each element in array, creating a |
440
|
|
|
|
|
|
|
# new HTML::Xit instance for each XML element |
441
|
|
|
|
|
|
|
$func->( $new_X->({_xml => $_}) ) for @$xml; |
442
|
|
|
|
|
|
|
} |
443
|
|
|
|
|
|
|
else { |
444
|
|
|
|
|
|
|
# call callback, creating a new HTML::Xit instance |
445
|
|
|
|
|
|
|
$func->( $new_X->({_xml => $xml}) ); |
446
|
|
|
|
|
|
|
} |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
return $X; |
449
|
|
|
|
|
|
|
} |
450
|
|
|
|
|
|
|
|
451
|
|
|
|
|
|
|
# find |
452
|
|
|
|
|
|
|
# |
453
|
|
|
|
|
|
|
# get all matching child elements |
454
|
|
|
|
|
|
|
sub find |
455
|
|
|
|
|
|
|
{ |
456
|
|
|
|
|
|
|
my($X, $query) = @_; |
457
|
|
|
|
|
|
|
|
458
|
|
|
|
|
|
|
my $nodes = []; |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
$X->each(sub { |
461
|
|
|
|
|
|
|
my($X) = @_; |
462
|
|
|
|
|
|
|
my $node = $X->($query)->get |
463
|
|
|
|
|
|
|
or return; |
464
|
|
|
|
|
|
|
$node = [$node] |
465
|
|
|
|
|
|
|
unless reftype $node eq 'ARRAY'; |
466
|
|
|
|
|
|
|
push(@$nodes, @$node); |
467
|
|
|
|
|
|
|
}); |
468
|
|
|
|
|
|
|
|
469
|
|
|
|
|
|
|
return $new_X->({_xml => $nodes}) |
470
|
|
|
|
|
|
|
} |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
# first |
473
|
|
|
|
|
|
|
# |
474
|
|
|
|
|
|
|
# return the first element from array or return arg if not an array. |
475
|
|
|
|
|
|
|
# |
476
|
|
|
|
|
|
|
# unlike the private version, this method creates a new |
477
|
|
|
|
|
|
|
# HTML::Xit instance for node being returned |
478
|
|
|
|
|
|
|
sub first { |
479
|
|
|
|
|
|
|
my($X) = @_; |
480
|
|
|
|
|
|
|
my $self = $X->($X); |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
my $xml = $self->{_xml} or return $X; |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
return ref $xml && reftype $xml eq 'ARRAY' |
485
|
|
|
|
|
|
|
? $new_X->( {_xml => $xml->[0]} ) |
486
|
|
|
|
|
|
|
: $new_X->( {_xml => $xml} ); |
487
|
|
|
|
|
|
|
}; |
488
|
|
|
|
|
|
|
|
489
|
|
|
|
|
|
|
# get |
490
|
|
|
|
|
|
|
# |
491
|
|
|
|
|
|
|
# return the XML::LibXML nodes or node identified by index |
492
|
|
|
|
|
|
|
sub get |
493
|
|
|
|
|
|
|
{ |
494
|
|
|
|
|
|
|
my($X, $index) = @_; |
495
|
|
|
|
|
|
|
my $self = $X->($X); |
496
|
|
|
|
|
|
|
|
497
|
|
|
|
|
|
|
my $xml = $self->{_xml} or return $X; |
498
|
|
|
|
|
|
|
# make sure we have nodes in array |
499
|
|
|
|
|
|
|
my $nodes = ref $xml && reftype $xml eq 'ARRAY' |
500
|
|
|
|
|
|
|
? $xml : [ $xml ]; |
501
|
|
|
|
|
|
|
# return either all nodes or specified index |
502
|
|
|
|
|
|
|
return defined $index && int $index |
503
|
|
|
|
|
|
|
? $nodes->[ $index ] |
504
|
|
|
|
|
|
|
: $nodes; |
505
|
|
|
|
|
|
|
} |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
# hasClass |
508
|
|
|
|
|
|
|
# |
509
|
|
|
|
|
|
|
# return true (1) / false (0) if any of the elements have |
510
|
|
|
|
|
|
|
# the class. return undef on errors. |
511
|
|
|
|
|
|
|
sub hasClass |
512
|
|
|
|
|
|
|
{ |
513
|
|
|
|
|
|
|
my($X, $class) = @_; |
514
|
|
|
|
|
|
|
# require class name to test |
515
|
|
|
|
|
|
|
return unless defined $class; |
516
|
|
|
|
|
|
|
# get hashref of class names |
517
|
|
|
|
|
|
|
my $classes = $X->classes() |
518
|
|
|
|
|
|
|
or return; |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
return $classes->{$class} ? 1 : 0; |
521
|
|
|
|
|
|
|
} |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
# html |
524
|
|
|
|
|
|
|
# |
525
|
|
|
|
|
|
|
# return html content or if that is not possible return text |
526
|
|
|
|
|
|
|
# content |
527
|
|
|
|
|
|
|
sub html |
528
|
|
|
|
|
|
|
{ |
529
|
|
|
|
|
|
|
my($X) = shift; |
530
|
|
|
|
|
|
|
my $self = $X->($X); |
531
|
|
|
|
|
|
|
|
532
|
|
|
|
|
|
|
my $xml = $self->{_xml}; |
533
|
|
|
|
|
|
|
# if there are args we modify the current elements |
534
|
|
|
|
|
|
|
# and return the current HTML::Xit instance |
535
|
|
|
|
|
|
|
if (@_) { |
536
|
|
|
|
|
|
|
# require current element |
537
|
|
|
|
|
|
|
return $X unless $xml; |
538
|
|
|
|
|
|
|
# build nodes to be appended from args which can |
539
|
|
|
|
|
|
|
# be either HTML strings or HTML::Xit objects |
540
|
|
|
|
|
|
|
my $child_nodes = $arg_to_nodes->(@_) |
541
|
|
|
|
|
|
|
or return $X; |
542
|
|
|
|
|
|
|
# go through each of the current elements and set |
543
|
|
|
|
|
|
|
# the child nodes from args to be their HTML content |
544
|
|
|
|
|
|
|
$each->($xml, sub { |
545
|
|
|
|
|
|
|
my $sel = shift or return; |
546
|
|
|
|
|
|
|
# must be able to have children |
547
|
|
|
|
|
|
|
return unless $sel->can('appendChild'); |
548
|
|
|
|
|
|
|
# html replaces any existing child nodes |
549
|
|
|
|
|
|
|
$sel->removeChildNodes() |
550
|
|
|
|
|
|
|
if $sel->can('removeChildNodes'); |
551
|
|
|
|
|
|
|
# append one or more child nodes |
552
|
|
|
|
|
|
|
$each->($child_nodes, sub { |
553
|
|
|
|
|
|
|
my $node = shift or return; |
554
|
|
|
|
|
|
|
# deep clone child |
555
|
|
|
|
|
|
|
$sel->appendChild( $node->cloneNode(1) ); |
556
|
|
|
|
|
|
|
}); |
557
|
|
|
|
|
|
|
}); |
558
|
|
|
|
|
|
|
# return HTML::Xit instance |
559
|
|
|
|
|
|
|
return $X; |
560
|
|
|
|
|
|
|
} |
561
|
|
|
|
|
|
|
# if there are no args then we return the HTML text of the |
562
|
|
|
|
|
|
|
# current elements or an empty string |
563
|
|
|
|
|
|
|
else { |
564
|
|
|
|
|
|
|
# require current element |
565
|
|
|
|
|
|
|
return undef unless $xml; |
566
|
|
|
|
|
|
|
# get the first element |
567
|
|
|
|
|
|
|
my $sel = $first->($xml); |
568
|
|
|
|
|
|
|
# if the current node has children then create html |
569
|
|
|
|
|
|
|
# by concatenating html values of child nodes |
570
|
|
|
|
|
|
|
if ( $sel->can('childNodes') && (my @child_nodes = $sel->childNodes) ) { |
571
|
|
|
|
|
|
|
return join('', map { |
572
|
|
|
|
|
|
|
$_->can('toStringHTML') |
573
|
|
|
|
|
|
|
? $_->toStringHTML |
574
|
|
|
|
|
|
|
: $_->can('toString') |
575
|
|
|
|
|
|
|
? $_->toString |
576
|
|
|
|
|
|
|
: '' |
577
|
|
|
|
|
|
|
} @child_nodes); |
578
|
|
|
|
|
|
|
} |
579
|
|
|
|
|
|
|
# if the node has no children then it can only have |
580
|
|
|
|
|
|
|
# text content (?) maybe not possible |
581
|
|
|
|
|
|
|
else { |
582
|
|
|
|
|
|
|
return $sel->can('toString') |
583
|
|
|
|
|
|
|
? $sel->toString |
584
|
|
|
|
|
|
|
: ''; |
585
|
|
|
|
|
|
|
} |
586
|
|
|
|
|
|
|
} |
587
|
|
|
|
|
|
|
} |
588
|
|
|
|
|
|
|
|
589
|
|
|
|
|
|
|
# last |
590
|
|
|
|
|
|
|
# |
591
|
|
|
|
|
|
|
# return the last matching element |
592
|
|
|
|
|
|
|
sub last { |
593
|
|
|
|
|
|
|
my($X) = @_; |
594
|
|
|
|
|
|
|
my $self = $X->($X); |
595
|
|
|
|
|
|
|
|
596
|
|
|
|
|
|
|
my $xml = $self->{_xml} or return $X; |
597
|
|
|
|
|
|
|
|
598
|
|
|
|
|
|
|
return ref $xml && reftype $xml eq 'ARRAY' |
599
|
|
|
|
|
|
|
? $new_X->( {_xml => $xml->[-1]} ) |
600
|
|
|
|
|
|
|
: $new_X->( {_xml => $xml} ); |
601
|
|
|
|
|
|
|
}; |
602
|
|
|
|
|
|
|
|
603
|
|
|
|
|
|
|
# prepend |
604
|
|
|
|
|
|
|
# |
605
|
|
|
|
|
|
|
# add nodes before first child |
606
|
|
|
|
|
|
|
sub prepend |
607
|
|
|
|
|
|
|
{ |
608
|
|
|
|
|
|
|
my($X) = shift; |
609
|
|
|
|
|
|
|
my $self = $X->($X); |
610
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
my $xml = $self->{_xml} or return $X; |
612
|
|
|
|
|
|
|
|
613
|
|
|
|
|
|
|
my $child_nodes = $arg_to_nodes->(@_) |
614
|
|
|
|
|
|
|
or return $X; |
615
|
|
|
|
|
|
|
|
616
|
|
|
|
|
|
|
my $first_child = shift @$child_nodes; |
617
|
|
|
|
|
|
|
|
618
|
|
|
|
|
|
|
$each->($xml, sub { |
619
|
|
|
|
|
|
|
my $sel = shift or return; |
620
|
|
|
|
|
|
|
|
621
|
|
|
|
|
|
|
if ($sel->can('firstChild')) { |
622
|
|
|
|
|
|
|
# insert first node before first child |
623
|
|
|
|
|
|
|
my $insert_after = $sel->insertBefore( |
624
|
|
|
|
|
|
|
$first_child->cloneNode(1), |
625
|
|
|
|
|
|
|
$sel->firstChild, |
626
|
|
|
|
|
|
|
) or return; |
627
|
|
|
|
|
|
|
|
628
|
|
|
|
|
|
|
$each->($child_nodes, sub { |
629
|
|
|
|
|
|
|
my $node = shift or return; |
630
|
|
|
|
|
|
|
|
631
|
|
|
|
|
|
|
return unless $insert_after |
632
|
|
|
|
|
|
|
and $insert_after->can('insertAfter'); |
633
|
|
|
|
|
|
|
|
634
|
|
|
|
|
|
|
$insert_after = $sel->insertAfter( |
635
|
|
|
|
|
|
|
$node->cloneNode(1), |
636
|
|
|
|
|
|
|
$insert_after |
637
|
|
|
|
|
|
|
); |
638
|
|
|
|
|
|
|
}); |
639
|
|
|
|
|
|
|
} |
640
|
|
|
|
|
|
|
elsif ($self->can('addChild')) { |
641
|
|
|
|
|
|
|
$sel->addChild( $first_child->cloneNode(1) ); |
642
|
|
|
|
|
|
|
$each->($child_nodes, sub { |
643
|
|
|
|
|
|
|
my $node = shift or return; |
644
|
|
|
|
|
|
|
}); |
645
|
|
|
|
|
|
|
} |
646
|
|
|
|
|
|
|
else { |
647
|
|
|
|
|
|
|
return; |
648
|
|
|
|
|
|
|
} |
649
|
|
|
|
|
|
|
}); |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
return $X; |
652
|
|
|
|
|
|
|
} |
653
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
# removeClass |
655
|
|
|
|
|
|
|
# |
656
|
|
|
|
|
|
|
# remove class using $mod_class |
657
|
|
|
|
|
|
|
sub removeClass { $mod_class->('remove', @_) } |
658
|
|
|
|
|
|
|
|
659
|
|
|
|
|
|
|
# text |
660
|
|
|
|
|
|
|
# |
661
|
|
|
|
|
|
|
# return text content |
662
|
|
|
|
|
|
|
sub text |
663
|
|
|
|
|
|
|
{ |
664
|
|
|
|
|
|
|
my($X, $value) = @_; |
665
|
|
|
|
|
|
|
my $self = $X->($X); |
666
|
|
|
|
|
|
|
|
667
|
|
|
|
|
|
|
my $xml = $self->{_xml}; |
668
|
|
|
|
|
|
|
|
669
|
|
|
|
|
|
|
if (defined $value) { |
670
|
|
|
|
|
|
|
return $X unless $xml; |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
$each->($xml, sub { |
673
|
|
|
|
|
|
|
my $sel = shift or return; |
674
|
|
|
|
|
|
|
# text replaces everything else so remove child nodes |
675
|
|
|
|
|
|
|
# if they exist |
676
|
|
|
|
|
|
|
$sel->removeChildNodes() if $sel->can('removeChildNodes'); |
677
|
|
|
|
|
|
|
# attempt different methods of adding text |
678
|
|
|
|
|
|
|
# XML::LibXML::Element |
679
|
|
|
|
|
|
|
if ($sel->can('appendText')) { |
680
|
|
|
|
|
|
|
$sel->appendText($value); |
681
|
|
|
|
|
|
|
} |
682
|
|
|
|
|
|
|
# XML::LibXML::Text |
683
|
|
|
|
|
|
|
elsif ($sel->can('setData')) { |
684
|
|
|
|
|
|
|
$sel->setData($value); |
685
|
|
|
|
|
|
|
} |
686
|
|
|
|
|
|
|
# XML::LibXML::Node |
687
|
|
|
|
|
|
|
elsif ($sel->can('appendChild')) { |
688
|
|
|
|
|
|
|
$sel->appendChild( $sel->createTextNode($value) ); |
689
|
|
|
|
|
|
|
} |
690
|
|
|
|
|
|
|
}); |
691
|
|
|
|
|
|
|
} |
692
|
|
|
|
|
|
|
else { |
693
|
|
|
|
|
|
|
return undef unless $xml; |
694
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
my $sel = $first->($xml); |
696
|
|
|
|
|
|
|
return $sel && $sel->can('textContent') |
697
|
|
|
|
|
|
|
? $sel->textContent |
698
|
|
|
|
|
|
|
: undef; |
699
|
|
|
|
|
|
|
} |
700
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
return $X; |
702
|
|
|
|
|
|
|
} |
703
|
|
|
|
|
|
|
|
704
|
|
|
|
|
|
|
# trimText |
705
|
|
|
|
|
|
|
sub trimText |
706
|
|
|
|
|
|
|
{ |
707
|
|
|
|
|
|
|
my($X) = @_; |
708
|
|
|
|
|
|
|
|
709
|
|
|
|
|
|
|
my $text = $X->text; |
710
|
|
|
|
|
|
|
return unless defined $text; |
711
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
$text =~ s{^\s*|\s*$}{}g; |
713
|
|
|
|
|
|
|
|
714
|
|
|
|
|
|
|
return $text; |
715
|
|
|
|
|
|
|
} |
716
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
# toggleClass |
718
|
|
|
|
|
|
|
# |
719
|
|
|
|
|
|
|
# toggle class using $mod_class |
720
|
|
|
|
|
|
|
sub toggleClass { $mod_class->('toggle', @_) } |
721
|
|
|
|
|
|
|
|
722
|
|
|
|
|
|
|
1; |
723
|
|
|
|
|
|
|
|
724
|
|
|
|
|
|
|
__END__ |