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's always burning, but |
866
|
|
|
|
|
|
|
there'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&#39;s always |
2065
|
|
|
|
|
|
|
burning but there&#39;s never enough wood. |
2066
|
|
|
|
|
|
|
</section> |
2067
|
|
|
|
|
|
|
<p id="foot">Here'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 </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 </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: </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: </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: Once</title> |
2838
|
|
|
|
|
|
|
</head> |
2839
|
|
|
|
|
|
|
<body> |
2840
|
|
|
|
|
|
|
<section> |
2841
|
|
|
|
|
|
|
<h1>Once</h1> |
2842
|
|
|
|
|
|
|
<p>By: 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: </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 |