| blib/lib/XML/Bare.pm | |||
|---|---|---|---|
| Criterion | Covered | Total | % |
| statement | 162 | 540 | 30.0 |
| branch | 68 | 294 | 23.1 |
| condition | 12 | 65 | 18.4 |
| subroutine | 19 | 38 | 50.0 |
| pod | 29 | 29 | 100.0 |
| total | 290 | 966 | 30.0 |
| line | stmt | bran | cond | sub | pod | time | code |
|---|---|---|---|---|---|---|---|
| 1 | package XML::Bare; | ||||||
| 2 | |||||||
| 3 | # ABSTRACT: Minimal XML parser implemented via a C state engine | ||||||
| 4 | |||||||
| 5 | |||||||
| 6 | 4 | 4 | 2986 | use 5.008; | |||
| 4 | 12 | ||||||
| 4 | 146 | ||||||
| 7 | 4 | 4 | 18 | use Carp; | |||
| 4 | 6 | ||||||
| 4 | 275 | ||||||
| 8 | 4 | 4 | 17 | use strict; | |||
| 4 | 6 | ||||||
| 4 | 163 | ||||||
| 9 | 4 | 4 | 13 | use vars qw( @ISA @EXPORT @EXPORT_OK $VERSION ); | |||
| 4 | 6 | ||||||
| 4 | 255 | ||||||
| 10 | 4 | 4 | 1713 | use utf8; | |||
| 4 | 25 | ||||||
| 4 | 17 | ||||||
| 11 | require Exporter; | ||||||
| 12 | require DynaLoader; | ||||||
| 13 | @ISA = qw(Exporter DynaLoader); | ||||||
| 14 | |||||||
| 15 | our $VERSION = '0.46_03'; # VERSION | ||||||
| 16 | our $AUTHORITY = 'cpan:NIGELM'; # AUTHORITY | ||||||
| 17 | |||||||
| 18 | 4 | 4 | 275 | use vars qw($VERSION *AUTOLOAD); | |||
| 4 | 7 | ||||||
| 4 | 13038 | ||||||
| 19 | |||||||
| 20 | *AUTOLOAD = \&XML::Bare::AUTOLOAD; | ||||||
| 21 | bootstrap XML::Bare $VERSION; | ||||||
| 22 | |||||||
| 23 | @EXPORT = qw( ); | ||||||
| 24 | @EXPORT_OK = qw( xget merge clean add_node del_node find_node del_node forcearray del_by_perl xmlin xval ); | ||||||
| 25 | |||||||
| 26 | sub new { | ||||||
| 27 | 38 | 38 | 1 | 2566 | my $class = shift; | ||
| 28 | 38 | 76 | my $self = {@_}; | ||||
| 29 | |||||||
| 30 | 38 | 100 | 68 | if ( $self->{'text'} ) { | |||
| 31 | 37 | 184 | XML::Bare::c_parse( $self->{'text'} ); | ||||
| 32 | 37 | 67 | $self->{'structroot'} = XML::Bare::get_root(); | ||||
| 33 | } | ||||||
| 34 | else { | ||||||
| 35 | 1 | 66 | my $res = open( my $XML, '<', $self->{'file'} ); | ||||
| 36 | 1 | 50 | 5 | if ( !$res ) { | |||
| 37 | 0 | 0 | $self->{'xml'} = 0; | ||||
| 38 | 0 | 0 | return 0; | ||||
| 39 | } | ||||||
| 40 | { | ||||||
| 41 | 1 | 1 | local $/ = undef; | ||||
| 1 | 5 | ||||||
| 42 | 1 | 21 | $self->{'text'} = <$XML>; | ||||
| 43 | } | ||||||
| 44 | 1 | 9 | close($XML); | ||||
| 45 | 1 | 6 | XML::Bare::c_parse( $self->{'text'} ); | ||||
| 46 | 1 | 6 | $self->{'structroot'} = XML::Bare::get_root(); | ||||
| 47 | } | ||||||
| 48 | 38 | 66 | bless $self, $class; | ||||
| 49 | 38 | 100 | 84 | return $self if ( !wantarray ); | |||
| 50 | 27 | 45 | return ( $self, $self->parse() ); | ||||
| 51 | } | ||||||
| 52 | |||||||
| 53 | sub DESTROY { | ||||||
| 54 | 37 | 37 | 14019 | my $self = shift; | |||
| 55 | 37 | 51 | $self->free_tree(); | ||||
| 56 | 37 | 245 | undef $self->{'xml'}; | ||||
| 57 | } | ||||||
| 58 | |||||||
| 59 | sub xget { | ||||||
| 60 | 0 | 0 | 1 | 0 | my $hash = shift; | ||
| 61 | 0 | 0 | return map $_->{'value'}, @{%$hash}{@_}; | ||||
| 0 | 0 | ||||||
| 62 | } | ||||||
| 63 | |||||||
| 64 | sub forcearray { | ||||||
| 65 | 0 | 0 | 1 | 0 | my $ref = shift; | ||
| 66 | 0 | 0 | 0 | return [] if ( !$ref ); | |||
| 67 | 0 | 0 | 0 | return $ref if ( ref($ref) eq 'ARRAY' ); | |||
| 68 | 0 | 0 | return [$ref]; | ||||
| 69 | } | ||||||
| 70 | |||||||
| 71 | sub merge { | ||||||
| 72 | |||||||
| 73 | # shift in the two array references as well as the field to merge on | ||||||
| 74 | 0 | 0 | 1 | 0 | my ( $a, $b, $id ) = @_; | ||
| 75 | 0 | 0 | 0 | my %hash = map { $_->{$id} ? ( $_->{$id}->{'value'} => $_ ) : ( 0 => 0 ) } @$a; | |||
| 0 | 0 | ||||||
| 76 | 0 | 0 | for my $one (@$b) { | ||||
| 77 | 0 | 0 | 0 | next if ( !$one->{$id} ); | |||
| 78 | 0 | 0 | my $short = $hash{ $one->{$id}->{'value'} }; | ||||
| 79 | 0 | 0 | 0 | next if ( !$short ); | |||
| 80 | 0 | 0 | foreach my $key ( keys %$one ) { | ||||
| 81 | 0 | 0 | 0 | 0 | next if ( $key eq '_pos' || $key eq 'id' ); | ||
| 82 | 0 | 0 | my $cur = $short->{$key}; | ||||
| 83 | 0 | 0 | my $add = $one->{$key}; | ||||
| 84 | 0 | 0 | 0 | if ( !$cur ) { $short->{$key} = $add; } | |||
| 0 | 0 | ||||||
| 85 | else { | ||||||
| 86 | 0 | 0 | my $type = ref($cur); | ||||
| 87 | 0 | 0 | 0 | if ( $type eq 'HASH' ) { | |||
| 88 | 0 | 0 | my @arr; | ||||
| 89 | 0 | 0 | $short->{$key} = \@arr; | ||||
| 90 | 0 | 0 | push( @arr, $cur ); | ||||
| 91 | } | ||||||
| 92 | 0 | 0 | 0 | if ( ref($add) eq 'HASH' ) { | |||
| 93 | 0 | 0 | push( @{ $short->{$key} }, $add ); | ||||
| 0 | 0 | ||||||
| 94 | } | ||||||
| 95 | else { # we are merging an array | ||||||
| 96 | 0 | 0 | push( @{ $short->{$key} }, @$add ); | ||||
| 0 | 0 | ||||||
| 97 | } | ||||||
| 98 | } | ||||||
| 99 | |||||||
| 100 | # we need to deal with the case where this node | ||||||
| 101 | # is already there, either alone or as an array | ||||||
| 102 | } | ||||||
| 103 | } | ||||||
| 104 | 0 | 0 | return $a; | ||||
| 105 | } | ||||||
| 106 | |||||||
| 107 | sub clean { | ||||||
| 108 | 0 | 0 | 1 | 0 | my $ob = new XML::Bare(@_); | ||
| 109 | 0 | 0 | my $root = $ob->parse(); | ||||
| 110 | 0 | 0 | 0 | if ( $ob->{'save'} ) { | |||
| 111 | 0 | 0 | 0 | $ob->{'file'} = $ob->{'save'} if ( "$ob->{'save'}" ne "1" ); | |||
| 112 | 0 | 0 | $ob->save(); | ||||
| 113 | 0 | 0 | return; | ||||
| 114 | } | ||||||
| 115 | 0 | 0 | return $ob->xml($root); | ||||
| 116 | } | ||||||
| 117 | |||||||
| 118 | sub xmlin { | ||||||
| 119 | 9 | 9 | 1 | 1677 | my $text = shift; | ||
| 120 | 9 | 12 | my %ops = (@_); | ||||
| 121 | 9 | 18 | my $ob = new XML::Bare( text => $text ); | ||||
| 122 | 9 | 21 | my $simple = $ob->simple(); | ||||
| 123 | 9 | 50 | 22 | if ( !$ops{'keeproot'} ) { | |||
| 124 | 9 | 24 | my @keys = keys %$simple; | ||||
| 125 | 9 | 13 | my $first = $keys[0]; | ||||
| 126 | 9 | 50 | 22 | $simple = $simple->{$first} if ($first); | |||
| 127 | } | ||||||
| 128 | 9 | 23 | return $simple; | ||||
| 129 | } | ||||||
| 130 | |||||||
| 131 | sub tohtml { | ||||||
| 132 | 0 | 0 | 1 | 0 | my %ops = (@_); | ||
| 133 | 0 | 0 | my $ob = new XML::Bare(%ops); | ||||
| 134 | 0 | 0 | 0 | return $ob->html( $ob->parse(), $ops{'root'} || 'xml' ); | |||
| 135 | } | ||||||
| 136 | |||||||
| 137 | # Load a file using XML::DOM, convert it to a hash, and return the hash | ||||||
| 138 | sub parse { | ||||||
| 139 | 29 | 29 | 1 | 30 | my $self = shift; | ||
| 140 | |||||||
| 141 | 29 | 213 | my $res = XML::Bare::xml2obj(); | ||||
| 142 | 29 | 69 | $self->{'structroot'} = XML::Bare::get_root(); | ||||
| 143 | 29 | 51 | $self->free_tree(); | ||||
| 144 | |||||||
| 145 | 29 | 50 | 64 | if ( defined( $self->{'scheme'} ) ) { | |||
| 146 | 0 | 0 | $self->{'xbs'} = new XML::Bare( %{ $self->{'scheme'} } ); | ||||
| 0 | 0 | ||||||
| 147 | } | ||||||
| 148 | 29 | 50 | 56 | if ( defined( $self->{'xbs'} ) ) { | |||
| 149 | 0 | 0 | my $xbs = $self->{'xbs'}; | ||||
| 150 | 0 | 0 | my $ob = $xbs->parse(); | ||||
| 151 | 0 | 0 | $self->{'xbso'} = $ob; | ||||
| 152 | 0 | 0 | readxbs($ob); | ||||
| 153 | } | ||||||
| 154 | |||||||
| 155 | 29 | 50 | 51 | if ( $res < 0 ) { croak "Error at " . $self->lineinfo( -$res ); } | |||
| 0 | 0 | ||||||
| 156 | 29 | 40 | $self->{'xml'} = $res; | ||||
| 157 | |||||||
| 158 | 29 | 50 | 47 | if ( defined( $self->{'xbso'} ) ) { | |||
| 159 | 0 | 0 | my $ob = $self->{'xbso'}; | ||||
| 160 | 0 | 0 | my $cres = $self->check( $res, $ob ); | ||||
| 161 | 0 | 0 | 0 | croak($cres) if ($cres); | |||
| 162 | } | ||||||
| 163 | |||||||
| 164 | 29 | 72 | return $self->{'xml'}; | ||||
| 165 | } | ||||||
| 166 | |||||||
| 167 | sub lineinfo { | ||||||
| 168 | 0 | 0 | 1 | 0 | my $self = shift; | ||
| 169 | 0 | 0 | my $res = shift; | ||||
| 170 | 0 | 0 | my $line = 1; | ||||
| 171 | 0 | 0 | my $j = 0; | ||||
| 172 | 0 | 0 | for ( my $i = 0; $i < $res; $i++ ) { | ||||
| 173 | 0 | 0 | my $let = substr( $self->{'text'}, $i, 1 ); | ||||
| 174 | 0 | 0 | 0 | if ( ord($let) == 10 ) { | |||
| 175 | 0 | 0 | $line++; | ||||
| 176 | 0 | 0 | $j = $i; | ||||
| 177 | } | ||||||
| 178 | } | ||||||
| 179 | 0 | 0 | my $part = substr( $self->{'text'}, $res, 10 ); | ||||
| 180 | 0 | 0 | $part =~ s/\n//g; | ||||
| 181 | 0 | 0 | $res -= $j; | ||||
| 182 | 0 | 0 | 0 | if ( $self->{'offset'} ) { | |||
| 183 | 0 | 0 | my $off = $self->{'offset'}; | ||||
| 184 | 0 | 0 | $line += $off; | ||||
| 185 | 0 | 0 | return "$off line $line char $res \"$part\""; | ||||
| 186 | } | ||||||
| 187 | 0 | 0 | return "line $line char $res \"$part\""; | ||||
| 188 | } | ||||||
| 189 | |||||||
| 190 | # xml bare schema | ||||||
| 191 | sub check { | ||||||
| 192 | 0 | 0 | 1 | 0 | my ( $self, $node, $scheme, $parent ) = @_; | ||
| 193 | |||||||
| 194 | 0 | 0 | my $fail = ''; | ||||
| 195 | 0 | 0 | 0 | if ( ref($scheme) eq 'ARRAY' ) { | |||
| 196 | 0 | 0 | for my $one (@$scheme) { | ||||
| 197 | 0 | 0 | my $res = $self->checkone( $node, $one, $parent ); | ||||
| 198 | 0 | 0 | 0 | return 0 if ( !$res ); | |||
| 199 | 0 | 0 | $fail .= "$res\n"; | ||||
| 200 | } | ||||||
| 201 | } | ||||||
| 202 | 0 | 0 | else { return $self->checkone( $node, $scheme, $parent ); } | ||||
| 203 | 0 | 0 | return $fail; | ||||
| 204 | } | ||||||
| 205 | |||||||
| 206 | sub checkone { | ||||||
| 207 | 0 | 0 | 1 | 0 | my ( $self, $node, $scheme, $parent ) = @_; | ||
| 208 | |||||||
| 209 | 0 | 0 | for my $key ( keys %$node ) { | ||||
| 210 | 0 | 0 | 0 | 0 | next if ( substr( $key, 0, 1 ) eq '_' || $key eq '_att' || $key eq 'comment' ); | ||
| 0 | |||||||
| 211 | 0 | 0 | 0 | if ( $key eq 'value' ) { | |||
| 212 | 0 | 0 | my $val = $node->{'value'}; | ||||
| 213 | 0 | 0 | my $regexp = $scheme->{'value'}; | ||||
| 214 | 0 | 0 | 0 | if ($regexp) { | |||
| 215 | 0 | 0 | 0 | if ( $val !~ m/^($regexp)$/ ) { | |||
| 216 | 0 | 0 | my $linfo = $self->lineinfo( $node->{'_i'} ); | ||||
| 217 | 0 | 0 | return "Value of '$parent' node ($val) does not match /$regexp/ [$linfo]"; | ||||
| 218 | } | ||||||
| 219 | } | ||||||
| 220 | 0 | 0 | next; | ||||
| 221 | } | ||||||
| 222 | 0 | 0 | my $sub = $node->{$key}; | ||||
| 223 | 0 | 0 | my $ssub = $scheme->{$key}; | ||||
| 224 | 0 | 0 | 0 | if ( !$ssub ) { #&& ref( $schemesub ) ne 'HASH' | |||
| 225 | 0 | 0 | my $linfo = $self->lineinfo( $sub->{'_i'} ); | ||||
| 226 | 0 | 0 | return "Invalid node '$key' in xml [$linfo]"; | ||||
| 227 | } | ||||||
| 228 | 0 | 0 | 0 | if ( ref($sub) eq 'HASH' ) { | |||
| 229 | 0 | 0 | my $res = $self->check( $sub, $ssub, $key ); | ||||
| 230 | 0 | 0 | 0 | return $res if ($res); | |||
| 231 | } | ||||||
| 232 | 0 | 0 | 0 | if ( ref($sub) eq 'ARRAY' ) { | |||
| 233 | 0 | 0 | my $asub = $ssub; | ||||
| 234 | 0 | 0 | 0 | if ( ref($asub) eq 'ARRAY' ) { | |||
| 235 | 0 | 0 | $asub = $asub->[0]; | ||||
| 236 | } | ||||||
| 237 | 0 | 0 | 0 | if ( $asub->{'_t'} ) { | |||
| 238 | 0 | 0 | 0 | my $max = $asub->{'_max'} || 0; | |||
| 239 | 0 | 0 | 0 | if ( $#$sub >= $max ) { | |||
| 240 | 0 | 0 | my $linfo = $self->lineinfo( $sub->[0]->{'_i'} ); | ||||
| 241 | 0 | 0 | return "Too many nodes of type '$key'; max $max; [$linfo]"; | ||||
| 242 | } | ||||||
| 243 | 0 | 0 | 0 | my $min = $asub->{'_min'} || 0; | |||
| 244 | 0 | 0 | 0 | if ( ( $#$sub + 1 ) < $min ) { | |||
| 245 | 0 | 0 | my $linfo = $self->lineinfo( $sub->[0]->{'_i'} ); | ||||
| 246 | 0 | 0 | return "Not enough nodes of type '$key'; min $min [$linfo]"; | ||||
| 247 | } | ||||||
| 248 | } | ||||||
| 249 | 0 | 0 | for (@$sub) { | ||||
| 250 | 0 | 0 | my $res = $self->check( $_, $ssub, $key ); | ||||
| 251 | 0 | 0 | 0 | return $res if ($res); | |||
| 252 | } | ||||||
| 253 | } | ||||||
| 254 | } | ||||||
| 255 | 0 | 0 | 0 | if ( my $dem = $scheme->{'_demand'} ) { | |||
| 256 | 0 | 0 | for my $req ( @{ $scheme->{'_demand'} } ) { | ||||
| 0 | 0 | ||||||
| 257 | 0 | 0 | my $ck = $node->{$req}; | ||||
| 258 | 0 | 0 | 0 | if ( !$ck ) { | |||
| 259 | 0 | 0 | my $linfo = $self->lineinfo( $node->{'_i'} ); | ||||
| 260 | 0 | 0 | return "Required node '$req' does not exist [$linfo]"; | ||||
| 261 | } | ||||||
| 262 | 0 | 0 | 0 | if ( ref($ck) eq 'ARRAY' ) { | |||
| 263 | 0 | 0 | my $linfo = $self->lineinfo( $node->{'_i'} ); | ||||
| 264 | 0 | 0 | 0 | return "Required node '$req' is empty array [$linfo]" if ( $#$ck == -1 ); | |||
| 265 | } | ||||||
| 266 | } | ||||||
| 267 | } | ||||||
| 268 | 0 | 0 | return 0; | ||||
| 269 | } | ||||||
| 270 | |||||||
| 271 | sub readxbs { # xbs = xml bare schema | ||||||
| 272 | 0 | 0 | 1 | 0 | my $node = shift; | ||
| 273 | 0 | 0 | my @demand; | ||||
| 274 | 0 | 0 | for my $key ( keys %$node ) { | ||||
| 275 | 0 | 0 | 0 | 0 | next if ( substr( $key, 0, 1 ) eq '_' || $key eq '_att' || $key eq 'comment' ); | ||
| 0 | |||||||
| 276 | 0 | 0 | 0 | if ( $key eq 'value' ) { | |||
| 277 | 0 | 0 | my $val = $node->{'value'}; | ||||
| 278 | 0 | 0 | 0 | delete $node->{'value'} if ( $val =~ m/^\W*$/ ); | |||
| 279 | 0 | 0 | next; | ||||
| 280 | } | ||||||
| 281 | 0 | 0 | my $sub = $node->{$key}; | ||||
| 282 | |||||||
| 283 | 0 | 0 | 0 | if ( $key =~ m/([a-z_]+)([^a-z_]+)/ ) { | |||
| 284 | 0 | 0 | my $name = $1; | ||||
| 285 | 0 | 0 | my $t = $2; | ||||
| 286 | 0 | 0 | my $min; | ||||
| 287 | my $max; | ||||||
| 288 | 0 | 0 | 0 | if ( $t eq '+' ) { | |||
| 0 | |||||||
| 0 | |||||||
| 0 | |||||||
| 0 | |||||||
| 289 | 0 | 0 | $min = 1; | ||||
| 290 | 0 | 0 | $max = 1000; | ||||
| 291 | } | ||||||
| 292 | elsif ( $t eq '*' ) { | ||||||
| 293 | 0 | 0 | $min = 0; | ||||
| 294 | 0 | 0 | $max = 1000; | ||||
| 295 | } | ||||||
| 296 | elsif ( $t eq '?' ) { | ||||||
| 297 | 0 | 0 | $min = 0; | ||||
| 298 | 0 | 0 | $max = 1; | ||||
| 299 | } | ||||||
| 300 | elsif ( $t eq '@' ) { | ||||||
| 301 | 0 | 0 | $name = 'multi_' . $name; | ||||
| 302 | 0 | 0 | $min = 1; | ||||
| 303 | 0 | 0 | $max = 1; | ||||
| 304 | } | ||||||
| 305 | elsif ( $t =~ m/\{([0-9]+),([0-9]+)\}/ ) { | ||||||
| 306 | 0 | 0 | $min = $1; | ||||
| 307 | 0 | 0 | $max = $2; | ||||
| 308 | 0 | 0 | $t = 'r'; # range | ||||
| 309 | } | ||||||
| 310 | |||||||
| 311 | 0 | 0 | my $res; | ||||
| 312 | 0 | 0 | 0 | if ( ref($sub) eq 'HASH' ) { | |||
| 313 | 0 | 0 | $res = readxbs($sub); | ||||
| 314 | 0 | 0 | $sub->{'_t'} = $t; | ||||
| 315 | 0 | 0 | $sub->{'_min'} = $min; | ||||
| 316 | 0 | 0 | $sub->{'_max'} = $max; | ||||
| 317 | } | ||||||
| 318 | 0 | 0 | 0 | if ( ref($sub) eq 'ARRAY' ) { | |||
| 319 | 0 | 0 | for my $item (@$sub) { | ||||
| 320 | 0 | 0 | $res = readxbs($item); | ||||
| 321 | 0 | 0 | $item->{'_t'} = $t; | ||||
| 322 | 0 | 0 | $item->{'_min'} = $min; | ||||
| 323 | 0 | 0 | $item->{'_max'} = $max; | ||||
| 324 | } | ||||||
| 325 | } | ||||||
| 326 | |||||||
| 327 | 0 | 0 | 0 | push( @demand, $name ) if ($min); | |||
| 328 | 0 | 0 | $node->{$name} = $node->{$key}; | ||||
| 329 | 0 | 0 | delete $node->{$key}; | ||||
| 330 | } | ||||||
| 331 | else { | ||||||
| 332 | 0 | 0 | 0 | if ( ref($sub) eq 'HASH' ) { | |||
| 333 | 0 | 0 | readxbs($sub); | ||||
| 334 | 0 | 0 | $sub->{'_t'} = 'r'; | ||||
| 335 | 0 | 0 | $sub->{'_min'} = 1; | ||||
| 336 | 0 | 0 | $sub->{'_max'} = 1; | ||||
| 337 | } | ||||||
| 338 | 0 | 0 | 0 | if ( ref($sub) eq 'ARRAY' ) { | |||
| 339 | 0 | 0 | for my $item (@$sub) { | ||||
| 340 | 0 | 0 | readxbs($item); | ||||
| 341 | 0 | 0 | $item->{'_t'} = 'r'; | ||||
| 342 | 0 | 0 | $item->{'_min'} = 1; | ||||
| 343 | 0 | 0 | $item->{'_max'} = 1; | ||||
| 344 | } | ||||||
| 345 | } | ||||||
| 346 | |||||||
| 347 | 0 | 0 | push( @demand, $key ); | ||||
| 348 | } | ||||||
| 349 | } | ||||||
| 350 | 0 | 0 | 0 | if (@demand) { $node->{'_demand'} = \@demand; } | |||
| 0 | 0 | ||||||
| 351 | } | ||||||
| 352 | |||||||
| 353 | sub simple { | ||||||
| 354 | 9 | 9 | 1 | 10 | my $self = shift; | ||
| 355 | |||||||
| 356 | 9 | 48 | my $res = XML::Bare::xml2obj_simple(); | ||||
| 357 | 9 | 19 | $self->{'structroot'} = XML::Bare::get_root(); | ||||
| 358 | 9 | 14 | $self->free_tree(); | ||||
| 359 | |||||||
| 360 | 9 | 11 | return $res; | ||||
| 361 | } | ||||||
| 362 | |||||||
| 363 | sub add_node { | ||||||
| 364 | 1 | 1 | 1 | 2 | my ( $self, $node, $name ) = @_; | ||
| 365 | 1 | 2 | my @newar; | ||||
| 366 | my %blank; | ||||||
| 367 | 1 | 50 | 5 | $node->{ 'multi_' . $name } = \%blank if ( !$node->{ 'multi_' . $name } ); | |||
| 368 | 1 | 50 | 4 | $node->{$name} = \@newar if ( !$node->{$name} ); | |||
| 369 | 1 | 5 | my $newnode = new_node( 0, splice( @_, 3 ) ); | ||||
| 370 | 1 | 2 | push( @{ $node->{$name} }, $newnode ); | ||||
| 1 | 2 | ||||||
| 371 | 1 | 3 | return $newnode; | ||||
| 372 | } | ||||||
| 373 | |||||||
| 374 | sub add_node_after { | ||||||
| 375 | 0 | 0 | 1 | 0 | my ( $self, $node, $prev, $name ) = @_; | ||
| 376 | 0 | 0 | my @newar; | ||||
| 377 | my %blank; | ||||||
| 378 | 0 | 0 | 0 | $node->{ 'multi_' . $name } = \%blank if ( !$node->{ 'multi_' . $name } ); | |||
| 379 | 0 | 0 | 0 | $node->{$name} = \@newar if ( !$node->{$name} ); | |||
| 380 | 0 | 0 | my $newnode = $self->new_node( splice( @_, 4 ) ); | ||||
| 381 | |||||||
| 382 | 0 | 0 | my $cur = 0; | ||||
| 383 | 0 | 0 | for my $anode ( @{ $node->{$name} } ) { | ||||
| 0 | 0 | ||||||
| 384 | 0 | 0 | 0 | $anode->{'_pos'} = $cur if ( !$anode->{'_pos'} ); | |||
| 385 | 0 | 0 | $cur++; | ||||
| 386 | } | ||||||
| 387 | 0 | 0 | my $opos = $prev->{'_pos'}; | ||||
| 388 | 0 | 0 | for my $anode ( @{ $node->{$name} } ) { | ||||
| 0 | 0 | ||||||
| 389 | 0 | 0 | 0 | $anode->{'_pos'}++ if ( $anode->{'_pos'} > $opos ); | |||
| 390 | } | ||||||
| 391 | 0 | 0 | $newnode->{'_pos'} = $opos + 1; | ||||
| 392 | |||||||
| 393 | 0 | 0 | push( @{ $node->{$name} }, $newnode ); | ||||
| 0 | 0 | ||||||
| 394 | |||||||
| 395 | 0 | 0 | return $newnode; | ||||
| 396 | } | ||||||
| 397 | |||||||
| 398 | sub find_by_perl { | ||||||
| 399 | 0 | 0 | 1 | 0 | my $arr = shift; | ||
| 400 | 0 | 0 | my $cond = shift; | ||||
| 401 | 0 | 0 | $cond =~ s/-([a-z]+)/\$ob->\{'$1'\}->\{'value'\}/g; | ||||
| 402 | 0 | 0 | my @res; | ||||
| 403 | ## no critic | ||||||
| 404 | 0 | 0 | 0 | foreach my $ob (@$arr) { push( @res, $ob ) if ( eval($cond) ); } | |||
| 0 | 0 | ||||||
| 405 | ## use critic | ||||||
| 406 | 0 | 0 | return \@res; | ||||
| 407 | } | ||||||
| 408 | |||||||
| 409 | sub find_node { | ||||||
| 410 | 0 | 0 | 1 | 0 | my $self = shift; | ||
| 411 | 0 | 0 | my $node = shift; | ||||
| 412 | 0 | 0 | my $name = shift; | ||||
| 413 | 0 | 0 | my %match = @_; | ||||
| 414 | |||||||
| 415 | #croak "Cannot search empty node for $name" if( !$node ); | ||||||
| 416 | #$node = $node->{ $name } or croak "Cannot find $name"; | ||||||
| 417 | 0 | 0 | 0 | $node = $node->{$name} or return 0; | |||
| 418 | 0 | 0 | 0 | return 0 if ( !$node ); | |||
| 419 | 0 | 0 | 0 | if ( ref($node) eq 'HASH' ) { | |||
| 420 | 0 | 0 | foreach my $key ( keys %match ) { | ||||
| 421 | 0 | 0 | my $val = $match{$key}; | ||||
| 422 | 0 | 0 | 0 | next if ( !$val ); | |||
| 423 | 0 | 0 | 0 | if ( $node->{$key}->{'value'} eq $val ) { | |||
| 424 | 0 | 0 | return $node; | ||||
| 425 | } | ||||||
| 426 | } | ||||||
| 427 | } | ||||||
| 428 | 0 | 0 | 0 | if ( ref($node) eq 'ARRAY' ) { | |||
| 429 | 0 | 0 | for ( my $i = 0; $i <= $#$node; $i++ ) { | ||||
| 430 | 0 | 0 | my $one = $node->[$i]; | ||||
| 431 | 0 | 0 | foreach my $key ( keys %match ) { | ||||
| 432 | 0 | 0 | my $val = $match{$key}; | ||||
| 433 | 0 | 0 | 0 | croak('undefined value in find') unless defined $val; | |||
| 434 | 0 | 0 | 0 | if ( $one->{$key}->{'value'} eq $val ) { | |||
| 435 | 0 | 0 | return $node->[$i]; | ||||
| 436 | } | ||||||
| 437 | } | ||||||
| 438 | } | ||||||
| 439 | } | ||||||
| 440 | 0 | 0 | return 0; | ||||
| 441 | } | ||||||
| 442 | |||||||
| 443 | sub del_node { | ||||||
| 444 | 0 | 0 | 1 | 0 | my $self = shift; | ||
| 445 | 0 | 0 | my $node = shift; | ||||
| 446 | 0 | 0 | my $name = shift; | ||||
| 447 | 0 | 0 | my %match = @_; | ||||
| 448 | 0 | 0 | $node = $node->{$name}; | ||||
| 449 | 0 | 0 | 0 | return if ( !$node ); | |||
| 450 | 0 | 0 | for ( my $i = 0; $i <= $#$node; $i++ ) { | ||||
| 451 | 0 | 0 | my $one = $node->[$i]; | ||||
| 452 | 0 | 0 | foreach my $key ( keys %match ) { | ||||
| 453 | 0 | 0 | my $val = $match{$key}; | ||||
| 454 | 0 | 0 | 0 | if ( $one->{$key}->{'value'} eq $val ) { | |||
| 455 | 0 | 0 | delete $node->[$i]; | ||||
| 456 | } | ||||||
| 457 | } | ||||||
| 458 | } | ||||||
| 459 | } | ||||||
| 460 | |||||||
| 461 | sub del_by_perl { | ||||||
| 462 | 0 | 0 | 1 | 0 | my $arr = shift; | ||
| 463 | 0 | 0 | my $cond = shift; | ||||
| 464 | 0 | 0 | $cond =~ s/-value/\$ob->\{'value'\}/g; | ||||
| 465 | 0 | 0 | $cond =~ s/-([a-z]+)/\$ob->\{'$1'\}->\{'value'\}/g; | ||||
| 466 | 0 | 0 | my @res; | ||||
| 467 | 0 | 0 | for ( my $i = 0; $i <= $#$arr; $i++ ) { | ||||
| 468 | 0 | 0 | my $ob = $arr->[$i]; | ||||
| 469 | ## no critic | ||||||
| 470 | 0 | 0 | 0 | delete $arr->[$i] if ( eval($cond) ); | |||
| 471 | ## use critic | ||||||
| 472 | } | ||||||
| 473 | 0 | 0 | return \@res; | ||||
| 474 | } | ||||||
| 475 | |||||||
| 476 | # Created a node of XML hash with the passed in variables already set | ||||||
| 477 | sub new_node { | ||||||
| 478 | 1 | 1 | 1 | 1 | my $self = shift; | ||
| 479 | 1 | 2 | my %parts = @_; | ||||
| 480 | |||||||
| 481 | 1 | 2 | my %newnode; | ||||
| 482 | 1 | 3 | foreach ( keys %parts ) { | ||||
| 483 | 1 | 3 | my $val = $parts{$_}; | ||||
| 484 | 1 | 50 | 33 | 7 | if ( m/^_/ || ref($val) eq 'HASH' ) { | ||
| 485 | 0 | 0 | $newnode{$_} = $val; | ||||
| 486 | } | ||||||
| 487 | else { | ||||||
| 488 | 1 | 5 | $newnode{$_} = { value => $val }; | ||||
| 489 | } | ||||||
| 490 | } | ||||||
| 491 | |||||||
| 492 | 1 | 3 | return \%newnode; | ||||
| 493 | } | ||||||
| 494 | |||||||
| 495 | 0 | 0 | 1 | 0 | sub newhash { shift; return { value => shift }; } | ||
| 0 | 0 | ||||||
| 496 | |||||||
| 497 | sub simplify { | ||||||
| 498 | 0 | 0 | 1 | 0 | my $self = shift; | ||
| 499 | 0 | 0 | my $root = shift; | ||||
| 500 | 0 | 0 | my %ret; | ||||
| 501 | 0 | 0 | foreach my $name ( keys %$root ) { | ||||
| 502 | 0 | 0 | 0 | 0 | next if ( $name =~ m|^_| || $name eq 'comment' || $name eq 'value' ); | ||
| 0 | |||||||
| 503 | 0 | 0 | my $val = xval $root->{$name}; | ||||
| 504 | 0 | 0 | $ret{$name} = $val; | ||||
| 505 | } | ||||||
| 506 | 0 | 0 | return \%ret; | ||||
| 507 | } | ||||||
| 508 | |||||||
| 509 | sub xval { | ||||||
| 510 | 0 | 0 | 0 | 0 | 1 | 0 | return $_[0] ? $_[0]->{'value'} : ( $_[1] || '' ); |
| 511 | } | ||||||
| 512 | |||||||
| 513 | # Save an XML hash tree into a file | ||||||
| 514 | sub save { | ||||||
| 515 | 1 | 1 | 1 | 2 | my $self = shift; | ||
| 516 | 1 | 50 | 4 | return if ( !$self->{'xml'} ); | |||
| 517 | |||||||
| 518 | 1 | 4 | my $xml = $self->xml( $self->{'xml'} ); | ||||
| 519 | |||||||
| 520 | 1 | 2 | my $len; | ||||
| 521 | { | ||||||
| 522 | 4 | 4 | 26 | use bytes; | |||
| 4 | 419 | ||||||
| 4 | 26 | ||||||
| 1 | 1 | ||||||
| 523 | 1 | 24 | $len = length($xml); | ||||
| 524 | } | ||||||
| 525 | 1 | 50 | 4 | return if ( !$len ); | |||
| 526 | |||||||
| 527 | 1 | 1 | 6 | open my $F, '>:encoding(UTF-8)', $self->{'file'}; | |||
| 1 | 1 | ||||||
| 1 | 7 | ||||||
| 1 | 33 | ||||||
| 528 | 1 | 11093 | print $F $xml; | ||||
| 529 | |||||||
| 530 | 1 | 66 | seek( $F, 0, 2 ); | ||||
| 531 | 1 | 4 | my $cursize = tell($F); | ||||
| 532 | 1 | 50 | 4 | if ( $cursize != $len ) { # concurrency; we are writing a smaller file | |||
| 533 | 0 | 0 | warn "Truncating File $self->{'file'}"; | ||||
| 534 | 0 | 0 | truncate( $F, $len ); | ||||
| 535 | } | ||||||
| 536 | 1 | 3 | seek( $F, 0, 2 ); | ||||
| 537 | 1 | 1 | $cursize = tell($F); | ||||
| 538 | 1 | 50 | 3 | if ( $cursize != $len ) { # still not the right size even after truncate?? | |||
| 539 | 0 | 0 | die "Write problem; $cursize != $len"; | ||||
| 540 | } | ||||||
| 541 | 1 | 17 | close $F; | ||||
| 542 | } | ||||||
| 543 | |||||||
| 544 | sub xml { | ||||||
| 545 | 16 | 16 | 1 | 39 | my ( $self, $obj, $name ) = @_; | ||
| 546 | 16 | 50 | 30 | if ( !$name ) { | |||
| 547 | 16 | 16 | my %hash; | ||||
| 548 | 16 | 30 | $hash{0} = $obj; | ||||
| 549 | 16 | 29 | return obj2xml( \%hash, '', 0 ); | ||||
| 550 | } | ||||||
| 551 | 0 | 0 | my %hash; | ||||
| 552 | 0 | 0 | $hash{$name} = $obj; | ||||
| 553 | 0 | 0 | return obj2xml( \%hash, '', 0 ); | ||||
| 554 | } | ||||||
| 555 | |||||||
| 556 | sub html { | ||||||
| 557 | 0 | 0 | 1 | 0 | my ( $self, $obj, $name ) = @_; | ||
| 558 | 0 | 0 | my $pre = ''; | ||||
| 559 | 0 | 0 | 0 | if ( $self->{'style'} ) { | |||
| 560 | 0 | 0 | $pre = ""; | ||||
| 561 | } | ||||||
| 562 | 0 | 0 | 0 | if ( !$name ) { | |||
| 563 | 0 | 0 | my %hash; | ||||
| 564 | 0 | 0 | $hash{0} = $obj; | ||||
| 565 | 0 | 0 | return $pre . obj2html( \%hash, '', 0 ); | ||||
| 566 | } | ||||||
| 567 | 0 | 0 | my %hash; | ||||
| 568 | 0 | 0 | $hash{$name} = $obj; | ||||
| 569 | 0 | 0 | return $pre . obj2html( \%hash, '', 0 ); | ||||
| 570 | } | ||||||
| 571 | |||||||
| 572 | sub obj2xml { | ||||||
| 573 | 78 | 78 | 1 | 102 | my ( $objs, $name, $pad, $level ) = @_; | ||
| 574 | 78 | 100 | 137 | $level = 0 if ( !$level ); | |||
| 575 | 78 | 100 | 127 | $pad = '' if ( $level <= 2 ); | |||
| 576 | 78 | 59 | my $xml = ''; | ||||
| 577 | 78 | 58 | my $att = ''; | ||||
| 578 | 78 | 63 | my $imm = 1; | ||||
| 579 | 78 | 50 | 106 | return '' if ( !$objs ); | |||
| 580 | |||||||
| 581 | #return $objs->{'_raw'} if( $objs->{'_raw'} ); | ||||||
| 582 | 356 | 296 | my @dex = sort { | ||||
| 583 | 78 | 235 | my $oba = $objs->{$a}; | ||||
| 584 | 356 | 314 | my $obb = $objs->{$b}; | ||||
| 585 | 356 | 234 | my $posa = 0; | ||||
| 586 | 356 | 227 | my $posb = 0; | ||||
| 587 | 356 | 100 | 476 | $oba = $oba->[0] if ( ref($oba) eq 'ARRAY' ); | |||
| 588 | 356 | 100 | 424 | $obb = $obb->[0] if ( ref($obb) eq 'ARRAY' ); | |||
| 589 | 356 | 100 | 100 | 445 | if ( ref($oba) eq 'HASH' ) { $posa = $oba->{'_pos'} || 0; } | ||
| 81 | 135 | ||||||
| 590 | 356 | 100 | 100 | 449 | if ( ref($obb) eq 'HASH' ) { $posb = $obb->{'_pos'} || 0; } | ||
| 77 | 126 | ||||||
| 591 | 356 | 411 | return $posa <=> $posb; | ||||
| 592 | } keys %$objs; | ||||||
| 593 | 78 | 105 | for my $i (@dex) { | ||||
| 594 | 286 | 100 | 521 | my $obj = $objs->{$i} || ''; | |||
| 595 | 286 | 242 | my $type = ref($obj); | ||||
| 596 | 286 | 100 | 66 | 754 | if ( $type eq 'ARRAY' ) { | ||
| 100 | |||||||
| 597 | 4 | 4 | $imm = 0; | ||||
| 598 | |||||||
| 599 | my @dex2 = sort { | ||||||
| 600 | 4 | 50 | 7 | if ( !$a ) { return 0; } | |||
| 3 | 8 | ||||||
| 0 | 0 | ||||||
| 601 | 3 | 50 | 6 | if ( !$b ) { return 0; } | |||
| 0 | 0 | ||||||
| 602 | 3 | 50 | 33 | 15 | if ( ref($a) eq 'HASH' && ref($b) eq 'HASH' ) { | ||
| 603 | 3 | 6 | my $posa = $a->{'_pos'}; | ||||
| 604 | 3 | 5 | my $posb = $b->{'_pos'}; | ||||
| 605 | 3 | 50 | 7 | if ( !$posa ) { $posa = 0; } | |||
| 0 | 0 | ||||||
| 606 | 3 | 50 | 6 | if ( !$posb ) { $posb = 0; } | |||
| 0 | 0 | ||||||
| 607 | 3 | 9 | return $posa <=> $posb; | ||||
| 608 | } | ||||||
| 609 | 0 | 0 | return 0; | ||||
| 610 | } @$obj; | ||||||
| 611 | |||||||
| 612 | 4 | 6 | for my $j (@dex2) { | ||||
| 613 | 7 | 20 | $xml .= obj2xml( $j, $i, $pad . ' ', $level + 1, $#dex ); | ||||
| 614 | } | ||||||
| 615 | } | ||||||
| 616 | elsif ( $type eq 'HASH' && $i !~ /^_/ ) { | ||||||
| 617 | 64 | 100 | 85 | if ( $obj->{'_att'} ) { | |||
| 618 | 9 | 50 | 48 | $att .= ' ' . $i . '="' . $obj->{'value'} . '"' if ( $i !~ /^_/ ); | |||
| 619 | } | ||||||
| 620 | else { | ||||||
| 621 | 55 | 43 | $imm = 0; | ||||
| 622 | 55 | 221 | $xml .= obj2xml( $obj, $i, $pad . ' ', $level + 1, $#dex ); | ||||
| 623 | } | ||||||
| 624 | } | ||||||
| 625 | else { | ||||||
| 626 | 218 | 100 | 608 | if ( $i eq 'comment' ) { $xml .= '' . "\n"; } | |||
| 3 | 100 | 12 | |||||
| 50 | |||||||
| 627 | elsif ( $i eq 'value' ) { | ||||||
| 628 | 26 | 100 | 49 | if ( $level > 1 ) { # $#dex < 4 && | |||
| 629 | 21 | 100 | 66 | 82 | if ( $obj && $obj =~ /[<>&;]/ ) { $xml .= ''; } | ||
| 2 | 7 | ||||||
| 630 | 19 | 100 | 61 | else { $xml .= $obj if ( $obj =~ /\S/ ); } | |||
| 631 | } | ||||||
| 632 | } | ||||||
| 633 | elsif ( $i =~ /^_/ ) { } | ||||||
| 634 | 0 | 0 | else { $xml .= '<' . $i . '>' . $obj . '' . $i . '>'; } | ||||
| 635 | } | ||||||
| 636 | } | ||||||
| 637 | 78 | 100 | 113 | my $pad2 = $imm ? '' : $pad; | |||
| 638 | 78 | 100 | 92 | my $cr = $imm ? '' : "\n"; | |||
| 639 | 78 | 50 | 153 | if ( substr( $name, 0, 1 ) ne '_' ) { | |||
| 640 | 78 | 100 | 108 | if ($name) { | |||
| 641 | 46 | 100 | 54 | if ($xml) { | |||
| 642 | 33 | 103 | $xml = $pad . '<' . $name . $att . '>' . $cr . $xml . $pad2 . '' . $name . '>'; | ||||
| 643 | } | ||||||
| 644 | else { | ||||||
| 645 | 13 | 25 | $xml = $pad . '<' . $name . $att . ' />'; | ||||
| 646 | } | ||||||
| 647 | } | ||||||
| 648 | 78 | 100 | 264 | return $xml . "\n" if ( $level > 1 ); | |||
| 649 | 32 | 93 | return $xml; | ||||
| 650 | } | ||||||
| 651 | 0 | 0 | return ''; | ||||
| 652 | } | ||||||
| 653 | |||||||
| 654 | sub obj2html { | ||||||
| 655 | 0 | 0 | 1 | 0 | my ( $objs, $name, $pad, $level ) = @_; | ||
| 656 | |||||||
| 657 | 0 | 0 | my $less = "<"; | ||||
| 658 | 0 | 0 | my $more = ">"; | ||||
| 659 | 0 | 0 | my $tn0 = ""; | ||||
| 660 | 0 | 0 | my $tn1 = ""; | ||||
| 661 | 0 | 0 | my $eq0 = ""; | ||||
| 662 | 0 | 0 | my $eq1 = ""; | ||||
| 663 | 0 | 0 | my $qo0 = ""; | ||||
| 664 | 0 | 0 | my $qo1 = ""; | ||||
| 665 | 0 | 0 | my $sp0 = ""; | ||||
| 666 | 0 | 0 | my $sp1 = ""; | ||||
| 667 | 0 | 0 | my $cd0 = ""; | ||||
| 668 | 0 | 0 | my $cd1 = ""; | ||||
| 669 | |||||||
| 670 | 0 | 0 | 0 | $level = 0 if ( !$level ); | |||
| 671 | 0 | 0 | 0 | $pad = '' if ( $level == 1 ); | |||
| 672 | 0 | 0 | my $xml = ''; | ||||
| 673 | 0 | 0 | my $att = ''; | ||||
| 674 | 0 | 0 | my $imm = 1; | ||||
| 675 | 0 | 0 | 0 | return '' if ( !$objs ); | |||
| 676 | 0 | 0 | my @dex = sort { | ||||
| 677 | 0 | 0 | my $oba = $objs->{$a}; | ||||
| 678 | 0 | 0 | my $obb = $objs->{$b}; | ||||
| 679 | 0 | 0 | my $posa = 0; | ||||
| 680 | 0 | 0 | my $posb = 0; | ||||
| 681 | 0 | 0 | 0 | $oba = $oba->[0] if ( ref($oba) eq 'ARRAY' ); | |||
| 682 | 0 | 0 | 0 | $obb = $obb->[0] if ( ref($obb) eq 'ARRAY' ); | |||
| 683 | 0 | 0 | 0 | 0 | if ( ref($oba) eq 'HASH' ) { $posa = $oba->{'_pos'} || 0; } | ||
| 0 | 0 | ||||||
| 684 | 0 | 0 | 0 | 0 | if ( ref($obb) eq 'HASH' ) { $posb = $obb->{'_pos'} || 0; } | ||
| 0 | 0 | ||||||
| 685 | 0 | 0 | return $posa <=> $posb; | ||||
| 686 | } keys %$objs; | ||||||
| 687 | |||||||
| 688 | 0 | 0 | 0 | if ( $objs->{'_cdata'} ) { | |||
| 689 | 0 | 0 | my $val = $objs->{'value'}; | ||||
| 690 | 0 | 0 | $val =~ s/^(\s*\n)+//; | ||||
| 691 | 0 | 0 | $val =~ s/\s+$//; | ||||
| 692 | 0 | 0 | $val =~ s/&/&/g; | ||||
| 693 | 0 | 0 | $val =~ s/</g; | ||||
| 694 | 0 | 0 | $objs->{'value'} = $val; | ||||
| 695 | |||||||
| 696 | #$xml = "$less![CDATA[ $val |
||||||
| 697 | 0 | 0 | $cd0 = "$less![CDATA[ "; |
||||
| 698 | 0 | 0 | $cd1 = "]]$more"; | ||||
| 699 | } | ||||||
| 700 | 0 | 0 | for my $i (@dex) { | ||||
| 701 | 0 | 0 | 0 | my $obj = $objs->{$i} || ''; | |||
| 702 | 0 | 0 | my $type = ref($obj); | ||||
| 703 | 0 | 0 | 0 | 0 | if ( $type eq 'ARRAY' ) { | ||
| 0 | |||||||
| 704 | 0 | 0 | $imm = 0; | ||||
| 705 | |||||||
| 706 | my @dex2 = sort { | ||||||
| 707 | 0 | 0 | 0 | if ( !$a ) { return 0; } | |||
| 0 | 0 | ||||||
| 0 | 0 | ||||||
| 708 | 0 | 0 | 0 | if ( !$b ) { return 0; } | |||
| 0 | 0 | ||||||
| 709 | 0 | 0 | 0 | 0 | if ( ref($a) eq 'HASH' && ref($b) eq 'HASH' ) { | ||
| 710 | 0 | 0 | my $posa = $a->{'_pos'}; | ||||
| 711 | 0 | 0 | my $posb = $b->{'_pos'}; | ||||
| 712 | 0 | 0 | 0 | if ( !$posa ) { $posa = 0; } | |||
| 0 | 0 | ||||||
| 713 | 0 | 0 | 0 | if ( !$posb ) { $posb = 0; } | |||
| 0 | 0 | ||||||
| 714 | 0 | 0 | return $posa <=> $posb; | ||||
| 715 | } | ||||||
| 716 | 0 | 0 | return 0; | ||||
| 717 | } @$obj; | ||||||
| 718 | |||||||
| 719 | 0 | 0 | for my $j (@dex2) { $xml .= obj2html( $j, $i, $pad . ' ', $level + 1, $#dex ); } | ||||
| 0 | 0 | ||||||
| 720 | } | ||||||
| 721 | elsif ( $type eq 'HASH' && $i !~ /^_/ ) { | ||||||
| 722 | 0 | 0 | 0 | if ( $obj->{'_att'} ) { | |||
| 723 | 0 | 0 | my $val = $obj->{'value'}; | ||||
| 724 | 0 | 0 | $val =~ s/</g; | ||||
| 725 | 0 | 0 | 0 | if ( $val eq '' ) { | |||
| 726 | 0 | 0 | 0 | $att .= " $i" if ( $i !~ /^_/ ); | |||
| 727 | } | ||||||
| 728 | else { | ||||||
| 729 | 0 | 0 | 0 | $att .= " $i$eq0=$eq1$qo0\"$qo1$val$qo0\"$qo1" if ( $i !~ /^_/ ); | |||
| 730 | } | ||||||
| 731 | } | ||||||
| 732 | else { | ||||||
| 733 | 0 | 0 | $imm = 0; | ||||
| 734 | 0 | 0 | $xml .= obj2html( $obj, $i, $pad . ' ', $level + 1, $#dex ); | ||||
| 735 | } | ||||||
| 736 | } | ||||||
| 737 | else { | ||||||
| 738 | 0 | 0 | 0 | if ( $i eq 'comment' ) { $xml .= "$less!--" . $obj . "--$more" . " \n"; } |
|||
| 0 | 0 | 0 | |||||
| 0 | |||||||
| 739 | elsif ( $i eq 'value' ) { | ||||||
| 740 | 0 | 0 | 0 | if ( $level > 1 ) { | |||
| 741 | 0 | 0 | 0 | 0 | if ( $obj && $obj =~ /[<>&;]/ && !$objs->{'_cdata'} ) { $xml .= "$less![CDATA[$obj]]$more"; } | ||
| 0 | 0 | 0 | |||||
| 742 | 0 | 0 | 0 | else { $xml .= $obj if ( $obj =~ /\S/ ); } | |||
| 743 | } | ||||||
| 744 | } | ||||||
| 745 | elsif ( $i =~ /^_/ ) { } | ||||||
| 746 | 0 | 0 | else { $xml .= "$less$tn0$i$tn1$more$obj$less/$tn0$i$tn1$more"; } | ||||
| 747 | } | ||||||
| 748 | } | ||||||
| 749 | 0 | 0 | 0 | if ( substr( $name, 0, 1 ) ne '_' ) { | |||
| 750 | 0 | 0 | 0 | if ($name) { | |||
| 751 | 0 | 0 | 0 | if ($imm) { | |||
| 752 | 0 | 0 | 0 | if ( $xml =~ /\S/ ) { | |||
| 753 | 0 | 0 | $xml = "$sp0$pad$sp1$less$tn0$name$tn1$att$more$cd0$xml$cd1$less/$tn0$name$tn1$more"; | ||||
| 754 | } | ||||||
| 755 | else { | ||||||
| 756 | 0 | 0 | $xml = "$sp0$pad$sp1$less$tn0$name$tn1$att/$more"; | ||||
| 757 | } | ||||||
| 758 | } | ||||||
| 759 | else { | ||||||
| 760 | 0 | 0 | 0 | if ( $xml =~ /\S/ ) { | |||
| 761 | 0 | 0 | $xml = | ||||
| 762 | "$sp0$pad$sp1$less$tn0$name$tn1$att$more $xml $sp0$pad$sp1$less/$tn0$name$tn1$more"; |
||||||
| 763 | } | ||||||
| 764 | 0 | 0 | else { $xml = "$sp0$pad$sp1$less$tn0$name$tn1$att/$more"; } | ||||
| 765 | } | ||||||
| 766 | } | ||||||
| 767 | 0 | 0 | 0 | $xml .= " " if ( $objs->{'_br'} ); |
|||
| 768 | 0 | 0 | 0 | if ( $objs->{'_note'} ) { | |||
| 769 | 0 | 0 | $xml .= " "; |
||||
| 770 | 0 | 0 | my $note = $objs->{'_note'}{'value'}; | ||||
| 771 | 0 | 0 | my @notes = split( /\|/, $note ); | ||||
| 772 | 0 | 0 | for (@notes) { | ||||
| 773 | 0 | 0 | $xml | ||||
| 774 | .= " $sp0$pad$sp1<!-- $_ --> "; |
||||||
| 775 | } | ||||||
| 776 | } | ||||||
| 777 | 0 | 0 | 0 | return $xml . " \n" if ($level); |
|||
| 778 | 0 | 0 | return $xml; | ||||
| 779 | } | ||||||
| 780 | 0 | 0 | return ''; | ||||
| 781 | } | ||||||
| 782 | |||||||
| 783 | sub free_tree { | ||||||
| 784 | 75 | 75 | 1 | 53 | my $self = shift; | ||
| 785 | 75 | 100 | 146 | if ( $self->{'structroot'} ) { | |||
| 786 | 38 | 76 | XML::Bare::free_tree_c( $self->{'structroot'} ); | ||||
| 787 | 38 | 64 | delete( $self->{'structroot'} ); | ||||
| 788 | } | ||||||
| 789 | } | ||||||
| 790 | |||||||
| 791 | 1; | ||||||
| 792 | |||||||
| 793 | |||||||
| 794 | |||||||
| 795 | =pod | ||||||
| 796 | |||||||
| 797 | =for stopwords CDATA GDSL LibXML Sergey Skvortsov XBS dequoting exe | ||||||
| 798 | executables html iff keeproot makebench nodeset notree recognised | ||||||
| 799 | subnode templated tmpl xml xmlin | ||||||
| 800 | |||||||
| 801 | =head1 NAME | ||||||
| 802 | |||||||
| 803 | XML::Bare - Minimal XML parser implemented via a C state engine | ||||||
| 804 | |||||||
| 805 | =head1 VERSION | ||||||
| 806 | |||||||
| 807 | version 0.46_03 | ||||||
| 808 | |||||||
| 809 | =head1 SYNOPSIS | ||||||
| 810 | |||||||
| 811 | use XML::Bare; | ||||||
| 812 | |||||||
| 813 | my $ob = new XML::Bare( text => ' |
||||||
| 814 | |||||||
| 815 | # Parse the xml into a hash tree | ||||||
| 816 | my $root = $ob->parse(); | ||||||
| 817 | |||||||
| 818 | # Print the content of the name node | ||||||
| 819 | print $root->{xml}->{name}->{value}; | ||||||
| 820 | |||||||
| 821 | # -------------------------------------------------------------- | ||||||
| 822 | |||||||
| 823 | # Load xml from a file ( assume same contents as first example ) | ||||||
| 824 | my $ob2 = new XML::Bare( file => 'test.xml' ); | ||||||
| 825 | |||||||
| 826 | my $root2 = $ob2->parse(); | ||||||
| 827 | |||||||
| 828 | $root2->{xml}->{name}->{value} = 'Tim'; | ||||||
| 829 | |||||||
| 830 | # Save the changes back to the file | ||||||
| 831 | $ob2->save(); | ||||||
| 832 | |||||||
| 833 | # -------------------------------------------------------------- | ||||||
| 834 | |||||||
| 835 | # Load xml and verify against XBS ( XML Bare Schema ) | ||||||
| 836 | my $xml_text = ' |
||||||
| 837 | my $schema_text = ' |
||||||
| 838 | my $ob3 = new XML::Bare( text => $xml_text, schema => { text => $schema_text } ); | ||||||
| 839 | $ob3->parse(); # this will error out if schema is invalid | ||||||
| 840 | |||||||
| 841 | =head1 DESCRIPTION | ||||||
| 842 | |||||||
| 843 | This module is a 'Bare' XML parser. It is implemented in C. The parser | ||||||
| 844 | itself is a simple state engine that is less than 500 lines of C. The | ||||||
| 845 | parser builds a C struct tree from input text. That C struct tree is | ||||||
| 846 | converted to a Perl hash by a Perl function that makes basic calls back | ||||||
| 847 | to the C to go through the nodes sequentially. | ||||||
| 848 | |||||||
| 849 | The parser itself will only cease parsing if it encounters tags that | ||||||
| 850 | are not closed properly. All other inputs will parse, even invalid | ||||||
| 851 | inputs. To allowing checking for validity, a schema checker is included | ||||||
| 852 | in the module as well. | ||||||
| 853 | |||||||
| 854 | The schema format is custom and is meant to be as simple as possible. | ||||||
| 855 | It is based loosely around the way multiplicity is handled in Perl | ||||||
| 856 | regular expressions. | ||||||
| 857 | |||||||
| 858 | =head2 Supported XML | ||||||
| 859 | |||||||
| 860 | To demonstrate what sort of XML is supported, consider the following | ||||||
| 861 | examples. Each of the PERL statements evaluates to true. | ||||||
| 862 | |||||||
| 863 | =over 2 | ||||||
| 864 | |||||||
| 865 | =item * Node containing just text | ||||||
| 866 | |||||||
| 867 | XML: |
||||||
| 868 | PERL: $root->{xml}->{value} eq "blah"; | ||||||
| 869 | |||||||
| 870 | =item * Subset nodes | ||||||
| 871 | |||||||
| 872 | XML: |
||||||
| 873 | PERL: $root->{xml}->{name}->{value} eq "Bob"; | ||||||
| 874 | |||||||
| 875 | =item * Attributes unquoted | ||||||
| 876 | |||||||
| 877 | XML: |
||||||
| 878 | PERL: $root->{xml}->{a}->{href}->{value} eq "index.htm"; | ||||||
| 879 | |||||||
| 880 | =item * Attributes quoted | ||||||
| 881 | |||||||
| 882 | XML: |
||||||
| 883 | PERL: $root->{xml}->{a}->{href}->{value} eq "index.htm"; | ||||||
| 884 | |||||||
| 885 | =item * CDATA nodes | ||||||
| 886 | |||||||
| 887 | XML: |
||||||
| 888 | PERL: $root->{xml}->{raw}->{value} eq "some raw \$~"; | ||||||
| 889 | |||||||
| 890 | =item * Multiple nodes; form array | ||||||
| 891 | |||||||
| 892 | XML: |
||||||
| 893 | PERL: $root->{xml}->{item}->[0]->{value} eq "1"; | ||||||
| 894 | |||||||
| 895 | =item * Forcing array creation | ||||||
| 896 | |||||||
| 897 | XML: |
||||||
| 898 | PERL: $root->{xml}->{item}->[0]->{value} eq "1"; | ||||||
| 899 | |||||||
| 900 | =item * One comment supported per node | ||||||
| 901 | |||||||
| 902 | XML: |
||||||
| 903 | PERL: $root->{xml}->{comment} eq 'test'; | ||||||
| 904 | |||||||
| 905 | =back | ||||||
| 906 | |||||||
| 907 | =head2 Schema Checking | ||||||
| 908 | |||||||
| 909 | Schema checking is done by providing the module with an XBS (XML::Bare Schema) to check | ||||||
| 910 | the XML against. If the XML checks as valid against the schema, parsing will continue as | ||||||
| 911 | normal. If the XML is invalid, the parse function will die, providing information about | ||||||
| 912 | the failure. | ||||||
| 913 | |||||||
| 914 | The following information is provided in the error message: | ||||||
| 915 | |||||||
| 916 | =over 2 | ||||||
| 917 | |||||||
| 918 | =item * The type of error | ||||||
| 919 | |||||||
| 920 | =item * Where the error occurred ( line and char ) | ||||||
| 921 | |||||||
| 922 | =item * A short snippet of the XML at the point of failure | ||||||
| 923 | |||||||
| 924 | =back | ||||||
| 925 | |||||||
| 926 | =head2 XBS ( XML::Bare Schema ) Format | ||||||
| 927 | |||||||
| 928 | =over 2 | ||||||
| 929 | |||||||
| 930 | =item * Required nodes | ||||||
| 931 | |||||||
| 932 | XML: |
||||||
| 933 | XBS: |
||||||
| 934 | |||||||
| 935 | =item * Optional nodes - allow one | ||||||
| 936 | |||||||
| 937 | XML: |
||||||
| 938 | XBS: |
||||||
| 939 | or XBS: |
||||||
| 940 | |||||||
| 941 | =item * Optional nodes - allow 0 or more | ||||||
| 942 | |||||||
| 943 | XML: |
||||||
| 944 | XBS: |
||||||
| 945 | |||||||
| 946 | =item * Required nodes - allow 1 or more | ||||||
| 947 | |||||||
| 948 | XML: |
||||||
| 949 | XBS: |
||||||
| 950 | |||||||
| 951 | =item * Nodes - specified minimum and maximum number | ||||||
| 952 | |||||||
| 953 | XML: |
||||||
| 954 | XBS: |
||||||
| 955 | or XBS: |
||||||
| 956 | or XBS: |
||||||
| 957 | |||||||
| 958 | =item * Multiple acceptable node formats | ||||||
| 959 | |||||||
| 960 | XML: |
||||||
| 961 | XBS: |
||||||
| 962 | |||||||
| 963 | =item * Regular expressions checking for values | ||||||
| 964 | |||||||
| 965 | XML: |
||||||
| 966 | XBS: |
||||||
| 967 | |||||||
| 968 | =item * Require multi_ tags | ||||||
| 969 | |||||||
| 970 | XML: |
||||||
| 971 | XBS: |
||||||
| 972 | |||||||
| 973 | =back | ||||||
| 974 | |||||||
| 975 | =head2 Parsed Hash Structure | ||||||
| 976 | |||||||
| 977 | The hash structure returned from XML parsing is created in a specific format. | ||||||
| 978 | Besides as described above, the structure contains some additional nodes in | ||||||
| 979 | order to preserve information that will allow that structure to be correctly | ||||||
| 980 | converted back to XML. | ||||||
| 981 | |||||||
| 982 | Nodes may contain the following 3 additional subnodes: | ||||||
| 983 | |||||||
| 984 | =over 2 | ||||||
| 985 | |||||||
| 986 | =item * _i | ||||||
| 987 | |||||||
| 988 | The character offset within the original parsed XML of where the node | ||||||
| 989 | begins. This is used to provide line information for errors when XML | ||||||
| 990 | fails a schema check. | ||||||
| 991 | |||||||
| 992 | =item * _pos | ||||||
| 993 | |||||||
| 994 | This is a number indicating the ordering of nodes. It is used to allow | ||||||
| 995 | items in a perl hash to be sorted when writing back to xml. Note that | ||||||
| 996 | items are not sorted after parsing in order to save time if all you | ||||||
| 997 | are doing is reading and you do not care about the order. | ||||||
| 998 | |||||||
| 999 | In future versions of this module an option will be added to allow | ||||||
| 1000 | you to sort your nodes so that you can read them in order. | ||||||
| 1001 | ( note that multiple nodes of the same name are stored in order ) | ||||||
| 1002 | |||||||
| 1003 | =item * _att | ||||||
| 1004 | |||||||
| 1005 | This is a boolean value that exists and is 1 iff the node is an | ||||||
| 1006 | attribute. | ||||||
| 1007 | |||||||
| 1008 | =back | ||||||
| 1009 | |||||||
| 1010 | =head2 Parsing Limitations / Features | ||||||
| 1011 | |||||||
| 1012 | =over 2 | ||||||
| 1013 | |||||||
| 1014 | =item * CDATA parsed correctly, but stripped if unneeded | ||||||
| 1015 | |||||||
| 1016 | Currently the contents of a node that are CDATA are read and | ||||||
| 1017 | put into the value hash, but the hash structure does not have | ||||||
| 1018 | a value indicating the node contains CDATA. | ||||||
| 1019 | |||||||
| 1020 | When converting back to XML, the contents of the value hash | ||||||
| 1021 | are parsed to check for xml incompatible data using a regular | ||||||
| 1022 | expression. If 'CDATA like' stuff is encountered, the node | ||||||
| 1023 | is output as CDATA. | ||||||
| 1024 | |||||||
| 1025 | =item * Standard XML quoted characters are decoded | ||||||
| 1026 | |||||||
| 1027 | The basic XML quoted characters - C<&> C<>> C<<> C |
||||||
| 1028 | and C<'> - are recognised and decoded when reading values. | ||||||
| 1029 | However when writing the builder will put any values that need quoting | ||||||
| 1030 | into a CDATA wrapper as described above. | ||||||
| 1031 | |||||||
| 1032 | =item * Node position stored, but hash remains unsorted | ||||||
| 1033 | |||||||
| 1034 | The ordering of nodes is noted using the '_pos' value, but | ||||||
| 1035 | the hash itself is not ordered after parsing. Currently | ||||||
| 1036 | items will be out of order when looking at them in the | ||||||
| 1037 | hash. | ||||||
| 1038 | |||||||
| 1039 | Note that when converted back to XML, the nodes are then | ||||||
| 1040 | sorted and output in the correct order to XML. Note that | ||||||
| 1041 | nodes of the same name with the same parent will be | ||||||
| 1042 | grouped together; the position of the first item to | ||||||
| 1043 | appear will determine the output position of the group. | ||||||
| 1044 | |||||||
| 1045 | =item * Comments are parsed but only one is stored per node. | ||||||
| 1046 | |||||||
| 1047 | For each node, there can be a comment within it, and that | ||||||
| 1048 | comment will be saved and output back when dumping to XML. | ||||||
| 1049 | |||||||
| 1050 | =item * Comments override output of immediate value | ||||||
| 1051 | |||||||
| 1052 | If a node contains only a comment node and a text value, | ||||||
| 1053 | only the comment node will be displayed. This is in line | ||||||
| 1054 | with treating a comment node as a node and only displaying | ||||||
| 1055 | immediate values when a node contains no subnodes. | ||||||
| 1056 | |||||||
| 1057 | =item * PI sections are parsed, but discarded | ||||||
| 1058 | |||||||
| 1059 | =item * Unknown C<< > sections are parsed, but discarded | ||||||
| 1060 | |||||||
| 1061 | =item * Attributes may use no quotes, single quotes, quotes | ||||||
| 1062 | |||||||
| 1063 | =item * Quoted attributes cannot contain escaped quotes | ||||||
| 1064 | |||||||
| 1065 | No escape character is recognized within quotes. As a result, | ||||||
| 1066 | regular quotes cannot be stored to XML, or the written XML | ||||||
| 1067 | will not be correct, due to all attributes always being written | ||||||
| 1068 | using quotes. | ||||||
| 1069 | |||||||
| 1070 | =item * Attributes are always written back to XML with quotes | ||||||
| 1071 | |||||||
| 1072 | =item * Nodes cannot contain subnodes as well as an immediate value | ||||||
| 1073 | |||||||
| 1074 | Actually nodes can in fact contain a value as well, but that | ||||||
| 1075 | value will be discarded if you write back to XML. That value is | ||||||
| 1076 | equal to the first continuous string of text besides a subnode. | ||||||
| 1077 | |||||||
| 1078 | |
||||||
| 1079 | ( the value of node is text ) | ||||||
| 1080 | |||||||
| 1081 | |
||||||
| 1082 | ( the value of node is text ) | ||||||
| 1083 | |||||||
| 1084 | |
||||||
| 1085 | |
||||||
| 1086 | |||||||
| 1087 | ( the value of node is "\n " ) | ||||||
| 1088 | |||||||
| 1089 | =back | ||||||
| 1090 | |||||||
| 1091 | =head2 Module Functions | ||||||
| 1092 | |||||||
| 1093 | =over 2 | ||||||
| 1094 | |||||||
| 1095 | =item * C<< $ob = new XML::Bare( text => "[some xml]" ) >> | ||||||
| 1096 | |||||||
| 1097 | Create a new XML object, with the given text as the xml source. | ||||||
| 1098 | |||||||
| 1099 | =item * C<< $object = new XML::Bare( file => "[filename]" ) >> | ||||||
| 1100 | |||||||
| 1101 | Create a new XML object, with the given filename/path as the xml source | ||||||
| 1102 | |||||||
| 1103 | =item * C<< $object = new XML::Bare( text => "[some xml]", file => "[filename]" ) >> | ||||||
| 1104 | |||||||
| 1105 | Create a new XML object, with the given text as the xml input, and the given | ||||||
| 1106 | filename/path as the potential output ( used by save() ) | ||||||
| 1107 | |||||||
| 1108 | =item * C<< $object = new XML::Bare( file => "data.xml", scheme => { file => "scheme.xbs" } ) >> | ||||||
| 1109 | |||||||
| 1110 | Create a new XML object and check to ensure it is valid xml by way of the XBS scheme. | ||||||
| 1111 | |||||||
| 1112 | =item * C<< $tree = $object->parse() >> | ||||||
| 1113 | |||||||
| 1114 | Parse the xml of the object and return a tree reference | ||||||
| 1115 | |||||||
| 1116 | =item * C<< $tree = $object->simple() >> | ||||||
| 1117 | |||||||
| 1118 | Alternate to the parse function which generates a tree similar to that | ||||||
| 1119 | generated by XML::Simple. Note that the sets of nodes are turned into | ||||||
| 1120 | arrays always, regardless of whether they have a 'name' attribute, unlike | ||||||
| 1121 | XML::Simple. | ||||||
| 1122 | |||||||
| 1123 | Note that currently the generated tree cannot be used with any of the | ||||||
| 1124 | functions in this module that operate upon trees. The function is provided | ||||||
| 1125 | purely as a quick and dirty way to read simple XML files. | ||||||
| 1126 | |||||||
| 1127 | =item * C<< $tree = xmlin( $xmlext, keeproot => 1 ) >> | ||||||
| 1128 | |||||||
| 1129 | The xmlin function is a shortcut to creating an XML::Bare object and | ||||||
| 1130 | parsing it using the simple function. It behaves similarly to the | ||||||
| 1131 | XML::Simple function by the same name. The keeproot option is optional | ||||||
| 1132 | and if left out the root node will be discarded, same as the function | ||||||
| 1133 | in XML::Simple. | ||||||
| 1134 | |||||||
| 1135 | =item * C<< $text = $object->xml( [root] ) >> | ||||||
| 1136 | |||||||
| 1137 | Take the hash tree in [root] and turn it into cleanly indented ( 2 spaces ) | ||||||
| 1138 | XML text. | ||||||
| 1139 | |||||||
| 1140 | =item * C<< $text = $object->html( [root], [root node name] ) >> | ||||||
| 1141 | |||||||
| 1142 | Take the hash tree in [root] and turn it into nicely colorized and styled | ||||||
| 1143 | html. [root node name] is optional. | ||||||
| 1144 | |||||||
| 1145 | =item * C<< $object->save() >> | ||||||
| 1146 | |||||||
| 1147 | The the current tree in the object, cleanly indent it, and save it | ||||||
| 1148 | to the file parameter specified when creating the object. | ||||||
| 1149 | |||||||
| 1150 | =item * C<< $value = xval $node, $default >> | ||||||
| 1151 | |||||||
| 1152 | Returns the value of $node or $default if the node does not exist. | ||||||
| 1153 | If default is not passed to the function, then '' is returned as | ||||||
| 1154 | a default value when the node does not exist. | ||||||
| 1155 | |||||||
| 1156 | =item * C<< ( $name, $age ) = xget( $personnode, qw/name age/ ) >> | ||||||
| 1157 | |||||||
| 1158 | Shortcut function to grab a number of values from a node all at the | ||||||
| 1159 | same time. Note that this function assumes that all of the subnodes | ||||||
| 1160 | exist; it will fail if they do not. | ||||||
| 1161 | |||||||
| 1162 | =item * C<< $text = XML::Bare::clean( text => "[some xml]" ) >> | ||||||
| 1163 | |||||||
| 1164 | Shortcut to creating an xml object and immediately turning it into clean xml text. | ||||||
| 1165 | |||||||
| 1166 | =item * C<< $text = XML::Bare::clean( file => "[filename]" ) >> | ||||||
| 1167 | |||||||
| 1168 | Similar to previous. | ||||||
| 1169 | |||||||
| 1170 | =item * C<< XML::Bare::clean( file => "[filename]", save => 1 ) >> | ||||||
| 1171 | |||||||
| 1172 | Clean up the xml in the file, saving the results back to the file | ||||||
| 1173 | |||||||
| 1174 | =item * C<< XML::Bare::clean( text => "[some xml]", save => "[filename]" ) >> | ||||||
| 1175 | |||||||
| 1176 | Clean up the xml provided, and save it into the specified file. | ||||||
| 1177 | |||||||
| 1178 | =item * C<< XML::Bare::clean( file => "[filename1]", save => "[filename2]" ) >> | ||||||
| 1179 | |||||||
| 1180 | Clean up the xml in filename1 and save the results to filename2. | ||||||
| 1181 | |||||||
| 1182 | =item * C<< $html = XML::Bare::tohtml( text => "[some xml]", root => 'xml' ) >> | ||||||
| 1183 | |||||||
| 1184 | Shortcut to creating an xml object and immediately turning it into html. | ||||||
| 1185 | Root is optional, and specifies the name of the root node for the xml | ||||||
| 1186 | ( which defaults to 'xml' ) | ||||||
| 1187 | |||||||
| 1188 | =item * C<< $object->add_node( [node], [nodeset name], name => value, name2 => value2, ... ) >> | ||||||
| 1189 | |||||||
| 1190 | Example: | ||||||
| 1191 | $object->add_node( $root->{xml}, 'item', name => 'Bob' ); | ||||||
| 1192 | |||||||
| 1193 | Result: | ||||||
| 1194 | |
||||||
| 1195 | |
||||||
| 1196 | |
||||||
| 1197 | |||||||
| 1198 | |||||||
| 1199 | |||||||
| 1200 | =item * C<< $object->add_node_after( [node], [subnode within node to add after], [nodeset name], ... ) >> | ||||||
| 1201 | |||||||
| 1202 | =item * C<< $object->del_node( [node], [nodeset name], name => value ) >> | ||||||
| 1203 | |||||||
| 1204 | Example: | ||||||
| 1205 | Starting XML: | ||||||
| 1206 | |
||||||
| 1207 | |||||||
| 1208 | 1 | ||||||
| 1209 | |||||||
| 1210 | |||||||
| 1211 | 2 | ||||||
| 1212 | |||||||
| 1213 | |||||||
| 1214 | |||||||
| 1215 | Code: | ||||||
| 1216 | $xml->del_node( $root->{xml}, 'a', b=>'1' ); | ||||||
| 1217 | |||||||
| 1218 | Ending XML: | ||||||
| 1219 | |
||||||
| 1220 | |||||||
| 1221 | 2 | ||||||
| 1222 | |||||||
| 1223 | |||||||
| 1224 | |||||||
| 1225 | =item * C<< $object->find_node( [node], [nodeset name], name => value ) >> | ||||||
| 1226 | |||||||
| 1227 | Example: | ||||||
| 1228 | Starting XML: | ||||||
| 1229 | |
||||||
| 1230 | |
||||||
| 1231 | |
||||||
| 1232 | |
||||||
| 1233 | |||||||
| 1234 | |
||||||
| 1235 | |
||||||
| 1236 | |
||||||
| 1237 | |||||||
| 1238 | |||||||
| 1239 | |||||||
| 1240 | Code: | ||||||
| 1241 | $object->find_node( $root->{xml}, 'ob', key => '1' )->{val}->{value} = 'test'; | ||||||
| 1242 | |||||||
| 1243 | Ending XML: | ||||||
| 1244 | |
||||||
| 1245 | |
||||||
| 1246 | |
||||||
| 1247 | |
||||||
| 1248 | |||||||
| 1249 | |
||||||
| 1250 | |
||||||
| 1251 | |
||||||
| 1252 | |||||||
| 1253 | |||||||
| 1254 | |||||||
| 1255 | =item * C<< $object->find_by_perl( [nodeset], "[perl code]" ) >> | ||||||
| 1256 | |||||||
| 1257 | find_by_perl evaluates some perl code for each node in a set of nodes, and | ||||||
| 1258 | returns the nodes where the perl code evaluates as true. In order to | ||||||
| 1259 | easily reference node values, node values can be directly referred | ||||||
| 1260 | to from within the perl code by the name of the node with a dash(-) in | ||||||
| 1261 | front of the name. See the example below. | ||||||
| 1262 | |||||||
| 1263 | Note that this function returns an array reference as opposed to a single | ||||||
| 1264 | node unlike the find_node function. | ||||||
| 1265 | |||||||
| 1266 | Example: | ||||||
| 1267 | Starting XML: | ||||||
| 1268 | |
||||||
| 1269 | |
||||||
| 1270 | |
||||||
| 1271 | |
||||||
| 1272 | |||||||
| 1273 | |
||||||
| 1274 | |
||||||
| 1275 | |
||||||
| 1276 | |||||||
| 1277 | |||||||
| 1278 | |||||||
| 1279 | Code: | ||||||
| 1280 | $object->find_by_perl( $root->{xml}->{ob}, "-key eq '1'" )->[0]->{val}->{value} = 'test'; | ||||||
| 1281 | |||||||
| 1282 | Ending XML: | ||||||
| 1283 | |
||||||
| 1284 | |
||||||
| 1285 | |
||||||
| 1286 | |
||||||
| 1287 | |||||||
| 1288 | |
||||||
| 1289 | |
||||||
| 1290 | |
||||||
| 1291 | |||||||
| 1292 | |||||||
| 1293 | |||||||
| 1294 | =item * C<< XML::Bare::merge( [nodeset1], [nodeset2], [id node name] ) >> | ||||||
| 1295 | |||||||
| 1296 | Merges the nodes from nodeset2 into nodeset1, matching the contents of | ||||||
| 1297 | each node based up the content in the id node. | ||||||
| 1298 | |||||||
| 1299 | Example: | ||||||
| 1300 | |||||||
| 1301 | Code: | ||||||
| 1302 | my $ob1 = new XML::Bare( text => " | ||||||
| 1303 | |
||||||
| 1304 | |
||||||
| 1305 | bob | ||||||
| 1306 | |||||||
| 1307 | |
||||||
| 1308 | |
||||||
| 1309 | |||||||
| 1310 | " ); | ||||||
| 1311 | my $ob2 = new XML::Bare( text => " | ||||||
| 1312 | |
||||||
| 1313 | |
||||||
| 1314 | john | ||||||
| 1315 | |||||||
| 1316 | |
||||||
| 1317 | |
||||||
| 1318 | |
||||||
| 1319 | |||||||
| 1320 | " ); | ||||||
| 1321 | my $root1 = $ob1->parse(); | ||||||
| 1322 | my $root2 = $ob2->parse(); | ||||||
| 1323 | merge( $root1->{'xml'}->{'a'}, $root2->{'xml'}->{'a'}, 'id' ); | ||||||
| 1324 | print $ob1->xml( $root1 ); | ||||||
| 1325 | |||||||
| 1326 | Output: | ||||||
| 1327 | |
||||||
| 1328 | |
||||||
| 1329 | bob | ||||||
| 1330 | |||||||
| 1331 | |
||||||
| 1332 | |
||||||
| 1333 | |
||||||
| 1334 | |
||||||
| 1335 | |||||||
| 1336 | |||||||
| 1337 | |||||||
| 1338 | =item * C<< XML::Bare::del_by_perl( ... ) >> | ||||||
| 1339 | |||||||
| 1340 | Works exactly like find_by_perl, but deletes whatever matches. | ||||||
| 1341 | |||||||
| 1342 | =item * C<< XML::Bare::forcearray( [noderef] ) >> | ||||||
| 1343 | |||||||
| 1344 | Turns the node reference into an array reference, whether that | ||||||
| 1345 | node is just a single node, or is already an array reference. | ||||||
| 1346 | |||||||
| 1347 | =item * C<< XML::Bare::new_node( ... ) >> | ||||||
| 1348 | |||||||
| 1349 | Creates a new node... | ||||||
| 1350 | |||||||
| 1351 | =item * C<< XML::Bare::newhash( ... ) >> | ||||||
| 1352 | |||||||
| 1353 | Creates a new hash with the specified value. | ||||||
| 1354 | |||||||
| 1355 | =item * C<< XML::Bare::simplify( [noderef] ) >> | ||||||
| 1356 | |||||||
| 1357 | Take a node with children that have immediate values and | ||||||
| 1358 | creates a hashref to reference those values by the name of | ||||||
| 1359 | each child. | ||||||
| 1360 | |||||||
| 1361 | =back | ||||||
| 1362 | |||||||
| 1363 | =head2 Functions Used Internally | ||||||
| 1364 | |||||||
| 1365 | =over 2 | ||||||
| 1366 | |||||||
| 1367 | =item * C<< check() checkone() readxbs() free_tree_c() >> | ||||||
| 1368 | |||||||
| 1369 | =item * C<< lineinfo() c_parse() c_parsefile() free_tree() xml2obj() >> | ||||||
| 1370 | |||||||
| 1371 | =item * C<< obj2xml() get_root() obj2html() xml2obj_simple() >> | ||||||
| 1372 | |||||||
| 1373 | =back | ||||||
| 1374 | |||||||
| 1375 | =head2 Performance | ||||||
| 1376 | |||||||
| 1377 | In comparison to other available perl xml parsers that create trees, XML::Bare | ||||||
| 1378 | is extremely fast. In order to measure the performance of loading and parsing | ||||||
| 1379 | compared to the alternatives, a templated speed comparison mechanism has been | ||||||
| 1380 | created and included with XML::Bare. | ||||||
| 1381 | |||||||
| 1382 | The include makebench.pl file runs when you make the module and creates perl | ||||||
| 1383 | files within the bench directory corresponding to the .tmpl contained there. | ||||||
| 1384 | |||||||
| 1385 | Currently there are three types of modules that can be tested against, | ||||||
| 1386 | executable parsers ( exe.tmpl ), tree parsers ( tree.tmpl ), and parsers | ||||||
| 1387 | that do not generated trees ( notree.tmpl ). | ||||||
| 1388 | |||||||
| 1389 | A full list of modules currently tested against is as follows: | ||||||
| 1390 | |||||||
| 1391 | Tiny XML (exe) | ||||||
| 1392 | EzXML (exe) | ||||||
| 1393 | XMLIO (exe) | ||||||
| 1394 | XML::LibXML (notree) | ||||||
| 1395 | XML::Parser (notree) | ||||||
| 1396 | XML::Parser::Expat (notree) | ||||||
| 1397 | XML::Descent (notree) | ||||||
| 1398 | XML::Parser::EasyTree | ||||||
| 1399 | XML::Handler::Trees | ||||||
| 1400 | XML::Twig | ||||||
| 1401 | XML::Smart | ||||||
| 1402 | XML::Simple using XML::Parser | ||||||
| 1403 | XML::Simple using XML::SAX::PurePerl | ||||||
| 1404 | XML::Simple using XML::LibXML::SAX::Parser | ||||||
| 1405 | XML::Simple using XML::Bare::SAX::Parser | ||||||
| 1406 | XML::TreePP | ||||||
| 1407 | XML::Trivial | ||||||
| 1408 | XML::SAX::Simple | ||||||
| 1409 | XML::Grove::Builder | ||||||
| 1410 | XML::XPath::XMLParser | ||||||
| 1411 | XML::DOM | ||||||
| 1412 | |||||||
| 1413 | To run the comparisons, run the appropriate perl file within the | ||||||
| 1414 | bench directory. ( exe.pl, tree.pl, or notree.pl ) | ||||||
| 1415 | |||||||
| 1416 | The script measures the milliseconds of loading and parsing, and | ||||||
| 1417 | compares the time against the time of XML::Bare. So a 7 means | ||||||
| 1418 | it takes 7 times as long as XML::Bare. | ||||||
| 1419 | |||||||
| 1420 | Here is a combined table of the script run against each alternative | ||||||
| 1421 | using the included test.xml: | ||||||
| 1422 | |||||||
| 1423 | -Module- load parse total | ||||||
| 1424 | XML::Bare 1 1 1 | ||||||
| 1425 | XML::TreePP 2.3063 33.1776 6.1598 | ||||||
| 1426 | XML::Parser::EasyTree 4.9405 25.7278 7.4571 | ||||||
| 1427 | XML::Handler::Trees 7.2303 26.5688 9.6447 | ||||||
| 1428 | XML::Trivial 5.0636 12.4715 7.3046 | ||||||
| 1429 | XML::Smart 6.8138 78.7939 15.8296 | ||||||
| 1430 | XML::Simple (XML::Parser) 2.3346 50.4772 10.7455 | ||||||
| 1431 | XML::Simple (PurePerl) 2.361 261.4571 33.6524 | ||||||
| 1432 | XML::Simple (LibXML) 2.3187 163.7501 23.1816 | ||||||
| 1433 | XML::Simple (XML::Bare) 2.3252 59.1254 10.9163 | ||||||
| 1434 | XML::SAX::Simple 8.7792 170.7313 28.3634 | ||||||
| 1435 | XML::Twig 27.8266 56.4476 31.3594 | ||||||
| 1436 | XML::Grove::Builder 7.1267 26.1672 9.4064 | ||||||
| 1437 | XML::XPath::XMLParser 9.7783 35.5486 13.0002 | ||||||
| 1438 | XML::LibXML (notree) 11.0038 4.5758 10.6881 | ||||||
| 1439 | XML::Parser (notree) 4.4698 17.6448 5.8609 | ||||||
| 1440 | XML::Parser::Expat(notree) 3.7681 50.0382 6.0069 | ||||||
| 1441 | XML::Descent (notree) 6.0525 37.0265 11.0322 | ||||||
| 1442 | Tiny XML (exe) 1.0095 | ||||||
| 1443 | EzXML (exe) 1.1284 | ||||||
| 1444 | XMLIO (exe) 1.0165 | ||||||
| 1445 | |||||||
| 1446 | Here is a combined table of the script run against each alternative | ||||||
| 1447 | using the included feed2.xml: | ||||||
| 1448 | |||||||
| 1449 | -Module- load parse total | ||||||
| 1450 | XML::Bare 1 1 1 | ||||||
| 1451 | XML::TreePP 2.3068 23.7554 7.6921 | ||||||
| 1452 | XML::Parser::EasyTree 4.8799 25.3691 9.6257 | ||||||
| 1453 | XML::Handler::Trees 6.8545 33.1007 13.0575 | ||||||
| 1454 | XML::Trivial 5.0105 32.0043 11.4113 | ||||||
| 1455 | XML::Simple (XML::Parser) 2.3498 41.9007 12.3062 | ||||||
| 1456 | XML::Simple (PurePerl) 2.3551 224.3027 51.7832 | ||||||
| 1457 | XML::Simple (LibXML) 2.3617 88.8741 23.215 | ||||||
| 1458 | XML::Simple (XML::Bare) 2.4319 37.7355 10.2343 | ||||||
| 1459 | XML::Simple 2.7168 90.7203 26.7525 | ||||||
| 1460 | XML::SAX::Simple 8.7386 94.8276 29.2166 | ||||||
| 1461 | XML::Twig 28.3206 48.1014 33.1222 | ||||||
| 1462 | XML::Grove::Builder 7.2021 30.7926 12.9334 | ||||||
| 1463 | XML::XPath::XMLParser 9.6869 43.5032 17.4941 | ||||||
| 1464 | XML::LibXML (notree) 11.0023 5.022 10.5214 | ||||||
| 1465 | XML::Parser (notree) 4.3748 25.0213 5.9803 | ||||||
| 1466 | XML::Parser::Expat(notree) 3.6555 51.6426 7.4316 | ||||||
| 1467 | XML::Descent (notree) 5.9206 155.0289 18.7767 | ||||||
| 1468 | Tiny XML (exe) 1.2212 | ||||||
| 1469 | EzXML (exe) 1.3618 | ||||||
| 1470 | XMLIO (exe) 1.0145 | ||||||
| 1471 | |||||||
| 1472 | These results show that XML::Bare is, at least on the | ||||||
| 1473 | test machine, running all tests within cygwin, faster | ||||||
| 1474 | at loading and parsing than everything being tested | ||||||
| 1475 | against. | ||||||
| 1476 | |||||||
| 1477 | The following things are shown as well: | ||||||
| 1478 | - XML::Bare can parse XML and create a hash tree | ||||||
| 1479 | in less time than it takes LibXML just to parse. | ||||||
| 1480 | - XML::Bare can parse XML and create a tree | ||||||
| 1481 | in less time than all three binary parsers take | ||||||
| 1482 | just to parse. | ||||||
| 1483 | |||||||
| 1484 | Note that the executable parsers are not perl modules | ||||||
| 1485 | and are timed using dummy programs that just uses the | ||||||
| 1486 | library to load and parse the example files. The | ||||||
| 1487 | executables are not included with this program. Any | ||||||
| 1488 | source modifications used to generate the shown test | ||||||
| 1489 | results can be found in the bench/src directory of | ||||||
| 1490 | the distribution | ||||||
| 1491 | |||||||
| 1492 | =head1 CONTRIBUTED CODE | ||||||
| 1493 | |||||||
| 1494 | The XML dequoting code used is taken from L | ||||||
| 1495 | Skvortsov> (I |
||||||
| 1496 | |||||||
| 1497 | =head1 INSTALLATION | ||||||
| 1498 | |||||||
| 1499 | See perlmodinstall for information and options on installing Perl modules. | ||||||
| 1500 | |||||||
| 1501 | =head1 BUGS AND LIMITATIONS | ||||||
| 1502 | |||||||
| 1503 | No bugs have been reported. | ||||||
| 1504 | |||||||
| 1505 | Please report any bugs or feature requests through the web interface at | ||||||
| 1506 | L |
||||||
| 1507 | |||||||
| 1508 | =head1 AVAILABILITY | ||||||
| 1509 | |||||||
| 1510 | The project homepage is L |
||||||
| 1511 | |||||||
| 1512 | The latest version of this module is available from the Comprehensive Perl | ||||||
| 1513 | Archive Network (CPAN). Visit L |
||||||
| 1514 | site near you, or see L |
||||||
| 1515 | |||||||
| 1516 | The development version lives at L |
||||||
| 1517 | and may be cloned from L |
||||||
| 1518 | Instead of sending patches, please fork this project using the standard | ||||||
| 1519 | git and github infrastructure. | ||||||
| 1520 | |||||||
| 1521 | =head1 AUTHORS | ||||||
| 1522 | |||||||
| 1523 | =over 4 | ||||||
| 1524 | |||||||
| 1525 | =item * | ||||||
| 1526 | |||||||
| 1527 | David Helkowski |
||||||
| 1528 | |||||||
| 1529 | =item * | ||||||
| 1530 | |||||||
| 1531 | Nigel Metheringham |
||||||
| 1532 | |||||||
| 1533 | =back | ||||||
| 1534 | |||||||
| 1535 | =head1 COPYRIGHT AND LICENSE | ||||||
| 1536 | |||||||
| 1537 | This software is Copyright (c) 2012 by David Helkowski. | ||||||
| 1538 | |||||||
| 1539 | This is free software, licensed under: | ||||||
| 1540 | |||||||
| 1541 | The GNU General Public License, Version 2, June 1991 | ||||||
| 1542 | |||||||
| 1543 | =cut | ||||||
| 1544 | |||||||
| 1545 | |||||||
| 1546 | __END__ |