| 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; |