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__ |