blib/lib/Pinwheel/View/Data.pm | |||
---|---|---|---|
Criterion | Covered | Total | % |
statement | 280 | 280 | 100.0 |
branch | 122 | 122 | 100.0 |
condition | 17 | 17 | 100.0 |
subroutine | 36 | 36 | 100.0 |
pod | 0 | 3 | 0.0 |
total | 455 | 458 | 99.3 |
line | stmt | bran | cond | sub | pod | time | code |
---|---|---|---|---|---|---|---|
1 | package Pinwheel::View::Data; | ||||||
2 | |||||||
3 | 5 | 5 | 46564 | use strict; | |||
5 | 10 | ||||||
5 | 357 | ||||||
4 | 5 | 5 | 28 | use warnings; | |||
5 | 10 | ||||||
5 | 135 | ||||||
5 | |||||||
6 | 5 | 5 | 26 | use Carp; | |||
5 | 11 | ||||||
5 | 397 | ||||||
7 | 5 | 5 | 13170 | use PPI; | |||
5 | 1043277 | ||||||
5 | 4794 | ||||||
8 | |||||||
9 | |||||||
10 | sub parse_template | ||||||
11 | { | ||||||
12 | 125 | 124 | 0 | 175866 | my ($s, $name) = @_; | ||
13 | 125 | 298 | my ($pkgname, $vars, $perlvars, $ctxvars); | ||||
14 | |||||||
15 | 125 | 355 | $pkgname = $name; | ||||
16 | 125 | 510 | $pkgname =~ s!\..*!!; | ||||
17 | 125 | 890 | $pkgname =~ s!(^|/)([^a-zA-Z])!$1_$2!g; | ||||
18 | 125 | 731 | $pkgname =~ s![^a-z0-9/]+!_!g; | ||||
19 | 125 | 780 | $pkgname =~ s!/!::!; | ||||
20 | 125 | 523 | $pkgname = 'Template::' . $pkgname; | ||||
21 | |||||||
22 | 125 | 2969 | $vars = find_parameters($s); | ||||
23 | # Can't override the $h helpers variable | ||||||
24 | 125 | 61104 | delete $vars->{'$h'}; | ||||
25 | 125 | 974 | $vars->{'$dummy'} = 1; | ||||
26 | 125 | 850 | $vars = [keys %$vars]; | ||||
27 | 125 | 554 | $perlvars = join(', ', @$vars); | ||||
28 | 125 | 604 | $ctxvars = join(', ', map { "'" . substr($_, 1) . "'" } @$vars); | ||||
127 | 750 | ||||||
29 | |||||||
30 | 2 | 2 | 21 | eval qq{ | |||
2 | 1 | 3 | |||||
2 | 86 | ||||||
2 | 12 | ||||||
2 | 5 | ||||||
2 | 288 | ||||||
2 | 21 | ||||||
2 | 5 | ||||||
2 | 90 | ||||||
2 | 14 | ||||||
2 | 5 | ||||||
2 | 697 | ||||||
125 | 12695 | ||||||
31 | package Pinwheel::View::Data::$pkgname; | ||||||
32 | use strict; | ||||||
33 | use warnings; | ||||||
34 | our \$h; | ||||||
35 | *AUTOLOAD = *Pinwheel::View::Data::Builder::AUTOLOAD; | ||||||
36 | *TAG = *Pinwheel::View::Data::Builder::TAG; | ||||||
37 | sub _render_ | ||||||
38 | { | ||||||
39 | my ($perlvars) = \@_; | ||||||
40 | #line 1 "$name" | ||||||
41 | $s; | ||||||
42 | } | ||||||
43 | }; | ||||||
44 | 125 | 100 | 988 | croak $@ if $@; | |||
45 | |||||||
46 | 124 | 29496 | return eval qq{ | ||||
47 | sub { | ||||||
48 | my (\$locals, \$globals, \$fn) = \@_; | ||||||
49 | my (\$vars, \@values); | ||||||
50 | |||||||
51 | \$vars = \{dummy => undef, \%\$globals, \%\$locals\}; | ||||||
52 | foreach (($ctxvars)) \{ | ||||||
53 | croak("Missing parameter '\$_'") if !exists(\$vars->\{\$_\}); | ||||||
54 | \} | ||||||
55 | \$Pinwheel::View::Data::$pkgname\::h = \$fn; | ||||||
56 | \@values = \@\$vars\{($ctxvars)\}; | ||||||
57 | Pinwheel::View::Data::Wrapper->new(Pinwheel::View::Data::$pkgname\::_render_(\@values)); | ||||||
58 | } | ||||||
59 | }; | ||||||
60 | } | ||||||
61 | |||||||
62 | sub find_parameters | ||||||
63 | { | ||||||
64 | 140 | 139 | 0 | 34207 | my ($s) = @_; | ||
65 | 140 | 309 | my ($d, $global, $subs, $declared, $undeclared); | ||||
66 | |||||||
67 | 140 | 1440 | $d = PPI::Document->new(\$s); | ||||
68 | 140 | 393707 | $global = $d->clone; | ||||
69 | 140 | 59078 | $global->prune('PPI::Statement::Sub'); | ||||
70 | 140 | 100 | 116165 | $subs = $d->find('PPI::Statement::Sub') || []; | |||
71 | |||||||
72 | 140 | 143257 | $undeclared = {}; | ||||
73 | 140 | 590 | $declared = find_undeclared($global, {}, $undeclared); | ||||
74 | 140 | 628 | find_undeclared($_, $declared, $undeclared) foreach (@$subs); | ||||
75 | |||||||
76 | 140 | 1035 | return $undeclared; | ||||
77 | } | ||||||
78 | |||||||
79 | sub find_undeclared | ||||||
80 | { | ||||||
81 | 148 | 147 | 0 | 317 | my ($d, $declared, $undeclared) = @_; | ||
82 | 148 | 586 | my ($nodes, $n, $var); | ||||
83 | |||||||
84 | $nodes = $d->find(sub { | ||||||
85 | 1985 | 100 | 1984 | 66433 | $_[1]->isa('PPI::Token::Symbol') || | ||
86 | $_[1]->isa('PPI::Statement::Variable') | ||||||
87 | 148 | 1517 | }); | ||||
88 | 148 | 100 | 2929 | $nodes = [] if !$nodes; | |||
89 | |||||||
90 | 148 | 542 | $declared = {%$declared}; | ||||
91 | 148 | 1376 | foreach $n (@$nodes) { | ||||
92 | 45 | 100 | 685 | if ($n->isa('PPI::Statement::Variable')) { | |||
100 | |||||||
93 | 11 | 70 | foreach (@{$n->find('PPI::Token')}) { | ||||
11 | 45 | ||||||
94 | 61 | 100 | 100 | 5487 | if ($_->isa('PPI::Token::Operator') && $_->content eq '=') { | ||
100 | |||||||
95 | 7 | 58 | last; | ||||
96 | } elsif ($_->isa('PPI::Token::Symbol')) { | ||||||
97 | 14 | 54 | $declared->{$_->content} = 1; | ||||
98 | } | ||||||
99 | } | ||||||
100 | } elsif (!$n->isa('PPI::Token::Magic')) { | ||||||
101 | 33 | 243 | $var = $n->content; | ||||
102 | 33 | 100 | 100 | 384 | $undeclared->{$var} = 1 if ($var =~ /^\$/ && !$declared->{$var}); | ||
103 | } | ||||||
104 | } | ||||||
105 | |||||||
106 | 148 | 709 | return $declared; | ||||
107 | } | ||||||
108 | |||||||
109 | sub _clear_templates | ||||||
110 | { | ||||||
111 | 5 | 4 | 609 | my ($pkg, $dir, $name); | |||
112 | |||||||
113 | 5 | 19 | $pkg = \%::; | ||||
114 | 5 | 33 | $pkg = $pkg->{'Pinwheel::'}{'View::'}{'Data::'}{'Template::'}; | ||||
115 | 5 | 143 | foreach $dir (keys %$pkg) { | ||||
116 | 7 | 22 | foreach $name (keys %{$pkg->{$dir}}) { | ||||
7 | 80 | ||||||
117 | 125 | 184 | foreach (keys %{$pkg->{$dir}{$name}}) { | ||||
125 | 567 | ||||||
118 | 800 | 3650 | delete $pkg->{$dir}{$name}{$_}; | ||||
119 | } | ||||||
120 | 125 | 1034 | delete $pkg->{$dir}{$name}; | ||||
121 | } | ||||||
122 | 7 | 215 | delete $pkg->{$dir}; | ||||
123 | } | ||||||
124 | } | ||||||
125 | |||||||
126 | |||||||
127 | |||||||
128 | package Pinwheel::View::Data::Builder; | ||||||
129 | |||||||
130 | 5 | 5 | 62 | use strict; | |||
5 | 12 | ||||||
5 | 237 | ||||||
131 | 5 | 5 | 29 | use warnings; | |||
5 | 13 | ||||||
5 | 711 | ||||||
132 | |||||||
133 | our $AUTOLOAD; | ||||||
134 | |||||||
135 | my @stack; | ||||||
136 | |||||||
137 | |||||||
138 | sub AUTOLOAD | ||||||
139 | { | ||||||
140 | 149 | 148 | 1392 | my ($name, $fn); | |||
141 | |||||||
142 | 149 | 513 | $name = $AUTOLOAD; | ||||
143 | 149 | 1457 | $name =~ s/.*://; | ||||
144 | |||||||
145 | 149 | 170 | 756 | $fn = sub { TAG($name, @_) }; | |||
171 | 776 | ||||||
146 | |||||||
147 | 5 | 5 | 30 | no strict 'refs'; | |||
5 | 12 | ||||||
5 | 1122 | ||||||
148 | 149 | 1060 | *$AUTOLOAD = $fn; | ||||
149 | 149 | 572 | goto &$fn; | ||||
150 | } | ||||||
151 | |||||||
152 | sub TAG | ||||||
153 | { | ||||||
154 | 184 | 183 | 413 | my ($name, $content, $attrs, $data); | |||
155 | |||||||
156 | 184 | 358 | $name = shift @_; | ||||
157 | 184 | 100 | 682 | $content = pop @_ if (@_ & 1); | |||
158 | 184 | 100 | 592 | $attrs = [@_] if @_; | |||
159 | |||||||
160 | 184 | 100 | 562 | push @stack, [] if (scalar(@stack) == 0); | |||
161 | 184 | 100 | 1031 | if (ref($content)) { | |||
162 | 39 | 151 | push @stack, []; | ||||
163 | 39 | 125 | &$content; | ||||
164 | 39 | 92 | $content = pop @stack; | ||||
165 | } | ||||||
166 | 184 | 638 | $data = [$name, $attrs, $content]; | ||||
167 | 184 | 339 | push @{$stack[-1]}, $data; | ||||
184 | 471 | ||||||
168 | |||||||
169 | 184 | 1587 | return $data; | ||||
170 | |||||||
171 | } | ||||||
172 | |||||||
173 | |||||||
174 | |||||||
175 | package Pinwheel::View::Data::Wrapper; | ||||||
176 | |||||||
177 | 5 | 5 | 287 | use strict; | |||
5 | 14 | ||||||
5 | 628 | ||||||
178 | 5 | 5 | 30 | use warnings; | |||
5 | 18 | ||||||
5 | 151 | ||||||
179 | |||||||
180 | 5 | 5 | 51 | use Carp; | |||
5 | 76 | ||||||
5 | 424 | ||||||
181 | 5 | 5 | 9846 | use Data::Dumper qw(); | |||
5 | 52187 | ||||||
5 | 14071 | ||||||
182 | |||||||
183 | |||||||
184 | sub new | ||||||
185 | { | ||||||
186 | 125 | 124 | 627 | my ($class, $raw) = @_; | |||
187 | 125 | 1687 | return bless({raw => $raw}, $class); | ||||
188 | } | ||||||
189 | |||||||
190 | sub to_string | ||||||
191 | { | ||||||
192 | 10 | 9 | 144 | my ($self, $format) = @_; | |||
193 | |||||||
194 | 10 | 100 | 84 | if ($format =~ /^(xml|atom|rss)$/) { | |||
100 | |||||||
100 | |||||||
100 | |||||||
195 | 5 | 19 | return $self->to_xml(); | ||||
196 | } elsif ($format eq 'json') { | ||||||
197 | 3 | 52 | return $self->to_json(); | ||||
198 | } elsif ($format eq 'yaml') { | ||||||
199 | 2 | 10 | return $self->to_yaml(); | ||||
200 | } elsif ($format eq 'html') { | ||||||
201 | 2 | 8 | return $self->to_html(); | ||||
202 | } else { | ||||||
203 | 2 | 256 | croak "Unsupported format"; | ||||
204 | } | ||||||
205 | } | ||||||
206 | |||||||
207 | sub to_json | ||||||
208 | { | ||||||
209 | 46 | 45 | 217 | my ($self) = @_; | |||
210 | |||||||
211 | 46 | 347 | return '{' . _to_json(@{$self->{raw}}) . '}'; | ||||
46 | 292 | ||||||
212 | } | ||||||
213 | |||||||
214 | sub to_yaml | ||||||
215 | { | ||||||
216 | 32 | 31 | 406 | my ($self) = @_; | |||
217 | |||||||
218 | 32 | 68 | return _to_yaml(@{$self->{raw}}, 0) . "\n"; | ||||
32 | 312 | ||||||
219 | } | ||||||
220 | |||||||
221 | sub to_xml | ||||||
222 | { | ||||||
223 | 33 | 32 | 114 | my ($self) = @_; | |||
224 | |||||||
225 | 33 | 86 | return "\n" . _to_xml(@{$self->{raw}}); | ||||
33 | 571 | ||||||
226 | } | ||||||
227 | |||||||
228 | ## JSON with HTML syntax highlighting | ||||||
229 | sub to_html | ||||||
230 | { | ||||||
231 | 38 | 37 | 163 | my ($self) = @_; | |||
232 | |||||||
233 | 38 | 442 | return "\n". | ||||
234 | "". | ||||||
235 | "". | ||||||
242 | "\n". | ||||||
243 | " {" . |
||||||
244 | 38 | 101 | _to_html(@{$self->{raw}}) . | ||||
245 | "}\n". | ||||||
246 | ""; | ||||||
247 | } | ||||||
248 | |||||||
249 | sub _to_json | ||||||
250 | { | ||||||
251 | 86 | 85 | 208 | my ($tag, $attrs, $content, $ignore_tag) = @_; | |||
252 | 86 | 122 | my ($is_list, $s, $i, $n, @values); | ||||
253 | |||||||
254 | 86 | 222 | $tag =~ s/:/\$/; | ||||
255 | 86 | 328 | $is_list = ($tag =~ s/_$//); | ||||
256 | 86 | 100 | 463 | $s = '"' . $tag . '":' unless $ignore_tag; | |||
257 | |||||||
258 | 86 | 100 | 432 | if ($attrs) { | |||
259 | 18 | 49 | $n = @$attrs; | ||||
260 | 18 | 71 | for ($i = 0; $i < $n; $i += 2) { | ||||
261 | 20 | 139 | push @values, [$attrs->[$i], undef, $attrs->[$i + 1]]; | ||||
262 | } | ||||||
263 | 18 | 100 | 94 | if (!defined($content)) { | |||
100 | |||||||
264 | 10 | 21 | $content = []; | ||||
265 | } elsif (!ref($content)) { | ||||||
266 | 6 | 115 | $content = [['$t', undef, $content]]; | ||||
267 | } | ||||||
268 | 18 | 70 | $content = [@values, @$content]; | ||||
269 | } | ||||||
270 | |||||||
271 | 86 | 100 | 8050 | if (!defined($content)) { | |||
100 | |||||||
100 | |||||||
272 | 12 | 67 | $s .= 'null'; | ||||
273 | } elsif (ref($content)) { | ||||||
274 | 25 | 100 | 71 | $s .= $is_list ? '[' : '{'; | |||
275 | 25 | 42 | $i = -1; | ||||
276 | 25 | 167 | foreach (@$content) { | ||||
277 | 41 | 100 | 110 | $s .= ',' if (++$i); | |||
278 | 41 | 119 | $s .= _to_json(@$_, $is_list); | ||||
279 | } | ||||||
280 | 25 | 100 | 121 | $s .= $is_list ? ']' : '}'; | |||
281 | } elsif ($content =~ /^-?[0-9]+(?:\.[0-9]+)?$/) { | ||||||
282 | 22 | 56 | $s .= $content; | ||||
283 | } else { | ||||||
284 | 30 | 100 | 113 | $content = _json_escape($content) if $content =~ /[\\"\x00-\x1f]/; | |||
285 | 30 | 170 | $s .= '"' . $content . '"'; | ||||
286 | } | ||||||
287 | |||||||
288 | 86 | 892 | return $s; | ||||
289 | } | ||||||
290 | |||||||
291 | sub _json_escape | ||||||
292 | { | ||||||
293 | 7 | 6 | 18 | my ($s) = @_; | |||
294 | |||||||
295 | 7 | 48 | $s =~ s/\\/\\\\/g; | ||||
296 | 7 | 20 | $s =~ s/\n/\\n/g; | ||||
297 | 7 | 18 | $s =~ s/"/\\"/g; | ||||
298 | 7 | 100 | 129 | return $s unless $s =~ /[\x00-\x1f]/; | |||
299 | |||||||
300 | 5 | 28 | $s =~ s/([\x00-\x1f])/sprintf('\u%04x', ord($1))/ge; | ||||
5 | 32 | ||||||
301 | 5 | 49 | return $s; | ||||
302 | } | ||||||
303 | |||||||
304 | sub _to_yaml | ||||||
305 | { | ||||||
306 | 69 | 68 | 186 | my ($tag, $attrs, $content, $depth, $ignore_tag) = @_; | |||
307 | 69 | 108 | my ($is_list, $s, $i, $n, @values, $indent); | ||||
308 | |||||||
309 | 69 | 320 | $tag =~ s/:/\$/; | ||||
310 | 69 | 164 | $is_list = ($tag =~ s/_$//); | ||||
311 | |||||||
312 | 69 | 100 | 186 | if ($attrs) { | |||
313 | 13 | 61 | $n = @$attrs; | ||||
314 | 13 | 62 | for ($i = 0; $i < $n; $i += 2) { | ||||
315 | 13 | 71 | push @values, [$attrs->[$i], undef, $attrs->[$i + 1]]; | ||||
316 | } | ||||||
317 | 13 | 100 | 180 | if (!defined($content)) { | |||
100 | |||||||
318 | 7 | 26 | $content = []; | ||||
319 | } elsif (!ref($content)) { | ||||||
320 | 5 | 18 | $content = [['$t', undef, $content]]; | ||||
321 | } | ||||||
322 | 13 | 72 | $content = [@values, @$content]; | ||||
323 | } | ||||||
324 | |||||||
325 | 69 | 100 | 362 | if (!$ignore_tag) { | |||
326 | 59 | 100 | $s = $tag . ':'; | ||||
327 | 59 | 100 | 100 | 583 | $s .= ' ' unless (ref($content) && @$content > 0); | ||
328 | } | ||||||
329 | |||||||
330 | 69 | 100 | 100 | 847 | if (!defined($content)) { | ||
100 | |||||||
100 | |||||||
100 | |||||||
100 | |||||||
331 | 9 | 18 | $s .= '~'; | ||||
332 | } elsif (ref($content) && @$content == 0) { | ||||||
333 | 3 | 100 | 43 | $s .= $is_list ? '[]' : '{}'; | |||
334 | } elsif (ref($content)) { | ||||||
335 | 23 | 98 | $depth += 1; | ||||
336 | 23 | 100 | 249 | $indent = "\n" . (' ' x $depth) . ($is_list ? '- ' : ''); | |||
337 | 23 | 121 | $i = -1; | ||||
338 | 23 | 73 | foreach (@$content) { | ||||
339 | 38 | 100 | 100 | 175 | $s .= $indent if (++$i || !$ignore_tag); | ||
340 | 38 | 145 | $s .= _to_yaml(@$_, $depth, $is_list); | ||||
341 | } | ||||||
342 | } elsif ($content =~ /^-?[0-9]+(?:\.[0-9]+)?$/) { | ||||||
343 | # Could check /^[\x20-\x22\x24-\x39\x3b-\x7e]+$/ instead, but for | ||||||
344 | # visual consistency with JSON just omit quotes from data that looks | ||||||
345 | # numeric. | ||||||
346 | 9 | 29 | $s .= $content; | ||||
347 | } elsif ($content =~ /[\x00-\x08\x0a-\x1f"\\\x7f\xe2\xed]/) { | ||||||
348 | 4 | 12 | $s .= '"' . _yaml_escape($content) . '"'; | ||||
349 | } else { | ||||||
350 | 26 | 159 | $s .= '"' . $content . '"'; | ||||
351 | } | ||||||
352 | |||||||
353 | 69 | 990 | return $s; | ||||
354 | } | ||||||
355 | |||||||
356 | sub _yaml_escape | ||||||
357 | { | ||||||
358 | 4 | 3 | 12 | my ($s) = @_; | |||
359 | |||||||
360 | 4 | 58 | $s =~ s/([\\"])/\\$1/g; | ||||
361 | 4 | 100 | 27 | return $s unless $s =~ /[\x00-\x08\x0a-\x1f\x7f\xe2\xed]/; | |||
362 | |||||||
363 | 3 | 10 | $s =~ s/([\x00-\x08\x0a-\x1f\x7f])/sprintf('\x%02x', ord($1))/ge; | ||||
2 | 142 | ||||||
364 | 3 | 17 | $s =~ s/\xe2\x80([\xa8\xa9])/sprintf('\u20%02x', ord($1) - 128)/ge; | ||||
3 | 345 | ||||||
365 | 3 | 46 | $s =~ s/\xed([\xa0-\xbf])([\x80-\xbf])/ | ||||
366 | 5 | 33 | sprintf('\ud%03x', ((ord($1) & 63) << 6) | (ord($2) & 63))/ge; | ||||
367 | 3 | 19 | return $s; | ||||
368 | } | ||||||
369 | |||||||
370 | sub _to_xml | ||||||
371 | { | ||||||
372 | 44 | 43 | 212 | my ($tag, $attrs, $content) = @_; | |||
373 | 44 | 82 | my ($s, $i, $n, $value); | ||||
374 | |||||||
375 | 44 | 116 | $tag =~ s/_$//; | ||||
376 | 44 | 152 | $s = '<' . $tag; | ||||
377 | |||||||
378 | 44 | 100 | 146 | $n = $attrs ? @$attrs : 0; | |||
379 | 44 | 160 | for ($i = 0; $i < $n; $i += 2) { | ||||
380 | 18 | 193 | $value = $attrs->[$i + 1]; | ||||
381 | 18 | 100 | 57 | $value = '' if !defined($value); | |||
382 | 18 | 100 | 72 | $value = _xml_escape($value) if $value =~ /[&<>'"]/; | |||
383 | 18 | 145 | $s .= ' ' . $attrs->[$i] . '="' . $value . '"'; | ||||
384 | } | ||||||
385 | |||||||
386 | 44 | 100 | 162 | if (!defined($content)) { | |||
100 | |||||||
387 | 16 | 34 | $s .= '/>'; | ||||
388 | } elsif (ref($content)) { | ||||||
389 | 8 | 133 | $s .= '>'; | ||||
390 | 8 | 51 | $s .= _to_xml(@$_) foreach (@$content); | ||||
391 | 8 | 26 | $s .= '' . $tag . '>'; | ||||
392 | } else { | ||||||
393 | 22 | 100 | 190 | $content = _xml_escape($content) if $content =~ /[&<>'"]/; | |||
394 | 22 | 70 | $s .= '>' . $content . '' . $tag . '>'; | ||||
395 | } | ||||||
396 | |||||||
397 | 44 | 276 | return $s; | ||||
398 | } | ||||||
399 | |||||||
400 | sub _xml_escape | ||||||
401 | { | ||||||
402 | 3 | 2 | 122 | my ($s) = @_; | |||
403 | |||||||
404 | 3 | 20 | $s =~ s/&/&/g; | ||||
405 | 3 | 9 | $s =~ s/</g; | ||||
406 | 3 | 46 | $s =~ s/>/>/g; | ||||
407 | 3 | 11 | $s =~ s/'/'/g; | ||||
408 | 3 | 11 | $s =~ s/\"/"/g; | ||||
409 | |||||||
410 | 3 | 105 | return $s; | ||||
411 | } | ||||||
412 | |||||||
413 | sub _to_html | ||||||
414 | { | ||||||
415 | 76 | 75 | 168 | my ($tag, $attrs, $content, $ignore_tag) = @_; | |||
416 | 76 | 121 | my ($is_list, $s); | ||||
417 | |||||||
418 | 76 | 190 | $tag =~ s/:/\$/; | ||||
419 | 76 | 157 | $is_list = ($tag =~ s/_$//); | ||||
420 | |||||||
421 | 76 | 140 | $s = " "; |
||||
422 | 76 | 100 | 403 | $s .= "\"" . $tag . "\": " unless ($ignore_tag); | |||
423 | |||||||
424 | 76 | 100 | 205 | if ($attrs) { | |||
425 | 17 | 44 | my $n = @$attrs; | ||||
426 | 17 | 70 | my @values = (); | ||||
427 | 17 | 78 | for (my $i = 0; $i < $n; $i += 2) { | ||||
428 | 19 | 105 | push @values, [$attrs->[$i], undef, $attrs->[$i + 1]]; | ||||
429 | } | ||||||
430 | 17 | 100 | 169 | if (!defined($content)) { | |||
100 | |||||||
431 | 10 | 31 | $content = []; | ||||
432 | } elsif (!ref($content)) { | ||||||
433 | 5 | 16 | $content = [['$t', undef, $content]]; | ||||
434 | } | ||||||
435 | 17 | 87 | $content = [@values, @$content]; | ||||
436 | } | ||||||
437 | |||||||
438 | 76 | 100 | 551 | if (!defined($content)) { | |||
100 | |||||||
439 | 12 | 30 | $s .= 'null'; | ||||
440 | } elsif (ref($content)) { | ||||||
441 | 24 | 160 | my $i = 0; | ||||
442 | 24 | 100 | 73 | $s .= $is_list ? '[' : "{"; | |||
443 | 24 | 67 | foreach (@$content) { | ||||
444 | 39 | 142 | $s .= _to_html(@$_, $is_list); | ||||
445 | 39 | 100 | 127 | $s .= "," unless (++$i == @$content); | |||
446 | 39 | 107 | $s .= ""; | ||||
447 | } | ||||||
448 | 24 | 100 | 175 | $s .= $is_list ? ']' : "}"; | |||
449 | } else { | ||||||
450 | 42 | 100 | 255 | unless ($content =~ /^-?[0-9]+(?:\.[0-9]+)?$/) { | |||
451 | 27 | 100 | 114 | $content = _json_escape($content) if $content =~ /[\\"\x00-\x1f]/; | |||
452 | 27 | 101 | $content = "\"$content\""; | ||||
453 | } | ||||||
454 | 42 | 208 | $s .= '' . _html_escape($content) . ""; | ||||
455 | } | ||||||
456 | |||||||
457 | 76 | 687 | return $s; | ||||
458 | } | ||||||
459 | |||||||
460 | sub _html_escape | ||||||
461 | { | ||||||
462 | 42 | 41 | 188 | my ($s) = @_; | |||
463 | 42 | 100 | 1522 | return $s unless ($s =~ /[&<>'"\x80-\xff]/); | |||
464 | 27 | 53 | $s =~ s/&/&/g; | ||||
465 | 27 | 82 | $s =~ s/</g; | ||||
466 | 27 | 46 | $s =~ s/>/>/g; | ||||
467 | 27 | 65 | $s =~ s/'/'/g; | ||||
468 | 27 | 240 | $s =~ s/\"/"/g; | ||||
469 | 27 | 98 | $s =~ s/([\xc0-\xef][\x80-\xbf]+)/_make_utf8_entity($1)/ge; | ||||
4 | 12 | ||||||
470 | 27 | 181 | return $s; | ||||
471 | } | ||||||
472 | |||||||
473 | sub _make_utf8_entity | ||||||
474 | { | ||||||
475 | 4 | 3 | 23 | my ($i, @bytes) = split(//, shift()); | |||
476 | 4 | 100 | 15 | $i = ord($i) & ((ord($i) < 0xe0) ? 0x1f : 0x0f); | |||
477 | 4 | 106 | $i = ($i << 6) + (ord($_) & 0x3f) foreach @bytes; | ||||
478 | 4 | 24 | return "$i;"; | ||||
479 | } | ||||||
480 | |||||||
481 | |||||||
482 | 1; |