| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Petal::Tiny; | 
| 2 |  |  |  |  |  |  | $Petal::Tiny::VERSION = '1.16'; | 
| 3 | 14 |  |  | 14 |  | 237847 | use warnings; | 
|  | 14 |  |  |  |  | 27 |  | 
|  | 14 |  |  |  |  | 470 |  | 
| 4 | 14 |  |  | 14 |  | 62 | use strict; | 
|  | 14 |  |  |  |  | 24 |  | 
|  | 14 |  |  |  |  | 461 |  | 
| 5 | 14 |  |  | 14 |  | 55 | use Carp; | 
|  | 14 |  |  |  |  | 21 |  | 
|  | 14 |  |  |  |  | 47710 |  | 
| 6 |  |  |  |  |  |  |  | 
| 7 |  |  |  |  |  |  | # REX/Perl 1.0 | 
| 8 |  |  |  |  |  |  | # Robert D. Cameron "REX: XML Shallow Parsing with Regular Expressions", | 
| 9 |  |  |  |  |  |  | # Technical Report TR 1998-17, School of Computing Science, Simon Fraser | 
| 10 |  |  |  |  |  |  | # University, November, 1998. | 
| 11 |  |  |  |  |  |  | # Copyright (c) 1998, Robert D. Cameron. | 
| 12 |  |  |  |  |  |  | # The following code may be freely used and distributed provided that | 
| 13 |  |  |  |  |  |  | # this copyright and citation notice remains intact and that modifications | 
| 14 |  |  |  |  |  |  | # or additions are clearly identified. | 
| 15 |  |  |  |  |  |  | my $TextSE = "[^<]+"; | 
| 16 |  |  |  |  |  |  | my $UntilHyphen = "[^-]*-"; | 
| 17 |  |  |  |  |  |  | my $Until2Hyphens = "$UntilHyphen(?:[^-]$UntilHyphen)*-"; | 
| 18 |  |  |  |  |  |  | my $CommentCE = "$Until2Hyphens>?"; | 
| 19 |  |  |  |  |  |  | my $UntilRSBs = "[^\\]]*](?:[^\\]]+])*]+"; | 
| 20 |  |  |  |  |  |  | my $CDATA_CE = "$UntilRSBs(?:[^\\]>]$UntilRSBs)*>"; | 
| 21 |  |  |  |  |  |  | my $S = "[ \\n\\t\\r]+"; | 
| 22 |  |  |  |  |  |  | my $NameStrt = "[A-Za-z_:]|[^\\x00-\\x7F]"; | 
| 23 |  |  |  |  |  |  | my $NameChar = "[A-Za-z0-9_:.-]|[^\\x00-\\x7F]"; | 
| 24 |  |  |  |  |  |  | my $Name = "(?:$NameStrt)(?:$NameChar)*"; | 
| 25 |  |  |  |  |  |  | my $QuoteSE = "\"[^\"]*\"|'[^']*'"; | 
| 26 |  |  |  |  |  |  | my $DT_IdentSE = "$S$Name(?:$S(?:$Name|$QuoteSE))*"; | 
| 27 |  |  |  |  |  |  | my $MarkupDeclCE = "(?:[^\\]\"'><]+|$QuoteSE)*>"; | 
| 28 |  |  |  |  |  |  | my $S1 = "[\\n\\r\\t ]"; | 
| 29 |  |  |  |  |  |  | my $UntilQMs = "[^?]*\\?+"; | 
| 30 |  |  |  |  |  |  | my $PI_Tail = "\\?>|$S1$UntilQMs(?:[^>?]$UntilQMs)*>"; | 
| 31 |  |  |  |  |  |  | my $DT_ItemSE = "<(?:!(?:--$Until2Hyphens>|[^-]$MarkupDeclCE)|\\?$Name(?:$PI_Tail))|%$Name;|$S"; | 
| 32 |  |  |  |  |  |  | my $DocTypeCE = "$DT_IdentSE(?:$S)?(?:\\[(?:$DT_ItemSE)*](?:$S)?)?>?"; | 
| 33 |  |  |  |  |  |  | my $DeclCE = "--(?:$CommentCE)?|\\[CDATA\\[(?:$CDATA_CE)?|DOCTYPE(?:$DocTypeCE)?"; | 
| 34 |  |  |  |  |  |  | my $PI_CE = "$Name(?:$PI_Tail)?"; | 
| 35 |  |  |  |  |  |  | my $EndTagCE = "$Name(?:$S)?>?"; | 
| 36 |  |  |  |  |  |  | my $AttValSE = "\"[^<\"]*\"|'[^<']*'"; | 
| 37 |  |  |  |  |  |  | my $ElemTagCE = "$Name(?:$S$Name(?:$S)?=(?:$S)?(?:$AttValSE))*(?:$S)?/?>?"; | 
| 38 |  |  |  |  |  |  | my $ElemTagCE_Mod = "$S($Name)(?:$S)?=(?:$S)?($AttValSE)"; | 
| 39 |  |  |  |  |  |  | my $MarkupSPE = "<(?:!(?:$DeclCE)?|\\?(?:$PI_CE)?|/(?:$EndTagCE)?|(?:$ElemTagCE)?)"; | 
| 40 |  |  |  |  |  |  | my $XML_SPE = "$TextSE|$MarkupSPE"; | 
| 41 |  |  |  |  |  |  | # REX END - thank you Robert for this 26 line XML parser - awesome ... | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | my $ATTR_RE = qr /$ElemTagCE_Mod/; | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | my $DEFAULT_NS = 'petal'; | 
| 46 |  |  |  |  |  |  |  | 
| 47 |  |  |  |  |  |  | sub new { | 
| 48 | 14 |  |  | 14 | 0 | 2888 | my $class = shift; | 
| 49 | 14 |  | 33 |  |  | 80 | $class    = ref $class || $class; | 
| 50 | 14 |  |  |  |  | 19 | my $thing = shift; | 
| 51 | 14 |  |  |  |  | 40 | my $self  = bless {}, $class; | 
| 52 | 14 | 100 | 66 |  |  | 171 | if (defined $thing and $thing =~ /(\<|\n|\>)/) { | 
|  |  | 50 |  |  |  |  |  | 
| 53 | 13 |  |  |  |  | 70 | $self->{xmldata} = $thing; | 
| 54 |  |  |  |  |  |  | } | 
| 55 |  |  |  |  |  |  | elsif (defined $thing) { | 
| 56 | 1 | 50 |  |  |  | 27 | open my $xmldatafile, "<", $thing or die "cannot read open $thing"; | 
| 57 | 1 |  |  |  |  | 15 | $self->{xmldata} = join '', <$xmldatafile>; | 
| 58 | 1 |  |  |  |  | 6 | close $xmldatafile; | 
| 59 |  |  |  |  |  |  | } | 
| 60 | 14 |  |  |  |  | 58 | return $self; | 
| 61 |  |  |  |  |  |  | } | 
| 62 |  |  |  |  |  |  |  | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | sub process { | 
| 65 | 14 |  |  | 14 | 0 | 555 | my $self    = shift; | 
| 66 | 14 |  |  |  |  | 107 | my $context = { @_ }; | 
| 67 | 14 |  |  |  |  | 32 | my $data    = $self->{xmldata}; | 
| 68 | 14 | 50 |  |  |  | 59 | defined $data or return; # empty data, empty result. | 
| 69 | 14 |  |  |  |  | 55 | return $self->makeitso($self->xml2nodes($data), $context); # earl grey. hot. | 
| 70 |  |  |  |  |  |  | } | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | sub xml2nodes { | 
| 73 | 14 |  |  | 14 | 0 | 23 | my ($self, $xml) = @_; | 
| 74 |  |  |  |  |  |  |  | 
| 75 | 14 |  |  |  |  | 3367 | my @flat = ( $xml =~ /$XML_SPE/og ); | 
| 76 | 14 |  |  |  |  | 104 | my $top = { _kids => [], _ns => $DEFAULT_NS }; | 
| 77 | 14 |  |  |  |  | 30 | my @nest = ( $top ); | 
| 78 | 14 |  |  |  |  | 36 | for my $tag (@flat) { | 
| 79 | 370 |  |  |  |  | 511 | my $node = tag2node($tag, $nest[-1]{_ns}); # if ns is not explicitly set, inherit parent ns | 
| 80 |  |  |  |  |  |  |  | 
| 81 | 370 | 100 |  |  |  | 510 | if ($node->{_close}) { | 
| 82 | 93 |  |  |  |  | 68 | my $open = pop @nest; | 
| 83 | 93 | 50 |  |  |  | 180 | confess "Too many close-tags! Last $node->{_tag}>." if $open == $top; | 
| 84 | 93 | 50 |  |  |  | 253 | if (lc($node->{_tag}) ne lc($open->{_tag})) { | 
| 85 | 0 |  |  |  |  | 0 | my $in = ""; | 
| 86 | 0 |  |  |  |  | 0 | $in .= $nest[$_]{_elem} for 1..$#nest; | 
| 87 | 0 |  |  |  |  | 0 | $in .= $open->{_elem}; | 
| 88 | 0 |  |  |  |  | 0 | confess "Wrong close-tag '$node->{_tag}>' following '$in'"; | 
| 89 |  |  |  |  |  |  | } | 
| 90 |  |  |  |  |  |  | } | 
| 91 |  |  |  |  |  |  | else { | 
| 92 | 277 |  |  |  |  | 165 | push @{ $nest[-1]{_kids} }, $node; | 
|  | 277 |  |  |  |  | 342 |  | 
| 93 | 277 | 100 | 100 |  |  | 697 | push @nest, $node unless ($node->{_simple} or $node->{_selfclose}); | 
| 94 |  |  |  |  |  |  | } | 
| 95 |  |  |  |  |  |  | } | 
| 96 | 14 | 50 |  |  |  | 39 | confess "Unbalanced tree, more open than close nodes" if @nest > 1; | 
| 97 |  |  |  |  |  |  |  | 
| 98 | 14 |  |  |  |  | 15 | my @nodes = @{ $top->{_kids} }; | 
|  | 14 |  |  |  |  | 33 |  | 
| 99 |  |  |  |  |  |  |  | 
| 100 | 14 |  |  |  |  | 81 | return \@nodes; | 
| 101 |  |  |  |  |  |  | } | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | sub makeitso { | 
| 104 | 162 |  |  | 162 | 0 | 139 | my ($self, $nodes, $context) = @_; | 
| 105 |  |  |  |  |  |  |  | 
| 106 | 162 | 100 |  |  |  | 216 | return "" unless @$nodes; | 
| 107 |  |  |  |  |  |  |  | 
| 108 | 153 |  |  |  |  | 109 | my @res; | 
| 109 | 153 |  |  |  |  | 147 | for my $node (@$nodes) { | 
| 110 | 588 | 100 |  |  |  | 684 | if ($node->{_simple}) { | 
| 111 | 313 |  |  |  |  | 448 | push @res, $self->_interpolate_dollar($context, $node->{_elem}, 'resolve_expression'); | 
| 112 |  |  |  |  |  |  | } | 
| 113 |  |  |  |  |  |  | else { | 
| 114 | 275 |  |  |  |  | 421 | push @res, $self->makeitso_node($node, $context); | 
| 115 |  |  |  |  |  |  | } | 
| 116 |  |  |  |  |  |  | } | 
| 117 |  |  |  |  |  |  |  | 
| 118 | 152 |  |  |  |  | 472 | return join "", @res; | 
| 119 |  |  |  |  |  |  | } | 
| 120 |  |  |  |  |  |  |  | 
| 121 |  |  |  |  |  |  | sub _interpolate_dollar { | 
| 122 | 332 |  |  | 332 |  | 351 | my ($self, $context, $string, $method) = @_; | 
| 123 |  |  |  |  |  |  |  | 
| 124 | 332 | 100 |  |  |  | 525 | if ($string =~ /\$/) { | 
| 125 |  |  |  |  |  |  | my $subst = sub { | 
| 126 | 10 |  |  | 10 |  | 19 | my $what = shift; | 
| 127 | 10 |  |  |  |  | 26 | my $res = $self->$method($what, $context); | 
| 128 | 10 | 50 |  |  |  | 54 | return $res if defined $res; | 
| 129 | 0 |  |  |  |  | 0 | carp "'$what' in \$-interpolation resolved to undef"; | 
| 130 | 0 |  |  |  |  | 0 | return ""; | 
| 131 | 4 |  |  |  |  | 21 | }; | 
| 132 |  |  |  |  |  |  |  | 
| 133 | 4 |  |  |  |  | 21 | $string =~ s/(?($1) /xegi; | 
|  | 3 |  |  |  |  | 8 |  | 
| 134 | 4 |  |  |  |  | 19 | $string =~ s/(?($1) /xegi; | 
|  | 7 |  |  |  |  | 14 |  | 
| 135 | 4 |  |  |  |  | 21 | $string =~ s/\$\$/\$/g; | 
| 136 |  |  |  |  |  |  | } | 
| 137 | 332 |  |  |  |  | 488 | return $string; | 
| 138 |  |  |  |  |  |  | } | 
| 139 |  |  |  |  |  |  |  | 
| 140 |  |  |  |  |  |  | sub _deep_copy { | 
| 141 | 590 |  |  | 590 |  | 385 | my $node = shift; | 
| 142 | 590 |  |  |  |  | 1324 | my %copy = %$node; | 
| 143 | 590 |  |  |  |  | 465 | my @kids; | 
| 144 | 590 |  |  |  |  | 316 | for my $kid (@{ $node->{_kids} }) { | 
|  | 590 |  |  |  |  | 607 |  | 
| 145 | 567 |  |  |  |  | 515 | push @kids, _deep_copy($kid); | 
| 146 |  |  |  |  |  |  | } | 
| 147 | 590 |  |  |  |  | 532 | $copy{_kids} = \@kids; | 
| 148 | 590 |  |  |  |  | 816 | return \%copy; | 
| 149 |  |  |  |  |  |  | } | 
| 150 |  |  |  |  |  |  |  | 
| 151 |  |  |  |  |  |  | sub makeitso_node { | 
| 152 | 301 |  |  | 301 | 0 | 251 | my ($self, $node, $context) = @_; | 
| 153 |  |  |  |  |  |  |  | 
| 154 | 301 |  |  |  |  | 249 | my $TAL = $node->{_ns}; | 
| 155 |  |  |  |  |  |  |  | 
| 156 | 301 |  |  |  |  | 217 | my $STOP_RECURSE = 0; | 
| 157 |  |  |  |  |  |  |  | 
| 158 | 301 | 100 |  |  |  | 402 | if ($node->{_has_tal}) { | 
| 159 | 188 |  |  |  |  | 182 | $node->{_change} = 1; | 
| 160 |  |  |  |  |  |  |  | 
| 161 | 188 | 100 |  |  |  | 352 | if (defined( my $stuff = delete $node->{"$TAL:on-error"} )) { | 
| 162 | 3 |  |  |  |  | 14 | my $nodeCopy = { %$node }; | 
| 163 | 3 |  |  |  |  | 6 | my $res = eval { $self->makeitso_node($node, $context); }; | 
|  | 3 |  |  |  |  | 16 |  | 
| 164 | 3 | 100 |  |  |  | 1007 | if ($@) { | 
| 165 | 2 | 100 |  |  |  | 6 | for my $k (keys %$nodeCopy) { delete $nodeCopy->{$k} if $k =~ /^$TAL:/ } | 
|  | 17 |  |  |  |  | 46 |  | 
| 166 | 2 |  |  |  |  | 5 | delete $nodeCopy->{_selfclose}; | 
| 167 | 2 |  |  |  |  | 6 | $nodeCopy->{_contents} = $self->resolve_expression($stuff, $context); | 
| 168 | 2 |  |  |  |  | 5 | return node2txt($nodeCopy); | 
| 169 |  |  |  |  |  |  | } | 
| 170 | 1 |  |  |  |  | 4 | return $res; | 
| 171 |  |  |  |  |  |  | } | 
| 172 |  |  |  |  |  |  |  | 
| 173 | 185 |  |  |  |  | 616 | $context = { %$context }; | 
| 174 |  |  |  |  |  |  |  | 
| 175 | 185 | 100 |  |  |  | 381 | if (defined( my $stuff = delete $node->{"$TAL:define"} )) { | 
| 176 | 7 |  |  |  |  | 28 | for my $def (split /;(?!;)/, $stuff) { | 
| 177 | 13 |  |  |  |  | 19 | my ($symbol, $expression) = split ' ', $def, 2; | 
| 178 | 13 |  |  |  |  | 22 | $context->{$symbol} = $self->resolve_expression($expression, $context); | 
| 179 |  |  |  |  |  |  | } | 
| 180 |  |  |  |  |  |  | } | 
| 181 |  |  |  |  |  |  |  | 
| 182 | 185 | 100 |  |  |  | 312 | if (defined( my $stuff = delete $node->{"$TAL:condition"} )) { | 
| 183 | 13 |  |  |  |  | 24 | for my $cond (split /;(?!;)/, $stuff) { | 
| 184 | 15 | 100 |  |  |  | 22 | return '' unless $self->resolve_expression($cond, $context); | 
| 185 |  |  |  |  |  |  | } | 
| 186 |  |  |  |  |  |  | } | 
| 187 |  |  |  |  |  |  |  | 
| 188 | 180 | 100 |  |  |  | 293 | if (defined( my $stuff = delete $node->{"$TAL:repeat"} )) { | 
| 189 | 7 |  |  |  |  | 19 | my @loops = split /;(?!;)/, $stuff; | 
| 190 | 7 |  |  |  |  | 6 | my $count = 0; | 
| 191 | 7 |  |  |  |  | 17 | return join "", $self->_do_repeat(\$count, 1, \@loops, $node, $context); | 
| 192 |  |  |  |  |  |  | } | 
| 193 |  |  |  |  |  |  |  | 
| 194 | 173 | 100 |  |  |  | 279 | if (defined( my $stuff = delete $node->{"$TAL:content"} )) { | 
| 195 | 22 |  |  |  |  | 41 | my $res = $self->resolve_expression($stuff, $context); | 
| 196 | 20 | 100 |  |  |  | 47 | $node->{_contents} = defined $res ? $res : ""; | 
| 197 | 20 |  |  |  |  | 30 | delete $node->{_selfclose}; | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | # set the stop recurse flag so that if content contains $foo and $bar, | 
| 200 |  |  |  |  |  |  | # those aren't interpolated as variables. | 
| 201 | 20 |  |  |  |  | 22 | $STOP_RECURSE = 1; | 
| 202 |  |  |  |  |  |  | } | 
| 203 |  |  |  |  |  |  |  | 
| 204 | 171 | 100 |  |  |  | 350 | if (defined( my $stuff = delete $node->{"$TAL:replace"} )) { | 
| 205 | 116 |  |  |  |  | 139 | my $res = $self->resolve_expression($stuff, $context); | 
| 206 | 116 | 100 |  |  |  | 408 | return defined $res ? $res : ''; | 
| 207 |  |  |  |  |  |  | } | 
| 208 |  |  |  |  |  |  |  | 
| 209 | 55 | 100 |  |  |  | 117 | if (defined( my $stuff = delete $node->{"$TAL:attributes"} )) { | 
| 210 | 21 |  |  |  |  | 70 | for my $att (split /;(?!;)/, $stuff) { | 
| 211 | 35 |  |  |  |  | 65 | my ($symbol, $expression) = split ' ', $att, 2; | 
| 212 | 35 |  |  |  |  | 55 | my $add = ($symbol =~ s/^\+//); | 
| 213 | 35 |  |  |  |  | 58 | my $new = $self->resolve_expression($expression, $context); | 
| 214 | 35 | 100 |  |  |  | 49 | if (defined $new) { | 
| 215 | 34 | 100 |  |  |  | 65 | if ($add) { | 
| 216 | 2 |  |  |  |  | 4 | my $old = $node->{$symbol}; | 
| 217 | 2 | 100 |  |  |  | 6 | $old = "" unless defined $old; | 
| 218 | 2 |  |  |  |  | 3 | $new = $old . $new; | 
| 219 |  |  |  |  |  |  | } | 
| 220 | 34 |  |  |  |  | 70 | $node->{$symbol} = $new; | 
| 221 |  |  |  |  |  |  | } | 
| 222 |  |  |  |  |  |  | else { | 
| 223 | 1 | 50 |  |  |  | 4 | delete $node->{$symbol} unless $add; | 
| 224 |  |  |  |  |  |  | } | 
| 225 |  |  |  |  |  |  | } | 
| 226 |  |  |  |  |  |  | } | 
| 227 |  |  |  |  |  |  |  | 
| 228 | 55 | 100 |  |  |  | 141 | if (defined(my $stuff = delete $node->{"$TAL:omit-tag"})) { | 
| 229 | 3 | 100 | 100 |  |  | 9 | if ($stuff eq '' or $self->resolve_expression($stuff, $context)) { | 
| 230 | 2 | 50 |  |  |  | 3 | return $node->{_contents} if $STOP_RECURSE; | 
| 231 | 2 |  |  |  |  | 5 | return $self->makeitso($node->{_kids}, $context); | 
| 232 |  |  |  |  |  |  | } | 
| 233 |  |  |  |  |  |  | } | 
| 234 |  |  |  |  |  |  | } | 
| 235 |  |  |  |  |  |  |  | 
| 236 | 166 | 100 |  |  |  | 261 | unless ($STOP_RECURSE) { | 
| 237 | 146 |  |  |  |  | 225 | $node->{_contents} = $self->makeitso($node->{_kids}, $context); | 
| 238 |  |  |  |  |  |  | } | 
| 239 | 165 |  |  |  |  | 223 | return node2txt($node); | 
| 240 |  |  |  |  |  |  | } | 
| 241 |  |  |  |  |  |  |  | 
| 242 |  |  |  |  |  |  | sub _do_repeat { | 
| 243 | 10 |  |  | 10 |  | 11 | my ($self, $count, $last, $loops_ref, $node, $context) = @_; | 
| 244 | 10 |  |  |  |  | 15 | my @loops = @$loops_ref; | 
| 245 | 10 |  |  |  |  | 11 | my $stuff = shift @loops; | 
| 246 | 10 |  |  |  |  | 15 | my ($symbol, $expression) = split ' ', $stuff, 2; | 
| 247 | 10 |  |  |  |  | 17 | my $array  = $self->resolve_expression($expression, $context); | 
| 248 | 10 | 100 |  |  |  | 19 | $array = [ $array ] unless ref $array; # we don't judge | 
| 249 | 10 |  |  |  |  | 9 | my @result; | 
| 250 | 10 |  |  |  |  | 17 | foreach my $idx (0 .. $#$array) { | 
| 251 | 26 |  |  |  |  | 32 | my $item = $array->[$idx]; | 
| 252 | 26 |  |  |  |  | 29 | $context->{$symbol} = $item; | 
| 253 | 26 | 100 |  |  |  | 29 | if (@loops) { | 
| 254 | 3 |  | 66 |  |  | 16 | push @result, $self->_do_repeat($count, $last && $idx == $#$array, \@loops, $node, $context); | 
| 255 |  |  |  |  |  |  | } | 
| 256 |  |  |  |  |  |  | else { | 
| 257 | 23 |  |  |  |  | 19 | $$count++; | 
| 258 | 23 |  |  |  |  | 28 | $context->{repeat} = {}; | 
| 259 | 23 |  |  |  |  | 41 | $context->{repeat}->{index}  = $$count; | 
| 260 | 23 |  |  |  |  | 23 | $context->{repeat}->{number} = $$count; | 
| 261 | 23 | 100 |  |  |  | 45 | $context->{repeat}->{even}   = $$count%2 ? 0 : 1; | 
| 262 | 23 | 100 |  |  |  | 31 | $context->{repeat}->{odd}    = $$count%2 ? 1 : 0; | 
| 263 | 23 | 100 |  |  |  | 36 | $context->{repeat}->{start}  = $$count == 1 ? 1 : 0; | 
| 264 | 23 | 100 | 100 |  |  | 71 | $context->{repeat}->{end}    = $last && $idx == $#$array ? 1 : 0; | 
| 265 | 23 | 100 | 100 |  |  | 78 | $context->{repeat}->{inner}  = $context->{repeat}->{start} || $context->{repeat}->{end} ? 0 : 1; | 
| 266 |  |  |  |  |  |  |  | 
| 267 | 23 |  |  |  |  | 34 | push @result, $self->makeitso_node(_deep_copy($node), $context); | 
| 268 |  |  |  |  |  |  | } | 
| 269 |  |  |  |  |  |  | } | 
| 270 | 10 |  |  |  |  | 68 | return @result; | 
| 271 |  |  |  |  |  |  | } | 
| 272 |  |  |  |  |  |  |  | 
| 273 |  |  |  |  |  |  |  | 
| 274 |  |  |  |  |  |  | sub resolve_expression { | 
| 275 | 236 |  |  | 236 | 0 | 828 | my ($self, $expr, $context) = @_; | 
| 276 |  |  |  |  |  |  |  | 
| 277 | 236 | 50 |  |  |  | 324 | $expr = "" unless defined $expr; | 
| 278 | 236 |  |  |  |  | 295 | $expr =~ s/[\n\r]/ /g; | 
| 279 | 236 |  |  |  |  | 329 | $expr =~ s/^\s+//; | 
| 280 | 236 |  |  |  |  | 278 | $expr =~ s/\s+$//; | 
| 281 |  |  |  |  |  |  |  | 
| 282 | 236 |  |  |  |  | 207 | $expr =~ s/([;\$])\1/$1/g; | 
| 283 | 236 | 50 |  |  |  | 342 | $expr eq 'nothing' and return undef; | 
| 284 | 236 |  |  |  |  | 184 | $expr =~ s/^fresh\s+//; | 
| 285 | 236 |  |  |  |  | 204 | my $structure = ($expr =~ s/^structure\s+//); | 
| 286 | 236 |  |  |  |  | 736 | my $resolved = $self->resolve($expr, $context); | 
| 287 | 234 | 100 |  |  |  | 417 | return $structure ? $resolved : xmlencode($resolved); | 
| 288 |  |  |  |  |  |  | } | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | sub reftype { | 
| 291 | 692 |  |  | 692 | 0 | 509 | my ($self, $obj) = @_; | 
| 292 | 692 |  |  |  |  | 861 | return ref $obj; | 
| 293 |  |  |  |  |  |  | } | 
| 294 |  |  |  |  |  |  |  | 
| 295 |  |  |  |  |  |  | sub resolve { | 
| 296 | 282 |  |  | 282 | 0 | 264 | my ($self, $expr, $context) = @_; | 
| 297 | 282 | 100 |  |  |  | 481 | $expr =~ /:(?!pattern)/ and do { # XXX what is :pattern? | 
| 298 | 36 |  |  |  |  | 97 | my ($mod, $expr) = split /:(?!pattern)\s*/, $expr, 2; | 
| 299 | 36 |  |  |  |  | 151 | my $meth = $self->can("modifier_$mod"); | 
| 300 | 36 | 50 |  |  |  | 107 | return $self->$meth($expr, $context) if $meth; | 
| 301 | 0 |  |  |  |  | 0 | confess "unknown modifier $mod"; | 
| 302 |  |  |  |  |  |  | }; | 
| 303 | 246 | 100 |  |  |  | 460 | return $expr if $expr =~ s/^--//; | 
| 304 |  |  |  |  |  |  |  | 
| 305 | 223 |  |  |  |  | 369 | my ($what, @args) = split ' ', $expr; | 
| 306 | 223 | 100 |  |  |  | 305 | defined $what or return; | 
| 307 |  |  |  |  |  |  |  | 
| 308 | 221 |  |  |  |  | 313 | my (@path)   = split /\//, $what; | 
| 309 | 221 |  |  |  |  | 168 | my @resolved; | 
| 310 | 221 |  |  |  |  | 154 | my $obj      = $context; | 
| 311 | 221 |  |  |  |  | 198 | @args        = map { $self->resolve($_, $context) } @args; | 
|  | 15 |  |  |  |  | 40 |  | 
| 312 | 221 |  |  |  |  | 307 | while (@path) { | 
| 313 | 344 |  |  |  |  | 339 | my $attribute_or_method = shift @path; | 
| 314 | 344 |  |  |  |  | 320 | push @resolved, $attribute_or_method; | 
| 315 | 344 |  |  |  |  | 350 | my $resolved = join '/', @resolved; | 
| 316 | 344 | 100 |  |  |  | 627 | $obj or confess "cannot fetch $what, because $resolved is undefined"; | 
| 317 | 343 |  |  |  |  | 408 | my $reftype = $self->reftype($obj); | 
| 318 | 343 | 100 |  |  |  | 609 | $reftype or confess "cannot fetch $what, because $resolved is not a reference"; | 
| 319 |  |  |  |  |  |  |  | 
| 320 | 342 | 100 |  |  |  | 599 | if ($reftype eq 'ARRAY') { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 321 | 2 |  |  |  |  | 6 | $obj = $obj->[$attribute_or_method]; | 
| 322 |  |  |  |  |  |  | } | 
| 323 |  |  |  |  |  |  | elsif ($reftype eq 'HASH') { | 
| 324 | 321 |  |  |  |  | 344 | $obj = $obj->{$attribute_or_method}; | 
| 325 |  |  |  |  |  |  | } | 
| 326 |  |  |  |  |  |  | elsif ($obj->can($attribute_or_method)) { | 
| 327 | 19 | 100 |  |  |  | 26 | if (@path) { | 
| 328 | 11 |  |  |  |  | 24 | $obj = $obj->$attribute_or_method(); | 
| 329 |  |  |  |  |  |  | } | 
| 330 |  |  |  |  |  |  | else { | 
| 331 | 8 |  |  |  |  | 22 | $obj = $obj->$attribute_or_method(@args); | 
| 332 | 8 |  |  |  |  | 30 | @args = (); | 
| 333 |  |  |  |  |  |  | } | 
| 334 |  |  |  |  |  |  | } | 
| 335 |  |  |  |  |  |  |  | 
| 336 |  |  |  |  |  |  | # now, check if what we found was a code-ref | 
| 337 | 342 |  |  |  |  | 385 | $reftype = $self->reftype($obj); | 
| 338 | 342 | 100 |  |  |  | 451 | if ($reftype eq 'CODE') { | 
| 339 | 1 | 50 |  |  |  | 3 | if (@path) { | 
| 340 | 0 |  |  |  |  | 0 | $obj = $obj->(); | 
| 341 |  |  |  |  |  |  | } | 
| 342 |  |  |  |  |  |  | else { | 
| 343 | 1 |  |  |  |  | 4 | $obj = $obj->(@args); | 
| 344 | 1 |  |  |  |  | 6 | @args = (); | 
| 345 |  |  |  |  |  |  | } | 
| 346 |  |  |  |  |  |  | } | 
| 347 |  |  |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | # if we're done with @path and there's a single arg, use it to look up in array/hash | 
| 349 | 342 | 100 | 100 |  |  | 1156 | if (not @path and @args == 1) { | 
| 350 | 7 |  |  |  |  | 11 | $reftype = $self->reftype($obj); | 
| 351 |  |  |  |  |  |  |  | 
| 352 | 7 | 100 |  |  |  | 17 | if ($reftype eq 'ARRAY') { | 
|  |  | 100 |  |  |  |  |  | 
| 353 | 2 |  |  |  |  | 3 | $obj = $obj->[ $args[0] ]; | 
| 354 | 2 |  |  |  |  | 2 | last; | 
| 355 |  |  |  |  |  |  | } | 
| 356 |  |  |  |  |  |  | elsif ($reftype eq 'HASH') { | 
| 357 | 4 |  |  |  |  | 5 | $obj = $obj->{ $args[0] }; | 
| 358 | 4 |  |  |  |  | 6 | last; | 
| 359 |  |  |  |  |  |  | } | 
| 360 |  |  |  |  |  |  | } | 
| 361 |  |  |  |  |  |  |  | 
| 362 | 336 | 100 | 100 |  |  | 1254 | not @path and @args and confess "cannot resolve expression $expr"; | 
| 363 |  |  |  |  |  |  | } | 
| 364 | 218 |  |  |  |  | 320 | return $obj; | 
| 365 |  |  |  |  |  |  | } | 
| 366 |  |  |  |  |  |  |  | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  | sub modifier_true { | 
| 369 | 20 |  |  | 20 | 0 | 30 | my ($self, $expr, $context) = @_; | 
| 370 | 20 |  |  |  |  | 60 | my $arg  = $self->resolve($expr, $context); | 
| 371 | 20 | 50 | 33 |  |  | 31 | ref $arg and $self->reftype($arg) eq 'ARRAY' and return scalar @$arg; | 
| 372 | 20 | 100 |  |  |  | 49 | return $arg ? 1 : 0; | 
| 373 |  |  |  |  |  |  | } | 
| 374 |  |  |  |  |  |  |  | 
| 375 |  |  |  |  |  |  |  | 
| 376 |  |  |  |  |  |  | sub modifier_false { | 
| 377 | 9 |  |  | 9 | 0 | 10 | my $self = shift; | 
| 378 | 9 |  |  |  |  | 17 | return not $self->modifier_true(@_); | 
| 379 |  |  |  |  |  |  | } | 
| 380 |  |  |  |  |  |  |  | 
| 381 |  |  |  |  |  |  |  | 
| 382 |  |  |  |  |  |  | sub modifier_string { | 
| 383 | 19 |  |  | 19 | 0 | 24 | my ($self, $string, $context) = @_; | 
| 384 | 19 |  |  |  |  | 44 | $string = $self->_interpolate_dollar($context, $string, 'resolve'); | 
| 385 | 19 |  |  |  |  | 90 | return $string; | 
| 386 |  |  |  |  |  |  | } | 
| 387 |  |  |  |  |  |  |  | 
| 388 |  |  |  |  |  |  |  | 
| 389 |  |  |  |  |  |  | sub node2txt { | 
| 390 | 167 |  |  | 167 | 0 | 125 | my $node  = shift; | 
| 391 |  |  |  |  |  |  |  | 
| 392 | 167 | 50 |  |  |  | 257 | return $node unless ref $node eq 'HASH'; # handle textnodes introduced in makeitso_node | 
| 393 | 167 | 50 |  |  |  | 238 | return $node->{_elem} if $node->{_simple}; | 
| 394 |  |  |  |  |  |  |  | 
| 395 | 167 |  |  |  |  | 186 | delete $node->{_ns}; | 
| 396 | 167 |  |  |  |  | 148 | delete $node->{_has_tal}; | 
| 397 | 167 |  |  |  |  | 563 | delete $node->{_kids}; | 
| 398 |  |  |  |  |  |  |  | 
| 399 | 167 |  |  |  |  | 138 | my $change   = delete $node->{_change}; | 
| 400 | 167 |  |  |  |  | 196 | my $elem     = delete $node->{_elem}; | 
| 401 | 167 |  |  |  |  | 154 | my $tag      = delete $node->{_tag}; | 
| 402 | 167 |  |  |  |  | 165 | my $close    = delete $node->{_selfclose}; | 
| 403 | 167 |  |  |  |  | 140 | my $quotes   = delete $node->{_quotes}; | 
| 404 | 167 |  |  |  |  | 167 | my $contents = delete $node->{_contents}; | 
| 405 | 167 |  | 100 |  |  | 301 | my $att      = join ' ', map { my $q = $quotes->{$_} || '"'; qq|$_=$q$node->{$_}$q| } keys %$node; | 
|  | 42 |  |  |  |  | 104 |  | 
|  | 42 |  |  |  |  | 113 |  | 
| 406 |  |  |  |  |  |  |  | 
| 407 | 167 | 100 |  |  |  | 247 | if ($close) { | 
| 408 | 8 | 100 |  |  |  | 35 | return $change ? ($att ? "<$tag $att />" : "<$tag />") : $elem; | 
|  |  | 100 |  |  |  |  |  | 
| 409 |  |  |  |  |  |  | } | 
| 410 |  |  |  |  |  |  |  | 
| 411 | 159 | 100 |  |  |  | 254 | my $start = $change ? ($att ? "<$tag $att>" : "<$tag>") : $elem; | 
|  |  | 100 |  |  |  |  |  | 
| 412 | 159 |  |  |  |  | 326 | my $end   = "$tag>"; | 
| 413 |  |  |  |  |  |  |  | 
| 414 | 159 | 50 |  |  |  | 219 | $contents = "" unless defined $contents; | 
| 415 |  |  |  |  |  |  |  | 
| 416 | 159 |  |  |  |  | 478 | return $start . $contents . $end; | 
| 417 |  |  |  |  |  |  | } | 
| 418 |  |  |  |  |  |  |  | 
| 419 |  |  |  |  |  |  | sub tag2node { | 
| 420 | 370 |  |  | 370 | 0 | 890 | my ($elem, $ns) = @_; | 
| 421 |  |  |  |  |  |  |  | 
| 422 | 370 | 100 | 66 |  |  | 1597 | if (my ($has_close,     $tag) = $elem =~ m, \A < ( /? ) ( [A-Za-z0-9] [A-Za-z0-9_:-]* ) ,x and | 
| 423 |  |  |  |  |  |  | my ($has_self_close     ) = $elem =~ m, ( /? ) > \z ,x) | 
| 424 |  |  |  |  |  |  | { | 
| 425 | 197 | 100 |  |  |  | 1040 | return { _tag => $tag, _close => 1 } if $has_close; # don't waste any time on  nodes, they're just for book-keeping | 
| 426 |  |  |  |  |  |  |  | 
| 427 | 104 |  |  |  |  | 138 | my %node          = extract_attributes($elem); | 
| 428 | 104 |  | 66 |  |  | 303 | $node{_ns}      ||= $ns; | 
| 429 |  |  |  |  |  |  |  | 
| 430 | 104 |  |  |  |  | 159 | $node{_has_tal}   = exists $node{_ns_prefix}{ $node{_ns} }; | 
| 431 | 104 |  |  |  |  | 102 | $node{_tag}       = $tag; | 
| 432 | 104 |  |  |  |  | 99 | $node{_selfclose} = $has_self_close; | 
| 433 | 104 |  |  |  |  | 113 | $node{_elem}      = $elem; | 
| 434 | 104 |  |  |  |  | 124 | $node{_kids}      = []; | 
| 435 |  |  |  |  |  |  |  | 
| 436 | 104 |  |  |  |  | 143 | delete $node{_ns_prefix}; | 
| 437 |  |  |  |  |  |  |  | 
| 438 | 104 |  |  |  |  | 149 | return \%node; | 
| 439 |  |  |  |  |  |  | } | 
| 440 |  |  |  |  |  |  |  | 
| 441 |  |  |  |  |  |  | return { | 
| 442 | 173 |  |  |  |  | 307 | _elem => $elem, | 
| 443 |  |  |  |  |  |  | _simple => 1, | 
| 444 |  |  |  |  |  |  | }; | 
| 445 |  |  |  |  |  |  | } | 
| 446 |  |  |  |  |  |  |  | 
| 447 |  |  |  |  |  |  | sub extract_attributes { | 
| 448 | 104 |  |  | 104 | 0 | 868 | my $tag = shift; | 
| 449 |  |  |  |  |  |  |  | 
| 450 | 104 |  |  |  |  | 1529 | my %attr = $tag =~ /$ATTR_RE/og; | 
| 451 |  |  |  |  |  |  |  | 
| 452 | 104 |  |  |  |  | 101 | my (%quotes, %prefix); | 
| 453 |  |  |  |  |  |  |  | 
| 454 | 104 |  |  |  |  | 584 | foreach my $key (keys %attr) { | 
| 455 | 101 |  |  |  |  | 414 | $attr{$key} =~ s/^(['"])(.*?)\1$/$2/; | 
| 456 | 101 |  | 50 |  |  | 231 | my $q = $1 || '"'; | 
| 457 |  |  |  |  |  |  |  | 
| 458 | 101 | 100 |  |  |  | 244 | if ($key =~ /^(.*?):/) { | 
| 459 | 92 | 100 | 66 |  |  | 214 | if ($1 eq 'xmlns' && $attr{$key} eq 'http://purl.org/petal/1.0/') { | 
| 460 | 10 |  |  |  |  | 24 | delete $attr{$key}; | 
| 461 | 10 |  |  |  |  | 30 | $key           =~ s/^xmlns\://; | 
| 462 | 10 |  |  |  |  | 18 | $attr{_ns}     = $key; | 
| 463 | 10 |  |  |  |  | 16 | $attr{_change} = 1; | 
| 464 | 10 |  |  |  |  | 27 | next; | 
| 465 |  |  |  |  |  |  | } | 
| 466 | 82 |  |  |  |  | 130 | $prefix{$1} = 1; | 
| 467 |  |  |  |  |  |  | } | 
| 468 | 91 |  |  |  |  | 156 | $quotes{$key} = $q; | 
| 469 |  |  |  |  |  |  | } | 
| 470 |  |  |  |  |  |  |  | 
| 471 | 104 |  |  |  |  | 147 | $attr{_quotes} = \%quotes; | 
| 472 | 104 |  |  |  |  | 103 | $attr{_ns_prefix} = \%prefix; | 
| 473 |  |  |  |  |  |  |  | 
| 474 | 104 |  |  |  |  | 342 | %attr; | 
| 475 |  |  |  |  |  |  | } | 
| 476 |  |  |  |  |  |  |  | 
| 477 |  |  |  |  |  |  | my %_encode_dict = ( | 
| 478 |  |  |  |  |  |  | '&' => '&', | 
| 479 |  |  |  |  |  |  | '<' => '<', | 
| 480 |  |  |  |  |  |  | '>' => '>', | 
| 481 |  |  |  |  |  |  | '"' => '"', | 
| 482 |  |  |  |  |  |  | "'" => ''', | 
| 483 |  |  |  |  |  |  | ); | 
| 484 |  |  |  |  |  |  |  | 
| 485 |  |  |  |  |  |  | sub xmlencode { | 
| 486 | 230 |  |  | 230 | 0 | 181 | my $string = shift; | 
| 487 | 230 | 100 | 100 |  |  | 631 | return $string if !$string or ref $string; | 
| 488 | 169 |  |  |  |  | 248 | $string =~ s/([&<>"'])/$_encode_dict{$1}/g; | 
| 489 | 169 |  |  |  |  | 313 | return $string; | 
| 490 |  |  |  |  |  |  | } | 
| 491 |  |  |  |  |  |  |  | 
| 492 |  |  |  |  |  |  |  | 
| 493 |  |  |  |  |  |  | 1; | 
| 494 |  |  |  |  |  |  |  | 
| 495 |  |  |  |  |  |  |  | 
| 496 |  |  |  |  |  |  | __END__ |