File Coverage

blib/lib/XML/Loy.pm
Criterion Covered Total %
statement 330 342 96.4
branch 163 190 85.7
condition 52 75 69.3
subroutine 30 31 96.7
pod 10 10 100.0
total 585 648 90.2


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