line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Positron::Handler::ArrayRef; |
2
|
|
|
|
|
|
|
our $VERSION = 'v0.1.3'; # VERSION |
3
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
=head1 NAME |
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
Positron::Handler::ArrayRef - a DOM interface for ArrayRefs |
7
|
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
=head1 VERSION |
9
|
|
|
|
|
|
|
|
10
|
|
|
|
|
|
|
version v0.1.3 |
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
=head1 SYNOPSIS |
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
my $engine = Positron::Template->new(); |
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
my $template = [ |
17
|
|
|
|
|
|
|
'a', |
18
|
|
|
|
|
|
|
{ href => "/"}, |
19
|
|
|
|
|
|
|
[ 'b', "Now: " ], |
20
|
|
|
|
|
|
|
"next page", |
21
|
|
|
|
|
|
|
]; |
22
|
|
|
|
|
|
|
my $data = { foo => 'bar', baz => [ 1, 2, 3 ] }; |
23
|
|
|
|
|
|
|
my $result = $engine->parse($template, $data); |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
=head1 DESCRIPTION |
26
|
|
|
|
|
|
|
|
27
|
|
|
|
|
|
|
This module allows C to work with a simple DOM representation: |
28
|
|
|
|
|
|
|
ArrayRefs. |
29
|
|
|
|
|
|
|
This module can also be used as a blueprint for writing more handlers; the |
30
|
|
|
|
|
|
|
documentation of the methods is therefore extra deep. |
31
|
|
|
|
|
|
|
|
32
|
|
|
|
|
|
|
=head2 ArrayRef representation |
33
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
In ArrayRef representation, a DOM element is simply a reference to an array |
35
|
|
|
|
|
|
|
with at least one element: the node tag, an optional hash (reference) with attributes, |
36
|
|
|
|
|
|
|
and any children the node might have. Pure text is represented by simple strings. |
37
|
|
|
|
|
|
|
Comments, processing instructions or similar have no intrinsic representation; |
38
|
|
|
|
|
|
|
at best they can be represented as simple nodes with special tag names. |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
An example: |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
[ |
43
|
|
|
|
|
|
|
'a', |
44
|
|
|
|
|
|
|
{ href => "/"}, |
45
|
|
|
|
|
|
|
[ 'b', "Now: " ], |
46
|
|
|
|
|
|
|
"next page >>", |
47
|
|
|
|
|
|
|
['br'], |
48
|
|
|
|
|
|
|
]; |
49
|
|
|
|
|
|
|
|
50
|
|
|
|
|
|
|
This corresponds to the HTML representation of: |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
Now: next page >> |
53
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
Note the plain C<<< >> >>> in the ArrayRef representation: text does B |
55
|
|
|
|
|
|
|
need to be encoded in HTML entities. |
56
|
|
|
|
|
|
|
Note also that the attributes, if present, need to occupy the second slot |
57
|
|
|
|
|
|
|
of the array reference. A missing attribute hash reference corresponds to |
58
|
|
|
|
|
|
|
no attributes. |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
=cut |
61
|
|
|
|
|
|
|
|
62
|
9
|
|
|
9
|
|
109
|
use v5.10; |
|
9
|
|
|
|
|
28
|
|
|
9
|
|
|
|
|
424
|
|
63
|
9
|
|
|
9
|
|
44
|
use strict; |
|
9
|
|
|
|
|
13
|
|
|
9
|
|
|
|
|
294
|
|
64
|
9
|
|
|
9
|
|
48
|
use warnings; |
|
9
|
|
|
|
|
14
|
|
|
9
|
|
|
|
|
255
|
|
65
|
|
|
|
|
|
|
|
66
|
9
|
|
|
9
|
|
44
|
use Carp; |
|
9
|
|
|
|
|
14
|
|
|
9
|
|
|
|
|
6016
|
|
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
# Format: |
69
|
|
|
|
|
|
|
# [ 'a', { href => "/"}, |
70
|
|
|
|
|
|
|
# [ 'b', [ "Now: " ] ], |
71
|
|
|
|
|
|
|
# "next page", |
72
|
|
|
|
|
|
|
# ] |
73
|
|
|
|
|
|
|
|
74
|
|
|
|
|
|
|
# TODO: is_regular_node? Places burden of checking types on caller |
75
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
=head1 CONSTRUCTOR |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
=head2 new |
79
|
|
|
|
|
|
|
|
80
|
|
|
|
|
|
|
$handler = Positron::Handler::ArrayRef->new(); |
81
|
|
|
|
|
|
|
|
82
|
|
|
|
|
|
|
The constructor has no parameters; this is a very basic class. |
83
|
|
|
|
|
|
|
Normally, the template engine will automatically call the constructor |
84
|
|
|
|
|
|
|
of the correct handler for whatever it is handed as template. |
85
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
=cut |
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
sub new { |
89
|
9
|
|
|
9
|
1
|
21
|
my ($class) = @_; |
90
|
9
|
|
|
|
|
98
|
return bless {}, $class; |
91
|
|
|
|
|
|
|
} |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
=head1 METHODS |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
The following methods are part of the "handler interface". The point |
96
|
|
|
|
|
|
|
of a handler is to present a unified interface for all DOM operations |
97
|
|
|
|
|
|
|
that C needs to do. So even though these methods |
98
|
|
|
|
|
|
|
are quite simple, even trivial, given the ArrayRef representation, |
99
|
|
|
|
|
|
|
they must be fully implemented. |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
=head2 shallow_clone |
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
$new_node = $handler->shallow_clone($orig_node); |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
This method returns a clone of the given node. This clone has the |
106
|
|
|
|
|
|
|
same attributes as the original, but no children. The clone is |
107
|
|
|
|
|
|
|
never identical to the original, even if it could be (i.e. the |
108
|
|
|
|
|
|
|
original has no children). |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
Text nodes, which are simple strings, are cloned to copies of |
111
|
|
|
|
|
|
|
themselves. |
112
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
=cut |
114
|
|
|
|
|
|
|
|
115
|
|
|
|
|
|
|
sub shallow_clone { |
116
|
326
|
|
|
326
|
1
|
495
|
my ($self, $node) = @_; |
117
|
326
|
50
|
|
|
|
605
|
if (ref($node)) { |
118
|
|
|
|
|
|
|
# should not clone children |
119
|
326
|
|
|
|
|
462
|
my ($tag, $attributes) = @$node; |
120
|
326
|
50
|
|
|
|
741
|
if (ref($attributes) ne 'HASH') { |
121
|
0
|
|
|
|
|
0
|
$attributes = {}; |
122
|
|
|
|
|
|
|
} |
123
|
326
|
|
|
|
|
968
|
my $new_node = [ $tag, { %$attributes } ]; |
124
|
326
|
|
|
|
|
1097
|
return $new_node; |
125
|
|
|
|
|
|
|
} else { |
126
|
0
|
|
|
|
|
0
|
return "$node"; |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
} |
129
|
|
|
|
|
|
|
|
130
|
|
|
|
|
|
|
=head2 get_attribute |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
$value = $handler->get_attribute($node, $attr_name); |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
Gets the I of a named attribute of the node. If the node does |
135
|
|
|
|
|
|
|
not have an attribute of this name, or it is a text node (which has |
136
|
|
|
|
|
|
|
no attributes), C is returned. |
137
|
|
|
|
|
|
|
|
138
|
|
|
|
|
|
|
=cut |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
sub get_attribute { |
141
|
|
|
|
|
|
|
# gets the value, not the attribute node. |
142
|
375
|
|
|
375
|
1
|
545
|
my ($self, $node, $attr) = @_; |
143
|
375
|
50
|
|
|
|
762
|
return unless ref($node); |
144
|
375
|
|
|
|
|
652
|
my ($tag, $attributes, @children) = @$node; |
145
|
375
|
50
|
|
|
|
797
|
if (ref($attributes) ne 'HASH') { |
146
|
0
|
|
|
|
|
0
|
$attributes = {}; |
147
|
|
|
|
|
|
|
} |
148
|
375
|
|
|
|
|
1181
|
return $attributes->{$attr}; |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
=head2 set_attribute |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
$handler->set_attribute($node, $attr_name => $new_value); |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
Sets the named attribute to the new value. Setting an attribute to |
156
|
|
|
|
|
|
|
C will delete the attribute. It is not an error to try to |
157
|
|
|
|
|
|
|
set an attribute on a text node, but nothing will happen. |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
Returns the new value (or C as needed), though C |
160
|
|
|
|
|
|
|
does not use the return value. |
161
|
|
|
|
|
|
|
|
162
|
|
|
|
|
|
|
=cut |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
sub set_attribute { |
165
|
123
|
|
|
123
|
1
|
193
|
my ($self, $node, $attr, $value) = @_; |
166
|
123
|
50
|
|
|
|
280
|
return unless ref($node); |
167
|
123
|
|
|
|
|
225
|
my ($tag, $attributes, @children) = @$node; |
168
|
123
|
50
|
|
|
|
283
|
if (ref($attributes) ne 'HASH') { |
169
|
0
|
|
|
|
|
0
|
$attributes = {}; |
170
|
0
|
|
|
|
|
0
|
splice @$node, 1, 0, $attributes; |
171
|
|
|
|
|
|
|
} |
172
|
123
|
100
|
|
|
|
222
|
if (defined($value)) { |
173
|
11
|
|
|
|
|
54
|
return $attributes->{$attr} = $value; |
174
|
|
|
|
|
|
|
} else { |
175
|
112
|
|
|
|
|
205
|
delete $attributes->{$attr}; |
176
|
112
|
|
|
|
|
461
|
return; |
177
|
|
|
|
|
|
|
} |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=head2 list_attributes |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
@attr_names = $handler->list_attributes($node); |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
Lists the I of all (defined) attributes on the node. |
185
|
|
|
|
|
|
|
Text nodes have no attributes and generate an empty list. |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
=cut |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
sub list_attributes { |
190
|
1067
|
|
|
1067
|
1
|
1910
|
my ($self, $node) = @_; |
191
|
1067
|
50
|
|
|
|
2422
|
return unless ref($node); |
192
|
1067
|
|
|
|
|
1957
|
my ($tag, $attributes, @children) = @$node; |
193
|
1067
|
50
|
|
|
|
2315
|
return unless ref($attributes) eq 'HASH'; |
194
|
1067
|
|
|
|
|
4661
|
return sort keys %$attributes; |
195
|
|
|
|
|
|
|
} |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
=head2 push_contents |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
$handler->push_contents($node, $child_1, $child_2, $child_3); |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
Push the passed nodes, in the given order, onto the I of the |
202
|
|
|
|
|
|
|
child list of the first argument. |
203
|
|
|
|
|
|
|
Text nodes, again, ignore this method silently. |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
=cut |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
sub push_contents { |
208
|
|
|
|
|
|
|
# set_contents? Will only be called on shallow clones, right? |
209
|
322
|
|
|
322
|
1
|
584
|
my ($self, $node, @contents) = @_; |
210
|
322
|
50
|
|
|
|
640
|
return unless ref($node); |
211
|
322
|
|
|
|
|
902
|
return push @$node, @contents; |
212
|
|
|
|
|
|
|
} |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
=head2 list_contents |
215
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
@child_nodes = $handler->list_contents($node); |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
Lists the contents, i.e. the child nodes, of the given node. These |
219
|
|
|
|
|
|
|
are not cloned nodes, but the actual children. Text nodes, of course, |
220
|
|
|
|
|
|
|
have none. |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
=cut |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
sub list_contents { |
225
|
341
|
|
|
341
|
1
|
434
|
my ($self, $node) = @_; |
226
|
341
|
50
|
|
|
|
703
|
return unless ref($node); |
227
|
341
|
50
|
|
|
|
730
|
return unless (@$node > 1); # neither attributes nor content |
228
|
341
|
|
|
|
|
615
|
my ($tag, $attributes, @children) = @$node; |
229
|
341
|
50
|
|
|
|
717
|
if (ref($attributes) ne 'HASH') { |
230
|
|
|
|
|
|
|
# not an attribute hash after all? |
231
|
0
|
|
|
|
|
0
|
unshift @children, $attributes; |
232
|
|
|
|
|
|
|
} |
233
|
341
|
|
|
|
|
1055
|
return @children; |
234
|
|
|
|
|
|
|
} |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
=head2 parse_file |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
$root_node = $handler->parse_file($filename); |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
Reads and parses a file with the given filename. It is recommended to pass |
241
|
|
|
|
|
|
|
an absolute filename, unless you can be sure about your current directory. |
242
|
|
|
|
|
|
|
Normally, this method would not be necessary (since the template engine works |
243
|
|
|
|
|
|
|
on already-parsed DOM trees by design), but there are template constructs that |
244
|
|
|
|
|
|
|
include files via filename. |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
If C<$filename> ends in C<.json> or C<.js>, the file is assumed to be in JSON |
247
|
|
|
|
|
|
|
format, and will be parsed with a freshly Cd C module. |
248
|
|
|
|
|
|
|
|
249
|
|
|
|
|
|
|
Otherwise, it is assumed to be an array reference serialized with the |
250
|
|
|
|
|
|
|
C module. |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
=cut |
253
|
|
|
|
|
|
|
|
254
|
|
|
|
|
|
|
sub parse_file { |
255
|
|
|
|
|
|
|
# Needs more info on directories! |
256
|
|
|
|
|
|
|
# Storable: { nodes = [ ... ] } |
257
|
18
|
|
|
18
|
1
|
28
|
my ($self, $filename) = @_; |
258
|
|
|
|
|
|
|
# TODO: select deserializer based on filename (Storable / JSON / eval?) |
259
|
18
|
50
|
|
|
|
49
|
if ($filename =~ m{ \. (json|js) $ }xms) { |
260
|
0
|
|
|
|
|
0
|
require JSON; # should use JSON::XS if available |
261
|
0
|
|
|
|
|
0
|
require File::Slurp; |
262
|
0
|
|
|
|
|
0
|
my $json = File::Slurp::read_file($filename); |
263
|
0
|
|
|
|
|
0
|
return JSON->new->utf8->allow_nonref->decode($json); |
264
|
|
|
|
|
|
|
} else { |
265
|
|
|
|
|
|
|
# Storable |
266
|
18
|
|
|
|
|
123
|
require Storable; |
267
|
18
|
|
|
|
|
64
|
my $dom = Storable::retrieve($filename); |
268
|
16
|
|
|
|
|
1574
|
return $dom; |
269
|
|
|
|
|
|
|
} |
270
|
|
|
|
|
|
|
} |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
1; |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
__END__ |