File Coverage

lib/Petal/Tiny.pm
Criterion Covered Total %
statement 348 354 98.3
branch 153 172 88.9
condition 49 72 68.0
subroutine 33 33 100.0
pod 0 27 0.0
total 583 658 88.6


line stmt bran cond sub pod time code
1             package Petal::Tiny;
2             $Petal::Tiny::VERSION = '1.13';
3 14     14   268678 use warnings;
  14         29  
  14         467  
4 14     14   62 use strict;
  14         20  
  14         413  
5 14     14   53 use Carp;
  14         20  
  14         68393  
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             our $TextSE = "[^<]+";
16             our $UntilHyphen = "[^-]*-";
17             our $Until2Hyphens = "$UntilHyphen(?:[^-]$UntilHyphen)*-";
18             our $CommentCE = "$Until2Hyphens>?";
19             our $UntilRSBs = "[^\\]]*](?:[^\\]]+])*]+";
20             our $CDATA_CE = "$UntilRSBs(?:[^\\]>]$UntilRSBs)*>";
21             our $S = "[ \\n\\t\\r]+";
22             our $NameStrt = "[A-Za-z_:]|[^\\x00-\\x7F]";
23             our $NameChar = "[A-Za-z0-9_:.-]|[^\\x00-\\x7F]";
24             our $Name = "(?:$NameStrt)(?:$NameChar)*";
25             our $QuoteSE = "\"[^\"]*\"|'[^']*'";
26             our $DT_IdentSE = "$S$Name(?:$S(?:$Name|$QuoteSE))*";
27             our $MarkupDeclCE = "(?:[^\\]\"'><]+|$QuoteSE)*>";
28             our $S1 = "[\\n\\r\\t ]";
29             our $UntilQMs = "[^?]*\\?+";
30             our $PI_Tail = "\\?>|$S1$UntilQMs(?:[^>?]$UntilQMs)*>";
31             our $DT_ItemSE = "<(?:!(?:--$Until2Hyphens>|[^-]$MarkupDeclCE)|\\?$Name(?:$PI_Tail))|%$Name;|$S";
32             our $DocTypeCE = "$DT_IdentSE(?:$S)?(?:\\[(?:$DT_ItemSE)*](?:$S)?)?>?";
33             our $DeclCE = "--(?:$CommentCE)?|\\[CDATA\\[(?:$CDATA_CE)?|DOCTYPE(?:$DocTypeCE)?";
34             our $PI_CE = "$Name(?:$PI_Tail)?";
35             our $EndTagCE = "$Name(?:$S)?>?";
36             our $AttValSE = "\"[^<\"]*\"|'[^<']*'";
37             our $ElemTagCE = "$Name(?:$S$Name(?:$S)?=(?:$S)?(?:$AttValSE))*(?:$S)?/?>?";
38             our $ElemTagCE_Mod = "$S($Name)(?:$S)?=(?:$S)?($AttValSE)";
39             our $MarkupSPE = "<(?:!(?:$DeclCE)?|\\?(?:$PI_CE)?|/(?:$EndTagCE)?|(?:$ElemTagCE)?)";
40             our $XML_SPE = "$TextSE|$MarkupSPE";
41             # REX END - thank you Robert for this 26 line XML parser - awesome ...
42              
43              
44             our $RE_1 = qr /$ElemTagCE/;
45             our $RE_2 = qr /$ElemTagCE_Mod/;
46             our $VARIABLE_RE_SIMPLE = qq |\$[A-Za-z_][A-Za-z0-9_\.:\/]+|;
47             our $VARIABLE_RE_BRACKETS = qq |(?
48             our $STRING_TOKEN_RE = "($VARIABLE_RE_SIMPLE|$VARIABLE_RE_BRACKETS)";
49              
50             our $TAL = 'petal';
51              
52             our $STOP_RECURSE = 0;
53              
54              
55             sub new {
56 4     4 0 1322 my $class = shift;
57 4   33     19 $class = ref $class || $class;
58 4         5 my $thing = shift;
59 4         10 my $self = bless {}, $class;
60 4 100 66     37 if (defined $thing and $thing =~ /(\<|\n|\>)/) { $self->{xmldata} = $thing }
  3 50       11  
61             elsif (defined $thing) {
62 1         1 $self->{xmldata} = do {
63 1 50       35 open my $xmldatafile, "<", $thing or die "cannot read open $thing";
64 1         25 my $xmldata = join '', <$xmldatafile>;
65 1         7 close $xmldatafile;
66 1         8 $xmldata;
67             };
68             }
69 4         9 return $self;
70             }
71              
72              
73             sub process {
74 4     4 0 525 my $self = shift;
75 4         11 my $context = { @_ };
76 4         10 my $data = $self->{xmldata};
77 4 50       14 defined $data or return; # empty data, empty result.
78 4         13 return $self->makeitso($data, $context); # earl grey. hot.
79             }
80              
81              
82             sub makeitso {
83 328     328 0 741 my $self = shift;
84 328         310 my $xml = shift;
85 328 100       4887 my @xml = ref $xml ? @{$xml} : ( $xml =~ /$XML_SPE/g );
  314         1468  
86 328   50     761 my $context = shift || {};
87 328         376 my @head = ();
88 328         313 my @body = ();
89 328         295 my @tail = ();
90 328         584 while (@xml) {
91 588         653 my $elem = shift @xml;
92 588 100       882 tag_self_close ($elem) and do {
93 16         18 push @body, $elem;
94 16         73 @tail = @xml;
95 16         22 last;
96             };
97 572         1019 my $opentag = tag_open ($elem);
98 572 100       1026 $opentag and do {
99 259         340 push @body, $elem;
100 259         261 my $balance = 1;
101 259         425 while ($balance) {
102 1353 50       2187 @xml or confess "cannot find closing tag for $elem";
103 1353         1378 my $elem = shift @xml;
104 1353 100       1870 tag_open ($elem) and $balance++;
105 1353 100       1977 tag_close ($elem) and $balance--;
106 1353         2997 push @body, $elem;
107             }
108 259         945 @tail = @xml;
109 259         550 last;
110             };
111 313 50       441 tag_close ($elem) and confess "cannot find opening tag for $elem";
112              
113 313         669 $elem = $self->_interpolate_dollar($context, $elem, 'resolve_expression');
114 313         716 push @head, $elem;
115             }
116 328         379 my @res = ();
117 328 100       747 push @res, @head if (@head);
118 328 100       1062 push @res, $self->makeitso_block(\@body, $context) if (@body);
119 327 100       1319 push @res, $self->makeitso(\@tail, $context) if (@tail);
120 327         1972 return join '', @res;
121             }
122              
123              
124             sub _interpolate_dollar {
125 332     332   464 my ($self, $context, $string, $method) = @_;
126              
127             my $subst = sub {
128 10     10   24 my $what = shift;
129 10         29 my $res = $self->$method($what, $context);
130 10 50       48 return $res if defined $res;
131 0         0 carp "'$what' in \$-interpolation resolved to undef";
132 0         0 return "";
133 332         1464 };
134              
135 332 100       729 if ($string =~ /\$/) {
136 4         25 $string =~ s/(?($1) /xegi;
  3         9  
137 4         25 $string =~ s/(?($1) /xegi;
  7         12  
138 4         9 $string =~ s/\$\$/\$/g;
139             }
140 332         1403 return $string;
141             }
142              
143              
144             sub namespace {
145 275     275 0 312 my $self = shift;
146 275         234 my $node = shift;
147 275         247 for my $k (keys %{$node}) {
  275         745  
148 1013 100       1906 $k =~ /^xmlns\:/ or next;
149 10         35 my $v = $node->{$k};
150 10 50       37 if ($v eq 'http://purl.org/petal/1.0/') {
151 10         26 delete $node->{$k};
152 10         40 $k =~ s/^xmlns\://;
153 10         31 return $k;
154             }
155             }
156 265         550 return;
157             }
158              
159              
160             sub makeitso_block {
161 275     275 0 283 my $self = shift;
162 275         234 my $xml = shift;
163 275         238 my $context = shift;
164 275 50       589 my @xml = ref $xml ? @{$xml} : ( $xml =~ /$XML_SPE/g );
  275         774  
165 275         330 my $tag = shift (@xml);
166 275         287 my $gat = pop (@xml);
167 275   66     384 my $node = tag_open ($tag) || tag_self_close ($tag);
168 275         602 my $ns = $self->namespace($node);
169 275   66     672 local $TAL = $ns || $TAL;
170 275 100       435 if (has_instructions ($node)) {
171 162         145 $context = { %{$context} };
  162         920  
172 162         539 return $self->tal_on_error($node, \@xml, $gat, $context);
173             }
174             else {
175 113 100       215 $tag = node2tag ($node) if ($ns);
176 113 100       336 if ($gat) { return $tag . $self->makeitso(\@xml, $context) . $gat }
  112         408  
177 1         3 else { return $tag } # self-closing tag
178             }
179             }
180              
181              
182             sub tal_on_error {
183 162     162 0 229 my ($self, $node, $xml, $end, $context) = @_;
184 162         324 my $stuff = delete $node->{"$TAL:on-error"};
185 162 100       497 defined $stuff or return $self->tal_define($node, $xml, $end, $context);
186 3         4 my $nodeCopy = { %{$node} };
  3         10  
187 3         4 my $res = eval { $self->tal_define($node, $xml, $end, $context) };
  3         10  
188 3 100       2648 if ($@) {
189 2         3 my @result = ();
190 2 100       3 for my $k (keys %{$nodeCopy}) { delete $nodeCopy->{$k} if $k =~ /^$TAL:/ }
  2         6  
  7         44  
191 2 100       7 delete $nodeCopy->{_close} and $end = "{_tag}>"; # deal with self closing tags
192 2         6 push @result, node2tag ($nodeCopy);
193 2         6 push @result, $self->resolve_expression($stuff, $context);
194 2         4 push @result, $end;
195 2         11 return join '', @result;
196             }
197             else {
198 1         5 return $res;
199             }
200             }
201              
202              
203             sub tal_define {
204 162     162 0 224 my ($self, $node, $xml, $end, $context) = @_;
205 162         228 my $stuff = delete $node->{"$TAL:define"};
206 162 100       507 defined $stuff || return $self->tal_condition($node, $xml, $end, $context);
207 7         6 my $newContext = { %{$context} };
  7         33  
208 7         24 my $define = trim ($stuff);
209 7         36 for my $def (split /;(?!;)/, $define) {
210 13         20 $def = trim($def);
211 13         26 my ($symbol, $expression) = split /\s+/, $def, 2;
212 13         30 $newContext->{$symbol} = $self->resolve_expression($expression, $newContext);
213             }
214 7         26 return $self->tal_condition($node, $xml, $end, $newContext);
215             }
216              
217              
218             sub tal_condition {
219 162     162 0 191 my ($self, $node, $xml, $end, $context) = @_;
220 162         357 my $stuff = delete $node->{"$TAL:condition"};
221 162 100       440 defined $stuff or return $self->tal_repeat($node, $xml, $end, $context);
222              
223 13         22 my $condition = trim ($stuff);
224 13         41 for my $cond (split /;(?!;)/, $condition) {
225 15         20 $cond = trim($cond);
226 15 100       51 $self->resolve_expression($cond, $context) or return '';
227             }
228 8         23 return $self->tal_repeat($node, $xml, $end, $context);
229             }
230              
231              
232             sub tal_repeat {
233 157     157 0 189 my ($self, $node, $xml, $end, $context) = @_;
234 157         229 my $stuff = delete $node->{"$TAL:repeat"};
235 157 100       465 defined $stuff or return $self->tal_content($node, $xml, $end, $context);
236            
237 7         27 my @loops = split /;/, $stuff;
238 7         9 my $count = 0;
239 7         50 return $self->_do_repeat(\$count, 1, \@loops, $node, $xml, $end, { %$context });
240             }
241              
242             sub _do_repeat {
243 10     10   30 my ($self, $count, $last, $loops_ref, $node, $xml, $end, $context) = @_;
244              
245 10         21 my @loops = @$loops_ref;
246 10         13 my $stuff = shift @loops;
247 10         22 my $repeat = trim ($stuff);
248 10         25 my ($symbol, $expression) = split /\s+/, $repeat, 2;
249 10         27 my $array = $self->resolve_expression($expression, $context);
250 10 100       31 $array = [ $array ] unless ref $array; # we don't judge
251 10         8 my @result;
252 10         28 foreach my $idx (0 .. $#$array) {
253 26         44 my $item = $array->[$idx];
254 26         45 $context->{$symbol} = $item;
255 26 100       47 if (@loops) {
256 3   66     22 push @result, $self->_do_repeat($count, $last && $idx == $#$array, \@loops, $node, $xml, $end, $context);
257             }
258             else {
259 23         26 $$count++;
260 23         39 $context->{repeat} = {};
261 23         69 $context->{repeat}->{index} = $$count;
262 23         35 $context->{repeat}->{number} = $$count;
263 23 100       76 $context->{repeat}->{even} = $$count%2 ? 0 : 1;
264 23 100       55 $context->{repeat}->{odd} = $$count%2 ? 1 : 0;
265 23 100       48 $context->{repeat}->{start} = $$count == 1 ? 1 : 0;
266 23 100 100     103 $context->{repeat}->{end} = $last && $idx == $#$array ? 1 : 0;
267 23 100 100     121 $context->{repeat}->{inner} = $context->{repeat}->{start} || $context->{repeat}->{end} ? 0 : 1;
268 23         101 push @result, $self->tal_content({ %$node }, $xml, $end, $context);
269             }
270             }
271 10         116 return join '', @result;
272             }
273              
274              
275             sub tal_content {
276 173     173 0 221 my ($self, $node, $xml, $end, $context) = @_;
277 173         273 my $stuff = delete $node->{"$TAL:content"};
278 173 100       469 defined $stuff or return $self->tal_replace($node, $xml, $end, $context);
279            
280 22         75 my $res = $self->resolve_expression($stuff, $context);
281 20 100       59 $xml = defined $res ? [ $res ] : [];
282 20 100       64 delete $node->{_close} and $end = "{_tag}>"; # deal with self closing tags
283            
284             # set the stop recurse flag so that if content contains $foo and $bar,
285             # those aren't interpolated as variables.
286 20         43 local ( $STOP_RECURSE ) = ( 1 );
287 20         62 return $self->tal_replace($node, $xml, $end, $context);
288             }
289              
290              
291             sub tal_replace {
292 171     171 0 205 my ($self, $node, $xml, $end, $context) = @_;
293 171         297 my $stuff = delete $node->{"$TAL:replace"};
294 171 100       381 defined $stuff or return $self->tal_attributes($node, $xml, $end, $context);
295 116         213 my $res = $self->resolve_expression($stuff, $context);
296 116 100       808 return defined $res ? $res : '';
297             }
298              
299              
300             sub tal_attributes {
301 55     55 0 76 my ($self, $node, $xml, $end, $context) = @_;
302 55         103 my $stuff = delete $node->{"$TAL:attributes"};
303 55 100       176 defined $stuff or return $self->tal_omit_tag($node, $xml, $end, $context);
304            
305 21         44 my $attributes = trim ($stuff);
306 21         104 for my $att (split /;(?!;)/, $attributes) {
307 35         54 $att = trim ($att);
308 35         81 my ($symbol, $expression) = split /\s+/, $att, 2;
309 35         58 my $add = ($symbol =~ s/^\+//);
310 35         71 my $new = $self->resolve_expression($expression, $context);
311 35 100       63 if (defined $new) {
312 34 100       58 if ($add) {
313 2         2 my $old = $node->{$symbol};
314 2 100       5 $old = "" unless defined $old;
315 2         2 $new = $old . $new;
316             }
317 34         104 $node->{$symbol} = $new;
318             }
319             else {
320 1 50       3 delete $node->{$symbol} unless $add;
321             }
322             }
323 21         123 return $self->tal_omit_tag($node, $xml, $end, $context);
324             }
325              
326              
327             sub tal_omit_tag {
328 55     55 0 80 my ($self, $node, $xml, $end, $context) = @_;
329 55         106 my $stuff = delete $node->{"$TAL:omit-tag"};
330 55 100       112 my $omit = defined $stuff ? do { $stuff eq '' ? 1 : $self->resolve_expression($stuff, $context) } : undef;
  3 100       15  
331 55 50 66     176 $omit and not $end and return ''; # omit-tag on a self-closing tag means *poof*, nothing left
332 55         80 my @result = ();
333 55 100       216 push @result, node2tag ($node) unless ($omit);
334 55 100       119 if ($end) {
335 48 100       55 push @result, do { $STOP_RECURSE ? join '', @{$xml} : $self->makeitso($xml, $context) };
  48         210  
  20         41  
336 47 100       119 push @result, $end unless ($omit);
337             }
338 54         466 return join '', @result;
339             }
340              
341              
342             sub resolve_expression {
343 236     236 0 811 my $self = shift;
344 236         360 my $expr = trim(shift);
345 236   33     456 my $context = shift || confess "resolve_expression() : no context";
346 236         235 $expr =~ s/\;\;/;/g;
347 236         238 $expr =~ s/\$\$/\$/g;
348 236 50       437 $expr eq 'nothing' and return undef;
349 236         236 $expr =~ s/^fresh\s+//;
350 236         376 my $structure = ($expr =~ s/^structure\s+//);
351 236         423 my $resolved = $self->resolve($expr, $context);
352 234 100       529 return $structure ? $resolved : xmlencode($resolved);
353             }
354              
355             sub reftype {
356 692     692 0 645 my ($self, $obj) = @_;
357 692         1100 return ref $obj;
358             }
359              
360             sub resolve {
361 282     282 0 293 my $self = shift;
362 282         352 my $expr = trim(shift);
363 282   33     571 my $context = shift || confess "resolve() : no context";
364 282 100       610 $expr =~ /:(?!pattern)/ and do {
365 36         118 my ($mod, $expr) = split /:(?!pattern)/, $expr, 2;
366 36         88 my $meth = "modifier_$mod";
367 36 50       305 $self->can("modifier_$mod") and return $self->$meth($expr, $context);
368 0         0 confess "unknown modifier $mod";
369             };
370 246 100       445 $expr =~ /^--/ and do {
371 23         57 $expr =~ s/^--//;
372 23         57 return $expr;
373             };
374 223         268 $expr =~ s/\r/ /g;
375 223         523 my ($what, @args) = split /\s+/, $expr;
376 223 100       406 defined $what or return;
377            
378 221         418 my (@path) = split /\//, $what;
379 221         229 my @resolved = ();
380 221         209 my $obj = $context;
381 221         278 @args = map { $self->resolve($_, $context) } @args;
  15         42  
382 221         386 while (@path) {
383 344         364 my $attribute_or_method = shift @path;
384 344         392 push @resolved, $attribute_or_method;
385 344         451 my $resolved = join '/', @resolved;
386 344 100       746 $obj or confess "cannot fetch $what, because $resolved is undefined";
387 343         550 my $reftype = $self->reftype($obj);
388 343 100       786 $reftype or confess "cannot fetch $what, because $resolved is not a reference";
389              
390 342 100       795 if ($reftype eq 'ARRAY') {
    100          
    50          
391 2         6 $obj = $obj->[$attribute_or_method];
392             }
393             elsif ($reftype eq 'HASH') {
394 321         477 $obj = $obj->{$attribute_or_method};
395             }
396             elsif ($obj->can($attribute_or_method)) {
397 19 100       27 if (@path) {
398 11         19 $obj = $obj->$attribute_or_method();
399             }
400             else {
401 8         19 $obj = $obj->$attribute_or_method(@args);
402 8         37 @args = ();
403             }
404             }
405              
406             # now, check if what we found was a code-ref
407 342         489 $reftype = $self->reftype($obj);
408 342 100       578 if ($reftype eq 'CODE') {
409 1 50       3 if (@path) {
410 0         0 $obj = $obj->();
411             }
412             else {
413 1         3 $obj = $obj->(@args);
414 1         4 @args = ();
415             }
416             }
417              
418             # if we're done with @path and there's a single arg, use it to look up in array/hash
419 342 100 100     1089 if (not @path and @args == 1) {
420 7         13 $reftype = $self->reftype($obj);
421              
422 7 100       23 if ($reftype eq 'ARRAY') {
    100          
423 2         5 $obj = $obj->[ $args[0] ];
424 2         3 last;
425             }
426             elsif ($reftype eq 'HASH') {
427 4         32 $obj = $obj->{ $args[0] };
428 4         6 last;
429             }
430             }
431              
432 336 100 100     1560 not @path and @args and confess "cannot resolve expression $expr";
433             }
434 218         470 return $obj;
435             }
436              
437              
438             sub modifier_true {
439 20     20 0 35 my $self = shift;
440 20         82 my $arg = $self->resolve(shift(), shift());
441 20 50 33     48 ref $arg and $self->reftype($arg) eq 'ARRAY' and return @{$arg};
  0         0  
442 20 100       74 return $arg ? 1 : 0;
443             }
444              
445              
446             sub modifier_false {
447 9     9 0 15 my $self = shift;
448 9         41 return not $self->modifier_true(@_);
449             }
450              
451              
452             sub modifier_string {
453 19     19 0 22 my $self = shift;
454 19         22 my $string = shift;
455 19         18 my $context = shift;
456 19         48 $string = $self->_interpolate_dollar($context, $string, 'resolve');
457 19         55 return $string;
458             }
459              
460              
461             sub node2tag {
462 64     64 0 76 my $node = shift;
463 64         133 my $tag = delete $node->{_tag};
464 64 50       75 for (keys %{$node}) { /^($TAL:)/ and delete $node->{$_} }
  64         169  
  142         694  
465 64   50     189 my $open = delete $node->{_open} || 0;
466 64   100     240 my $close = delete $node->{_close} || 0;
467 64         76 my $att = join ' ', map { qq|$_="$node->{$_}"| } keys %{$node};
  36         113  
  64         140  
468 64 100 66     305 $open and $close and return $att ? "<$tag $att />" : "<$tag />";
    100          
469 57 50       124 $close and return "";
470 57 100       275 $open and return $att ? "<$tag $att>" : "<$tag>";
    50          
471 0         0 die "There is probably a bug somewhere. A tag that's not and open tag and not a close tag?";
472             }
473              
474              
475             sub trim {
476 632     632 0 609 my $string = shift;
477 632 100       933 $string or return $string;
478 627         655 $string =~ s/\r//g;
479 627         522 $string =~ s/\n/ /g;
480 627         917 $string =~ s/^\s+//;
481 627         852 $string =~ s/\s+$//;
482 627         893 return $string;
483             }
484              
485              
486             sub has_instructions {
487 275     275 0 266 my $node = shift;
488 275         236 return grep /^$TAL:/, keys %{$node};
  275         2444  
489             }
490              
491              
492             sub tag_open {
493 2200     2200 0 2205 my $elem = shift;
494 2200         1877 my $node = undef;
495 2200 50       3398 not defined $elem and confess ('undefined elem');
496              
497             $elem !~ /^<\!/ and
498             $elem !~ /^<\// and
499             $elem !~ /\/>$/ and
500             $elem !~ /^<\?/ and
501 2200 100 66     15866 $elem =~ /^
      100        
502 758         1155 my %node = extract_attributes ($elem);
503 758         2653 ($node{_tag}) = $elem =~ /.*?([A-Za-z0-9][A-Za-z0-9_:-]*)/;
504 758         1022 $node{_open} = 1;
505 758         759 $node{_close} = 0;
506 758         1109 $node = \%node;
507             };
508 2200         4079 return $node;
509             }
510              
511              
512             sub tag_close {
513 1666     1666 0 1649 my $elem = shift;
514 1666         1535 my $node = undef;
515             $elem !~ /^<\!/ and
516             $elem =~ /^<\// and
517 1666 100 66     7402 $elem !~ /\/>$/ and do {
      66        
518 499         624 my %node = ();
519 499         1773 ($node{_tag}) = $elem =~ /.*?([A-Za-z0-9][A-Za-z0-9_:-]*)/;
520 499         673 $node{_open} = 0;
521 499         487 $node{_close} = 1;
522 499         660 $node = \%node;
523             };
524 1666         3160 return $node;
525             }
526              
527              
528             sub tag_self_close {
529 604     604 0 626 my $elem = shift;
530 604         574 my $node = undef;
531             $elem !~ /^<\!/ and
532             $elem !~ /^<\// and
533             $elem =~ /\/>$/ and
534 604 100 33     4006 $elem =~ /^
      66        
      66        
535 32         41 my %node = extract_attributes ($elem);
536 32         98 ($node{_tag}) = $elem =~ /.*?([A-Za-z0-9][A-Za-z0-9_:-]*)/;
537 32         37 $node{_open} = 1;
538 32         31 $node{_close} = 1;
539 32         39 $node = \%node;
540             };
541 604         1202 return $node;
542             }
543              
544              
545             sub extract_attributes {
546 790     790 0 891 my $tag = shift;
547 790         6562 my ($tags) = $tag =~ /$RE_1/g;
548 790         4691 my %attr = $tag =~ /$RE_2/g;
549 790         1823 foreach my $key (keys %attr)
550             {
551 604         764 my $val = $attr{$key};
552 604         1670 $val =~ s/^(\"|\')//;
553 604         1452 $val =~ s/(\"|\')$//;
554 604         1264 $attr{$key} = $val;
555             }
556            
557 790         2165 %attr;
558             }
559              
560              
561             sub xmlencode {
562 230     230 0 234 my $string = shift;
563 230 100 100     831 return $string if !$string or ref $string;
564 169         241 $string =~ s/&/&/g;
565 169         165 $string =~ s/
566 169         167 $string =~ s/>/>/g;
567 169         161 $string =~ s/"/"/g;
568 169         164 $string =~ s/'/'/g;
569 169         358 return $string;
570             }
571              
572              
573             1;
574              
575              
576             __END__