File Coverage

blib/lib/Template/Pure.pm
Criterion Covered Total %
statement 411 453 90.7
branch 187 240 77.9
condition 68 82 82.9
subroutine 66 72 91.6
pod 7 20 35.0
total 739 867 85.2


line stmt bran cond sub pod time code
1 26     26   2165173 use strict;
  26         39  
  26         613  
2 26     26   82 use warnings;
  26         29  
  26         1051  
3              
4             package Template::Pure;
5              
6             our $VERSION = '0.033';
7              
8 26     26   12299 use Mojo::DOM58;
  26         475243  
  26         747  
9 26     26   188 use Scalar::Util;
  26         35  
  26         866  
10 26     26   12059 use Template::Pure::ParseUtils;
  26         267  
  26         685  
11 26     26   9551 use Template::Pure::Filters;
  26         43  
  26         625  
12 26     26   8392 use Template::Pure::DataContext;
  26         46  
  26         614  
13 26     26   8373 use Template::Pure::DataProxy;
  26         38  
  26         570  
14 26     26   107 use Template::Pure::EncodedString;
  26         19  
  26         384  
15 26     26   8337 use Template::Pure::Iterator;
  26         43  
  26         662  
16 26     26   14264 use Storable qw(dclone);
  26         60400  
  26         118792  
17              
18             sub new {
19 50     50 1 70083 my ($proto, %args) = @_;
20 50   33     260 my $class = ref($proto) || $proto;
21              
22 50   50     240 my $template = delete($args{template}) || $class->template || die "Can't find a template";
23 50   100     191 my $directives = delete($args{directives}) || [$class->directives];
24            
25             my $self = bless +{
26             filters => delete($args{filters}) || +{},
27             directives => $directives,
28 50   50     548 components => delete($args{components}) || +{},
      100        
29             %args,
30             }, $class;
31              
32 50         179 my ($dom, @directives) = $self->_prepare_dom($template);
33 50         84 unshift @directives, @{$self->{directives}};
  50         213  
34              
35 50         85 $self->{dom} = $dom;
36 50         84 $self->{directives} = \@directives;
37 50         385 return $self;
38             }
39              
40             sub _process_pi {
41 6     6   9 my ($self, %params) = @_;
42 6         12 my ($target, %attrs) = $self->parse_processing_instruction($params{node}->tree->[1]);
43 6         11 my $ctx = delete $attrs{ctx};
44 6         7 my $src = delete $attrs{src};
45              
46 6 100       22 if($target eq 'pure-include') {
    100          
    100          
    50          
47 3         13 $params{node}->replace("<span id='include-$params{cnt}'>include placeholder</span>");
48 3         575 my @include_directives;
49 3 100       15 if($ctx) {
    50          
50 1         5 @include_directives = ("#include-$params{cnt}" => +{ $ctx => ['^.' => "/$src"]});
51             } elsif(%attrs) {
52 2         6 $attrs{$src} = "/$src";
53             @include_directives = (
54             "#include-$params{cnt}" => [
55             \%attrs,
56             '^.' => sub {
57 2     2   3 my ($t, $dom, $data) = @_;
58 2         4 return $data->{$src};
59             },
60 2         14 ]);
61             } else {
62 0         0 @include_directives = ("^#include-$params{cnt}", $src)
63             }
64 3         5 push @{$params{directives}}, @include_directives;
  3         8  
65             } elsif($target eq 'pure-wrapper') {
66 1         4 $params{node}->following('*')->first->attr('data-pure-wrapper-id'=>"wrapper-$params{cnt}");
67 1         229 $params{node}->remove;
68 1 50       97 if($ctx) {
    0          
69 1         11 push @{$params{directives}}, (
70             "^*[data-pure-wrapper-id=wrapper-$params{cnt}]", +{ $ctx => ['^.' => "/$src"]},
71 1     1   1 "*[data-pure-wrapper-id=wrapper-$params{cnt}]\@data-pure-wrapper-id", sub { undef },
72 1         1 );
73             } elsif(%attrs) {
74 0         0 $attrs{$src} = "/$src";
75 0         0 push @{$params{directives}}, (
76             "^*[data-pure-wrapper-id=wrapper-$params{cnt}]", [\%attrs, '^.' => "$src"],
77 0     0   0 "*[data-pure-wrapper-id=wrapper-$params{cnt}]\@data-pure-wrapper-id", sub { undef },
78 0         0 );
79             } else {
80 0         0 push @{$params{directives}}, (
81             "^*[data-pure-wrapper-id=wrapper-$params{cnt}]", $src,
82 0     0   0 "*[data-pure-wrapper-id=wrapper-$params{cnt}]\@data-pure-wrapper-id", sub { undef },
83 0         0 );
84             }
85             } elsif($target eq 'pure-filter') {
86 1         3 $params{node}->following('*')->first->attr('data-pure-filter-id'=>"filter-$params{cnt}");
87 1         205 $params{node}->remove;
88 1 50       105 if($ctx) {
    50          
89 0         0 push @{$params{directives}}, (
90             "^*[data-pure-filter-id=filter-$params{cnt}]", +{ $ctx => ['^.' => sub {
91 0     0   0 my ($t, $dom, $data) = @_;
92 0         0 $t->data_at_path($data, "/$src")->($dom);
93             } ]},
94 0     0   0 "*[data-pure-filter-id=filter-$params{cnt}]\@data-pure-filter-id", sub { undef },
95 0         0 );
96             } elsif(%attrs) {
97 1         11 push @{$params{directives}}, (
98             "^*[data-pure-filter-id=filter-$params{cnt}]", [\%attrs, '^.' => sub {
99 1     1   2 my ($t, $dom, $data) = @_;
100 1         5 $t->data_at_path($data, "/$src")->($dom);
101             } ],
102 1     1   1 "*[data-pure-filter-id=filter-$params{cnt}]\@data-pure-filter-id", sub { undef },
103 1         1 );
104             } else {
105 0         0 push @{$params{directives}}, (
106             "^*[data-pure-filter-id=filter-$params{cnt}]", sub {
107 0     0   0 my ($t, $dom, $data) = @_;
108 0         0 $t->data_at_path($data, $src)->($dom);
109             },
110 0     0   0 "*[data-pure-filter-id=filter-$params{cnt}]\@data-pure-filter-id", sub { undef },
111 0         0 );
112             }
113             } elsif($target eq 'pure-overlay') {
114 1         4 $params{node}->following('*')->first->attr('data-pure-overlay-id'=>"overlay-$params{cnt}");
115 1         383 $params{node}->remove;
116              
117 1         115 push @{$params{directives}}, (
  1         9  
118             "^*[data-pure-overlay-id=overlay-$params{cnt}]", [ +{%attrs, src=>$src }, '^.' => 'src'],
119             # "*[data-pure-overlay-id=overlay-$params{cnt}]\@data-pure-overlay-id", sub { undef },
120             );
121             } else {
122 0         0 warn "Encountering processing instruction $target that I can't process";
123             }
124 6         6 $params{cnt}++;
125 6         24 return %params;
126             }
127              
128 8     8 0 28 sub components { shift->{components} }
129 10     10 0 2568 sub initialized_components { shift->{initialized_components} }
130              
131             sub initialize_component {
132 8     8 0 10 my ($self, $name, %params) = @_;
133 8   50     17 return ($self->components->{$name} || die "No Component $name")->($self, %params);
134             }
135              
136             sub _process_components {
137 8     8   15 my ($self, %params) = @_;
138             my %fields = (
139 8 50       15 %{$params{node}->attr||+{}},
140             parent => $params{component_current_parent}[-1]||undef,
141             node => $params{node},
142 8   100     6 container => $self,
143             );
144              
145 8         121 my $component_id = $params{component_name}.'-'.$params{cnt};
146             my $component = $self->{initialized_components}{$component_id}
147 8         21 = $self->initialize_component($params{component_name}, %fields);
148              
149             $params{component_current_parent}[-1]->add_child($component)
150 8 100       77 if $params{component_current_parent}[-1];
151              
152 8         10 push @{$params{component_current_parent}}, $component;
  8         9  
153 8         17 $params{node}->attr('data-pure-component-id'=>$component_id);
154              
155 8 50       149 %params = $component->on_process_components($self, %params)
156             if $component->can('on_process_components');
157              
158 8         7 push @{$params{directives}}, (
  8         37  
159             "^*[data-pure-component-id=$component_id]",
160             $component->prepare_render_callback );
161              
162 8         13 $params{cnt}++;
163 8         39 return %params;
164             }
165              
166             sub _process_node {
167 886     886   1250 my ($self, %params) = @_;
168 886 100       1288 if($params{node}->type eq 'pi') {
169 6         52 %params = $self->_process_pi(%params);
170             }
171              
172 886         6144 my $component_name;
173 886 100 100     1313 if(($component_name) = (($params{node}->tag||'') =~m/^pure\-(.+)?/)) {
174 8 100       103 $params{component_current_parent} = [] unless defined $params{component_current_parent};
175 8         10 $params{component_name} = $component_name;
176 8         17 %params = $self->_process_components(%params);
177 8         15 delete $params{component_name};
178             }
179             $params{node}->child_nodes->each(sub {
180 758     758   19302 %params = $self->_process_node(%params, node=>$_);
181 886         8656 });
182              
183 886 100 100     15753 pop @{$params{component_current_parent}} if defined $params{component_current_parent} && $component_name;
  8         9  
184              
185 886         3400 return %params;
186             }
187              
188             sub _prepare_dom {
189 50     50   71 my ($self, $template) = @_;
190 50         83 my @directives = ();
191 50         238 my $dom = Mojo::DOM58->new($template);
192 50         27818 my %params = (cnt=>0, node=>$dom, directives=>\@directives);
193 50         189 my $nodes = $dom->child_nodes;
194              
195             $nodes->each(sub {
196 128     128   746 %params = $self->_process_node(%params, node=>$_);
197 50         4418 });
198            
199 50         146 return ($dom, @{$params{directives}});
  50         205  
200             }
201              
202 54     54 0 5840 sub clone_dom { return dclone(shift->{dom}) }
203              
204             sub render {
205 54     54 1 721 my ($self, $data_proto, $extra_directives) = @_;
206 54         273 $data_proto = Template::Pure::DataProxy->new($data_proto, self=>$self);
207 54 50       149 $extra_directives = [] unless $extra_directives;
208              
209 54         199 my $dom = $self->clone_dom;
210            
211 54         193 return $self->process_dom($dom, $data_proto, $extra_directives)->to_string;
212             }
213              
214             sub process_dom {
215 54     54 1 85 my ($self, $dom, $data_proto, $extra_directives) = @_;
216             return $self->_process_dom_recursive(
217             $data_proto,
218             $dom,
219 54         98 @{$self->{directives}},
220 54 50       74 @{$extra_directives||[]},
  54         260  
221             );
222             }
223              
224 16     16 0 58 sub default_filters { Template::Pure::Filters->all }
225 202     202 1 467 sub escape_html { Template::Pure::Filters::escape_html($_[1]) }
226 56     56 1 4503 sub encoded_string { Template::Pure::EncodedString->new($_[1]) }
227              
228             my %match_specs = ();
229 247   100 247 0 211 sub parse_match_spec { return %{ $match_specs{$_[1]} ||= +{Template::Pure::ParseUtils::parse_match_spec($_[1])} } }
  247         1082  
230              
231             my %data_specs = ();
232 175   100 175 0 130 sub parse_data_spec { return %{ $data_specs{$_[1]} ||= +{Template::Pure::ParseUtils::parse_data_spec($_[1])} } }
  175         655  
233              
234             my %data_templates = ();
235 11   100 11 0 9 sub parse_data_template { return @{ $data_templates{$_[1]} ||= [Template::Pure::ParseUtils::parse_data_template($_[1])] } }
  11         48  
236              
237             my %processing_instruction_specs = ();
238             sub parse_processing_instruction {
239 6   50 6 0 38 return @{ $processing_instruction_specs{$_[1]} ||= [Template::Pure::ParseUtils::parse_processing_instruction($_[1])] };
  6         33  
240             }
241              
242             my %itr_specs = ();
243 17   100 17 0 17 sub parse_itr_spec { return %{ $itr_specs{$_[1]} ||= +{Template::Pure::ParseUtils::parse_itr_spec($_[1])} } }
  17         73  
244              
245             sub data_at_path {
246 7     7 1 1697 my ($self, $data, $path) = @_;
247 7         13 my %data_spec = $self->parse_data_spec($path);
248              
249 7 50 66     43 unless(Scalar::Util::blessed($data) and $data->isa('Template::Pure::DataContext') ) {
250 7         17 $data = Template::Pure::DataContext->new($data, $self->{root_data});
251             }
252              
253 7         16 return $self->_value_from_data($data, %data_spec);
254             }
255              
256             sub at_or_die {
257 28     28 0 55 my ($self, $dom, $css) = @_;
258 28 100       65 my $new = $css eq '.' ? $dom : $dom->at($css);
259 28 50       3189 die "$css is not a matching path" unless defined $new;
260 28         68 return $new;
261             }
262              
263             sub find_or_die {
264 190     190 0 221 my ($self, $dom, $css) = @_;
265 190         407 my $collection = $dom->find($css);
266 190 100       55440 die "Match specification '$css' produces no nodes on $dom" unless $collection->size;
267 189         773 return $collection;
268             }
269              
270             sub _process_dom_recursive {
271 118     118   4383 my ($self, $data_proto, $dom, @directives) = @_;
272              
273 118 100       313 $self->{root_data} = $data_proto unless exists $self->{root_data};
274              
275 118         498 my $data = Template::Pure::DataContext->new($data_proto, $self->{root_data});
276              
277 118         318 ($data, @directives) = $self->_process_directive_instructions($dom, $data, @directives);
278              
279 118         264 while(@directives) {
280 235         10094 my $directive = shift @directives;
281              
282 235 100 100     783 if(ref($directive)||'' eq 'CODE') {
283 1         3 $directive->($self, $dom, $data);
284 1         489 next;
285             }
286              
287 234 100       460 if($directive =~/\=\{/g) {
288             $directive = join '', map {
289 1 100       3 ref $_ eq 'HASH' ? $self->_value_from_data($data, %$_) : $_;
  2         12  
290             } $self->parse_data_template($directive);
291             }
292              
293 234         386 my %match_spec = $self->parse_match_spec($directive);
294 234         336 my $action_proto = shift @directives;
295              
296 234 50       384 $dom = $dom->root if $match_spec{absolute};
297              
298 234 100 100     1836 if($match_spec{mode} eq 'filter') {
    100 100        
    100 100        
    100          
    100          
299 1         9 $self->_process_dom_filter($dom, $data, $match_spec{css}, $action_proto);
300             } elsif((ref($action_proto)||'') eq 'HASH') {
301 24         32 $self->_process_sub_data($dom, $data, \%match_spec, %{$action_proto});
  24         91  
302             } elsif((ref($action_proto)||'') eq 'ARRAY') {
303 17         50 $self->process_sub_directives($dom, $data->value, $match_spec{css}, @{$action_proto});
  17         49  
304             } elsif((ref($action_proto)||'') eq 'CODE') {
305 21         55 $self->_process_code($dom, $data, $action_proto, %match_spec);
306             } elsif(Scalar::Util::blessed($action_proto)) {
307 10         33 $self->_process_obj($dom, $data, $action_proto, %match_spec);
308             } else {
309 161         386 my $value_proto = $self->_value_from_action_proto($dom, $data, $action_proto, %match_spec);
310 161         528 $self->_process_value_proto($dom, $data, $value_proto, %match_spec);
311             }
312             }
313              
314 116         15285 return $dom;
315             }
316              
317             sub _process_value_proto {
318 188     188   380 my ($self, $dom, $data, $value_proto, %match_spec) = @_;
319 188 100 100     1619 if(
    100 66        
    100 100        
      100        
320             Scalar::Util::blessed($value_proto) &&
321             ($value_proto->isa('Template::Pure') || $value_proto->can('TO_HTML'))
322             ) {
323 9         37 $self->_process_obj($dom, $data, $value_proto, %match_spec);
324             } elsif((ref($value_proto)||'') eq 'CODE') {
325 2         8 $self->_process_code($dom, $data, $value_proto, %match_spec);
326             } elsif( (ref($value_proto)||'') eq 'ARRAY') {
327 1         3 $self->process_sub_directives($dom, $data->value, $match_spec{css}, @{$value_proto});
  1         6  
328             } else {
329 176         413 $self->_process_match_spec($dom, $value_proto, %match_spec);
330             }
331             }
332              
333             sub _process_obj {
334 19     19   51 my ($self, $dom, $data, $obj, %match_spec) = @_;
335 19         32 my $css = $match_spec{css};
336              
337 19 100       115 if($obj->isa(ref $self)) {
    100          
    50          
338 15 100       28 if($css eq '.') {
339 12         31 my $value = $self->_value_from_template_obj($dom, $data, $obj, %match_spec);
340 12         95 $self->_process_mode($dom, $value, %match_spec);
341             } else {
342 3         8 my $collection = $self->find_or_die($dom,$css);
343             $collection->each(sub {
344              
345 4     4   134 my $content;
346 4 100       20 if($match_spec{target} eq 'content') {
    50          
    0          
347 3         12 $content = $self->encoded_string($_->content);
348             } elsif($match_spec{target} eq 'node') {
349 1         3 $content = $self->encoded_string($_->to_string);
350 0         0 } elsif(my $attr = ${$match_spec{target}}) {
351 0         0 $content = $_->attr($attr);
352             }
353              
354 4         15 my $new_data = Template::Pure::DataProxy->new(
355             $data->value,
356             content => $self->encoded_string($content));
357              
358 4         16 my $value = $self->encoded_string($obj->render($new_data));
359              
360 4         29 $self->_process_mode($_, $value, %match_spec);
361 3         20 });
362             }
363             } elsif($obj->can('TO_HTML')) {
364 3 100       8 if($css eq '.') {
365 1         4 my $value = $obj->TO_HTML($self, $dom, $data->value);
366 1         7 $self->_process_mode($dom, $value, %match_spec);
367             } else {
368 2         7 my $collection = $self->find_or_die($dom,$css);;
369             $collection->each(sub {
370 3     3   110 my $value = $obj->TO_HTML($self, $_, $data->value);
371 3         41 $self->_process_mode($_, $value, %match_spec);
372 2         12 });
373             }
374             } elsif($obj->isa('Mojo::DOM58')) {
375 1         4 $self->_process_match_spec($dom, $obj, %match_spec);
376             } else {
377 0         0 die "Can't process object of type $obj.";
378             }
379             }
380              
381             sub _value_from_action_proto {
382 180     180   353 my ($self, $dom, $data, $action_proto, %match_spec) = @_;
383 180 100 50     350 if(ref \$action_proto eq 'SCALAR') {
    50          
384 167         273 return $self->_value_from_scalar_action($data, $action_proto);
385             } elsif((ref($action_proto)||'') eq 'SCALAR') {
386 13         26 return $self->_value_from_dom($dom, $$action_proto);
387             } else {
388 0         0 die "I encountered an action I don't know what to do with: $action_proto";
389             }
390             }
391              
392             sub _value_from_scalar_action {
393 167     167   159 my ($self, $data, $action_proto) = @_;
394              
395             ## If a $action_proto contains a ={ with no | first OR it contains a ={ and no |
396             ## That means it is a string with placeholders
397              
398 167         242 my $first_pipe = index($action_proto, '|');
399 167         140 my $first_open = index($action_proto, '={');
400            
401 167 100 100     676 if(
      100        
      66        
402             (
403             ($first_open >= 0) &&
404             ($first_open < $first_pipe)
405             ) || (
406             ($first_open >= 0) &&
407             ($first_pipe == -1)
408             )
409             ) {
410             my @parts = map {
411 10 100       18 ref $_ eq 'HASH' ? $self->_value_from_data($data, %$_) : $_;
  31         97  
412             } $self->parse_data_template($action_proto);
413              
414             # If the last part is a literal AND it has trailing filters
415             # we need to process the filters. And deal with all the special cases...
416 10 100 100     45 if(Scalar::Util::blessed($parts[-1]) && (index("$parts[-1]", '|') >0) ) {
417 3         4 my $last = substr "$parts[-1]", 0, index("$parts[-1]", '|');
418 3         10 $last=~s/\s+$//;
419 3         12 my %data_spec = $self->parse_data_spec(pop @parts);
420 3         9 my $return = join('', @parts, $last);
421 3         4 foreach my $filter (@{$data_spec{filters}}) {
  3         4  
422 3         6 $return = $self->_apply_data_filter($return, $data, $filter);
423             }
424 3         9 return $return;
425             }
426            
427 7         57 return join('', @parts);
428             } else {
429 157         235 my %data_spec = $self->parse_data_spec($action_proto);
430 157 100       333 if(defined(my $literal = $data_spec{literal})) {
431 2         7 return $literal;
432             } else {
433 155         346 return $self->_value_from_data($data, %data_spec);
434             }
435             }
436             }
437              
438             sub _process_code {
439 23     23   48 my ($self, $dom, $data, $code, %match_spec) = @_;
440 23         32 my $css = $match_spec{css};
441 23 100       36 if($css eq '.') {
442 8         20 my $value = $self->_call_coderef($code, $dom, $data->value);
443 8         23 $self->_process_value_proto($dom, $data, $value, %match_spec);
444             } else {
445 15         27 my $collection = $self->find_or_die($dom,$css);
446             $collection->each(sub {
447 19     19   229 my $value = $self->_call_coderef($code, $_, $data->value);
448 19         158 my %local_match_spec = (%match_spec, css=>'.');
449 19         53 $self->_process_value_proto($_, $data, $value, %local_match_spec);
450 15         75 });
451             }
452             }
453              
454             sub _call_coderef {
455 27     27   34 my ($self, $code, $dom, $value) = @_;
456 27         71 return $self->$code($dom, $value);
457             }
458              
459             sub _value_from_template_obj {
460 15     15   31 my ($self, $dom, $data, $template, %match_spec) = @_;
461 15         34 my $content = $self->_value_from_dom($dom, \%match_spec);
462 15         278 my $new_data = Template::Pure::DataProxy->new(
463             $data->value,
464             content => $self->encoded_string($content));
465            
466 15         44 return $self->encoded_string($template->render($new_data));
467             }
468              
469             sub _process_directive_instructions {
470 118     118   267 my ($self, $dom, $data, @directives) = @_;
471 118 100 100     473 if( (ref($directives[0])||'') eq 'HASH') {
472 9         11 my %map = %{shift(@directives)};
  9         31  
473 9         13 my %new_data;
474 9         17 foreach my $key (keys %map) {
475 19         1066 $new_data{$key} = $self->_value_from_action_proto($dom, $data, $map{$key});
476             }
477 9         1123 $data = Template::Pure::DataContext->new(\%new_data);
478             }
479 118         363 return ($data, @directives);
480             }
481              
482             sub _process_sub_data {
483 24     24   38 my ($self, $dom, $data, $match_spec, %action) = @_;
484              
485             # I don't know what it means to match repeat on attribes or append/prepent
486             # right now, so just doing match on the CSS and welcome specifications for
487             # this behavior.
488              
489 24         40 my $css = $match_spec->{css};
490            
491             # Pull out any sort or filters
492 24 100       55 my $sort_cb = exists $action{order_by} ? delete $action{order_by} : undef;
493 24 100       46 my $grep_cb = exists $action{grep} ? delete $action{grep} : undef;
494 24 50       57 my $filter_cb = exists $action{filter} ? delete $action{filter} : undef;
495 24 50       44 my $display_fields = exists $action{display_fields} ? delete $action{display_fields} : undef;
496 24 100       39 my $following_directives = exists $action{directives} ? delete $action{directives} : undef;
497 24         48 my ($sub_data_proto, $sub_data_action) = %action;
498              
499 24 100       70 if(index($sub_data_proto,'<-') > 0) {
500              
501 17 100       53 if(ref \$sub_data_action eq 'SCALAR') {
502 5         5 my $new_match_spec = '.';
503 5 100       10 $new_match_spec = "+$new_match_spec" if $match_spec->{mode} eq 'append';
504 5 50       9 $new_match_spec = "$new_match_spec+" if $match_spec->{mode} eq 'prepend';
505 5 50       10 $new_match_spec = "$new_match_spec|" if $match_spec->{mode} eq 'filter';
506 5 50       8 $new_match_spec = "^$new_match_spec" if $match_spec->{target} eq 'node';
507 5         8 $sub_data_action = [ $new_match_spec => $sub_data_action ];
508             }
509              
510 17 100       39 if(ref $sub_data_action eq 'CODE') {
511 2         3 my $new_match_spec = '.';
512 2 50       4 $new_match_spec = "+$new_match_spec" if $match_spec->{mode} eq 'append';
513 2 100       5 $new_match_spec = "$new_match_spec+" if $match_spec->{mode} eq 'prepend';
514 2 50       3 $new_match_spec = "$new_match_spec|" if $match_spec->{mode} eq 'filter';
515 2 50       3 $new_match_spec = "^$new_match_spec" if $match_spec->{target} eq 'node';
516 2         4 $sub_data_action = [ $new_match_spec => $sub_data_action ];
517             }
518              
519 17 100       57 if(Scalar::Util::blessed($sub_data_action)) {
520 2         3 my $new_match_spec = '.';
521 2 50       4 $new_match_spec = "+$new_match_spec" if $match_spec->{mode} eq 'append';
522 2 50       5 $new_match_spec = "$new_match_spec+" if $match_spec->{mode} eq 'prepend';
523 2 50       4 $new_match_spec = "$new_match_spec|" if $match_spec->{mode} eq 'filter';
524 2 100       5 $new_match_spec = "^$new_match_spec" if $match_spec->{target} eq 'node';
525 2         3 $sub_data_action = [ $new_match_spec => $sub_data_action ];
526             }
527              
528 17 50       40 die "Action for '$sub_data_proto' must be an arrayref of new directives"
529             unless ref $sub_data_action eq 'ARRAY';
530              
531 17         37 my ($new_key, $itr_data_spec) = $self->parse_itr_spec($sub_data_proto);
532 17         62 my $itr_data_proto = $self->_value_from_data($data, %$itr_data_spec);
533              
534             ## For now if the found value is undef, we second it along ti be trimmed
535             ## this behavior might be tweaked as examples of usage arise, also for now
536             ## we just pass through an empty iterator instead of considering it undef
537             ## ie [] is not considered like undef for now...
538              
539 17 100       38 return $self->_process_match_spec($dom, $itr_data_proto, %$match_spec)
540             if $self->_value_is_undef($itr_data_proto);
541              
542 16         15 my %options;
543 16 50       35 if($display_fields) {
544 0         0 $options{display_fields} = $display_fields;
545             }
546              
547 16 100       26 if($sort_cb) {
548 2 50       6 if(ref(\$sort_cb) eq 'SCALAR') {
549 0         0 my %sub_data_spec = $self->parse_data_spec($sort_cb);
550 0         0 my $value = $self->_value_from_data($data, %sub_data_spec);
551 0         0 $sort_cb = $value;
552             }
553 2 50       7 die "the 'sort' key must point to an anonymous subroutine" unless ref($sort_cb) eq 'CODE';
554 2         4 $options{sort} = $sort_cb;
555             }
556 16 100       27 if($grep_cb) {
557 1 50       3 if(ref(\$grep_cb) eq 'SCALAR') {
558 1         2 my %sub_data_spec = $self->parse_data_spec($grep_cb);
559 1         3 my $value = $self->_value_from_data($data, %sub_data_spec);
560 1         2 $grep_cb = $value;
561             }
562 1 50       4 die "the 'grep' key must point to an anonymous subroutine" unless ref($grep_cb) eq 'CODE';
563 1         2 $options{grep} = $grep_cb;
564             }
565 16 50       26 if($filter_cb) {
566 0 0       0 if(ref(\$filter_cb) eq 'SCALAR') {
567 0         0 my %sub_data_spec = $self->parse_data_spec($filter_cb);
568 0         0 my $value = $self->_value_from_data($data, %sub_data_spec);
569 0         0 $filter_cb = $value;
570             }
571 0 0       0 die "the 'sort' key must point to an anonymous subroutine" unless ref($filter_cb) eq 'CODE';
572 0         0 $options{filter} = $filter_cb;
573             }
574              
575 16 50       29 $options{display_fields} = $display_fields if $display_fields;
576              
577 16         73 my $iterator = Template::Pure::Iterator->from_proto($itr_data_proto, $self, \%options);
578            
579 16 50       39 if($css eq '.') {
580 0         0 $self->_process_iterator($dom, $new_key, $iterator, @{$sub_data_action});
  0         0  
581             } else {
582 16         32 my $collection = $self->find_or_die($dom,$css);
583             $collection->each(sub {
584 17     17   185 $self->_process_iterator($_, $new_key, $iterator, @{$sub_data_action});
  17         40  
585 16         75 });
586             }
587             } else {
588 7         16 my %sub_data_spec = $self->parse_data_spec($sub_data_proto);
589 7         22 my $value = $self->_value_from_data($data, %sub_data_spec);
590              
591             ## If the value is undefined, we dont' continue... should we remove all this...?
592 7 100 33     40 if(ref $sub_data_action eq 'ARRAY') {
    50          
593 4         4 $self->process_sub_directives($dom, $value, $css, @{$sub_data_action});
  4         12  
594             } elsif(Scalar::Util::blessed($sub_data_action) && $sub_data_action->isa(ref $self)) {
595 3         12 my $new_data = Template::Pure::DataContext->new($value);
596 3         13 my $new_value = $self->_value_from_template_obj($dom, $new_data, $sub_data_action, %$match_spec);
597 3         33 $self->_process_match_spec($dom, $new_value, %$match_spec);
598             } else {
599 0         0 die "Don't know how to process $value on $css for $sub_data_action";
600             }
601             }
602              
603             ## Todo... not sure if this is right or useful
604 23 100       4834 if($following_directives) {
605 1         2 $self->process_sub_directives($dom, $data, $css, @{$following_directives});
  1         5  
606             }
607             }
608              
609             sub _process_iterator {
610 17     17   29 my ($self, $dom, $key, $iterator, @actions) = @_;
611 17         1769 my $template = dclone($dom);
612 17         62 while(my $datum = $iterator->next) {
613 41         50 $datum = $$datum;
614 41         76 my $new_dom = Mojo::DOM58->new($template);
615 41         7597 my $new_data;
616 41 100       75 if($key eq '.') {
617 2         6 $new_data = Template::Pure::DataProxy
618             ->new($datum, i=>$iterator);
619             } else {
620 39         71 $new_data = +{
621             $key => $datum,
622             i => $iterator,
623             };
624             }
625 41         87 $self->_process_dom_recursive($new_data, $new_dom->descendant_nodes->[0], @actions);
626 41         166 $dom->replace($new_dom);
627             }
628 17         43 $dom->remove;
629             }
630              
631             sub process_sub_directives {
632 23     23 0 44 my ($self, $dom, $data, $css, @directives) = @_;
633 23 100       46 if($css eq '.') {
634 2         5 $self->_process_dom_recursive($data, $dom, @directives);
635             } else {
636 21         42 my $collection = $self->find_or_die($dom,$css);
637             $collection->each(sub {
638 21     21   169 $self->_process_dom_recursive($data, $_, @directives);
639 21         104 });
640             }
641             }
642              
643             sub _value_from_dom {
644 28     28   25 my $self = shift;
645 28         26 my $dom = shift;
646 28 100       55 my %match_spec = ref $_[0] ? %{$_[0]} : $self->parse_match_spec($_[0]);
  15         40  
647              
648 28 100       69 $dom = $dom->root if $match_spec{absolute};
649              
650             ## TODO, perhaps this could do a find instead of at and return
651             ## a collection, which populates an iterator if requested?
652              
653 28 100       201 if($match_spec{target} eq 'content') {
    100          
    50          
654             #return $self->encoded_string($self->at_or_die($dom, $match_spec{css})->content);
655             # Not sure if there is a more effecient way to make this happen...
656 10         30 return Mojo::DOM58->new($self->at_or_die($dom, $match_spec{css})->content);
657             } elsif($match_spec{target} eq 'node') {
658             ## When we want a full node, with HTML tags, we encode the string
659             ## since I presume they want a copy not escaped. T 'think' this is
660             ## the commonly desired thing and you can always apply and escape_html filter
661             ## yourself when you don't want it.
662             #return $self->encoded_string($self->at_or_die($dom, $match_spec{css})->to_string);
663 17         33 return $self->at_or_die($dom, $match_spec{css});
664 1         5 } elsif(my $attr = ${$match_spec{target}}) {
665             ## TODO not sure what if any encoding we need here.
666 1         2 return $self->at_or_die($dom, $match_spec{css})->attr($attr);
667             }
668             }
669              
670             sub _value_from_data {
671 209     209   339 my ($self, $data, %data_spec) = @_;
672 209         539 my $value = $data->at(%data_spec)->value;
673 209         383 foreach my $filter (@{$data_spec{filters}}) {
  209         310  
674 13         26 $value = $self->_apply_data_filter($value, $data, $filter);
675             }
676 209         682 return $value;
677             }
678              
679             sub _apply_data_filter {
680 16     16   21 my ($self, $value, $data, $filter) = @_;
681 16         22 my ($name, @args) = @$filter;
682 16 100       21 @args = map { ref $_ ? $self->_value_from_data($data, %$_) : $_ } @args;
  10         31  
683 16         34 return $self->filter($name, $value, @args);
684             }
685              
686             sub _process_dom_filter {
687 1     1   2 my ($self, $dom, $data, $css, $cb) = @_;
688 1 50       3 if($css eq '.') {
689 0         0 $cb->($self, $dom, $data);
690             } else {
691 1         2 my $collection = $self->find_or_die($dom,$css);
692             $collection->each(sub {
693 1     1   7 $cb->($self, $_, $data);
694 1         6 });
695             }
696             }
697              
698             sub _process_match_spec {
699 181     181   345 my ($self, $dom, $value, %match_spec) = @_;
700 181 100       279 if($match_spec{css} eq '.') {
701 49         101 $self->_process_mode($dom, $value, %match_spec);
702             } else {
703 132         219 my $collection = $self->find_or_die($dom,$match_spec{css});
704             $collection->each(sub {
705 133     133   1280 $self->_process_mode($_, $value, %match_spec);
706 131         575 });
707             }
708             }
709              
710             sub _value_is_undef {
711 348     348   295 my ($self, $value) = @_;
712 348 100       497 return 1 if !defined($value);
713 336 100 100     959 return 1 if Scalar::Util::blessed($value) && $value->isa('Template::Pure::UndefObject');
714 335         744 return 0;
715             }
716              
717              
718             sub _process_mode {
719 202     202   440 my ($self, $dom, $value, %match_spec) = @_;
720              
721 202         206 my $mode = $match_spec{mode};
722 202         174 my $target = $match_spec{target};
723 202         357 my $safe_value = $self->escape_html($value);
724              
725             ## This behavior may be tweaked in the future.
726 202 100       318 if($self->_value_is_undef($safe_value)) {
727 12 50       39 if($target eq 'node') {
    100          
    50          
728 0         0 return $dom->remove;
729             } elsif($target eq 'content') {
730 9 100 100     31 if( ($mode eq 'append') or ($mode eq 'prepend')) {
731             # Don't remove anything since there's not a target mode here
732             # just stuff we wanted to add to the start or end.
733 4         15 return;
734             } else {
735 5         12 return $dom->remove; # TODO, or should this remove just the content..?
736             }
737             } elsif(my $attr = $$target) {
738 3         8 return delete $dom->attr->{$attr};
739             }
740             }
741              
742 190 100       339 if($mode eq 'replace') {
    100          
    50          
743 161 100       280 if($target eq 'content') {
    100          
    50          
744 129 50       164 $dom->content($safe_value) unless $self->_value_is_undef($safe_value);
745             } elsif($target eq 'node') {
746 21         86 $dom->replace($safe_value);
747             } elsif(my $attr = $$target) {
748 11         30 $dom->attr($attr=>$safe_value);
749             } else {
750 0         0 die "Don't understand target of $target";
751             }
752             } elsif($mode eq 'append') {
753 24 100       58 if($target eq 'content') {
    100          
    50          
754 14         43 $dom->append_content($safe_value);
755             } elsif($target eq 'node') {
756 5         45 $dom->append($safe_value);
757             } elsif(my $attr = $$target) {
758 5   100     8 my $current_attr = $dom->attr($attr)||'';
759 5         72 $dom->attr($attr=>"$current_attr$safe_value" );
760             } else {
761 0         0 die "Don't understand target of $target";
762             }
763             } elsif($mode eq 'prepend') {
764 5 100       10 if($target eq 'content') {
    100          
    50          
765 3         14 $dom->prepend_content($safe_value);
766             } elsif($target eq 'node') {
767 1         4 $dom->prepend($safe_value);
768             } elsif(my $attr = $$target) {
769 1   50     3 my $current_attr = $dom->attr($attr)||'';
770 1         16 $dom->attr($attr=> "$safe_value$current_attr" );
771             } else {
772 0         0 die "Don't understand target of $target";
773             }
774             } else {
775 0         0 die "Not sure how to handle mode '$mode'";
776             }
777             }
778              
779             sub filter {
780 16     16 1 20 my ($self, $name, $data, @args) = @_;
781             my %filters = (
782             $self->default_filters,
783 16         30 %{$self->{filters}}
  16         129  
784             );
785            
786 16   50     48 my $filter = $filters{$name} ||
787             die "Filter $name does not exist";
788            
789 16         44 return $filter->($self, $data, @args);
790             }
791              
792             1;
793              
794             =head1 NAME
795              
796             Template::Pure - Perlish Port of pure.js and more
797              
798             =head1 SYNOPSIS
799              
800             use Template::Pure;
801              
802             my $html = q[
803             <html>
804             <head>
805             <title>Page Title</title>
806             </head>
807             <body>
808             <section id="article">
809             <h1>Header</h1>
810             <div>Story</div>
811             </section>
812             <ul id="friendlist">
813             <li>Friends</li>
814             </ul>
815             </body>
816             </html>
817             ];
818              
819             my $pure = Template::Pure->new(
820             template=>$html,
821             directives=> [
822             'head title' => 'meta.title',
823             '#article' => [
824             'h1' => 'header',
825             'div' => 'content',
826             ],
827             'ul li' => {
828             'friend<-user.friends' => [
829             '.' => '={friend}, #={i.index}',
830             ],
831             },
832             ],
833             );
834              
835             my $data = +{
836             meta => {
837             title => 'Travel Poetry',
838             created_on => '1/1/2000',
839             },
840             header => 'Fire',
841             content => q[
842             Are you doomed to discover that you never recovered from the narcoleptic
843             country in which you once stood? Where the fire's always burning, but
844             there's never enough wood?
845             ],
846             user => {
847             name => 'jnap',
848             friends => [qw/jack jane joe/],
849             },
850             };
851              
852             print $pure->render($data);
853              
854             Results in:
855              
856             <html>
857             <head>
858             <title>Travel Poetry</title>
859             </head>
860             <body>
861             <section id="article">
862             <h1>Fire</h1>
863             <div>
864             Are you doomed to discover that you never recovered from the narcoleptic
865             country in which you once stood? Where the fire&#39;s always burning, but
866             there&#39;s never enough wood?
867             </div>
868             </section>
869             <ul id="friendlist">
870             <li>jack, #1</li>
871             <li>jane, #2</li>
872             <li>joe, #3</li>
873             </ul>
874             </body>
875             </html>
876              
877             =head1 DESCRIPTION
878              
879             B<NOTE> WARNING: Early access module. Although we have a lot of test cases and this is the
880             third redo of the code I've not well tested certain features (such as using an object as
881             a data context) and other parts such as the way we handle undefined values (or empty
882             iterators) are still 'first draft'. Code currently is entirely unoptimized. Additionally the
883             documenation could use another detailed review, and we'd benefit from some 'cookbook' style docs.
884             Nevertheless its all working well enough that I'd like to publish it so I can start using it
885             more widely and hopefully some of you will like what you see and be inspired to try and help
886             close the gaps.
887              
888             B<NOTE> UPDATE (version 0.015): The code is starting to shape up and at this point I'm started to commit to
889             things that pass the current test case should still pass in the future unless breaking changes
890             are absolutely required to move the project forward. Main things to be worked out is if the
891             rules around handling undef values and when we have an object as the loop iterator has not
892             been as well tested as it should be.
893              
894             B<NOTE> UPDATE (version 0.023): Error messaging is tremendously improved and a number of edge case
895             issues have worked out while working on the Catalyst View adaptor (not on CPAN at the time of this
896             writing). Main blockers before I can consider this stable include lots of performance tuning,
897             completion of a working Catalyst view adaptor, and refactoring of the way we use the L<Mojo::DOM58>
898             parser so that parsers are plugable. I also need to refactor how processing instructions are
899             handled so that its not a pile of inlined code (ideally you should be able to write your own
900             processing instructions). I feel commited to the existing test suite and documented
901             API.
902              
903             L<Template::Pure> HTML/XML Templating system, inspired by pure.js L<http://beebole.com/pure/>, with
904             some additions and modifications to make it more Perlish and to be more suitable
905             as a server side templating framework for larger scale needs instead of single page
906             web applications.
907              
908             The core concept is you have your templates in pure HTML and create CSS style
909             matches to run transforms on the HTML to populate data into the template. This allows you
910             to have very clean, truely logicless templates. This approach can be useful when the HTML designers
911             know little more than HTML and related technologies. It helps promote separation of concerns
912             between your UI developers and your server side developers. Over the long term the separate
913             and possibilities for code reuse can lead to an easier to maintain system.
914              
915             The main downside is that it can place more work on the server side developers, who have to
916             write the directives unless your UI developers are able and willing to learn the minimal Perl
917             required for that job. Also since the CSS matching directives can be based on the document
918             structure, it can lead to onerous tight binding between yout document structure and the layout/display
919             logic. For example due to some limitations in the DOM parser, you might have to add some extra markup
920             just so you have a place to match, when you have complex and deeply nested data.
921              
922             Additionally many UI designers already are familiar with some basic templating systems and
923             might really prefer to use that so that they can maintain more autonomy and avoid the additional
924             learning curve that L<Template::Pure> will requires (most people seem to find its a bit more
925             effort to learn off the top compared to more simple systems like Mustache or even L<Template::Toolkit>.
926              
927             Although inspired by pure.js L<http://beebole.com/pure/> this module attempts to help mitigate some
928             of the listed possible downsides with additional features that are a superset of the original
929             pure.js specification. For example you may include templates inside of templates as includes or even
930             overlays that provide much of the same benefit that template inheritance offers in many other
931             popular template frameworks. These additional features are intended to make it more suitable as a general
932             purpose server side templating system.
933              
934             =head1 CREATING TEMPLATE OBJECTS
935              
936             The first step is to create a L<Template::Pure> object:
937              
938             my $pure = Template::Pure->new(
939             template=>$html,
940             directives=> \@directives);
941              
942             L<Template::Pure> has two required parameters:
943              
944             =over 4
945              
946             =item template
947              
948             This is a string that is an HTML template that can be parsed by L<Mojo::DOM58>
949              
950             =item directives
951              
952             An arrayref of directives, which are commands used to transform the template when
953             rendering against data. For more on directives, see L</DIRECTIVES>
954              
955             =back
956              
957             L<Template::Pure> has a third optional parameter, 'filters', which is a hashref of
958             user created filters. For more see L<Template::Pure::Filters> and L</FILTERS>.
959              
960             Once you have a created object, you may call the following methods:
961              
962             =over 4
963              
964             =item render ($data, ?\@extra_directives?)
965              
966             Render a template with the given '$data', which may be a hashref or an object with
967             fields that match data paths defined in the directions section (see L</DIRECTIVES>)
968              
969             Returns a string. You may pass in an arrayref of extra directives, which are executed
970             just like directives defined at instantiation time (although future versions of this
971             distribution may offer optimizations to directives known at create time). These optional
972             added directives are executed after the directives defined at create time.
973              
974             Since we often traverse the $data structure as part of rendering a template, we usually call
975             the current path the 'data context'. We always track the base or root context and you can
976             always return to it, as you will later see in the L</DIRECTIVES> section.
977              
978             =item process_dom ($data, ?\@extra_directives?)
979              
980             Works just like 'render', except we return a L<Mojo::DOM58> object instead of a string directly.
981             Useful if you wish to retrieve the L<Mojo::DOM58> object for advanced, custom tranformations.
982              
983             =item data_at_path ($data, $path)
984              
985             Given a $data object, returns the value at the defined $path. Useful in your coderef actions
986             (see below) when you wish to grab data from the current data context but wish to avoid
987             using $data implimentation specific lookup.
988              
989             =item escape_html ($string)
990              
991             Given a string, returns a version of it that has been properly HTML escaped. Since we do
992             such escaping automatically for most directives you won't need it a lot, but could be useful
993             in a coderef action. Can also be called as a filter (see L</FILTERS>).
994              
995             =item encoded_string ($string)
996              
997             As mentioned we automatically escape values to help protect you against HTML injection style
998             attacked, but there might be cases when you don't wish this protection. Can also be called
999             as a filter (see L</FILTERS>).
1000              
1001             =back
1002              
1003             There are other methods in the code but please consider all that stuff part of my 'black box'
1004             and only reach into it if you are willing to suffer possible breakage on version changes.
1005              
1006             =head1 DIRECTIVES
1007              
1008             Directives are instructions you prepare against a template, upon which later we render
1009             data against. Directives are ordered and are excuted in the order defined. The general
1010             form of a directive is C<CSS Match> => C<Action>, where action can be a path to fetch data
1011             from, more directives, a coderef, etc. The main idea is that the CSS matches
1012             a node in the HTML template, and an 'action' is performed on that node. The following actions are allowed
1013             against a match specification:
1014              
1015             =head2 Scalar - Replace the value indicated by the match.
1016              
1017             my $html = qq[
1018             <div>
1019             Hello <span id='name'>John Doe</span>!
1020             </div>
1021             ];
1022              
1023             my $pure = Template::Pure->new(
1024             template => $html,
1025             directives => [
1026             '#name' => 'fullname',
1027             ]);
1028              
1029             my %data = (
1030             fullname => 'H.P Lovecraft');
1031              
1032             print $pure->render(\%data);
1033              
1034             Results in:
1035              
1036             <div>
1037             Hello <span id='name'>H.P Lovecraft</span>!
1038             </div>
1039              
1040             In this simple case the value of the CSS match '#name' is replaced by the value 'fullname'
1041             indicated at the current data context (as you can see the starting context is always the
1042             root, or top level data object.)
1043              
1044             If instead of a hashref the rendered data context is an object, we look for a method
1045             matching the name of the indicated path. If there is no matching method or key, we generate
1046             an exception.
1047              
1048             If there is a key matching the requested data path as indicated by the directive, but the associated
1049             value is undef, then the matching node (tag included) is removed. If there is no matching key,
1050             this raises an error.
1051              
1052             B<NOTE>: Remember that you can use dot notation in your action value to indicate a path on the
1053             current data context, for example:
1054              
1055             my %data = (
1056             identity => {
1057             first_name => 'Howard',
1058             last_name => 'Lovecraft',
1059             });
1060              
1061             my $pure = Template::Pure->new(
1062             template => $html,
1063             directives => [ '#last_name' => 'identity.last_name']
1064             );
1065              
1066             In this case the value of the node indicated by '#last_name' will be set to 'Lovecraft'.
1067              
1068             B<NOTE>: If your scalar action returns a L<Template::Pure> object, it will render as if
1069             it was an object action as described below L</Object - Set the match value to another Pure Template>.
1070              
1071             For example:
1072              
1073             my $wrapper_html = qq[
1074             <section>Example Wrapped Stuff</section>];
1075              
1076             my $wrapper = Template::Pure->new(
1077             template=>$wrapper_html,
1078             directives=> [
1079             'section' => 'content',
1080             ]);
1081              
1082             my $template => qq[
1083             <html>
1084             <head>
1085             <title>Title Goes Here!</title>
1086             </head>
1087             <body>
1088             <p>Hi Di Ho!</p>
1089             </body>
1090             </html>
1091             ];
1092              
1093             my @directives = (
1094             title => 'title | upper',
1095             body => 'info',
1096             );
1097              
1098             my $pure = Template::Pure->new(
1099             template => $template,
1100             directives => \@directives);
1101              
1102             my $data = +{
1103             title => 'Scalar objects',
1104             info => $wrapper,
1105             };
1106              
1107             ok my $string = $pure->render($data);
1108              
1109             Results in:
1110              
1111             <html>
1112             <head>
1113             <title>SCALAR OBJECTS</title>
1114             </head>
1115             <body>
1116             <section>
1117             <p>Hi Di Ho!</p>
1118             </section></body>
1119             </html>
1120              
1121             This feature is currently only active for scalar actions but may be extended to other action
1122             types in the future.
1123              
1124             B<NOTE> If your scalar action returns a coderefence, we process it as if the
1125             scalar action was itself a code reference. See L<\'Coderef - Programmatically replace the value indicated'>.
1126              
1127             =head2 ScalarRef - Set the value to the results of a match
1128              
1129             There may be times when you want to set the value of something to an existing
1130             value in the current template:
1131              
1132             my $html = qq[
1133             <html>
1134             <head>
1135             <title>Welcome Page</title>
1136             </head>
1137             <body>
1138             <h1>Page Title</h1>
1139             </body>
1140             </html>
1141             ];
1142              
1143             my $pure = Template::Pure->new(
1144             template => $html,
1145             directives => [
1146             'h1#title' => \'/title',
1147             ]);
1148              
1149             print $pure->render({});
1150              
1151             Results in:
1152              
1153             <html>
1154             <head>
1155             <title>Welcome Page</title>
1156             </head>
1157             <body>
1158             <h1>Welcome Page</h1>
1159             </body>
1160             </html>
1161              
1162             B<NOTE> Since directives are processed in order, this means that you can
1163             reference the rendered value of a previous directive via this alias.
1164              
1165             B<NOTE> The match runs against the current selected node, as defined by the last
1166             successful match. If you need to match a value from the root of the DOM tree you
1167             can use the special '/' syntax on your CSS match, as shown in the above example,
1168             or:
1169              
1170             directives => [
1171             'h1#title' => \'/title',
1172             ]);
1173              
1174              
1175             =head2 Coderef - Programmatically replace the value indicated
1176              
1177             my $html = qq[
1178             <div>
1179             Hello <span id='name'>John Doe</span>!
1180             </div>
1181             ];
1182              
1183             my $pure = Template::Pure->new(
1184             template => $html,
1185             directives => [
1186             '#name' => sub {
1187             my ($instance, $dom, $data) = @_;
1188             return $instance->data_at_path($data, 'id.first_name') .' '.
1189             $instance->data_at_path($data, 'id.last_name') ;
1190             },
1191             ]
1192             );
1193              
1194             my %data = (
1195             id => {
1196             first_name => 'Howard',
1197             last_name => 'Lovecraft',
1198             });
1199              
1200             print $pure->render(\%data);
1201              
1202              
1203             Results in:
1204              
1205             <div>
1206             Hello <span id='name'>Howard Lovecraft</span>!
1207             </div>
1208              
1209             For cases where the display logic is complex, you may use an anonymous subroutine to
1210             provide the matched value. This anonymous subroutine receives the following three
1211             arguments:
1212              
1213             $instance: The template instance
1214             $dom: The DOM Node at the current match (as a L<Mojo::DOM58> object).
1215             $data: Data reference at the current context.
1216              
1217             Your just need to return the value desired which will substitute for the matched node's
1218             current value.
1219              
1220             B<NOTE>: Please note in the above example code that we used 'data_at_path' rather than
1221             dereferenced the $data scalar directly. This is required since internally we wrap your
1222             $data in helper objects, so you can't be 100% certain of the actual structure. In general
1223             using this method wouldbe a good idea anyway since it lets you achieve an API that is
1224             complete independent of your actual data structure (this way if you later change from a
1225             simple hashref to an object, your code wouldn't break.
1226              
1227             =head3 Coderef - No match specification
1228              
1229             Sometimes you may wish to have highly customized transformations, ones that are
1230             not directly attached to a match specification. In those cases you may pass a
1231             match specification without a CSS match:
1232              
1233             my $html = q[
1234             <html>
1235             <head>
1236             <title>Page Title</title>
1237             </head>
1238             <body>
1239             <p>foo</p>
1240             <p>baz</p>
1241             <div id="111"></div>
1242             </body>
1243             </html>
1244             ];
1245              
1246             my $pure = Template::Pure->new(
1247             template=>$html,
1248             directives=> [
1249             sub {
1250             my ($template, $dom, $data) = @_;
1251             $dom->at('#111')->content("coderef");
1252             },
1253             'p' => sub {
1254             my ($template, $dom, $data) = @_;
1255             return $template->data_at_path($data, $dom->content)
1256             }
1257             ]);
1258              
1259             my $data = +{
1260             foo => 'foo is you',
1261             baz => 'baz is raz',
1262             };
1263              
1264             Renders as:
1265              
1266             <html>
1267             <head>
1268             <title>Page Title</title>
1269             </head>
1270             <body>
1271             <p>foo is you</p>
1272             <p>baz is raz</p>
1273             <div id="111">coderef</div>
1274             </body>
1275             </html>
1276              
1277             =head2 Arrayref - Run directives under a new DOM root
1278              
1279             Somtimes its handy to group a set of directives under a given node. For example:
1280              
1281             my $html = qq[
1282             <dl id='contact'>
1283             <dt>Phone</dt>
1284             <dd class='phone'>(xxx) xxx-xxxx</dd>
1285             <dt>Email</dt>
1286             <dd class='email'>aaa@email.com</dd>
1287             </dl>
1288             ];
1289              
1290             my $pure = Template::Pure->new(
1291             template => $html,
1292             directives => [
1293             '#contact' => [
1294             '.phone' => 'contact.phone',
1295             '.email' => 'contact.email',
1296             ],
1297             );
1298              
1299             my %data = (
1300             contact => {
1301             phone => '(212) 387-9509',
1302             email => 'jjnapiork@cpan.org',
1303             }
1304             );
1305              
1306             print $pure->render(\%data);
1307              
1308             Results in:
1309              
1310             <dl id='contact'>
1311             <dt>Phone</dt>
1312             <dd class='phone'>(212) 387-9509</dd>
1313             <dt>Email</dt>
1314             <dd class='email'>jjnapiork@cpan.org'</dd>
1315             </dl>
1316              
1317             For this simple case you could have made it more simple and avoided the nested directives, but
1318             in a complex template with a lot of organization you might find this leads to more readable and
1319             concise directives. It can also promote reusability.
1320              
1321             =head2 Hashref - Move the root of the Data Context
1322              
1323             Just like it may be valuable to move the root DOM context to an inner node, sometimes you'd
1324             like to move the root of the current Data context to an inner path point. This can result in cleaner
1325             templates with less repeated syntax, as well as promote reusability. In order to do this you
1326             use a Hashref whose key is the path under the data context you wish to move to and who's value
1327             is an Arrayref of new directives. These new directives can be any type of directive as already
1328             shown or later documented.
1329              
1330             my $html = qq[
1331             <dl id='contact'>
1332             <dt>Phone</dt>
1333             <dd class='phone'>(xxx) xxx-xxxx</dd>
1334             <dt>Email</dt>
1335             <dd class='email'>aaa@email.com</dd>
1336             </dl>
1337             ];
1338              
1339             my $pure = Template::Pure->new(
1340             template => $html,
1341             directives => [
1342             '#contact' => {
1343             'contact' => [
1344             '.phone' => 'phone',
1345             '.email' => 'email',
1346             ],
1347             },
1348             ]
1349             );
1350              
1351             my %data = (
1352             contact => {
1353             phone => '(212) 387-9509',
1354             email => 'jjnapiork@cpan.org',
1355             }
1356             );
1357              
1358             print $pure->render(\%data);
1359              
1360             Results in:
1361              
1362             <dl id='contact'>
1363             <dt>Phone</dt>
1364             <dd class='phone'>(212) 387-9509</dd>
1365             <dt>Email</dt>
1366             <dd class='email'>jjnapiork@cpan.org'</dd>
1367             </dl>
1368              
1369             In addition to an arrayref of new directives, you may assign the new DOM and Data context
1370             directly to a template object (see L</Object - Set the match value to another Pure Template>
1371             For example:
1372              
1373             my $contact_include = Template::Pure->new(
1374             template => q[
1375             <dl>
1376             <dt>Name</dt>
1377             <dd class='name'>First Last</dd>
1378             <dt>Email</dt>
1379             <dd class='email'>Email@email.com</dd>
1380             </dl>
1381             ],
1382             directives => [
1383             '.name' => 'fullname',
1384             '.email' => 'email',
1385              
1386              
1387             my $pure = Template::Pure->new(
1388             template => $html,
1389             directives => [
1390             '#contact' => {
1391             'contact' => $contact_include;
1392             },
1393             ]
1394             );
1395              
1396             print $pure->render({
1397             person => {
1398             contact => {
1399             fullname => 'John Doe',
1400             email => 'jd@email.com'.
1401             }
1402             },
1403             });
1404              
1405             This lets you isolate the data structure of your includes to improve reuse and clarity.
1406              
1407             =head2 Hashref - Create a Loop
1408              
1409             Besides moving the current data context, setting the value of a match spec key to a
1410             hashref can be used to perform loops over a node, such as when you wish to create
1411             a list:
1412              
1413             my $html = qq[
1414             <ol>
1415             <li class='name'>
1416             <span class='first-name'>John</span>
1417             <span class='last-name'>Doe</span>
1418             </li>
1419             </ol>
1420             ];
1421              
1422             my $pure = Template::Pure->new(
1423             template => $html,
1424             directives => [
1425             '#name' => {
1426             'name<-names' => [
1427             '.first-name' => 'name.first',
1428             '.last-name' => 'name.last',
1429             ],
1430             },
1431             ]
1432             );
1433              
1434             my %data = (
1435             names => [
1436             {first => 'Mary', last => 'Jane'},
1437             {first => 'Jared', last => 'Prex'},
1438             {first => 'Lisa', last => 'Dig'},
1439             ]
1440             );
1441              
1442             print $pure->render(\%data);
1443              
1444             Results in:
1445              
1446             <ol id='names'>
1447             <li class='name'>
1448             <span class='first-name'>Mary</span>
1449             <span class='last-name'>Jane</span>
1450             </li>
1451             <li class='name'>
1452             <span class='first-name'>Jared</span>
1453             <span class='last-name'>Prex</span>
1454             </li>
1455             <li class='name'>
1456             <span class='first-name'>Lisa</span>
1457             <span class='last-name'>Dig</span>
1458             </li>
1459             </ol>
1460              
1461             The indicated data path must be either an ArrayRef, a Hashref, or an object that provides
1462             an iterator interface (see below).
1463              
1464             For each item in the array we render the selected node against that data and
1465             add it to parent node. So the originally selected node is completely replaced by a
1466             collection on new nodes based on the data. Basically just think you are repeating over the
1467             node value for as many times as there is items of data.
1468              
1469             In the case the referenced data is explicitly set to undefined, the full node is
1470             removed (the matched node, not just the value).
1471              
1472             =head3 Special value injected into a loop
1473              
1474             When you create a loop we automatically add a special data key called 'i' which is an object
1475             that contains meta data on the current state of the loop. Fields that can be referenced are:
1476              
1477             =over 4
1478              
1479             =item current_value
1480              
1481             An alias to the current value of the iterator.
1482              
1483             =item index
1484              
1485             The current index of the iterator (starting from 1.. or from the first key in a hashref or fields
1486             interator).
1487              
1488             =item max_index
1489              
1490             The last index item, either number or field based.
1491              
1492             =item count
1493              
1494             The total number of items in the iterator (as a number, starting from 1).
1495              
1496             =item is_first
1497              
1498             Is this the first item in the loop?
1499              
1500             =item is_last
1501              
1502             Is this the last item in the loop?
1503              
1504             =item is_even
1505              
1506             Is this item 'even' in regards to its position (starting with position 2 (the first position, or also
1507             known as index '1') being even).
1508              
1509             =item is_odd
1510              
1511             Is this item 'even' in regards to its position (starting with position 1 (the first position, or also
1512             known as index '0') being odd).
1513              
1514             =back
1515              
1516             =head3 Looping over a Hashref
1517              
1518             You may loop over a hashref as in the following example:
1519              
1520             my $html = qq[
1521             <dl id='dlist'>
1522             <section>
1523             <dt>property</dt>
1524             <dd>value</dd>
1525             </section>
1526             </dl>];
1527              
1528             my $pure = Template::Pure->new(
1529             template => $html,
1530             directives => [
1531             'dl#dlist section' => {
1532             'property<-author' => [
1533             'dt' => 'i.index',
1534             'dd' => 'property',
1535             ],
1536             },
1537             ]
1538             );
1539              
1540             my %data = (
1541             author => {
1542             first_name => 'John',
1543             last_name => 'Napiorkowski',
1544             email => 'jjn1056@yahoo.com',
1545             },
1546             );
1547              
1548             print $pure->render(\%data);
1549              
1550             Results in:
1551              
1552             <dl id="dlist">
1553             <section>
1554             <dt>first_name</dt>
1555             <dd>John</dd>
1556             </section>
1557             <section>
1558             <dt>last_name</dt>
1559             <dd>Napiorkowski</dd>
1560             </section>
1561             <section>
1562             <dt>email</dt>
1563             <dd>jjn1056@yahoo.com</dd>
1564             </section>
1565             </dl>
1566              
1567             B<NOTE> This is a good example of a current limitation in the CSS Match Specification that
1568             requires adding a 'section' tag as a fudge to give the look something to target. Future
1569             versions of this distribution may offer additional match syntax to get around this problem.
1570              
1571             B<NOTE> Notice the usage of the special data path 'i.index' which for a hashref or fields
1572             type loop contains the field or hashref key name.
1573              
1574             B<NOTE> Please remember that in Perl Hashrefs are not ordered. If you wish to order your
1575             Hashref based loop please see L</Sorting and filtering a Loop> below.
1576              
1577             =head3 Iterating over an Object
1578              
1579             If the value indicated by the required path is an object, we need that object to provide
1580             an interface indicating if we should iterate like an ArrayRef (for example a L<DBIx::Class::ResultSet>
1581             which is a collection of database rows) or like a HashRef (for example a L<DBIx::Class>
1582             result object which is one row in the returned database query consisting of field keys
1583             and associated values).
1584              
1585             =head4 Objects that iterate like a Hashref
1586              
1587             The object should provide a method called 'display_fields' (which can be overridden with
1588             the key 'display_fields_handler', see below) which should return a list of methods that are used
1589             as 'keys' to provide values for the iterator. Each method return represents one item
1590             in the loop.
1591              
1592             =head4 Objects that iterate like an ArrayRef
1593              
1594             Your object should defined the follow methods:
1595              
1596             =over 4
1597              
1598             =item next
1599              
1600             Returns the next item in the iterator or undef if there are no more items
1601              
1602             =item count
1603              
1604             The number of items in the iterator (counting from 1 for one item)
1605              
1606             =item reset
1607              
1608             Reset the iterator to the starting item.
1609              
1610             =item all
1611              
1612             Returns all the items in the iterator
1613              
1614             =back
1615              
1616             =head3 Sorting a Loop
1617              
1618             You may provide a custom anonymous subroutine to provide a display
1619             specific order to your loop. For simple values such as Arrayrefs
1620             and hashrefs this is simple:
1621              
1622             my $html = qq[
1623             <ol id='names'>
1624             <li class='name'>
1625             <span class='first-name'>John</span>
1626             <span class='last-name'>Doe</span>
1627             </li>
1628             </ol>
1629             ];
1630              
1631             my $pure = Template::Pure->new(
1632             template => $html,
1633             directives => [
1634             '#name' => {
1635             'name<-names' => [
1636             '.first-name' => 'name.first',
1637             '.last-name' => 'name.last',
1638             ],
1639             'order_by' => sub {
1640             my ($pure, $hashref, $a, $b) = @_;
1641             return $a->{last} cmp $b->{last};
1642             },
1643             },
1644             ]
1645             );
1646              
1647             my %data = (
1648             names => [
1649             {first => 'Mary', last => 'Jane'},
1650             {first => 'Jared', last => 'Prex'},
1651             {first => 'Lisa', last => 'Dig'},
1652             ]
1653             );
1654              
1655             print $pure->render(\%data);
1656              
1657             Results in:
1658              
1659             <ol id='names'>
1660             <li class='name'>
1661             <span class='first-name'>Lisa</span>
1662             <span class='last-name'>Dig</span>
1663             </li>
1664             <li class='name'>
1665             <span class='first-name'>Mary</span>
1666             <span class='last-name'>Jane</span>
1667             </li>
1668             <li class='name'>
1669             <span class='first-name'>Jared</span>
1670             <span class='last-name'>Prex</span>
1671             </li>
1672             </ol>
1673              
1674             So you have a key 'order_by' at the same level as the loop action declaration
1675             which is an anonynous subroutine that takes four arguments; the $pure object,
1676             a reference to the data you are sorting (an arrayref or hashref)
1677             followed by the $a and $b items to be compared for example as in:
1678              
1679             my @display = sort { $a->{last} cmp $b->{last} } @list;
1680              
1681             If your iterator is over an object the interface is slightly more complex since
1682             we allow for the object to provide a sort method based on its internal needs.
1683             For example if you have a L<DBIx::Class::Resultset> as your iterator, you may
1684             wish to order your display at the database level:
1685              
1686             'order_by' => sub {
1687             my ($pure, $object) = @_;
1688             return $object->order_by_last_name;
1689             },
1690              
1691             We recommend avoiding implementation specific details when possible (for example
1692             in L<DBIx::Class> use a custom resultset method, not a ->search query.).
1693              
1694             B<NOTE:> if you need more dynamic control over the way sorting works, you can instead
1695             of hard coding an anonymous subroutine, instead use a string that is a path on the
1696             current data context to an subroutine reference.
1697              
1698             =head3 Perform a 'grep' on your loop items
1699              
1700             You may wish for the purposes of display to skip items in your loop. Similar to
1701             'order_by', you may create a 'grep' key that returns either true or false to determine
1702             if an item in the loop is allowed (works like the 'grep' function).
1703              
1704             # Only show items where the value is greater than 10.
1705             'grep' => sub {
1706             my ($pure, $item) = @_;
1707             return $item > 10;
1708             },
1709              
1710             Just like with 'order_by', if your iterator is over an object, you recieve that
1711             object as the argument and are expected to return a new iterator that is properly
1712             filtered:
1713              
1714             'grep' => sub {
1715             my ($pure, $iterator) = @_;
1716             return $iterator->only_over_10;
1717             },
1718              
1719             B<NOTE:> if you need more dynamic control over the way grep works, you can instead
1720             of hard coding an anonymous subroutine, instead use a string that is a path on the
1721             current data context to an subroutine reference.
1722              
1723             =head3 Perform a 'filter' on your loop items
1724              
1725             Lastly you may wish for the purposes of display to perform so sort of tranformation
1726             on the loop item. For example you may wish rename fields or to flatten a
1727             L<DBIx::Class> result from an object to a hashref in order to prevent your template
1728             authors from accidentally modifying th database. In this case you may add a hash
1729             key 'filter' in the same way as you did with 'sort' or 'grep', which is an anonymous
1730             subroutine that gets the template object followed by the interator item reference (or
1731             scalar). You must return a new reference (or scalar). Example:
1732              
1733             'filter' => sub {
1734             my ($pure, $item) = @_;
1735             return + {
1736             fullname => $item->first_name .' '. $item->last_name,
1737             age => $item->age,
1738             };
1739             },
1740              
1741             Recommendation is to keep this as simple as possible rather than to do very heavy
1742             rewriting of the data structure.
1743              
1744             B<NOTE:> if you need more dynamic control over the way filtering works, you can instead
1745             of hard coding an anonymous subroutine, instead use a string that is a path on the
1746             current data context to an subroutine reference.
1747              
1748             B<NOTE> Should you have more than one special key on your iterator loop, the keys are
1749             processed in the following order 'filter', 'grep', 'order_by'.
1750              
1751             =head3 Generating display_fields
1752              
1753             When you are iterating over an object that is like a Hashref, you need
1754             to inform us of how to get the list of field names which should be the
1755             names of methods on your object who's value you wish to display. By default
1756             we look for a method called 'display fields' but you can customize this
1757             in one of two ways. You can set a key 'display_fields' to be the name of
1758             an alternative method:
1759              
1760             directives => [
1761             '#meta' => {
1762             'field<-info' => [
1763             '.name' => 'field.key',
1764             '.value' => 'field.value',
1765             ],
1766             'display_fields' => 'columns',
1767             },
1768             ]
1769              
1770             =head3 Setting the Data Context in the Interator Specification
1771              
1772             In order to simplify usage of the iterator, you may set the current data
1773             context directly in the interator specification. In order to do this you
1774             would set the target iterator variable name to '.' as in the following example:
1775              
1776             my $html = qq[
1777             <ol>
1778             <li>
1779             <span class='priority'>high|medium|low</span>
1780             <span class='title'>title</span>
1781             </li>
1782             </ol>
1783             ];
1784              
1785             my $pure = Template::Pure->new(
1786             template => $html,
1787             directives => [
1788             'ol li' => {
1789             '.<-tasks' => [
1790             '.priority' => 'priority',
1791             '.title' => 'title',
1792             ],
1793             },
1794             ]);
1795              
1796             my %data = (
1797             tasks => [
1798             { priority => 'high', title => 'Walk Dogs'},
1799             { priority => 'medium', title => 'Buy Milk'},
1800             ],
1801             );
1802              
1803             Returns:
1804              
1805             <ol>
1806             <li>
1807             <span class="priority">high</span>
1808             <span class="title">Walk Dogs</span>
1809             </li>
1810             <li>
1811             <span class="priority">medium</span>
1812             <span class="title">Buy Milk</span>
1813             </li>
1814             </ol>
1815              
1816             =head3 Shortcuts on Loops
1817              
1818             If you are doing a simple loop where the match specification is the current
1819             match point in the DOM and there is only going to be one modification you
1820             can just use a scalar data context path for your action:
1821              
1822             my $html = qq[
1823             <ol>
1824             <li>Things to Do...</li>
1825             </ol>
1826             ];
1827              
1828             my $pure = Template::Pure->new(
1829             template => $html,
1830             directives => [
1831             'ol li' => {
1832             'task<-tasks' => 'task',
1833             },
1834             ]);
1835              
1836             You can also use a coderef in the same way:
1837              
1838             my $pure = Template::Pure->new(
1839             template => $html,
1840             directives => [
1841             'ol li' => {
1842             'task<-tasks' => sub {
1843             my ($pure, $dom, $data) = @_;
1844             $pure->data_at_path($data, 'task');
1845             }
1846             }
1847             ]);
1848              
1849             Both the above would return output like the following:
1850              
1851             my %data = (
1852             tasks => [
1853             'Walk Dogs',
1854             'Buy Milk',
1855             ],
1856             );
1857              
1858             my $string = $pure->render(\%data);
1859              
1860             <ol>
1861             <li>Walk Dogs</li>
1862             <li>Buy Milk</li>
1863             </ol>
1864              
1865             Finally you can use an object that is another L<Template::Pure> instance
1866             in which class it will ack as a wrapper on the matched DOM:
1867              
1868             my $pure = Template::Pure->new(
1869             template => q[
1870             <ol>
1871             <li>Items</li>
1872             </ol>
1873             ],
1874             directives => [
1875             '^ol li' => {
1876             'task<-tasks' => Template::Pure->new(
1877             template => q[<span></span>],
1878             directives => [
1879             'span' => 'task',
1880             '.' => [
1881             { inner => \'^span', content => 'content' },
1882             '.' => 'content',
1883             'li+' => 'inner',
1884             ],
1885             ],
1886             ),
1887             }
1888             ]);
1889              
1890             Produces:
1891              
1892             <ol>
1893             <li>Items<span>Walk Dogs</span></li>
1894             <li>Items<span>Buy Milk</span></li>
1895             </ol>
1896              
1897             =head2 Object - Set the match value to another Pure Template
1898              
1899             my $section_html = qq[
1900             <div>
1901             <h2>Example Section Title</h2>
1902             <p>Example Content</p>
1903             </div>
1904             ];
1905              
1906             my $pure_section = Template::Pure->new(
1907             template => $section_html,
1908             directives => [
1909             'h2' => 'title',
1910             'p' => 'story'
1911             ]);
1912              
1913             my $html = qq[
1914             <div class="story">Example Content</div>
1915             ];
1916              
1917             my $pure = Template::Pure->new(
1918             template => $html,
1919             directives => [
1920             'div.story' => $pure_section,
1921             ]);
1922              
1923             my %data = (
1924             title => 'The Supernatural in Literature',
1925             story => $article_text,
1926             );
1927              
1928             print $pure->render(\%data);
1929              
1930             Results in:
1931              
1932             <div class="story">
1933             <div>
1934             <h2>The Supernatural in Literature</h2>
1935             <p>$article_text</p>
1936             </div>
1937             </div>
1938              
1939             When the action is an object it must be an object that conformation
1940             to the interface and behavior of a L<Template::Pure> object. For the
1941             most part this means it must be an object that does a method 'render' that
1942             takes the current data context refernce and returns an HTML string suitable
1943             to become that value of the matched node.
1944              
1945             When encountering such an object we pass the current data context, but we
1946             add one additional field called 'content' which is the value of the matched
1947             node. You can use this so that you can 'wrap' nodes with a template (similar
1948             to the L<Template> WRAPPER directive).
1949              
1950             my $wrapper_html = qq[
1951             <p class="headline">To Be Wrapped</p>
1952             ];
1953              
1954             my $wrapper = Template::Pure->new(
1955             template => $wrapper_html,
1956             directives => [
1957             'p.headline' => 'content',
1958             ]);
1959              
1960             my $html = qq[
1961             <div>This is a test of the emergency broadcasting
1962             network... This is only a test</div>
1963             ];
1964              
1965             my $wrapper = Template::Pure->new(
1966             template => $html,
1967             directives => [
1968             'div' => $wrapper,
1969             ]);
1970              
1971             Results in:
1972              
1973             <div>
1974             <p class="headline">This is a test of the emergency broadcasting
1975             network... This is only a test</p>
1976             </div>
1977              
1978             Lastly you can mimic a type of inheritance using data mapping and
1979             node aliasing:
1980              
1981             my $overlay_html = q[
1982             <html>
1983             <head>
1984             <title>Example Title</title>
1985             <link rel="stylesheet" href="/css/pure-min.css"/>
1986             <link rel="stylesheet" href="/css/grids-responsive-min.css"/>
1987             <link rel="stylesheet" href="/css/common.css"/>
1988             <script src="/js/3rd-party/angular.min.js"></script>
1989             <script src="/js/3rd-party/angular.resource.min.js"></script>
1990             </head>
1991             <body>
1992             <section id="content">...</section>
1993             <p id="foot">Here's the footer</p>
1994             </body>
1995             </html>
1996             ];
1997              
1998             my $overlay = Template::Pure->new(
1999             template=>$overlay_html,
2000             directives=> [
2001             'title' => 'title',
2002             '^title+' => 'scripts',
2003             'body section#content' => 'content',
2004             ]);
2005              
2006             my $page_html = q[
2007             <html>
2008             <head>
2009             <title>The Real Page</title>
2010             <script>
2011             function foo(bar) {
2012             return baz;
2013             }
2014             </script>
2015             </head>
2016             <body>
2017             You are doomed to discover that you never
2018             recovered from the narcolyptic country in
2019             which you once stood; where the fire's always
2020             burning but there's never enough wood.
2021             </body>
2022             </html>
2023             ];
2024              
2025             my $page = Template::Pure->new(
2026             template=>$page_html,
2027             directives=> [
2028             'title' => 'meta.title',
2029             'html' => [
2030             {
2031             title => \'title',
2032             scripts => \'^head script',
2033             content => \'body',
2034             },
2035             '^.' => $overlay,
2036             ]
2037             ]);
2038              
2039             my $data = +{
2040             meta => {
2041             title => 'Inner Stuff',
2042             },
2043             };
2044              
2045             Results in:
2046              
2047             <html>
2048             <head>
2049             <title>Inner Stuff</title><script>
2050             function foo(bar) {
2051             return baz;
2052             }
2053             </script>
2054             <link href="/css/pure-min.css" rel="stylesheet">
2055             <link href="/css/grids-responsive-min.css" rel="stylesheet">
2056             <link href="/css/common.css" rel="stylesheet">
2057             <script src="/js/3rd-party/angular.min.js"></script>
2058             <script src="/js/3rd-party/angular.resource.min.js"></script>
2059             </head>
2060             <body>
2061             <section id="content">
2062             You are doomed to discover that you never
2063             recovered from the narcolyptic country in
2064             which you once stood; where the fire&amp;#39;s always
2065             burning but there&amp;#39;s never enough wood.
2066             </section>
2067             <p id="foot">Here&#39;s the footer</p>
2068             </body>
2069             </html>
2070              
2071             =head2 Object - A Mojo::DOM58 instance
2072              
2073             In the case where you set the value of the action target to an instance of
2074             L<Mojo::DOM58>, we let the value of that perform the replacement indicated by
2075             the match specification:
2076              
2077             my $html = q[
2078             <html>
2079             <head>
2080             <title>Page Title</title>
2081             </head>
2082             <body>
2083             <p class="foo">aaa</a>
2084             </body>
2085             </html>
2086             ];
2087              
2088             my $pure = Template::Pure->new(
2089             template=>$html,
2090             directives=> [
2091             'p' => Mojo::DOM58->new("<a href='localhost:foo'>Foo!</a>"),
2092             ]);
2093              
2094             my $data = +{
2095             title => 'A Shadow Over Innsmouth',
2096             };
2097              
2098             my $string = $pure->render($data);
2099              
2100             Results in:
2101              
2102             <html>
2103             <head>
2104             <title>A Shadow Over Innsmouth/title>
2105             </head>
2106             <body>
2107             <p class="foo"><a href='localhost:foo'>Foo!</a></a>
2108             </body>
2109             </html>
2110              
2111             =head2 Object - Any Object that does 'TO_HTML'
2112              
2113             In addition to using a L<Template::Pure> object as the target action for
2114             a match specification, you may use any object that does a method called
2115             'TO_HTML'. Such a method would expect to recieve the current template
2116             object, the current matched DOM, and the current value of the Data context
2117             as arguments. It should return a string that is used as the replacement
2118             value for the given match specification. For example:
2119              
2120             {
2121             package Local::Example;
2122              
2123             sub new {
2124             my ($class, %args) = @_;
2125             return bless \%args, $class;
2126             }
2127              
2128             sub TO_HTML {
2129             my ($self, $pure, $dom, $data) = @_;
2130             return $dom->attr('class');
2131             }
2132             }
2133              
2134             my $html = q[
2135             <html>
2136             <head>
2137             <title>Page Title</title>
2138             </head>
2139             <body>
2140             <p class="foo">aaa</a>
2141             <p class="bar">bbb</a>
2142             </body>
2143             </html>
2144             ];
2145              
2146             my $pure = Template::Pure->new(
2147             template=>$html,
2148             directives=> [
2149             'title' => 'title',
2150             'p' => Local::Example->new,
2151             ]);
2152              
2153             my $data = +{
2154             title => 'A Shadow Over Innsmouth',
2155             };
2156              
2157             print $pure->render($data);
2158              
2159             Results in:
2160              
2161             <html>
2162             <head>
2163             <title>A Shadow Over Innsmouth</title>
2164             </head>
2165             <body>
2166             <p class="foo">foo</p>
2167             <p class="bar">bar</p>
2168             </body>
2169             </html>
2170              
2171             B<NOTE> For an alternative method see L</PROCESSING INSTRUCTIONS>
2172              
2173             =head2 Using Dot Notation in Directive Data Mapping
2174              
2175             L<Template::Pure> allows you to indicate a path to a point in your
2176             data context using 'dot' notation, similar to many other template
2177             systems such as L<Template>. In general this offers an abstraction
2178             that smooths over the type of reference your data is (an object, or
2179             a hashref) such as to make it easier to swap the type later on as
2180             needs grow, or for testing:
2181              
2182             directives => [
2183             'title' => 'meta.title',
2184             'copyright => 'meta.license_info.copyright_date',
2185             ...,
2186             ],
2187              
2188             my %data = (
2189             meta => {
2190             title => 'Hello World!',
2191             license_info => {
2192             type => 'Artistic',
2193             copyright_date => 2016,
2194             },
2195             },
2196             );
2197              
2198             Basically you use '.' to replace '->' and we figure out if the path
2199             is to a key in a hashref or method on an object for you.
2200              
2201             In the case when the value of a path is explictly undefined, the behavior
2202             is to remove the matching node (the full matching node, not just the value).
2203              
2204             Trying to resolve a key or method that does not exist returns an error.
2205             However its not uncommon for some types of paths to have optional parts
2206             and in these cases its not strictly and error when the path does not exist.
2207             In this case you may prefix 'optional:' to your path part, which will surpress
2208             an error in the case the requested path does not exist:
2209              
2210             directives => [
2211             'title' => 'meta.title',
2212             'copyright => 'meta.license_info.optional:copyright_date',
2213             ...,
2214             ],
2215              
2216             In this case instead of returning an error we treat the path as though it
2217             returned 'undefined' (which means we trim out the matching node).
2218              
2219             In other cases your path might exist, but returns undefined. This can be an
2220             issue if you have following paths (common case when traversing L<DBIx::Class>
2221             relationships...) and you don't want to throw an exception. In this case you
2222             may use a 'maybe:' prefix, which returns undefined and treats the entire remaining
2223             path as undefined:
2224              
2225             directives => [
2226             'title' => 'meta.title',
2227             'copyright => 'meta.maybe:license_info.copyright_date',
2228             ...,
2229             ],
2230              
2231             =head2 Using a Literal Value in your Directive Action
2232              
2233             Generally the action part of your directive will be a path that maps to
2234             a section of the data that is passed to the template at render. However
2235             there can be some cases when its useful to indicate a literal value, particularly
2236             doing template development when you might not have written all the backend code
2237             that generates data. In those cases you may indicate that the action is a
2238             string literal using single or double quotes as in the following example:
2239              
2240             my $html = q[
2241             <html>
2242             <head>
2243             <title>Page Title</title>
2244             </head>
2245             <body>
2246             <p id="literal_q">aaa</a>
2247             <p id="literal_qq">bbb</a>
2248             </body>
2249             </html>
2250             ];
2251              
2252             my $pure = Template::Pure->new(
2253             template=>$html,
2254             directives=> [
2255             title=>'title',
2256             '#literal_q' => "'literal data single quote'",
2257             '#literal_qq' => '"literal data double quote"',
2258              
2259             ]);
2260              
2261             my $data = +{
2262             title => 'A Shadow Over Innsmouth',
2263             };
2264              
2265             Returns on processing:
2266              
2267             <html>
2268             <head>
2269             <title>A Shadow Over Innsmouth</title>
2270             </head>
2271             <body>
2272             <p id="literal_q">literal data single quote'</a>
2273             <p id="literal_qq">literal data double quote</a>
2274             </body>
2275             </html>
2276              
2277             This feature is of limited value since at this time there is no way to indicate
2278             a literal other than a string.
2279              
2280             =head2 Defaults in your Data Context
2281              
2282             By default there will be a key 'self' in your data context which refers to
2283             the current instance of your L<Template::Pure>. This is handy for introspection
2284             and for subclassing:
2285              
2286             {
2287             package Local::Template::Pure::Custom;
2288              
2289             use Moo;
2290             extends 'Template::Pure';
2291              
2292             has 'version' => (is=>'ro', required=>1);
2293              
2294             sub time { return 'Mon Apr 11 10:49:42 2016' }
2295             }
2296              
2297             my $html_template = qq[
2298             <html>
2299             <head>
2300             <title>Page Title</title>
2301             </head>
2302             <body>
2303             <div id='version'>Version</div>
2304             <div id='main'>Test Body</div>
2305             <div id='foot'>Footer</div>
2306             </body>
2307             </html>
2308             ];
2309              
2310             my $pure = Local::Template::Pure::Custom->new(
2311             version => 100,
2312             template=>$html_template,
2313             directives=> [
2314             'title' => 'meta.title',
2315             '#version' => 'self.version',
2316             '#main' => 'story',
2317             '#foot' => 'self.time',
2318             ]
2319             );
2320              
2321             Results in:
2322              
2323             <html>
2324             <head>
2325             <title>A subclass</title>
2326             </head>
2327             <body>
2328             <div id="version">100</div>
2329             <div id="main">XXX</div>
2330             <div id="foot">Mon Apr 11 10:49:42 2016</div>
2331             </body>
2332             </html>
2333              
2334             Creating subclasses of L<Template::Pure> to encapsulate some of the view
2335             data abd view logic should probably be considered a best practice approach.
2336              
2337             B<NOTE> if you create a subclass and want your methods to have access to
2338             and to modify the DOM, you can return a CODEREF:
2339              
2340             {
2341             package Local::Template::Pure::Custom;
2342              
2343             use Moo;
2344             extends 'Template::Pure';
2345              
2346             has 'version' => (is=>'ro', required=>1);
2347              
2348             sub time {
2349             return sub {
2350             my ($self, $dom, $data) = @_;
2351             $dom->attr(foo=>'bar');
2352             return 'Mon Apr 11 10:49:42 2016';
2353             };
2354             }
2355             }
2356              
2357             Such a coderef may return a scalar value, an object or any other type of
2358             data type we can process.
2359              
2360             =head2 Remapping Your Data Context
2361              
2362             If the first element of your directives (either at the root of the directives
2363             or when you create a new directives list under a given node) is a hashref
2364             we take that as special instructions to remap the current data context to
2365             a different structure. Useful for increase reuse and decreasing complexity
2366             in some situations:
2367              
2368             my $html = qq[
2369             <dl id='contact'>
2370             <dt>Phone</dt>
2371             <dd class='phone'>(xxx) xxx-xxxx</dd>
2372             <dt>Email</dt>
2373             <dd class='email'>aaa@email.com</dd>
2374             </dl>
2375             ];
2376              
2377             my $pure = Template::Pure->new(
2378             template => $html,
2379             directives => [
2380             '#contact' => [
2381             {
2382             phone => 'contact.phone',
2383             email => 'contact.email,
2384             }, [
2385             '.phone' => 'phone',
2386             '.email' => 'email',
2387             ],
2388             },
2389             ]
2390             );
2391              
2392             my %data = (
2393             contact => {
2394             phone => '(212) 387-9509',
2395             email => 'jjnapiork@cpan.org',
2396             }
2397             );
2398              
2399             print $pure->render(\%data);
2400              
2401             Results in:
2402              
2403             <dl id='contact'>
2404             <dt>Phone</dt>
2405             <dd class='phone'>(212) 387-9509</dd>
2406             <dt>Email</dt>
2407             <dd class='email'>jjnapiork@cpan.org'</dd>
2408             </dl>
2409              
2410             =head2 Using Placeholders in your Actions
2411              
2412             Sometimes it makes sense to compose your replacement value of several
2413             bits of information. Although you could do this with lots of extra 'span'
2414             tags, sometimes its much more clear and brief to put it all together. For
2415             example:
2416              
2417             my $pure = Template::Pure->new(
2418             template => $html,
2419             directives => [
2420             '#content' => 'Hi ={name}, glad to meet you on=#{today}',
2421             ]
2422             );
2423              
2424             In the case your value does not refer itself to a path, but instead contains
2425             one or more placeholders which are have data paths inside them. These data
2426             paths can be simple or complex, and even contain filters:
2427              
2428             my $pure = Template::Pure->new(
2429             template => $html,
2430             directives => [
2431             '#content' => 'Hi ={name | uc}, glad to meet you on ={today}',
2432             ]
2433             );
2434              
2435             For more on filters see L</FILTERS>
2436              
2437             =head2 Using Placeholders in your Match Specification
2438              
2439             Sometimes you may wish to allow the user that is rendering a template the
2440             ability to influence the match specification. To grant this ability you
2441             may use a placeholder:
2442              
2443             my $html = q[
2444             <html>
2445             <head>
2446             <title>Page Title</title>
2447             </head>
2448             <body>
2449             <p id="story">Some Stuff</p>
2450             <p id="footer">...</p>
2451             </body>
2452             </html>
2453             ];
2454              
2455             my $pure = Template::Pure->new(
2456             template=>$html,
2457             directives=> [
2458             'body ={story_target}' => '={meta.title | upper}: ={story} on ={meta.date}',
2459             '#footer' => '={meta.title} on ={meta.date}',
2460             ]);
2461              
2462             my $data = +{
2463             story_target => '#story',
2464             meta => {
2465             title => 'Inner Stuff',
2466             date => '1/1/2020',
2467             },
2468             story => 'XX' x 10,
2469             };
2470              
2471             =head2 Special indicators in your Match Specification
2472              
2473             In General your match specification is a CSS match supported by the
2474             underlying HTML parser. However the following specials are supported
2475             for needs unique to the needs of templating:
2476              
2477             =over 4
2478              
2479             =item '.': Select the current node
2480              
2481             Used to indicate the current root node. Useful when you have created a match
2482             with sub directives.
2483              
2484             my $pure = Template::Pure->new(
2485             template => $html,
2486             directives => [
2487             'body' => [
2488             ]
2489             ]
2490             );
2491              
2492             =item '/': The root node
2493              
2494             Used when you which to select from the root of the template DOM, not the current
2495             selected node.
2496              
2497             =item '@': Select an attribute within the current node
2498              
2499             Used to update values inside a node:
2500              
2501             my $pure = Template::Pure->new(
2502             template => $html,
2503             directives => [
2504             'h1@class' => 'header_class',
2505             ],
2506             );
2507              
2508             =item '+': Append or prepend a value
2509              
2510             my $pure = Template::Pure->new(
2511             template => $html,
2512             directives => [
2513             '+h1' => 'title',
2514             '#footer+' => 'copyright_date',
2515             ],
2516             );
2517              
2518             The default behavior is for a match to replace the matched node's content. In some
2519             cases you may wish to preserve the template content and instead either add more
2520             content to the front or back of it.
2521              
2522             B<NOTE> Can be combined with '@' to append / prepend to an attribute.
2523              
2524             B<NOTE> Special handling when appending or prepending to a class attribute (we add a
2525             space if there is an existing since that is expected).
2526              
2527             =item '^': Replace current node completely
2528              
2529             Normally we replace, append or prepend to the value of the selected node. Using the
2530             '^' at the front of your match indicates operation should happen on the entire node,
2531             not just the value. Can be combined with '+' for append/prepend.
2532              
2533             =item '|': Run a filter on the current node
2534              
2535             Passed the currently selected node to a code reference. You can run L<Mojo::DOM58>
2536             transforms on the entire selected node. Nothing should be returned from this
2537             coderef.
2538              
2539             'body|' => sub {
2540             my ($template, $dom, $data) = @_;
2541             $dom->find('p')->each( sub {
2542             $_->attr('data-pure', 1);
2543             });
2544             }
2545              
2546             =back
2547              
2548             =head1 FILTERS
2549              
2550             You may filter you data via a provided built in display filter:
2551              
2552             my $pure = Template::Pure->new(
2553             template => $html,
2554             directives => [
2555             '#content' => 'data.content | escape_html',
2556             ]
2557             );
2558              
2559             If a filter takes arguments you may fill those arguments with either literal
2560             values or a 'placeholder' which should point to a path in the current data
2561             context.
2562              
2563             my $pure = Template::Pure->new(
2564             template => $html,
2565             directives => [
2566             '#content' => 'data.content | repeat(#{times}) | escape_html',
2567             ]
2568             );
2569              
2570             You may add a custom filter when you define your template:
2571              
2572             my $pure = Template::Pure->new(
2573             filters => {
2574             custom_filter => sub {
2575             my ($template, $data, @args) = @_;
2576             # Do something with the $data, possible using @args
2577             # to control what that does
2578             return $data;
2579             },
2580             },
2581             );
2582              
2583             An example custom Filter:
2584              
2585             my $pure = Template::Pure->new(
2586             filters => {
2587             custom_filter => sub {
2588             my ($template, $data, @args) = @_;
2589             # TBD
2590             # return $data;
2591             },
2592             },
2593             );
2594              
2595             In general you can use filters to reduce the need to write your action as a coderef
2596             which should make it easier for you to give the job of writing directives / actions
2597             to non programmers.
2598              
2599             See L<Template::Pure::Filters> for all bundled filters.
2600              
2601             =head1 PROCESSING INSTRUCTIONS
2602              
2603             Generally L<Template::Pure> proposes its best to keep your actual HTML templates as simple
2604             and valid as possible, instead putting your transformations and data binding logic into
2605             directives. This leads to a strong separate of responsibilities and prevents your templates
2606             from getting messy. However there are a few situations where we'd like to offer the template
2607             designer some options to control the overall template structure and to encapsulate common
2608             design elements or template rules. For example its common in a website to have some common
2609             layouts that set overall page structure and import common CSS and Javascript libraries. Additionally
2610             its common to have 'snippets' of HTML that are shared across lots of documents (such as common
2611             header or footer elements, or advertizements panels, etc.) You can describe these via directives
2612             but in order to empower designers and reduce your directive complexity L<Template::Pure> allowes
2613             one to insert HTML Processing instructions into your templates that get parsed when the template
2614             object is instantiated and added as additional directives. This allows one to create directives
2615             declaratively in the template, rather than programtically in your code.
2616              
2617             The availability of this feature in no way suggests that one approach or the other is best. You
2618             should determine that based on your team and project needs.
2619              
2620             L<Template::Pure> currently offers the following three processing instructions, and does not
2621             yet offer an API to create your own. This may change in the future.
2622              
2623             B<NOTE> All processing instructions are parsed and evaluated during instantiation of your
2624             template object and all generated directives are adding to the end of your existing ones. As
2625             a result these instructions are run last.
2626              
2627             =head2 Includes
2628              
2629             Allows one to inject a template render into a placeholder spot in the current template. Example:
2630              
2631             my $include_html = qq[
2632             <span id="footer">Copyright&nbsp;</span>];
2633              
2634             my $include = Template::Pure->new(
2635             template=>$include_html,
2636             directives=> [
2637             '#footer+' => 'copyright_year',
2638             ]);
2639              
2640             my $base_html = q[
2641             <html>
2642             <head>
2643             <title>Page Title: </title>
2644             </head>
2645             <body>
2646             <div id='story'>Example Story</div>
2647             <?pure-include src='foot_include' copyright_year='meta.copyright.year'?>
2648             </body>
2649             </html>
2650             ];
2651              
2652             my $base = Template::Pure->new(
2653             template => $base_html,
2654             directives => [
2655             'title+' => 'meta.title',
2656             '#story' => 'story',
2657             ]
2658             );
2659              
2660             print $base->render({
2661             story => 'It was a dark and stormy night...',
2662             foot_include => $include
2663             meta => {
2664             title=>'Dark and Stormy..',
2665             copyright => {
2666             year => 2016,
2667             author=>'jnap'}
2668             }
2669             },
2670             });
2671              
2672             Returns:
2673              
2674             <html>
2675             <head>
2676             <title>Page Title: Dark and Stormy..'</title>
2677             </head>
2678             <body>
2679             <div id='story'>It was a dark and stormy night...</div>
2680             <span id="footer">Copyright 2016</span>
2681             </body>
2682             </html>
2683              
2684             This is basically the same as:
2685              
2686             my $base_html = q[
2687             <html>
2688             <head>
2689             <title>Page Title: </title>
2690             </head>
2691             <body>
2692             <div id='story'>Example Story</div>
2693             <span id='footer'>...</span>
2694             </body>
2695             </html>
2696             ];
2697              
2698             my $base = Template::Pure->new(
2699             template => $base_html,
2700             directives => [
2701             'title+' => 'meta.title',
2702             '#story' => 'story',
2703             '^#footer' => 'foot_include',
2704             ]
2705             );
2706              
2707             print $base->render({
2708             story => 'It was a dark and stormy night...',
2709             foot_include => $include
2710             meta => {
2711             title=>'Dark and Stormy..',
2712             copyright => {
2713             year => 2016,
2714             author=>'jnap'}
2715             }
2716             },
2717             });
2718              
2719             Or alternatively (if you don't want to allow one to alter the include via
2720             processing data):
2721              
2722             my $base_html = q[
2723             <html>
2724             <head>
2725             <title>Page Title: </title>
2726             </head>
2727             <body>
2728             <div id='story'>Example Story</div>
2729             <span id='footer'>...</span>
2730             </body>
2731             </html>
2732             ];
2733              
2734             my $base = Template::Pure->new(
2735             template => $base_html,
2736             directives => [
2737             'title+' => 'meta.title',
2738             '#story' => 'story',
2739             '^#footer' => $include,
2740             ]
2741             );
2742              
2743             print $base->render({
2744             story => 'It was a dark and stormy night...',
2745             meta => {
2746             title=>'Dark and Stormy..',
2747             copyright => {
2748             year => 2016,
2749             author=>'jnap'}
2750             }
2751             },
2752             });
2753              
2754             Basically you set the processing directive and the PI is fully replaced by the referenced
2755             template. Format is like:
2756              
2757             <?pure-include src=$data_path @args?>
2758              
2759             Where 'src' must be a data context path (see L<\Using Dot Notation in Directive Data Mapping>
2760             for more on referencing a data path) that is an instance of L<Template::Pure> and @args are
2761             a list of mappings to import data into the target include from the calling instance current
2762             data context. Alternatively, you may set a data context root instead using 'ctx' as an
2763             argument:
2764              
2765             my $include_html = qq[
2766             <span id="footer">Copyright&nbsp;</span>];
2767              
2768             my $include = Template::Pure->new(
2769             template=>$include_html,
2770             directives=> [
2771             '#footer+' => 'copyright.year',
2772             ]);
2773              
2774             ...
2775             <?pure-include src='foot_include' ctx='meta'?>
2776             ...
2777              
2778             This might be the preferred method when you wish to copy a full section of data to your
2779             target include. You may not combine the 'ctx' method and the named args method.
2780              
2781             If you do not specify a 'ctx' or named args, we default to a context of the root data
2782             context. This probably leaks too much information into your include but is not terrible
2783             for prototyping.
2784              
2785             =head2 Wrapper
2786              
2787             Similar to the include processing instruction, it provides template authors with a declaritive
2788             approach to L</Object - Set the match value to another Pure Template>. Example:
2789              
2790             my $story_section_wrapper_html = qq[
2791             <section>
2792             <h1>story title</h1>
2793             <p>By:&nbsp;</p>
2794             </section>];
2795              
2796             my $story_section_wrapper = Template::Pure->new(
2797             template=>$story_section_wrapper_html,
2798             directives=> [
2799             'h1' => 'title',
2800             'p+' => 'author',
2801             '^p+' => 'content',
2802             ]);
2803              
2804             my $base_html = q[
2805             <html>
2806             <head>
2807             <title>Page Title:&nbsp;</title>
2808             </head>
2809             <body>
2810             <?pure-wrapper src='section_wrapper' ctx='meta'?>
2811             <div id='story'>Example Story</div>
2812             </body>
2813             </html>
2814             ];
2815              
2816             my $base = Template::Pure->new(
2817             template=>$base_html,
2818             directives=> [
2819             'title+' => 'meta.title',
2820             '#story' => 'story,
2821             ]
2822             );
2823              
2824             print $base->render({
2825             story => 'Once Upon a Time...',
2826             section_wrapper => $story_section_wrapper,
2827             meta => {
2828             title=>'Once',
2829             author=>'jnap',
2830             },
2831             });
2832              
2833             Results in:
2834              
2835             <html>
2836             <head>
2837             <title>Page Title:&nbsp;Once</title>
2838             </head>
2839             <body>
2840             <section>
2841             <h1>Once</h1>
2842             <p>By:&nbsp;jnap</p>
2843             <div id='story'>Once Upon a Time</div>
2844             </section>
2845             </body>
2846             </html>
2847              
2848             This processing instructions 'wraps' the following tag node with the template that
2849             is the target of 'src'. Like L</Includes> you may pass data via named parameters or
2850             by setting a new data context, as in the given example.
2851              
2852             Similar approach using directives only:
2853              
2854             my $base = Template::Pure->new(
2855             template=>$base_html,
2856             directives=> [
2857             'title+' => 'meta.title',
2858             '#story' => 'story,
2859             '^#story => $story_section_wrapper,
2860             ]
2861             );
2862              
2863             =head2 Overlay
2864              
2865             An overlay replaces the selected node with the results on another template. Typically
2866             you will pass selected nodes of the original template as directives to the new template.
2867             This can be used to minic features like template inheritance, that exist in other templating
2868             systems. One example:
2869              
2870             my $overlay_html = q[
2871             <html>
2872             <head>
2873             <title>Example Title</title>
2874             <link rel="stylesheet" href="/css/pure-min.css"/>
2875             <link rel="stylesheet" href="/css/grids-responsive-min.css"/>
2876             <link rel="stylesheet" href="/css/common.css"/>
2877             <script src="/js/3rd-party/angular.min.js"></script>
2878             <script src="/js/3rd-party/angular.resource.min.js"></script>
2879             </head>
2880             <body>
2881             </body>
2882             </html>
2883             ];
2884              
2885             my $overlay = Template::Pure->new(
2886             template=>$overlay_html,
2887             directives=> [
2888             'title' => 'title',
2889             'head+' => 'scripts',
2890             'body' => 'content',
2891             ]);
2892              
2893             my $base_html = q[
2894             <?pure-overlay src='layout'
2895             title=\'title'
2896             scripts=\'^head script'
2897             content=\'body'?>
2898             <html>
2899             <head>
2900             <title>Page Title:&nbsp;</title>
2901             <script>
2902             function foo(bar) {
2903             return baz;
2904             }
2905             </script>
2906             </head>
2907             <body>
2908             <div id='story'>Example Story</div>
2909             </body>
2910             </html>
2911             ];
2912              
2913             my $base = Template::Pure->new(
2914             template=>$base_html,
2915             directives=> [
2916             'title+' => 'meta.title',
2917             '#story' => 'story,
2918             ]
2919             );
2920              
2921             print $base->render({
2922             layout => $overlay,
2923             story => 'Once Upon a Time...',
2924             meta => {
2925             title=>'Once',
2926             author=>'jnap',
2927             },
2928             });
2929              
2930             Renders As:
2931              
2932             <html>
2933             <head>
2934             <title>Once</title>
2935             <link rel="stylesheet" href="/css/pure-min.css"/>
2936             <link rel="stylesheet" href="/css/grids-responsive-min.css"/>
2937             <link rel="stylesheet" href="/css/common.css"/>
2938             <script src="/js/3rd-party/angular.min.js"></script>
2939             <script src="/js/3rd-party/angular.resource.min.js"></script>
2940             <script>
2941             function foo(bar) {
2942             return baz;
2943             }
2944             </script>
2945             </head>
2946             <body>
2947             <div id='story'>Once Upon a Time...</div>
2948             </body>
2949             </html>
2950              
2951             The syntax of the processing instruction is:
2952              
2953             <?pure-overlay src='' @args ?>
2954              
2955             Where 'src' is a data path to the template you want to use as the overlay, and @args is
2956             a list of key values which populate the data context of the overlay when you process it.
2957             Often these values will be references to existing nodes in the base template (as in the
2958             examples \'title' and \'body' above) but they can also be used to map values from your
2959             data context in the same way we do so for L</Include> and L</Wrapper>.
2960              
2961             If you were to write this as 'directives only' it would look like:
2962              
2963             my $base = Template::Pure->new(
2964             template=>$base_html,
2965             directives=> [
2966             'title+' => 'meta.title',
2967             '#story' => 'story,
2968             'html' => [
2969             {
2970             title => \'title'
2971             script s=> \'^head script'
2972             content => \'body'
2973             },
2974             '^.' => 'layout',
2975             ],
2976             ]
2977             );
2978              
2979             Please note that although in this example the overlay wrapped over the entire template, it is
2980             not limited to that, rather like the L</Wrapper> processing instruction it just takes the next
2981             tag node following as its overlay target. So you could have more than one overlap in a document
2982             and can overlay sections for those cases where a L</Wrapper> is not sufficently complex.
2983              
2984             =head2 Filter
2985              
2986             A Filter will process the following node on a L<Template::Pure> instance as if that node was the
2987             source for its template. This means that the target source template must be a coderef that builds
2988             a <Template::Pure> object, and not an already instantiated one. For Example:
2989              
2990             my $base_html = q[
2991             <html>
2992             <head>
2993             <title>Title Goes Here...</title>
2994             </head>
2995             <body>
2996             <?pure-filter src=?>
2997             <ul>
2998             <li>One</li>
2999             <li>Two</li>
3000             <li>Three</li>
3001             </ul>
3002             </body>
3003             </html>
3004             ];
3005              
3006             my $base = Template::Pure->new(
3007             template => $base_html,
3008             directives => [
3009             'title' => 'title',
3010             ]
3011             );
3012              
3013             print $base->render({
3014             title => 'Dark and Stormy..',
3015             style => 'red',
3016             filter => sub {
3017             my $dom = shift;
3018             return Template::Pure->new(
3019             template => $dom,
3020             directives => [
3021             'li@class' => 'style'
3022             ]
3023             },
3024             });
3025              
3026             Outputs:
3027              
3028             <html>
3029             <head>
3030             <title>Dark and Stormy..</title>
3031             </head>
3032             <body>
3033             <ul>
3034             <li class='red'>One</li>
3035             <li class='red'>Two</li>
3036             <li class='red'>Three</li>
3037             </ul>
3038             </body>
3039             </html>
3040              
3041             As you can see, its similar to the Wrapper instruction, just instead of the matched template
3042             being passed as the 'content' argument to be used in anther template, it becomes the template.
3043              
3044             =head1 IMPORTANT NOTE REGARDING VALID HTML
3045              
3046             Please note that L<Mojo::DOM58> tends to enforce rule regarding valid HTML5. For example, you
3047             cannot nest a block level element inside a 'P' element. This might at times lead to some
3048             surprising results in your output.
3049              
3050             =head1 ERROR MESSAGES AND DEBUGGING
3051              
3052             Some error messages will use L<Class::MOP> if its available for introspection. Having this installed
3053             will greatly improve your debugging, so I recommend installing it on your development machines (good
3054             change you already have it via L<Moose> anyway). If its not installed we just do a general L<Data::Dumper>
3055             which results in a lot of data that is not easy to read, but suitable for production.
3056              
3057             =head1 AUTHOR
3058            
3059             John Napiorkowski L<email:jjnapiork@cpan.org>
3060            
3061             =head1 SEE ALSO
3062            
3063             L<Mojo::DOM58>, L<HTML::Zoom>. Both of these are approaches to programmatically examining and
3064             altering a DOM.
3065              
3066             L<Template::Semantic> is a similar system that uses XPATH instead of a CSS inspired matching
3067             specification. It has more dependencies (including L<XML::LibXML> and doesn't separate the actual
3068             template data from the directives. You might find this more simple approach appealing,
3069             so its worth alook.
3070              
3071             L<HTML::Seamstress> Seems to also be prior art along these lines but I have trouble following
3072             the code and it seems not active. Might be worth looking at at least for ideas!
3073            
3074             =head1 COPYRIGHT & LICENSE
3075            
3076             Copyright 2017, John Napiorkowski L<email:jjnapiork@cpan.org>
3077            
3078             This library is free software; you can redistribute it and/or modify it under
3079             the same terms as Perl itself.
3080              
3081             =cut