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   18103 use Mojo::ByteStream 'b';
  21         2256890  
  21         1307  
3 21     21   9486 use Mojo::Loader qw/load_class/;
  21         586133  
  21         1471  
4 21     21   257 use Carp qw/croak carp/;
  21         87  
  21         1150  
5 21     21   141 use Scalar::Util qw/blessed weaken/;
  21         44  
  21         999  
6 21     21   120 use Mojo::Base 'Mojo::DOM';
  21         42  
  21         164  
7              
8             our $VERSION = '0.50';
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   1343 my $class = shift;
50              
51 58 100       2497 return unless my $flag = shift;
52              
53 31 100       280 return unless $flag =~ /^-?(?i:base|with)$/;
54              
55             # Allow for manipulating the symbol table
56 21     21   371802 no strict 'refs';
  21         55  
  21         799  
57 21     21   118 no warnings 'once';
  21         47  
  21         6649  
58              
59             # The caller is the calling (inheriting) class
60 30         93 my $caller = caller;
61 30         221 push @{"${caller}::ISA"}, __PACKAGE__;
  30         436  
62              
63 30 100       108 if (@_) {
64              
65             # Get class variables
66 29         135 my %param = @_;
67              
68             # Set class variables
69 29         73 foreach (qw/namespace prefix mime/) {
70 87 100       206 if (exists $param{$_}) {
71 72         128 ${ "${caller}::" . uc $_ } = delete $param{$_};
  72         361  
72             };
73             };
74              
75             # Set class hook
76 29 100       110 if (exists $param{on_init}) {
77 3         9 *{"${caller}::ON_INIT"} = delete $param{on_init};
  3         15  
78             };
79             };
80              
81             # Make inheriting classes strict and modern
82 30         156 strict->import;
83 30         341 warnings->import;
84 30         155 utf8->import;
85 30         35182 feature->import(':5.10');
86             };
87              
88              
89             # Return class variables
90             {
91 21     21   170 no strict 'refs';
  21         47  
  21         117015  
92 700 100   700   2274 sub _namespace { ${"${_[0]}::NAMESPACE"} || '' };
  700         2337  
93 72 50   72   96 sub _prefix { ${"${_[0]}::PREFIX"} || '' };
  72         461  
94             sub mime {
95 10 100 66 10 1 384 ${ (blessed $_[0] || $_[0]) . '::MIME'} || 'application/xml'
  10         151  
96             };
97             sub _on_init {
98 3027     3027   4114 my $class = shift;
99 3027         3549 my $self = $class;
100              
101             # Run object method
102 3027 100       8036 if (blessed $class) {
103 3007         5176 $class = blessed $class;
104             }
105              
106             # Run class method
107             else {
108 20         35 $self = shift;
109             };
110              
111             # Run init hook
112 3027 100       12509 if ($class->can('ON_INIT')) {
113 530         725 *{"${class}::ON_INIT"}->($self) ;
  530         1630  
114             };
115             };
116             };
117              
118              
119             # Construct new XML::Loy object
120             sub new {
121 3004     3004 1 265752 my $class = shift;
122              
123 3004         3341 my $self;
124              
125             # Create from parent class
126             # Empty constructor
127 3004 100       7083 unless ($_[0]) {
    100          
    100          
128 2370         4514 $self = $class->SUPER::new->xml(1);
129             }
130              
131             # XML::Loy object
132 0         0 elsif (ref $_[0]) {
133 1         33 $self = $class->SUPER::new(@_)->xml(1);
134             }
135              
136             # XML string
137 0 100       0 elsif (index($_[0],'<') >= 0 || index($_[0],' ') >= 0) {
138 20         88 $self = $class->SUPER::new->xml(1)->parse(@_);
139             }
140              
141             # Create a new node
142             else {
143 613         837 my $name = shift;
144 613 100       1406 my $att = ref( $_[0] ) eq 'HASH' ? shift : +{};
145 613         1043 my ($text, $comment) = @_;
146              
147 613         1270 $att->{'xmlns:loy'} = 'http://sojolicious.example/ns/xml-loy';
148              
149             # Transform special attributes
150 613 50       1806 _special_attributes($att) if $att;
151              
152             # Create root
153 613         1572 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       1098 push(@$tree, [ comment => $comment ]) if $comment;
160              
161             # Create Tag element
162 613         1127 my $element = [ tag => $name, $att, $tree ];
163              
164             # Add element
165 613         1018 push(@$tree, $element);
166              
167             # Add text if given
168 613 100       1094 push(@$element, [ text => $text ]) if defined $text;
169              
170             # Create root element by parent class
171 613         1556 $self = $class->SUPER::new->xml(1);
172              
173             # Add newly created tree
174 613         14845 $self->tree($tree);
175              
176             # The class is derived
177 613 100       6290 if ($class ne __PACKAGE__) {
178              
179             # Set namespace if given
180 582 100       145882 if (my $ns = $class->_namespace) {
181 70         167 $att->{xmlns} = $ns;
182             };
183             };
184             };
185              
186             # Start init hook
187 3004         208924 $self->_on_init;
188              
189             # Return root node
190 3004         6284 return $self;
191             };
192              
193              
194             # Append a new child node to the XML Node
195             sub add {
196 204     204 1 3886 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     665 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         4541 $self = $self->at('*');
207             };
208              
209             # Add element
210 204 100       4195 my $element = $self->_add_clean(@_) or return;
211              
212 203         1282 my $tree = $element->tree;
213              
214             # Prepend with no prefix
215 203 100       1630 if (index($tag, '-') == 0) {
216 19         52 $tree->[1] = substr($tag, 1);
217 19         70 return $element;
218             };
219              
220             # Element is no tag
221 184 50       1289 return $element unless $tree->[0] eq 'tag';
222              
223             # Prepend prefix if necessary
224 184         375 my $caller = caller;
225 184         744 my $class = ref $self;
226              
227             # Caller and class are not the same
228 184 100 100     947 if ($caller ne $class && $caller->can('_prefix')) {
229 27 50 33     63 if ((my $prefix = $caller->_prefix) && $caller->_namespace) {
230 27         105 $element->tree->[1] = "${prefix}:$tag";
231             };
232             };
233              
234             # Return element
235 184         1222 return $element;
236             };
237              
238              
239             # Append a child only once to the XML node.
240             sub set {
241 109     109 1 4143 my $self = shift;
242              
243 109         128 my $tag;
244              
245             # If node is root, use first element
246 109 100 66     282 if (!$self->parent && $self->tree->[1]->[0] eq 'pi') {
247 49         1068 $self = $self->at('*');
248             };
249              
250             # Get tag from document object
251 109 100       2171 if (ref $_[0]) {
252 37         88 $tag = $_[0]->at('*')->tag;
253             }
254              
255             # Get tag
256             else {
257              
258             # Store tag
259 72         125 $tag = shift;
260              
261             # No prefix
262 72 50       190 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         167 my $caller = caller;
270 72         505 my $class = ref $self;
271              
272             # Caller and class are not the same
273 72 100 100     415 if ($caller ne $class && $caller->can('_prefix')) {
274 25 50 33     99 if ((my $prefix = $caller->_prefix) && $caller->_namespace) {
275 25         70 $tag = "${prefix}:$tag";
276             };
277             };
278             };
279             };
280              
281 109         1238 my $att = $self->tree->[2];
282              
283             # Introduce attribute 'once'
284 109   100     990 $att->{'loy:once'} //= '';
285              
286             # Check if set to once
287 109 100       367 if (index($att->{'loy:once'}, "($tag)") >= 0) {
288              
289             # Todo: Maybe escaping - check in extensions
290 33         102 $self->children("$tag")->map('remove');
291             }
292              
293             # Set if not already set
294             else {
295 76         189 $att->{'loy:once'} .= "($tag)";
296             };
297              
298             # Add a ref, not the tag
299 109 100       1233 unshift(@_, $tag) unless blessed $_[0];
300              
301             # Add element (Maybe prefixed)
302 109         306 return $self->_add_clean(@_);
303             };
304              
305              
306             # Children of the node
307             sub children {
308 470     470 1 6520 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     1079 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         1325 $self = $self->at('*');
321             };
322              
323 470         9825 my @children;
324 470         862 my $xml = $self->xml;
325 470         3064 my $tree = $self->tree;
326 470 100       3126 my $type_l = $type ? length $type : 0;
327 470 100       1590 for my $e (@$tree[($tree->[0] eq 'root' ? 1 : 4) .. $#$tree]) {
328              
329             # Make sure child is the right type
330 2050 100       16783 next unless $e->[0] eq 'tag';
331              
332             # Type is given
333 1936 100       2810 if (defined $type) {
334              
335             # Type is already prefixed or element is not prefixed
336 788 100 100     2253 if (index($type, ':') > 0 || index($e->[1], ':') < 0) {
    50          
337 718 100       1332 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       216 next if substr($e->[1], (index($e->[1], ':') + 1)) ne $type;
343             };
344             };
345              
346 1318         2156 push(@children, $self->new->tree($e)->xml($xml));
347             }
348              
349             # Create new Mojo::Collection
350 470         8202 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   472 my $self = shift;
357              
358             # Node is a node object
359 313 100       584 if (ref $_[0]) {
360              
361             # Serialize node
362 45         96 my $node = $self->SUPER::new->xml(1)->tree( shift->tree );
363              
364             # Get root attributes
365 45         1528 my $root_attr = $node->_root_element->[2];
366              
367             # Push namespaces to new root
368 45         263 foreach ( grep( index($_, 'xmlns:') == 0, keys %$root_attr ) ) {
369              
370             # Strip xmlns prefix
371 44         114 $_ = substr($_, 6);
372              
373             # Add namespace
374 44         204 $self->namespace( $_ => delete $root_attr->{ "xmlns:$_" } );
375             };
376              
377             # Delete namespace information, if already set
378 45 100       114 if (exists $root_attr->{xmlns}) {
379              
380             # Namespace information can be deleted
381 39 50       57 if (my $ns = $self->namespace) {
382 39 100       1534 delete $root_attr->{xmlns} if $root_attr->{xmlns} eq $ns;
383             };
384             };
385              
386             # Get root of parent node
387 45         99 my $base_root_attr = $self->_root_element->[2];
388              
389             # Copy extensions
390 45 50       121 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         101 my $sec = $node->tree->[1];
400 45 100 66     438 if (ref $sec eq 'ARRAY' && $sec->[0] eq 'pi') {
401 43         64 splice( @{ $node->tree }, 1,1 );
  43         88  
402             };
403              
404             # Append new node
405 45         365 $self->append_content($node);
406              
407             # Return first child
408 45         5354 return $self->children->[-1];
409             }
410              
411             # Node is a string
412             else {
413 268         391 my $name = shift;
414              
415             # Pretty sloppy check for valid names
416 268 100       1373 return unless $name =~ m!^-?[^\s<>]+$!;
417              
418 266 100       678 my $att = shift if ref( $_[0] ) eq 'HASH';
419 266         458 my ($text, $comment) = @_;
420              
421             # Node content with text
422 266         494 my $string = "<$name";
423              
424 266 100       459 if (defined $text) {
425 159         472 $string .= '>' . b($text)->trim->xml_escape . "";
426             }
427              
428             # Empty element
429             else {
430 107         160 $string .= ' />';
431             };
432              
433             # Append new node
434 266         7453 $self->append_content( $string );
435              
436             # Get first child
437 266         50704 my $node = $self->children->[-1];
438              
439             # Attributes were given
440 266 100       2384 if ($att) {
441              
442             # Transform special attributes
443 105         271 _special_attributes($att);
444              
445             # Add attributes to node
446 105         390 $node->attr($att);
447             };
448              
449             # Add comment
450 266 100       2594 $node->comment($comment) if $comment;
451              
452 266         1082 return $node;
453             };
454             };
455              
456              
457             # Transform special attributes
458             sub _special_attributes {
459 718     718   890 my $att = shift;
460              
461 718         2044 foreach ( grep { index($_, '-') == 0 } keys %$att ) {
  878         2719  
462              
463             # Set special attribute
464 43         209 $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 1410 my $self = shift;
472              
473 23         37 my $parent;
474              
475             # If node is root, use first element
476 23 50       65 return $self unless $parent = $self->parent;
477              
478             # Find previous sibling
479 23         538 my $previous;
480              
481             # Find previous node
482 23         36 for my $e (@{$parent->tree}) {
  23         63  
483 152 100       418 last if $e eq $self->tree;
484 129         957 $previous = $e;
485             };
486              
487             # Trim and encode comment text
488 23         218 my $comment_text = b( shift )->trim->xml_escape;
489              
490             # Add to previous comment
491 23 100 66     986 if ($previous && $previous->[0] eq 'comment') {
492 7         24 $previous->[1] .= '; ' . $comment_text;
493             }
494              
495             # Create new comment node
496             else {
497 16         76 $self->prepend("");
498             };
499              
500             # Return node
501 23         2425 return $self;
502             };
503              
504              
505             # Add extension to document
506             sub extension {
507 167     167 1 312 my $self = shift;
508              
509             # Get root element
510 167         311 my $root = $self->_root_element;
511              
512             # No root to associate extension to
513 167 100       309 unless ($root) {
514 1         19 carp 'There is no document to associate the extension with';
515 1         588 return;
516             };
517              
518             # Get ext string
519 166   100     802 my @ext = split(/;\s/, $root->[2]->{'loy:ext'} || '');
520              
521 166 100       579 return @ext unless $_[0];
522              
523             # New Loader
524             # my $loader = Mojo::Loader->new;
525              
526             # Try all given extension names
527 19         109 while (my $ext = shift( @_ )) {
528              
529 24 100       71 next if grep { $ext eq $_ } @ext;
  14         47  
530              
531             # Default 'XML::Loy::' prefix
532 20 100       91 if (index($ext, '-') == 0) {
533 9         56 $ext =~ s/^-/XML::Loy::/;
534             };
535              
536             # Unable to load extension
537 20 50       91 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         417 push(@ext, $ext);
545              
546             # Start init hook
547 20         118 $ext->_on_init($self);
548              
549 20 50 33     99 if ((my $n_ns = $ext->_namespace) &&
550             (my $n_pref = $ext->_prefix)) {
551 20         117 $root->[2]->{"xmlns:$n_pref"} = $n_ns;
552             };
553             };
554              
555             # Save extension list as attribute
556 19         72 $root->[2]->{'loy:ext'} = join('; ', @ext);
557              
558 19         88 return $self;
559             };
560              
561              
562             # Get or add namespace to root
563             sub namespace {
564 682     682 1 1992 my $self = shift;
565              
566             # Get namespace
567 682 100       1139 unless ($_[0]) {
568 102   100     344 return $self->SUPER::namespace || undef;
569             };
570              
571 580         659 my $ns = pop;
572 580         592 my $prefix = shift;
573              
574             # Get root element
575 580         983 my $root = $self->_root_element;
576              
577             # No warning, but not able to set
578 580 50       907 return unless $root;
579              
580             # Save namespace as attribute
581 580 100       1588 $root->[2]->{'xmlns' . ($prefix ? ":$prefix" : '')} = $ns;
582 580         955 return $prefix;
583             };
584              
585              
586             # As another object
587             sub as {
588 3     3 1 1266 my $self = shift;
589              
590             # Base object
591 3         5 my $base = shift;
592              
593             # Default 'XML::Loy::' prefix
594 3 100       11 if (index($base, '-') == 0) {
595 1         4 for ($base) {
596              
597             # Was Loy prefix
598 1         4 s/^-Loy$/XML::Loy/;
599 1         6 s/^-/XML::Loy::/;
600             };
601             };
602              
603             # Unable to load extension
604 3 50       21 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         92 my $xml = $base->new( $self->to_string );
612              
613             # Start init hook
614 3         10 $xml->_on_init;
615              
616             # Set base namespace
617 3 50       13 if ($base->_namespace) {
618 3         7 $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         1187 $xml->extension( @_ );
630              
631             # Return XML document
632 3         13 return $xml;
633             };
634              
635              
636             # Render as pretty xml
637             sub to_pretty_xml {
638 76     76 1 236 my $self = shift;
639 76   100     444 return _render_pretty( shift // 0, $self->tree);
640             };
641              
642              
643             # Render subtrees with pretty printing
644             sub _render_pretty {
645 348     348   1212 my $i = shift; # Indentation
646 348         415 my $tree = shift;
647              
648 348         501 my $e = $tree->[0];
649              
650             # No element
651 348 50 0     584 croak('No element') and return unless $e;
652              
653             # Element is tag
654 348 100       972 if ($e eq 'tag') {
    100          
    100          
    100          
    50          
655             my $subtree = [
656 172         370 @{ $tree }[ 0 .. 2 ],
657             [
658 172         249 @{ $tree }[ 4 .. $#$tree ]
  172         419  
659             ]
660             ];
661              
662 172         433 return _element($i, $subtree);
663             }
664              
665             # Element is text
666             elsif ($e eq 'text') {
667              
668 5         11 my $escaped = $tree->[1];
669              
670 5         10 for ($escaped) {
671 5 50       14 next unless $_;
672              
673             # Escape and trim whitespaces from both ends
674 5         16 $_ = b($_)->xml_escape->trim;
675             };
676              
677 5         229 return $escaped;
678             }
679              
680             # Element is comment
681             elsif ($e eq 'comment') {
682              
683             # Padding for every line
684 31         63 my $p = ' ' x $i;
685 31         148 my $comment = join "\n$p ", split(/;\s+/, $tree->[1]);
686              
687 31         135 return "\n" . (' ' x $i) . "\n";
688              
689             }
690              
691             # Element is processing instruction
692             elsif ($e eq 'pi') {
693 69         372 return (' ' x $i) . '[1] . "?>\n";
694             }
695              
696             # Element is root
697             elsif ($e eq 'root') {
698              
699 71         95 my $content;
700              
701             # Pretty print the content
702 71         320 $content .= _render_pretty( $i, $tree->[ $_ ] ) for 1 .. $#$tree;
703              
704 71         672 return $content;
705             };
706             };
707              
708              
709             # Render element with pretty printing
710             sub _element {
711 172     172   245 my $i = shift;
712 172         209 my ($type, $qname, $attr, $child) = @{ shift() };
  172         349  
713              
714             # Is the qname valid?
715 172 50       999 croak "$qname is no valid QName"
716             unless $qname =~ /^(?:[a-zA-Z_]+:)?[^\s]+$/;
717              
718             # Start start tag
719 172         435 my $content = (' ' x $i) . "<$qname";
720              
721             # Add attributes
722 172         520 $content .= _attr((' ' x $i). (' ' x ( length($qname) + 2)), $attr);
723              
724             # Has the element a child?
725 172 100       872 if ($child->[0]) {
726              
727             # Close start tag
728 98         147 $content .= '>';
729              
730             # There is only a textual child - no indentation
731 98 100 66     466 if (!$child->[1] && ($child->[0] && $child->[0]->[0] eq 'text')) {
    100 100        
732              
733             # Special content treatment
734 56 100       119 if (exists $attr->{'loy:type'}) {
735              
736             # With base64 indentation
737 5 100       37 if ($attr->{'loy:type'} =~ /^armour(?::(\d+))?$/i) {
738 3   50     17 my $n = $1 || 60;
739              
740 3         8 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         28 $content .= join "\n" . ( ' ' x ($i + 1) ), (unpack "(A$n)*", $string );
748 3         14 $content .= "\n" . (' ' x $i);
749             }
750              
751             # No special treatment
752             else {
753              
754             # Escape
755 2         8 $content .= b($child->[0]->[1])->trim->xml_escape;
756             };
757             }
758              
759             # No special content treatment indentation
760             else {
761              
762             # Escape
763 51         137 $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       9 if ($attr->{'loy:type'} eq 'raw') {
    50          
772 1         2 foreach (@$child) {
773              
774             # Create new dom object
775 2         101 my $dom = __PACKAGE__->new;
776 2         7 $dom->xml(1);
777              
778             # Print without prettifying
779 2         19 $content .= $dom->tree($_)->to_string;
780             };
781             }
782              
783             # Todo:
784             elsif ($attr->{'loy:type'} eq 'escape') {
785 2         3 $content .= "\n";
786              
787 2         3 foreach (@$child) {
788              
789             # Create new dom object
790 5         116 my $dom = __PACKAGE__->new;
791 5         36 $dom->xml(1);
792              
793             # Pretty print
794 5         64 my $string = $dom->tree($_)->to_pretty_xml($i + 1);
795              
796             # Encode
797 5         12 $content .= b($string)->xml_escape;
798             };
799              
800             # Correct Indent
801 2         65 $content .= ' ' x $i;
802             };
803             }
804              
805             # There are a couple of children
806             else {
807              
808 39         69 my $offset = 0;
809              
810             # First element is unformatted textual
811 39 100 33     232 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         12 $content .= b($child->[0]->[1])->trim->xml_escape;
817 4         131 $offset = 1;
818             };
819              
820             # Start on a new line
821 39         69 $content .= "\n";
822              
823             # Loop through all child elements
824 39         74 foreach (@{$child}[ $offset .. $#$child ]) {
  39         92  
825              
826             # Render next element
827 121         304 $content .= _render_pretty( $i + 1, $_ );
828             };
829              
830             # Correct Indent
831 39         89 $content .= (' ' x $i);
832             };
833              
834             # End Tag
835 98         2009 $content .= "\n";
836             }
837              
838             # No child - close start element as empty tag
839             else {
840 74         127 $content .= " />\n";
841             };
842              
843             # Return content
844 172         627 return $content;
845             };
846              
847              
848             # Render attributes with pretty printing
849             sub _attr {
850 172     172   245 my $indent_space = shift;
851 172         420 my %attr = %{$_[0]};
  172         535  
852              
853             # Delete special and namespace attributes
854             my @special = grep {
855 172 100       363 $_ eq 'xmlns:loy' || index($_, 'loy:') == 0
  189         753  
856             } keys %attr;
857              
858             # Delete special attributes
859 172         414 delete $attr{$_} foreach @special;
860              
861             # Prepare attribute values
862 172         401 $_ = b($_)->xml_escape->quote foreach values %attr;
863              
864             # Return indented attribute string
865 172 100       3350 if (keys %attr) {
866             return ' ' .
867 67         232 join "\n$indent_space", map { "$_=" . $attr{$_} } sort keys %attr;
  112         487  
868             };
869              
870             # Return nothing
871 105         255 return '';
872             };
873              
874              
875             # Get root element (not as an object)
876             sub _root_element {
877 980     980   1169 my $self = shift;
878              
879             # Todo: Optimize! Often called!
880              
881             # Find root (Based on Mojo::DOM::root)
882 980 50       2013 my $root = $self->tree or return;
883 980         8362 my $tag;
884              
885             # Root is root node
886 980 100       1802 if ($root->[0] eq 'root') {
887 778         919 my $i = 1;
888              
889             # Search for the first tag
890 778   100     3911 $i++ while $root->[$i] && $root->[$i]->[0] ne 'tag';
891              
892             # Tag found
893 778         1060 $tag = $root->[$i];
894             }
895              
896             # Root is a tag
897             else {
898              
899             # Tag found
900 202         392 while ($root->[0] eq 'tag') {
901 377         430 $tag = $root;
902              
903 377 50       607 last unless my $parent = $root->[3];
904              
905 377         667 $root = $parent;
906             };
907             };
908              
909             # Return root element
910 980         1330 return $tag;
911             };
912              
913              
914             # Autoload for extensions
915             sub AUTOLOAD {
916 143     143   25397 my $self = shift;
917 143         297 my @param = @_;
918              
919             # Split parameter
920 143         979 my ($package, $method) = our $AUTOLOAD =~ /^([\w\:]+)\:\:(\w+)$/;
921              
922             # Choose root element
923 143         390 my $root = $self->_root_element;
924              
925             # Get extension array
926 143         332 my @ext = $self->extension;
927              
928             {
929 21     21   253 no strict 'refs';
  21         47  
  21         5203  
  143         200  
930              
931 143         301 foreach (@ext) {
932              
933             # Method does not exist in extension
934 155 100       788 next unless $_->can($method);
935              
936             # Release method
937 138         211 return *{ "${_}::$method" }->($self, @param);
  138         596  
938             };
939             };
940              
941 5         20 my $errstr = qq{Can't locate "${method}" in "$package"};
942 5 100       27 if (@ext) {
943 3 100       17 $errstr .= ' with extension' . (@ext > 1 ? 's' : '');
944 3         14 $errstr .= ' "' . join('", "', @ext) . '"';
945             };
946              
947 5 50       66 carp $errstr and return;
948             };
949              
950              
951             1;
952              
953              
954             __END__