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