| 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 . "$name>"; | 
| 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) . '' . $tree->[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 .= "$qname>\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__ |