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   17509 use Mojo::ByteStream 'b';
  21         2229708  
  21         1247  
3 21     21   8193 use Mojo::Loader qw/load_class/;
  21         580554  
  21         1524  
4 21     21   214 use Carp qw/croak carp/;
  21         100  
  21         1041  
5 21     21   141 use Scalar::Util qw/blessed weaken/;
  21         41  
  21         919  
6 21     21   118 use Mojo::Base 'Mojo::DOM';
  21         42  
  21         189  
7              
8             our $VERSION = '0.52';
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   1411 my $class = shift;
50              
51 58 100       2630 return unless my $flag = shift;
52              
53 31 100       281 return unless $flag =~ /^-?(?i:base|with)$/;
54              
55             # Allow for manipulating the symbol table
56 21     21   371396 no strict 'refs';
  21         48  
  21         785  
57 21     21   116 no warnings 'once';
  21         48  
  21         6301  
58              
59             # The caller is the calling (inheriting) class
60 30         89 my $caller = caller;
61 30         210 push @{"${caller}::ISA"}, __PACKAGE__;
  30         376  
62              
63 30 100       103 if (@_) {
64              
65             # Get class variables
66 29         144 my %param = @_;
67              
68             # Set class variables
69 29         76 foreach (qw/namespace prefix mime/) {
70 87 100       249 if (exists $param{$_}) {
71 72         125 ${ "${caller}::" . uc $_ } = delete $param{$_};
  72         356  
72             };
73             };
74              
75             # Set class hook
76 29 100       117 if (exists $param{on_init}) {
77 3         6 *{"${caller}::ON_INIT"} = delete $param{on_init};
  3         17  
78             };
79             };
80              
81             # Make inheriting classes strict and modern
82 30         184 strict->import;
83 30         318 warnings->import;
84 30         153 utf8->import;
85 30         35760 feature->import(':5.10');
86             };
87              
88              
89             # Return class variables
90             {
91 21     21   164 no strict 'refs';
  21         50  
  21         119300  
92 700 100   700   2199 sub _namespace { ${"${_[0]}::NAMESPACE"} || '' };
  700         2212  
93 72 50   72   98 sub _prefix { ${"${_[0]}::PREFIX"} || '' };
  72         467  
94             sub mime {
95 10 100 66 10 1 368 ${ (blessed $_[0] || $_[0]) . '::MIME'} || 'application/xml'
  10         171  
96             };
97             sub _on_init {
98 3029     3029   4277 my $class = shift;
99 3029         3518 my $self = $class;
100              
101             # Run object method
102 3029 100       7910 if (blessed $class) {
103 3009         5523 $class = blessed $class;
104             }
105              
106             # Run class method
107             else {
108 20         40 $self = shift;
109             };
110              
111             # Run init hook
112 3029 100       12263 if ($class->can('ON_INIT')) {
113 530         635 *{"${class}::ON_INIT"}->($self) ;
  530         1611  
114             };
115             };
116             };
117              
118              
119             # Construct new XML::Loy object
120             sub new {
121 3006     3006 1 272885 my $class = shift;
122              
123 3006         3495 my $self;
124              
125             # Create from parent class
126             # Empty constructor
127 3006 100       6921 unless ($_[0]) {
    100          
    100          
128 2372         4629 $self = $class->SUPER::new->xml(1);
129             }
130              
131             # XML::Loy object
132 0         0 elsif (ref $_[0]) {
133 1         22 $self = $class->SUPER::new(@_)->xml(1);
134             }
135              
136             # XML string
137 0 100       0 elsif (index($_[0],'<') >= 0 || index($_[0],' ') >= 0) {
138 20         77 $self = $class->SUPER::new->xml(1)->parse(@_);
139             }
140              
141             # Create a new node
142             else {
143 613         829 my $name = shift;
144 613 100       1447 my $att = ref( $_[0] ) eq 'HASH' ? shift : +{};
145 613         1025 my ($text, $comment) = @_;
146              
147 613         1172 $att->{'xmlns:loy'} = 'http://sojolicious.example/ns/xml-loy';
148              
149             # Transform special attributes
150 613 50       1764 _special_attributes($att) if $att;
151              
152             # Create root
153 613         1563 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       1075 push(@$tree, [ comment => $comment ]) if $comment;
160              
161             # Create Tag element
162 613         1097 my $element = [ tag => $name, $att, $tree ];
163              
164             # Add element
165 613         976 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         1470 $self = $class->SUPER::new->xml(1);
172              
173             # Add newly created tree
174 613         14562 $self->tree($tree);
175              
176             # The class is derived
177 613 100       6056 if ($class ne __PACKAGE__) {
178              
179             # Set namespace if given
180 582 100       148551 if (my $ns = $class->_namespace) {
181 70         189 $att->{xmlns} = $ns;
182             };
183             };
184             };
185              
186             # Start init hook
187 3006         212907 $self->_on_init;
188              
189             # Return root node
190 3006         6207 return $self;
191             };
192              
193              
194             # Append a new child node to the XML Node
195             sub add {
196 204     204 1 4828 my $self = shift;
197              
198             # Store tag
199 204         325 my $tag = $_[0];
200              
201             # If node is root, use first element
202 204 100 66     660 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         4380 $self = $self->at('*');
207             };
208              
209             # Add element
210 204 100       4181 my $element = $self->_add_clean(@_) or return;
211              
212 203         1181 my $tree = $element->tree;
213              
214             # Prepend with no prefix
215 203 50       1609 if (index($tag, 'loy:') == 0) {
216 0         0 $tree->[1] = substr($tag, 4);
217 0         0 return $element;
218             };
219              
220 203 100       1347 if (index($tag, '-') == 0) {
221 19         46 $tree->[1] = substr($tag, 1);
222 19         77 return $element;
223             };
224              
225             # Element is no tag
226 184 50       1032 return $element unless $tree->[0] eq 'tag';
227              
228             # Prepend prefix if necessary
229 184         382 my $caller = caller;
230 184         730 my $class = ref $self;
231              
232             # Caller and class are not the same
233 184 100 100     880 if ($caller ne $class && $caller->can('_prefix')) {
234 27 50 33     58 if ((my $prefix = $caller->_prefix) && $caller->_namespace) {
235 27         89 $element->tree->[1] = "${prefix}:$tag";
236             };
237             };
238              
239             # Return element
240 184         1195 return $element;
241             };
242              
243              
244             # Append a child only once to the XML node.
245             sub set {
246 109     109 1 4557 my $self = shift;
247              
248 109         176 my $tag;
249              
250             # If node is root, use first element
251 109 100 66     259 if (!$self->parent && $self->tree->[1]->[0] eq 'pi') {
252 49         1110 $self = $self->at('*');
253             };
254              
255             # Get tag from document object
256 109 100       2201 if (ref $_[0]) {
257 37         80 $tag = $_[0]->at('*')->tag;
258             }
259              
260             # Get tag
261             else {
262              
263             # Store tag
264 72         119 $tag = shift;
265              
266             # No prefix
267 72 50       276 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         154 my $caller = caller;
279 72         516 my $class = ref $self;
280              
281             # Caller and class are not the same
282 72 100 100     352 if ($caller ne $class && $caller->can('_prefix')) {
283 25 50 33     86 if ((my $prefix = $caller->_prefix) && $caller->_namespace) {
284 25         73 $tag = "${prefix}:$tag";
285             };
286             };
287             };
288             };
289              
290 109         1244 my $att = $self->tree->[2];
291              
292             # Introduce attribute 'once'
293 109   100     1017 $att->{'loy:once'} //= '';
294              
295             # Check if set to once
296 109 100       362 if (index($att->{'loy:once'}, "($tag)") >= 0) {
297              
298             # Todo: Maybe escaping - check in extensions
299 33         116 $self->children("$tag")->map('remove');
300             }
301              
302             # Set if not already set
303             else {
304 76         173 $att->{'loy:once'} .= "($tag)";
305             };
306              
307             # Add a ref, not the tag
308 109 100       1258 unshift(@_, $tag) unless blessed $_[0];
309              
310             # Add element (Maybe prefixed)
311 109         284 return $self->_add_clean(@_);
312             };
313              
314              
315             # Children of the node
316             sub children {
317 470     470 1 4651 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     1096 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         1331 $self = $self->at('*');
330             };
331              
332 470         9921 my @children;
333 470         894 my $xml = $self->xml;
334 470         3132 my $tree = $self->tree;
335 470 100       3234 my $type_l = $type ? length $type : 0;
336 470 100       1549 for my $e (@$tree[($tree->[0] eq 'root' ? 1 : 4) .. $#$tree]) {
337              
338             # Make sure child is the right type
339 2050 100       16863 next unless $e->[0] eq 'tag';
340              
341             # Type is given
342 1936 100       2978 if (defined $type) {
343              
344             # Type is already prefixed or element is not prefixed
345 788 100 100     2316 if (index($type, ':') > 0 || index($e->[1], ':') < 0) {
    50          
346 718 100       1361 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       203 next if substr($e->[1], (index($e->[1], ':') + 1)) ne $type;
352             };
353             };
354              
355 1318         2149 push(@children, $self->new->tree($e)->xml($xml));
356             }
357              
358             # Create new Mojo::Collection
359 470         8085 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   442 my $self = shift;
366              
367             # Node is a node object
368 313 100       587 if (ref $_[0]) {
369              
370             # Serialize node
371 45         105 my $node = $self->SUPER::new->xml(1)->tree( shift->tree );
372              
373             # Get root attributes
374 45         1384 my $root_attr = $node->_root_element->[2];
375              
376             # Push namespaces to new root
377 45         215 foreach ( grep( index($_, 'xmlns:') == 0, keys %$root_attr ) ) {
378              
379             # Strip xmlns prefix
380 44         100 $_ = substr($_, 6);
381              
382             # Add namespace
383 44         172 $self->namespace( $_ => delete $root_attr->{ "xmlns:$_" } );
384             };
385              
386             # Delete namespace information, if already set
387 45 100       116 if (exists $root_attr->{xmlns}) {
388              
389             # Namespace information can be deleted
390 39 50       68 if (my $ns = $self->namespace) {
391 39 100       1516 delete $root_attr->{xmlns} if $root_attr->{xmlns} eq $ns;
392             };
393             };
394              
395             # Get root of parent node
396 45         91 my $base_root_attr = $self->_root_element->[2];
397              
398             # Copy extensions
399 45 50       137 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         88 my $sec = $node->tree->[1];
409 45 100 66     479 if (ref $sec eq 'ARRAY' && $sec->[0] eq 'pi') {
410 43         57 splice( @{ $node->tree }, 1,1 );
  43         85  
411             };
412              
413             # Append new node
414 45         378 $self->append_content($node);
415              
416             # Return first child
417 45         5168 return $self->children->[-1];
418             }
419              
420             # Node is a string
421             else {
422 268         369 my $name = shift;
423              
424             # Pretty sloppy check for valid names
425 268 100       1371 return unless $name =~ m!^-?[^\s<>]+$!;
426              
427 266 100       649 my $att = shift if ref( $_[0] ) eq 'HASH';
428 266         508 my ($text, $comment) = @_;
429              
430 266 100       657 if (index($name, '-') == 0) {
431 19         55 $name = 'loy:' . substr($name, 1);
432             };
433              
434             # Node content with text
435 266         489 my $string = "<$name";
436              
437 266 100       445 if (defined $text) {
438 159         495 $string .= '>' . b($text)->trim->xml_escape . "";
439             }
440              
441             # Empty element
442             else {
443 107         160 $string .= ' />';
444             };
445              
446             # Append new node
447 266         7283 $self->append_content( $string );
448              
449             # Get first child
450 266         51108 my $node = $self->children->[-1];
451              
452             # Attributes were given
453 266 100       2334 if ($att) {
454              
455             # Transform special attributes
456 105         282 _special_attributes($att);
457              
458             # Add attributes to node
459 105         429 $node->attr($att);
460             };
461              
462             # Add comment
463 266 100       2512 $node->comment($comment) if $comment;
464              
465 266         1085 return $node;
466             };
467             };
468              
469              
470             # Transform special attributes
471             sub _special_attributes {
472 718     718   835 my $att = shift;
473              
474 718         1973 foreach ( grep { index($_, '-') == 0 } keys %$att ) {
  878         2784  
475              
476             # Set special attribute
477 43         241 $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 1471 my $self = shift;
485              
486 23         39 my $parent;
487              
488             # If node is root, use first element
489 23 50       63 return $self unless $parent = $self->parent;
490              
491             # Find previous sibling
492 23         509 my $previous;
493              
494             # Find previous node
495 23         74 for my $e (@{$parent->tree}) {
  23         56  
496 152 100       402 last if $e eq $self->tree;
497 129         951 $previous = $e;
498             };
499              
500             # Trim and encode comment text
501 23         231 my $comment_text = b( shift )->trim->xml_escape;
502              
503             # Add to previous comment
504 23 100 66     979 if ($previous && $previous->[0] eq 'comment') {
505 7         23 $previous->[1] .= '; ' . $comment_text;
506             }
507              
508             # Create new comment node
509             else {
510 16         59 $self->prepend("");
511             };
512              
513             # Return node
514 23         2365 return $self;
515             };
516              
517              
518             # Add extension to document
519             sub extension {
520 167     167 1 278 my $self = shift;
521              
522             # Get root element
523 167         276 my $root = $self->_root_element;
524              
525             # No root to associate extension to
526 167 100       333 unless ($root) {
527 1         11 carp 'There is no document to associate the extension with';
528 1         506 return;
529             };
530              
531             # Get ext string
532 166   100     723 my @ext = split(/;\s/, $root->[2]->{'loy:ext'} || '');
533              
534 166 100       575 return @ext unless $_[0];
535              
536             # New Loader
537             # my $loader = Mojo::Loader->new;
538              
539             # Try all given extension names
540 19         80 while (my $ext = shift( @_ )) {
541              
542 24 100       64 next if grep { $ext eq $_ } @ext;
  14         39  
543              
544             # Default 'XML::Loy::' prefix
545 20 100       88 if (index($ext, '-') == 0) {
546 9         49 $ext =~ s/^-/XML::Loy::/;
547             };
548              
549             # Unable to load extension
550 20 50       83 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         395 push(@ext, $ext);
558              
559             # Start init hook
560 20         115 $ext->_on_init($self);
561              
562 20 50 33     97 if ((my $n_ns = $ext->_namespace) &&
563             (my $n_pref = $ext->_prefix)) {
564 20         156 $root->[2]->{"xmlns:$n_pref"} = $n_ns;
565             };
566             };
567              
568             # Save extension list as attribute
569 19         73 $root->[2]->{'loy:ext'} = join('; ', @ext);
570              
571 19         107 return $self;
572             };
573              
574              
575             # Get or add namespace to root
576             sub namespace {
577 682     682 1 2135 my $self = shift;
578              
579             # Get namespace
580 682 100       1255 unless ($_[0]) {
581 102   100     284 return $self->SUPER::namespace || undef;
582             };
583              
584 580         692 my $ns = pop;
585 580         634 my $prefix = shift;
586              
587             # Get root element
588 580         936 my $root = $self->_root_element;
589              
590             # No warning, but not able to set
591 580 50       963 return unless $root;
592              
593             # Save namespace as attribute
594 580 100       1994 $root->[2]->{'xmlns' . ($prefix ? ":$prefix" : '')} = $ns;
595 580         965 return $prefix;
596             };
597              
598              
599             # As another object
600             sub as {
601 3     3 1 1900 my $self = shift;
602              
603             # Base object
604 3         5 my $base = shift;
605              
606             # Default 'XML::Loy::' prefix
607 3 100       13 if (index($base, '-') == 0) {
608 1         4 for ($base) {
609              
610             # Was Loy prefix
611 1         3 s/^-Loy$/XML::Loy/;
612 1         7 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         94 my $xml = $base->new( $self->to_string );
625              
626             # Start init hook
627 3         8 $xml->_on_init;
628              
629             # Set base namespace
630 3 50       16 if ($base->_namespace) {
631 3         43 $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         23 );
640              
641             # Add extensions
642 3         1148 $xml->extension( @_ );
643              
644             # Return XML document
645 3         13 return $xml;
646             };
647              
648              
649             # Render as pretty xml
650             sub to_pretty_xml {
651 76     76 1 198 my $self = shift;
652 76   100     401 return _render_pretty( shift // 0, $self->tree);
653             };
654              
655              
656             # Render subtrees with pretty printing
657             sub _render_pretty {
658 348     348   1159 my $i = shift; # Indentation
659 348         406 my $tree = shift;
660              
661 348         487 my $e = $tree->[0];
662              
663             # No element
664 348 50 0     592 croak('No element') and return unless $e;
665              
666             # Element is tag
667 348 100       952 if ($e eq 'tag') {
    100          
    100          
    100          
    50          
668             my $subtree = [
669 172         394 @{ $tree }[ 0 .. 2 ],
670             [
671 172         241 @{ $tree }[ 4 .. $#$tree ]
  172         388  
672             ]
673             ];
674              
675 172         446 return _element($i, $subtree);
676             }
677              
678             # Element is text
679             elsif ($e eq 'text') {
680              
681 5         10 my $escaped = $tree->[1];
682              
683 5         10 for ($escaped) {
684 5 50       13 next unless $_;
685              
686             # Escape and trim whitespaces from both ends
687 5         29 $_ = b($_)->xml_escape->trim;
688             };
689              
690 5         252 return $escaped;
691             }
692              
693             # Element is comment
694             elsif ($e eq 'comment') {
695              
696             # Padding for every line
697 31         85 my $p = ' ' x $i;
698 31         157 my $comment = join "\n$p ", split(/;\s+/, $tree->[1]);
699              
700 31         148 return "\n" . (' ' x $i) . "\n";
701              
702             }
703              
704             # Element is processing instruction
705             elsif ($e eq 'pi') {
706 69         372 return (' ' x $i) . '[1] . "?>\n";
707             }
708              
709             # Element is root
710             elsif ($e eq 'root') {
711              
712 71         98 my $content;
713              
714             # Pretty print the content
715 71         288 $content .= _render_pretty( $i, $tree->[ $_ ] ) for 1 .. $#$tree;
716              
717 71         811 return $content;
718             };
719             };
720              
721              
722             # Render element with pretty printing
723             sub _element {
724 172     172   223 my $i = shift;
725 172         204 my ($type, $qname, $attr, $child) = @{ shift() };
  172         337  
726              
727             # Is the qname valid?
728 172 50       899 croak "$qname is no valid QName"
729             unless $qname =~ /^(?:[a-zA-Z_]+:)?[^\s]+$/;
730              
731             # Start start tag
732 172         484 my $content = (' ' x $i) . "<$qname";
733              
734             # Add attributes
735 172         489 $content .= _attr((' ' x $i). (' ' x ( length($qname) + 2)), $attr);
736              
737             # Has the element a child?
738 172 100       902 if ($child->[0]) {
739              
740             # Close start tag
741 98         152 $content .= '>';
742              
743             # There is only a textual child - no indentation
744 98 100 66     453 if (!$child->[1] && ($child->[0] && $child->[0]->[0] eq 'text')) {
    100 100        
745              
746             # Special content treatment
747 56 100       116 if (exists $attr->{'loy:type'}) {
748              
749             # With base64 indentation
750 5 100       22 if ($attr->{'loy:type'} =~ /^armour(?::(\d+))?$/i) {
751 3   50     16 my $n = $1 || 60;
752              
753 3         6 my $string = $child->[0]->[1];
754              
755             # Delete whitespace
756 3         13 $string =~ tr{\t-\x0d }{}d;
757              
758             # Introduce newlines after n characters
759 3         10 $content .= "\n" . (' ' x ($i + 1));
760 3         55 $content .= join "\n" . ( ' ' x ($i + 1) ), (unpack "(A$n)*", $string );
761 3         15 $content .= "\n" . (' ' x $i);
762             }
763              
764             # No special treatment
765             else {
766              
767             # Escape
768 2         8 $content .= b($child->[0]->[1])->trim->xml_escape;
769             };
770             }
771              
772             # No special content treatment indentation
773             else {
774              
775             # Escape
776 51         141 $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       11 if ($attr->{'loy:type'} eq 'raw') {
    50          
785 1         4 foreach (@$child) {
786              
787             # Create new dom object
788 2         118 my $dom = __PACKAGE__->new;
789 2         7 $dom->xml(1);
790              
791             # Print without prettifying
792 2         20 $content .= $dom->tree($_)->to_string;
793             };
794             }
795              
796             # Todo:
797             elsif ($attr->{'loy:type'} eq 'escape') {
798 2         4 $content .= "\n";
799              
800 2         23 foreach (@$child) {
801              
802             # Create new dom object
803 5         142 my $dom = __PACKAGE__->new;
804 5         13 $dom->xml(1);
805              
806             # Pretty print
807 5         47 my $string = $dom->tree($_)->to_pretty_xml($i + 1);
808              
809             # Encode
810 5         16 $content .= b($string)->xml_escape;
811             };
812              
813             # Correct Indent
814 2         67 $content .= ' ' x $i;
815             };
816             }
817              
818             # There are a couple of children
819             else {
820              
821 39         63 my $offset = 0;
822              
823             # First element is unformatted textual
824 39 100 33     304 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         16 $content .= b($child->[0]->[1])->trim->xml_escape;
830 4         125 $offset = 1;
831             };
832              
833             # Start on a new line
834 39         59 $content .= "\n";
835              
836             # Loop through all child elements
837 39         71 foreach (@{$child}[ $offset .. $#$child ]) {
  39         74  
838              
839             # Render next element
840 121         288 $content .= _render_pretty( $i + 1, $_ );
841             };
842              
843             # Correct Indent
844 39         88 $content .= (' ' x $i);
845             };
846              
847             # End Tag
848 98         1932 $content .= "\n";
849             }
850              
851             # No child - close start element as empty tag
852             else {
853 74         113 $content .= " />\n";
854             };
855              
856             # Return content
857 172         604 return $content;
858             };
859              
860              
861             # Render attributes with pretty printing
862             sub _attr {
863 172     172   244 my $indent_space = shift;
864 172         202 my %attr = %{$_[0]};
  172         492  
865              
866             # Delete special and namespace attributes
867             my @special = grep {
868 172 100       375 $_ eq 'xmlns:loy' || index($_, 'loy:') == 0
  189         753  
869             } keys %attr;
870              
871             # Delete special attributes
872 172         408 delete $attr{$_} foreach @special;
873              
874             # Prepare attribute values
875 172         406 $_ = b($_)->xml_escape->quote foreach values %attr;
876              
877             # Return indented attribute string
878 172 100       3319 if (keys %attr) {
879             return ' ' .
880 67         252 join "\n$indent_space", map { "$_=" . $attr{$_} } sort keys %attr;
  112         499  
881             };
882              
883             # Return nothing
884 105         266 return '';
885             };
886              
887              
888             # Get root element (not as an object)
889             sub _root_element {
890 980     980   1129 my $self = shift;
891              
892             # Todo: Optimize! Often called!
893              
894             # Find root (Based on Mojo::DOM::root)
895 980 50       2090 my $root = $self->tree or return;
896 980         8290 my $tag;
897              
898             # Root is root node
899 980 100       1691 if ($root->[0] eq 'root') {
900 778         862 my $i = 1;
901              
902             # Search for the first tag
903 778   100     3871 $i++ while $root->[$i] && $root->[$i]->[0] ne 'tag';
904              
905             # Tag found
906 778         1051 $tag = $root->[$i];
907             }
908              
909             # Root is a tag
910             else {
911              
912             # Tag found
913 202         403 while ($root->[0] eq 'tag') {
914 377         418 $tag = $root;
915              
916 377 50       640 last unless my $parent = $root->[3];
917              
918 377         679 $root = $parent;
919             };
920             };
921              
922             # Return root element
923 980         1376 return $tag;
924             };
925              
926              
927             # Autoload for extensions
928             sub AUTOLOAD {
929 143     143   26350 my $self = shift;
930 143         291 my @param = @_;
931              
932             # Split parameter
933 143         957 my ($package, $method) = our $AUTOLOAD =~ /^([\w\:]+)\:\:(\w+)$/;
934              
935             # Choose root element
936 143         357 my $root = $self->_root_element;
937              
938             # Get extension array
939 143         323 my @ext = $self->extension;
940              
941             {
942 21     21   191 no strict 'refs';
  21         59  
  21         5083  
  143         209  
943              
944 143         272 foreach (@ext) {
945              
946             # Method does not exist in extension
947 155 100       761 next unless $_->can($method);
948              
949             # Release method
950 138         200 return *{ "${_}::$method" }->($self, @param);
  138         605  
951             };
952             };
953              
954 5         20 my $errstr = qq{Can't locate "${method}" in "$package"};
955 5 100       13 if (@ext) {
956 3 100       26 $errstr .= ' with extension' . (@ext > 1 ? 's' : '');
957 3         14 $errstr .= ' "' . join('", "', @ext) . '"';
958             };
959              
960 5 50       69 carp $errstr and return;
961             };
962              
963              
964             1;
965              
966              
967             __END__