File Coverage

blib/lib/XML/Loy.pm
Criterion Covered Total %
statement 333 348 95.6
branch 167 196 85.2
condition 52 75 69.3
subroutine 30 31 96.7
pod 10 10 100.0
total 592 660 89.7


line stmt bran cond sub pod time code
1             package XML::Loy;
2 21     21   1917520 use Mojo::ByteStream 'b';
  21         1653474  
  21         1228  
3 21     21   7150 use Mojo::Loader qw/load_class/;
  21         527598  
  21         1330  
4 21     21   151 use Carp qw/croak carp/;
  21         34  
  21         816  
5 21     21   83 use Scalar::Util qw/blessed weaken/;
  21         32  
  21         833  
6 21     21   98 use Mojo::Base 'Mojo::DOM';
  21         37  
  21         133  
7              
8             our $VERSION = '0.53';
9              
10             sub DESTROY;
11              
12             # TODO:
13             # - Support Mojolicious version > 7.77
14             # - "ns|*" namespace selector
15             #
16             # - Add ->clone
17             # (Maybe via JSON serialisation of ->tree or using Storable or Dumper)
18             #
19             # Maybe necessary: *AUTOLOAD = \&XML::Loy::AUTOLOAD;
20             #
21             # - sub try_further { };
22             # # usage:
23             # sub author {
24             # return $autor or $self->try_further;
25             # };
26             #
27             # - ALERT!
28             # Do not allow for namespace islands
29             # Search $obj->find('* *[xmlns]') and change prefixing
30             # After ->SUPER::new;
31             # Or:
32             # Do allow for namespace islands and check for the
33             # namespace to add instead of the package name before
34             # prefixing.
35             #
36             # - set() should really try to overwrite.
37             #
38             # - add() with -before => '' and -after => ''
39             # - maybe possible to save to element
40             # - Maybe with small changes a change to the object
41             # (encoding, xml etc.) can be done
42             #
43             # - closest() (jQuery)
44              
45             our @CARP_NOT;
46              
47             # Import routine, run when calling the class properly
48             sub import {
49 58     58   51597 my $class = shift;
50              
51 58 100       94702 return unless my $flag = shift;
52              
53 31 100       229 return unless $flag =~ /^-?(?i:base|with)$/;
54              
55             # Allow for manipulating the symbol table
56 21     21   286983 no strict 'refs';
  21         34  
  21         678  
57 21     21   90 no warnings 'once';
  21         35  
  21         6327  
58              
59             # The caller is the calling (inheriting) class
60 30         71 my $caller = caller;
61 30         154 push @{"${caller}::ISA"}, __PACKAGE__;
  30         414  
62              
63 30 100       76 if (@_) {
64              
65             # Get class variables
66 29         100 my %param = @_;
67              
68             # Set class variables
69 29         54 foreach (qw/namespace prefix mime/) {
70 87 100       157 if (exists $param{$_}) {
71 72         81 ${ "${caller}::" . uc $_ } = delete $param{$_};
  72         277  
72             };
73             };
74              
75             # Set class hook
76 29 100       80 if (exists $param{on_init}) {
77 3         4 *{"${caller}::ON_INIT"} = delete $param{on_init};
  3         15  
78             };
79             };
80              
81             # Make inheriting classes strict and modern
82 30         128 strict->import;
83 30         565 warnings->import;
84 30         148 utf8->import;
85 30         34486 feature->import(':5.10');
86             };
87              
88              
89             # Return class variables
90             {
91 21     21   111 no strict 'refs';
  21         38  
  21         110260  
92 700 100   700   1749 sub _namespace { ${"${_[0]}::NAMESPACE"} || '' };
  700         1725  
93 72 50   72   82 sub _prefix { ${"${_[0]}::PREFIX"} || '' };
  72         395  
94             sub mime {
95 10 100 66 10 1 317 ${ (blessed $_[0] || $_[0]) . '::MIME'} || 'application/xml'
  10         91  
96             };
97             sub _on_init {
98 3028     3028   2554 my $class = shift;
99 3028         2503 my $self = $class;
100              
101             # Run object method
102 3028 100       3344 if (blessed $class) {
103 3008         3057 $class = blessed $class;
104             }
105              
106             # Run class method
107             else {
108 20         30 $self = shift;
109             };
110              
111             # Run init hook
112 3028 100       8644 if ($class->can('ON_INIT')) {
113 530         485 *{"${class}::ON_INIT"}->($self) ;
  530         1168  
114             };
115             };
116             };
117              
118              
119             # Construct new XML::Loy object
120             sub new {
121 3005     3005 1 478699 my $class = shift;
122              
123 3005         2603 my $self;
124              
125             # Create from parent class
126             # Empty constructor
127 3005 100       5360 unless ($_[0]) {
    100          
    100          
128 2371         3644 $self = $class->SUPER::new->xml(1);
129             }
130              
131             # XML::Loy object
132 0         0 elsif (ref $_[0]) {
133 1         7 $self = $class->SUPER::new(@_)->xml(1);
134             }
135              
136             # XML string
137 0 100       0 elsif (index($_[0],'<') >= 0 || index($_[0],' ') >= 0) {
138 20         64 $self = $class->SUPER::new->xml(1)->parse(@_);
139             }
140              
141             # Create a new node
142             else {
143 613         611 my $name = shift;
144 613 100       909 my $att = ref( $_[0] ) eq 'HASH' ? shift : +{};
145 613         755 my ($text, $comment) = @_;
146              
147 613         955 $att->{'xmlns:loy'} = 'http://sojolicious.example/ns/xml-loy';
148              
149             # Transform special attributes
150 613 50       1219 _special_attributes($att) if $att;
151              
152             # Create root
153 613         1132 my $tree = [
154             'root',
155             [ pi => 'xml version="1.0" encoding="UTF-8" standalone="yes"']
156             ];
157              
158             # Add comment if given
159 613 100       966 push(@$tree, [ comment => $comment ]) if $comment;
160              
161             # Create Tag element
162 613         889 my $element = [ tag => $name, $att, $tree ];
163              
164             # Add element
165 613         754 push(@$tree, $element);
166              
167             # Add text if given
168 613 100       825 push(@$element, [ text => $text ]) if defined $text;
169              
170             # Create root element by parent class
171 613         1196 $self = $class->SUPER::new->xml(1);
172              
173             # Add newly created tree
174 613         10870 $self->tree($tree);
175              
176             # The class is derived
177 613 100       4658 if ($class ne __PACKAGE__) {
178              
179             # Set namespace if given
180 582 100       95262 if (my $ns = $class->_namespace) {
181 70         135 $att->{xmlns} = $ns;
182             };
183             };
184             };
185              
186             # Start init hook
187 3005         136497 $self->_on_init;
188              
189             # Return root node
190 3005         4448 return $self;
191             };
192              
193              
194             # Append a new child node to the XML Node
195             sub add {
196 204     204 1 4375 my $self = shift;
197              
198             # Store tag
199 204         230 my $tag = $_[0];
200              
201             # If node is root, use first element
202 204 100 66     544 if (!$self->parent &&
      66        
      66        
203             ref($self->tree->[1]) &&
204             ref($self->tree->[1]) eq 'ARRAY' &&
205             $self->tree->[1]->[0] eq 'pi') {
206 115         3240 $self = $self->at('*');
207             };
208              
209             # Add element
210 204 100       3121 my $element = $self->_add_clean(@_) or return;
211              
212 203         879 my $tree = $element->tree;
213              
214             # Prepend with no prefix
215 203 50       1098 if (index($tag, 'loy:') == 0) {
216 0         0 $tree->[1] = substr($tag, 4);
217 0         0 return $element;
218             };
219              
220 203 100       968 if (index($tag, '-') == 0) {
221 19         46 $tree->[1] = substr($tag, 1);
222 19         57 return $element;
223             };
224              
225             # Element is no tag
226 184 50       677 return $element unless $tree->[0] eq 'tag';
227              
228             # Prepend prefix if necessary
229 184         331 my $caller = caller;
230 184         614 my $class = ref $self;
231              
232             # Caller and class are not the same
233 184 100 100     829 if ($caller ne $class && $caller->can('_prefix')) {
234 27 50 33     50 if ((my $prefix = $caller->_prefix) && $caller->_namespace) {
235 27         48 $element->tree->[1] = "${prefix}:$tag";
236             };
237             };
238              
239             # Return element
240 184         916 return $element;
241             };
242              
243              
244             # Append a child only once to the XML node.
245             sub set {
246 109     109 1 3886 my $self = shift;
247              
248 109         103 my $tag;
249              
250             # If node is root, use first element
251 109 100 66     274 if (!$self->parent && $self->tree->[1]->[0] eq 'pi') {
252 49         792 $self = $self->at('*');
253             };
254              
255             # Get tag from document object
256 109 100       1702 if (ref $_[0]) {
257 37         112 $tag = $_[0]->at('*')->tag;
258             }
259              
260             # Get tag
261             else {
262              
263             # Store tag
264 72         105 $tag = shift;
265              
266             # No prefix
267 72 50       190 if (index($tag, 'loy:') == 0) {
    50          
268 0         0 $tag = substr($tag, 4);
269             }
270              
271             elsif (index($tag, '-') == 0) {
272 0         0 $tag = substr($tag, 1);
273             }
274              
275             # Maybe prefix
276             else {
277             # Prepend prefix if necessary
278 72         126 my $caller = caller;
279 72         397 my $class = ref $self;
280              
281             # Caller and class are not the same
282 72 100 100     291 if ($caller ne $class && $caller->can('_prefix')) {
283 25 50 33     63 if ((my $prefix = $caller->_prefix) && $caller->_namespace) {
284 25         49 $tag = "${prefix}:$tag";
285             };
286             };
287             };
288             };
289              
290 109         927 my $att = $self->tree->[2];
291              
292             # Introduce attribute 'once'
293 109   100     716 $att->{'loy:once'} //= '';
294              
295             # Check if set to once
296 109 100       233 if (index($att->{'loy:once'}, "($tag)") >= 0) {
297              
298             # Todo: Maybe escaping - check in extensions
299 33         110 $self->children("$tag")->map('remove');
300             }
301              
302             # Set if not already set
303             else {
304 76         117 $att->{'loy:once'} .= "($tag)";
305             };
306              
307             # Add a ref, not the tag
308 109 100       935 unshift(@_, $tag) unless blessed $_[0];
309              
310             # Add element (Maybe prefixed)
311 109         212 return $self->_add_clean(@_);
312             };
313              
314              
315             # Children of the node
316             sub children {
317 470     470 1 4400 my ($self, $type) = @_;
318              
319             # This method is a modified version of
320             # the children method of Mojo::DOM
321             # It works as written in the documentation,
322             # but is also aware of namespace prefixes.
323              
324             # If node is root, use first element
325 470 100 66     833 if (!$self->parent &&
      66        
      66        
326             ref($self->tree->[1]) &&
327             ref($self->tree->[1]) eq 'ARRAY' &&
328             $self->tree->[1]->[0] eq 'pi') {
329 36         991 $self = $self->at('*');
330             };
331              
332 470         7437 my @children;
333 470         686 my $xml = $self->xml;
334 470         2300 my $tree = $self->tree;
335 470 100       2270 my $type_l = $type ? length $type : 0;
336 470 100       1204 for my $e (@$tree[($tree->[0] eq 'root' ? 1 : 4) .. $#$tree]) {
337              
338             # Make sure child is the right type
339 2050 100       11923 next unless $e->[0] eq 'tag';
340              
341             # Type is given
342 1936 100       2065 if (defined $type) {
343              
344             # Type is already prefixed or element is not prefixed
345 788 100 100     1596 if (index($type, ':') > 0 || index($e->[1], ':') < 0) {
    50          
346 718 100       989 next if $e->[1] ne $type;
347             }
348              
349             # Check, if type is valid, and ignore prefixes, cause tag is prefixed
350             elsif (index($e->[1], ':') > 0) {
351 70 100       162 next if substr($e->[1], (index($e->[1], ':') + 1)) ne $type;
352             };
353             };
354              
355 1318         1615 push(@children, $self->new->tree($e)->xml($xml));
356             }
357              
358             # Create new Mojo::Collection
359 470         5751 return Mojo::Collection->new( @children );
360             };
361              
362              
363             # Append a new child node to the XML Node
364             sub _add_clean {
365 313     313   352 my $self = shift;
366              
367             # Node is a node object
368 313 100       434 if (ref $_[0]) {
369              
370             # Serialize node
371 45         68 my $node = $self->SUPER::new->xml(1)->tree( shift->tree );
372              
373             # Get root attributes
374 45         909 my $root_attr = $node->_root_element->[2];
375              
376             # Push namespaces to new root
377 45         178 foreach ( grep( index($_, 'xmlns:') == 0, keys %$root_attr ) ) {
378              
379             # Strip xmlns prefix
380 44         71 $_ = substr($_, 6);
381              
382             # Add namespace
383 44         142 $self->namespace( $_ => delete $root_attr->{ "xmlns:$_" } );
384             };
385              
386             # Delete namespace information, if already set
387 45 100       128 if (exists $root_attr->{xmlns}) {
388              
389             # Namespace information can be deleted
390 39 50       46 if (my $ns = $self->namespace) {
391 39 100       1152 delete $root_attr->{xmlns} if $root_attr->{xmlns} eq $ns;
392             };
393             };
394              
395             # Get root of parent node
396 45         82 my $base_root_attr = $self->_root_element->[2];
397              
398             # Copy extensions
399 45 50       73 if (exists $root_attr->{'loy:ext'}) {
400 0         0 my $ext = $base_root_attr->{'loy:ext'};
401              
402             $base_root_attr->{'loy:ext'} =
403 0         0 join('; ', $ext, split(/;\s/, delete $root_attr->{'loy:ext'}));
404             };
405              
406              
407             # Delete pi from node
408 45         64 my $sec = $node->tree->[1];
409 45 100 66     335 if (ref $sec eq 'ARRAY' && $sec->[0] eq 'pi') {
410 43         43 splice( @{ $node->tree }, 1,1 );
  43         52  
411             };
412              
413             # Append new node
414 45         249 $self->append_content($node);
415              
416             # Return first child
417 45         5748 return $self->children->[-1];
418             }
419              
420             # Node is a string
421             else {
422 268         286 my $name = shift;
423              
424             # Pretty sloppy check for valid names
425 268 100       1206 return unless $name =~ m!^-?[^\s<>]+$!;
426              
427 266 100       451 my $att = shift if ref( $_[0] ) eq 'HASH';
428 266         373 my ($text, $comment) = @_;
429              
430 266 100       458 if (index($name, '-') == 0) {
431 19         38 $name = 'loy:' . substr($name, 1);
432             };
433              
434             # Node content with text
435 266         306 my $string = "<$name";
436              
437 266 100       321 if (defined $text) {
438 159         388 $string .= '>' . b($text)->trim->xml_escape . "";
439             }
440              
441             # Empty element
442             else {
443 107         113 $string .= ' />';
444             };
445              
446             # Append new node
447 266         5980 $self->append_content( $string );
448              
449             # Get first child
450 266         42276 my $node = $self->children->[-1];
451              
452             # Attributes were given
453 266 100       1852 if ($att) {
454              
455             # Transform special attributes
456 105         176 _special_attributes($att);
457              
458             # Add attributes to node
459 105         280 $node->attr($att);
460             };
461              
462             # Add comment
463 266 100       1867 $node->comment($comment) if $comment;
464              
465 266         951 return $node;
466             };
467             };
468              
469              
470             # Transform special attributes
471             sub _special_attributes {
472 718     718   656 my $att = shift;
473              
474 718         1411 foreach ( grep { index($_, '-') == 0 } keys %$att ) {
  878         1902  
475              
476             # Set special attribute
477 43         167 $att->{'loy:' . substr($_, 1) } = lc delete $att->{$_};
478             };
479             };
480              
481              
482             # Prepend a comment to the XML node
483             sub comment {
484 23     23 1 1364 my $self = shift;
485              
486 23         30 my $parent;
487              
488             # If node is root, use first element
489 23 50       59 return $self unless $parent = $self->parent;
490              
491             # Find previous sibling
492 23         446 my $previous;
493              
494             # Find previous node
495 23         32 for my $e (@{$parent->tree}) {
  23         40  
496 152 100       293 last if $e eq $self->tree;
497 129         663 $previous = $e;
498             };
499              
500             # Trim and encode comment text
501 23         193 my $comment_text = b( shift )->trim->xml_escape;
502              
503             # Add to previous comment
504 23 100 66     911 if ($previous && $previous->[0] eq 'comment') {
505 7         23 $previous->[1] .= '; ' . $comment_text;
506             }
507              
508             # Create new comment node
509             else {
510 16         58 $self->prepend("");
511             };
512              
513             # Return node
514 23         1943 return $self;
515             };
516              
517              
518             # Add extension to document
519             sub extension {
520 167     167 1 237 my $self = shift;
521              
522             # Get root element
523 167         243 my $root = $self->_root_element;
524              
525             # No root to associate extension to
526 167 100       238 unless ($root) {
527 1         12 carp 'There is no document to associate the extension with';
528 1         383 return;
529             };
530              
531             # Get ext string
532 166   100     596 my @ext = split(/;\s/, $root->[2]->{'loy:ext'} || '');
533              
534 166 100       484 return @ext unless $_[0];
535              
536             # New Loader
537             # my $loader = Mojo::Loader->new;
538              
539             # Try all given extension names
540 19         51 while (my $ext = shift( @_ )) {
541              
542 24 100       50 next if grep { $ext eq $_ } @ext;
  14         31  
543              
544             # Default 'XML::Loy::' prefix
545 20 100       46 if (index($ext, '-') == 0) {
546 9         32 $ext =~ s/^-/XML::Loy::/;
547             };
548              
549             # Unable to load extension
550 20 50       110 if (my $e = load_class $ext) {
551 0 0       0 carp "Exception: $e" if ref $e;
552 0         0 carp qq{Unable to load extension "$ext"};
553 0         0 next;
554             };
555              
556             # Add extension to extensions list
557 20         387 push(@ext, $ext);
558              
559             # Start init hook
560 20         108 $ext->_on_init($self);
561              
562 20 50 33     90 if ((my $n_ns = $ext->_namespace) &&
563             (my $n_pref = $ext->_prefix)) {
564 20         100 $root->[2]->{"xmlns:$n_pref"} = $n_ns;
565             };
566             };
567              
568             # Save extension list as attribute
569 19         56 $root->[2]->{'loy:ext'} = join('; ', @ext);
570              
571 19         90 return $self;
572             };
573              
574              
575             # Get or add namespace to root
576             sub namespace {
577 682     682 1 1491 my $self = shift;
578              
579             # Get namespace
580 682 100       868 unless ($_[0]) {
581 102   100     215 return $self->SUPER::namespace || undef;
582             };
583              
584 580         522 my $ns = pop;
585 580         432 my $prefix = shift;
586              
587             # Get root element
588 580         665 my $root = $self->_root_element;
589              
590             # No warning, but not able to set
591 580 50       689 return unless $root;
592              
593             # Save namespace as attribute
594 580 100       1220 $root->[2]->{'xmlns' . ($prefix ? ":$prefix" : '')} = $ns;
595 580         754 return $prefix;
596             };
597              
598              
599             # As another object
600             sub as {
601 3     3 1 1692 my $self = shift;
602              
603             # Base object
604 3         5 my $base = shift;
605              
606             # Default 'XML::Loy::' prefix
607 3 100       9 if (index($base, '-') == 0) {
608 1         2 for ($base) {
609              
610             # Was Loy prefix
611 1         3 s/^-Loy$/XML::Loy/;
612 1         3 s/^-/XML::Loy::/;
613             };
614             };
615              
616             # Unable to load extension
617 3 50       13 if (my $e = load_class $base) {
618 0 0       0 carp "Exception: $e" if ref $e;
619 0         0 carp qq{Unable to load base class "$e"};
620 0         0 return;
621             };
622              
623             # Create new base document
624 3         73 my $xml = $base->new( $self->to_string );
625              
626             # Start init hook
627 3         6 $xml->_on_init;
628              
629             # Set base namespace
630 3 50       15 if ($base->_namespace) {
631 3         18 $xml->namespace( $base->_namespace );
632             };
633              
634             # Delete extension information
635             $xml->find('*[loy\:ext]')->each(
636             sub {
637 0     0   0 delete $_->{attrs}->{'loy:ext'}
638             }
639 3         13 );
640              
641             # Add extensions
642 3         686 $xml->extension( @_ );
643              
644             # Return XML document
645 3         11 return $xml;
646             };
647              
648              
649             # Render as pretty xml
650             sub to_pretty_xml {
651 76     76 1 153 my $self = shift;
652 76   100     334 return _render_pretty( shift // 0, $self->tree);
653             };
654              
655              
656             # Render subtrees with pretty printing
657             sub _render_pretty {
658 348     348   923 my $i = shift; # Indentation
659 348         337 my $tree = shift;
660              
661 348         369 my $e = $tree->[0];
662              
663             # No element
664 348 50 0     454 croak('No element') and return unless $e;
665              
666             # Element is tag
667 348 100       720 if ($e eq 'tag') {
    100          
    100          
    100          
    50          
668             my $subtree = [
669 172         285 @{ $tree }[ 0 .. 2 ],
670             [
671 172         176 @{ $tree }[ 4 .. $#$tree ]
  172         302  
672             ]
673             ];
674              
675 172         360 return _element($i, $subtree);
676             }
677              
678             # Element is text
679             elsif ($e eq 'text') {
680              
681 5         5 my $escaped = $tree->[1];
682              
683 5         5 for ($escaped) {
684 5 50       29 next unless $_;
685              
686             # Escape and trim whitespaces from both ends
687 5         10 $_ = b($_)->xml_escape->trim;
688             };
689              
690 5         141 return $escaped;
691             }
692              
693             # Element is comment
694             elsif ($e eq 'comment') {
695              
696             # Padding for every line
697 31         49 my $p = ' ' x $i;
698 31         110 my $comment = join "\n$p ", split(/;\s+/, $tree->[1]);
699              
700 31         141 return "\n" . (' ' x $i) . "\n";
701              
702             }
703              
704             # Element is processing instruction
705             elsif ($e eq 'pi') {
706 69         295 return (' ' x $i) . '[1] . "?>\n";
707             }
708              
709             # Element is root
710             elsif ($e eq 'root') {
711              
712 71         72 my $content;
713              
714             # Pretty print the content
715 71         238 $content .= _render_pretty( $i, $tree->[ $_ ] ) for 1 .. $#$tree;
716              
717 71         485 return $content;
718             };
719             };
720              
721              
722             # Render element with pretty printing
723             sub _element {
724 172     172   172 my $i = shift;
725 172         154 my ($type, $qname, $attr, $child) = @{ shift() };
  172         294  
726              
727             # Is the qname valid?
728 172 50       737 croak "$qname is no valid QName"
729             unless $qname =~ /^(?:[a-zA-Z_]+:)?[^\s]+$/;
730              
731             # Start start tag
732 172         290 my $content = (' ' x $i) . "<$qname";
733              
734             # Add attributes
735 172         382 $content .= _attr((' ' x $i). (' ' x ( length($qname) + 2)), $attr);
736              
737             # Has the element a child?
738 172 100       628 if ($child->[0]) {
739              
740             # Close start tag
741 98         108 $content .= '>';
742              
743             # There is only a textual child - no indentation
744 98 100 66     384 if (!$child->[1] && ($child->[0] && $child->[0]->[0] eq 'text')) {
    100 100        
745              
746             # Special content treatment
747 56 100       100 if (exists $attr->{'loy:type'}) {
748              
749             # With base64 indentation
750 5 100       19 if ($attr->{'loy:type'} =~ /^armour(?::(\d+))?$/i) {
751 3   50     17 my $n = $1 || 60;
752              
753 3         5 my $string = $child->[0]->[1];
754              
755             # Delete whitespace
756 3         9 $string =~ tr{\t-\x0d }{}d;
757              
758             # Introduce newlines after n characters
759 3         5 $content .= "\n" . (' ' x ($i + 1));
760 3         21 $content .= join "\n" . ( ' ' x ($i + 1) ), (unpack "(A$n)*", $string );
761 3         6 $content .= "\n" . (' ' x $i);
762             }
763              
764             # No special treatment
765             else {
766              
767             # Escape
768 2         4 $content .= b($child->[0]->[1])->trim->xml_escape;
769             };
770             }
771              
772             # No special content treatment indentation
773             else {
774              
775             # Escape
776 51         114 $content .= b($child->[0]->[1])->trim->xml_escape;
777             };
778             }
779              
780             # Treat children special
781             elsif (exists $attr->{'loy:type'}) {
782              
783             # Raw
784 3 100       8 if ($attr->{'loy:type'} eq 'raw') {
    50          
785 1         2 foreach (@$child) {
786              
787             # Create new dom object
788 2         68 my $dom = __PACKAGE__->new;
789 2         4 $dom->xml(1);
790              
791             # Print without prettifying
792 2         12 $content .= $dom->tree($_)->to_string;
793             };
794             }
795              
796             # Todo:
797             elsif ($attr->{'loy:type'} eq 'escape') {
798 2         2 $content .= "\n";
799              
800 2         3 foreach (@$child) {
801              
802             # Create new dom object
803 5         99 my $dom = __PACKAGE__->new;
804 5         10 $dom->xml(1);
805              
806             # Pretty print
807 5         29 my $string = $dom->tree($_)->to_pretty_xml($i + 1);
808              
809             # Encode
810 5         10 $content .= b($string)->xml_escape;
811             };
812              
813             # Correct Indent
814 2         43 $content .= ' ' x $i;
815             };
816             }
817              
818             # There are a couple of children
819             else {
820              
821 39         46 my $offset = 0;
822              
823             # First element is unformatted textual
824 39 100 33     180 if (!exists $attr->{'loy:type'} &&
      66        
825             $child->[0] &&
826             $child->[0]->[0] eq 'text') {
827              
828             # Append directly to the last tag
829 4         7 $content .= b($child->[0]->[1])->trim->xml_escape;
830 4         89 $offset = 1;
831             };
832              
833             # Start on a new line
834 39         56 $content .= "\n";
835              
836             # Loop through all child elements
837 39         68 foreach (@{$child}[ $offset .. $#$child ]) {
  39         63  
838              
839             # Render next element
840 121         198 $content .= _render_pretty( $i + 1, $_ );
841             };
842              
843             # Correct Indent
844 39         85 $content .= (' ' x $i);
845             };
846              
847             # End Tag
848 98         1416 $content .= "\n";
849             }
850              
851             # No child - close start element as empty tag
852             else {
853 74         103 $content .= " />\n";
854             };
855              
856             # Return content
857 172         538 return $content;
858             };
859              
860              
861             # Render attributes with pretty printing
862             sub _attr {
863 172     172   187 my $indent_space = shift;
864 172         152 my %attr = %{$_[0]};
  172         369  
865              
866             # Delete special and namespace attributes
867             my @special = grep {
868 172 100       281 $_ eq 'xmlns:loy' || index($_, 'loy:') == 0
  189         514  
869             } keys %attr;
870              
871             # Delete special attributes
872 172         299 delete $attr{$_} foreach @special;
873              
874             # Prepare attribute values
875 172         305 $_ = b($_)->xml_escape->quote foreach values %attr;
876              
877             # Return indented attribute string
878 172 100       2434 if (keys %attr) {
879             return ' ' .
880 67         178 join "\n$indent_space", map { "$_=" . $attr{$_} } sort keys %attr;
  112         403  
881             };
882              
883             # Return nothing
884 105         185 return '';
885             };
886              
887              
888             # Get root element (not as an object)
889             sub _root_element {
890 980     980   834 my $self = shift;
891              
892             # Todo: Optimize! Often called!
893              
894             # Find root (Based on Mojo::DOM::root)
895 980 50       1504 my $root = $self->tree or return;
896 980         6074 my $tag;
897              
898             # Root is root node
899 980 100       1286 if ($root->[0] eq 'root') {
900 778         702 my $i = 1;
901              
902             # Search for the first tag
903 778   100     3079 $i++ while $root->[$i] && $root->[$i]->[0] ne 'tag';
904              
905             # Tag found
906 778         833 $tag = $root->[$i];
907             }
908              
909             # Root is a tag
910             else {
911              
912             # Tag found
913 202         331 while ($root->[0] eq 'tag') {
914 377         341 $tag = $root;
915              
916 377 50       500 last unless my $parent = $root->[3];
917              
918 377         546 $root = $parent;
919             };
920             };
921              
922             # Return root element
923 980         1068 return $tag;
924             };
925              
926              
927             # Autoload for extensions
928             sub AUTOLOAD {
929 143     143   24453 my $self = shift;
930 143         258 my @param = @_;
931              
932             # Split parameter
933 143         937 my ($package, $method) = our $AUTOLOAD =~ /^([\w\:]+)\:\:(\w+)$/;
934              
935             # Choose root element
936 143         321 my $root = $self->_root_element;
937              
938             # Get extension array
939 143         283 my @ext = $self->extension;
940              
941             {
942 21     21   152 no strict 'refs';
  21         54  
  21         4859  
  143         169  
943              
944 143         207 foreach (@ext) {
945              
946             # Method does not exist in extension
947 155 100       773 next unless $_->can($method);
948              
949             # Release method
950 138         177 return *{ "${_}::$method" }->($self, @param);
  138         494  
951             };
952             };
953              
954 5         9 my $errstr = qq{Can't locate "${method}" in "$package"};
955 5 100       11 if (@ext) {
956 3 100       8 $errstr .= ' with extension' . (@ext > 1 ? 's' : '');
957 3         7 $errstr .= ' "' . join('", "', @ext) . '"';
958             };
959              
960 5 50       65 carp $errstr and return;
961             };
962              
963              
964             1;
965              
966              
967             __END__