File Coverage

blib/lib/Positron/Handler/ArrayRef.pm
Criterion Covered Total %
statement 52 62 83.8
branch 15 28 53.5
condition n/a
subroutine 12 12 100.0
pod 8 8 100.0
total 87 110 79.0


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__