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