File Coverage

blib/lib/XML/Bare.pm
Criterion Covered Total %
statement 193 586 32.9
branch 79 322 24.5
condition 14 76 18.4
subroutine 25 44 56.8
pod 16 16 100.0
total 327 1044 31.3


line stmt bran cond sub pod time code
1             #!/usr/bin/perl -w
2             package XML::Bare;
3              
4 8     8   10868 use Carp;
  8         18  
  8         744  
5 8     8   47 use strict;
  8         15  
  8         333  
6 8     8   54 use vars qw( @ISA @EXPORT @EXPORT_OK $VERSION );
  8         16  
  8         892  
7 8     8   8296 use utf8;
  8         78  
  8         48  
8             require Exporter;
9             require DynaLoader;
10             @ISA = qw(Exporter DynaLoader);
11             $VERSION = "0.53";
12 8     8   817 use vars qw($VERSION *AUTOLOAD);
  8         15  
  8         3423  
13              
14             *AUTOLOAD = \&XML::Bare::AUTOLOAD;
15             bootstrap XML::Bare $VERSION;
16              
17             @EXPORT = qw( );
18             @EXPORT_OK = qw( xget merge clean add_node del_node find_node del_node forcearray del_by_perl xmlin xval );
19              
20             =head1 NAME
21              
22             XML::Bare - Minimal XML parser implemented via a C state engine
23              
24             =head1 VERSION
25              
26             0.53
27              
28             =cut
29              
30             sub new {
31 43     43 1 7855 my $class = shift;
32 43         117 my $self = { @_ };
33            
34 43         75 $self->{'i'} = 0;
35 43 100       104 if( $self->{ 'text' } ) {
36 41 50       81 if( $self->{'unsafe'} ) {
37 0         0 $self->{'parser'} = XML::Bare::c_parse_unsafely( $self->{'text'} );
38             }
39             else {
40 41         404 $self->{'parser'} = XML::Bare::c_parse( $self->{'text'} );
41             }
42             }
43             else {
44 2         128 my $res = open( my $XML, $self->{ 'file' } );
45 2 50       9 if( !$res ) {
46 0         0 $self->{ 'xml' } = 0;
47 0         0 return 0;
48             }
49             {
50 2         3 local $/ = undef;
  2         11  
51 2         67 $self->{'text'} = <$XML>;
52             }
53 2         37 close( $XML );
54 2         24 $self->{'parser'} = XML::Bare::c_parse( $self->{'text'} );
55             }
56 43         107 bless $self, "XML::Bare::Object";
57 43 100       126 return $self if( !wantarray );
58 33 100       160 return ( $self, ( $self->{'simple'} ? $self->simple() : $self->parse() ) );
59             }
60              
61             sub simple {
62 2     2 1 1921 return new( @_, simple => 1 );
63             }
64              
65             package XML::Bare::Object;
66              
67 8     8   53 use Carp;
  8         18  
  8         487  
68 8     8   52 use strict;
  8         20  
  8         989  
69              
70             # Stubs ( to allow these functions to be used via an object as well, not just via import or namespace )
71 1     1   7 sub find_by_perl { shift; return XML::Bare::find_by_perl( @_ ); }
  1         5  
72 1     1   6 sub find_node { shift; return XML::Bare::find_node( @_ ); }
  1         4  
73              
74             sub DESTROY {
75 42     42   17392 my $self = shift;
76 8     8   8443 use Data::Dumper;
  8         66617  
  8         26426  
77             #print Dumper( $self );
78 42         79 undef $self->{'text'};
79 42         63 undef $self->{'i'};
80 42         97 $self->free_tree();
81 42         976 undef $self->{'parser'};
82             }
83              
84             sub read_more {
85 0     0   0 my $self = shift;
86 0         0 my %p = ( @_ );
87 0         0 my $i = $self->{'i'}++;
88 0 0       0 if( $p{'text'} ) {
89 0         0 $self->{"text$i"} = $p{'text'};
90 0         0 XML::Bare::c_parse_more( $self->{"text$i"}, $self->{'parser'} );
91             }
92             }
93              
94             sub parse {
95 33     33   48 my $self = shift;
96            
97 33         398 my $res = XML::Bare::xml2obj( $self->{'parser'} );
98            
99 33 50       91 if( defined( $self->{'scheme'} ) ) {
100 0         0 $self->{'xbs'} = new XML::Bare( %{ $self->{'scheme'} } );
  0         0  
101             }
102 33 50       88 if( defined( $self->{'xbs'} ) ) {
103 0         0 my $xbs = $self->{'xbs'};
104 0         0 my $ob = $xbs->parse();
105 0         0 $self->{'xbso'} = $ob;
106 0         0 readxbs( $ob );
107             }
108            
109 33 50 33     98 if( !ref( $res ) && $res < 0 ) { croak "Error at ".$self->lineinfo( -$res ); }
  0         0  
110 33         198 $self->{ 'xml' } = $res;
111            
112 33 50       76 if( defined( $self->{'xbso'} ) ) {
113 0         0 my $ob = $self->{'xbso'};
114 0         0 my $cres = $self->check( $res, $ob );
115 0 0       0 croak( $cres ) if( $cres );
116             }
117            
118 33         103 return $self->{ 'xml' };
119             }
120              
121             # xml bare schema
122             sub check {
123 0     0   0 my ( $self, $node, $scheme, $parent ) = @_;
124            
125 0         0 my $fail = '';
126 0 0       0 if( ref( $scheme ) eq 'ARRAY' ) {
127 0         0 for my $one ( @$scheme ) {
128 0         0 my $res = $self->checkone( $node, $one, $parent );
129 0 0       0 return 0 if( !$res );
130 0         0 $fail .= "$res\n";
131             }
132             }
133 0         0 else { return $self->checkone( $node, $scheme, $parent ); }
134 0         0 return $fail;
135             }
136              
137             sub checkone {
138 0     0   0 my ( $self, $node, $scheme, $parent ) = @_;
139            
140 0         0 for my $key ( keys %$node ) {
141 0 0 0     0 next if( substr( $key, 0, 1 ) eq '_' || $key eq '_att' || $key eq 'comment' );
      0        
142 0 0       0 if( $key eq 'value' ) {
143 0         0 my $val = $node->{ 'value' };
144 0         0 my $regexp = $scheme->{'value'};
145 0 0       0 if( $regexp ) {
146 0 0       0 if( $val !~ m/^($regexp)$/ ) {
147 0         0 my $linfo = $self->lineinfo( $node->{'_i'} );
148 0         0 return "Value of '$parent' node ($val) does not match /$regexp/ [$linfo]";
149             }
150             }
151 0         0 next;
152             }
153 0         0 my $sub = $node->{ $key };
154 0         0 my $ssub = $scheme->{ $key };
155 0 0       0 if( !$ssub ) { #&& ref( $schemesub ) ne 'HASH'
156 0         0 my $linfo = $self->lineinfo( $sub->{'_i'} );
157 0         0 return "Invalid node '$key' in xml [$linfo]";
158             }
159 0 0       0 if( ref( $sub ) eq 'HASH' ) {
160 0         0 my $res = $self->check( $sub, $ssub, $key );
161 0 0       0 return $res if( $res );
162             }
163 0 0       0 if( ref( $sub ) eq 'ARRAY' ) {
164 0         0 my $asub = $ssub;
165 0 0       0 if( ref( $asub ) eq 'ARRAY' ) {
166 0         0 $asub = $asub->[0];
167             }
168 0 0       0 if( $asub->{'_t'} ) {
169 0   0     0 my $max = $asub->{'_max'} || 0;
170 0 0       0 if( $#$sub >= $max ) {
171 0         0 my $linfo = $self->lineinfo( $sub->[0]->{'_i'} );
172 0         0 return "Too many nodes of type '$key'; max $max; [$linfo]"
173             }
174 0   0     0 my $min = $asub->{'_min'} || 0;
175 0 0       0 if( ($#$sub+1)<$min ) {
176 0         0 my $linfo = $self->lineinfo( $sub->[0]->{'_i'} );
177 0         0 return "Not enough nodes of type '$key'; min $min [$linfo]"
178             }
179             }
180 0         0 for( @$sub ) {
181 0         0 my $res = $self->check( $_, $ssub, $key );
182 0 0       0 return $res if( $res );
183             }
184             }
185             }
186 0 0       0 if( my $dem = $scheme->{'_demand'} ) {
187 0         0 for my $req ( @{$scheme->{'_demand'}} ) {
  0         0  
188 0         0 my $ck = $node->{ $req };
189 0 0       0 if( !$ck ) {
190 0         0 my $linfo = $self->lineinfo( $node->{'_i'} );
191 0         0 return "Required node '$req' does not exist [$linfo]"
192             }
193 0 0       0 if( ref( $ck ) eq 'ARRAY' ) {
194 0         0 my $linfo = $self->lineinfo( $node->{'_i'} );
195 0 0       0 return "Required node '$req' is empty array [$linfo]" if( $#$ck == -1 );
196             }
197             }
198             }
199 0         0 return 0;
200             }
201              
202             sub simple {
203 10     10   13 my $self = shift;
204            
205 10         71 my $res = XML::Bare::xml2obj_simple( $self->{'parser'} );#$self->xml2obj();
206            
207 10 50 33     52 if( !ref( $res ) && $res < 0 ) { croak "Error at ".$self->lineinfo( -$res ); }
  0         0  
208 10         14 $self->{ 'xml' } = $res;
209            
210 10         26 return $res;
211             }
212              
213             sub add_node {
214 1     1   2 my ( $self, $node, $name ) = @_;
215 1         4 my @newar;
216             my %blank;
217 1 50       8 $node->{ 'multi_'.$name } = \%blank if( ! $node->{ 'multi_'.$name } );
218 1 50       4 $node->{ $name } = \@newar if( ! $node->{ $name } );
219 1         7 my $newnode = new_node( 0, splice( @_, 3 ) );
220 1         2 push( @{ $node->{ $name } }, $newnode );
  1         3  
221 1         2 return $newnode;
222             }
223              
224             sub add_node_after {
225 0     0   0 my ( $self, $node, $prev, $name ) = @_;
226 0         0 my @newar;
227             my %blank;
228 0 0       0 $node->{ 'multi_'.$name } = \%blank if( ! $node->{ 'multi_'.$name } );
229 0 0       0 $node->{ $name } = \@newar if( ! $node->{ $name } );
230 0         0 my $newnode = $self->new_node( splice( @_, 4 ) );
231            
232 0         0 my $cur = 0;
233 0         0 for my $anode ( @{ $node->{ $name } } ) {
  0         0  
234 0 0       0 $anode->{'_pos'} = $cur if( !$anode->{'_pos'} );
235 0         0 $cur++;
236             }
237 0         0 my $opos = $prev->{'_pos'};
238 0         0 for my $anode ( @{ $node->{ $name } } ) {
  0         0  
239 0 0       0 $anode->{'_pos'}++ if( $anode->{'_pos'} > $opos );
240             }
241 0         0 $newnode->{'_pos'} = $opos + 1;
242            
243 0         0 push( @{ $node->{ $name } }, $newnode );
  0         0  
244            
245 0         0 return $newnode;
246             }
247              
248             sub del_node {
249 0     0   0 my $self = shift;
250 0         0 my $node = shift;
251 0         0 my $name = shift;
252 0         0 my %match = @_;
253 0         0 $node = $node->{ $name };
254 0 0       0 return if( !$node );
255 0         0 for( my $i = 0; $i <= $#$node; $i++ ) {
256 0         0 my $one = $node->[ $i ];
257 0         0 foreach my $key ( keys %match ) {
258 0         0 my $val = $match{ $key };
259 0 0       0 if( $one->{ $key }->{'value'} eq $val ) {
260 0         0 delete $node->[ $i ];
261             }
262             }
263             }
264             }
265              
266             # Created a node of XML hash with the passed in variables already set
267             sub new_node {
268 1     1   1 my $self = shift;
269 1         3 my %parts = @_;
270            
271 1         3 my %newnode;
272 1         3 foreach( keys %parts ) {
273 1         2 my $val = $parts{$_};
274 1 50 33     11 if( m/^_/ || ref( $val ) eq 'HASH' ) {
275 0         0 $newnode{ $_ } = $val;
276             }
277             else {
278 1         4 $newnode{ $_ } = { value => $val };
279             }
280             }
281            
282 1         4 return \%newnode;
283             }
284              
285             sub simplify {
286 0     0   0 my $node = CORE::shift;
287 0         0 my $ref = ref( $node );
288 0 0       0 if( $ref eq 'ARRAY' ) {
289 0         0 my @ret;
290 0         0 for my $sub ( @$node ) {
291 0         0 CORE::push( @ret, simplify( $sub ) );
292             }
293 0         0 return \@ret;
294             }
295 0 0       0 if( $ref eq 'HASH' ) {
296 0         0 my %ret;
297 0         0 my $cnt = 0;
298 0         0 for my $key ( keys %$node ) {
299 0 0 0     0 next if( $key eq 'comment' || $key eq 'value' || $key =~ m/^_/ );
      0        
300 0         0 $cnt++;
301 0         0 $ret{ $key } = simplify( $node->{ $key } );
302             }
303 0 0       0 if( $cnt == 0 ) {
304 0         0 return $node->{'value'};
305             }
306 0         0 return \%ret;
307             }
308 0         0 return $node;
309             }
310              
311             sub hash2xml {
312 0     0   0 my ( $node, $name ) = @_;
313 0         0 my $ref = ref( $node );
314 0 0 0     0 return '' if( $name && $name =~ m/^\_/ );
315 0 0       0 my $txt = $name ? "<$name>" : '';
316 0 0       0 if( $ref eq 'ARRAY' ) {
    0          
317 0         0 $txt = '';
318 0         0 for my $sub ( @$node ) {
319 0         0 $txt .= hash2xml( $sub, $name );
320             }
321 0         0 return $txt;
322             }
323             elsif( $ref eq 'HASH' ) {
324 0         0 for my $key ( keys %$node ) {
325 0         0 $txt .= hash2xml( $node->{ $key }, $key );
326             }
327             }
328             else {
329 0   0     0 $node ||= '';
330 0 0       0 if( $node =~ /[<]/ ) { $txt .= ''; }
  0         0  
331 0         0 else { $txt .= $node; }
332             }
333 0 0       0 if( $name ) {
334 0         0 $txt .= "";
335             }
336            
337 0         0 return $txt;
338             }
339              
340             # Save an XML hash tree into a file
341             sub save {
342 2     2   11047 my $self = shift;
343 2 50       12 return if( ! $self->{ 'xml' } );
344            
345 2         9 my $xml = $self->xml( $self->{'xml'} );
346            
347 2         6 my $len;
348             {
349 8     8   107 use bytes;
  8         641  
  8         58  
  2         3  
350 2         4 $len = length( $xml );
351             }
352 2 50       8 return if( !$len );
353            
354             # This is intentionally just :utf8 and not :encoding(UTF-8)
355             # :encoding(UTF-8) checks the data for actually being valid UTF-8, and doing so would slow down the file write
356             # See http://perldoc.perl.org/functions/binmode.html
357            
358 2         8 my $os = $^O;
359 2         5 my $F;
360            
361             # Note on the following conditional OS check... WTF? This is total bullshit.
362 2 50       9 if( $os eq 'MSWin32' ) {
363 0         0 open( $F, '>:utf8', $self->{ 'file' } );
364 0         0 binmode $F;
365             }
366             else {
367 2         320 open( $F, '>', $self->{ 'file' } );
368 2         16 binmode $F, ':utf8';
369             }
370 2         25 print $F $xml;
371            
372 2         93 seek( $F, 0, 2 );
373 2         41 my $cursize = tell( $F );
374 2 50       9 if( $cursize != $len ) { # concurrency; we are writing a smaller file
375 0         0 warn "Truncating File $self->{'file'}";
376 0         0 `cp $self->{'file'} $self->{'file'}.bad`;
377 0         0 truncate( F, $len );
378             }
379 2         122 seek( $F, 0, 2 );
380 2         5 $cursize = tell( $F );
381 2 50       9 if( $cursize != $len ) { # still not the right size even after truncate??
382 0         0 die "Write problem; $cursize != $len";
383             }
384 2         168 close $F;
385             }
386              
387             sub xml {
388 18     18   60 my ( $self, $obj, $name ) = @_;
389 18 50       38 if( !$name ) {
390 18         21 my %hash;
391 18         31 $hash{0} = $obj;
392 18         45 return XML::Bare::obj2xml( \%hash, '', 0 );
393             }
394 0         0 my %hash;
395 0         0 $hash{$name} = $obj;
396 0         0 return XML::Bare::obj2xml( \%hash, '', 0 );
397             }
398              
399             sub html {
400 0     0   0 my ( $self, $obj, $name ) = @_;
401 0         0 my $pre = '';
402 0 0       0 if( $self->{'style'} ) {
403 0         0 $pre = "";
404             }
405 0 0       0 if( !$name ) {
406 0         0 my %hash;
407 0         0 $hash{0} = $obj;
408 0         0 return $pre.obj2html( \%hash, '', 0 );
409             }
410 0         0 my %hash;
411 0         0 $hash{$name} = $obj;
412 0         0 return $pre.obj2html( \%hash, '', 0 );
413             }
414              
415             sub lineinfo {
416 0     0   0 my $self = shift;
417 0         0 my $res = shift;
418 0         0 my $line = 1;
419 0         0 my $j = 0;
420 0         0 for( my $i=0;$i<$res;$i++ ) {
421 0         0 my $let = substr( $self->{'text'}, $i, 1 );
422 0 0       0 if( ord($let) == 10 ) {
423 0         0 $line++;
424 0         0 $j = $i;
425             }
426             }
427 0         0 my $part = substr( $self->{'text'}, $res, 10 );
428 0         0 $part =~ s/\n//g;
429 0         0 $res -= $j;
430 0 0       0 if( $self->{'offset'} ) {
431 0         0 my $off = $self->{'offset'};
432 0         0 $line += $off;
433 0         0 return "$off line $line char $res \"$part\"";
434             }
435 0         0 return "line $line char $res \"$part\"";
436             }
437              
438 42     42   45 sub free_tree { my $self = shift; XML::Bare::free_tree_c( $self->{'parser'} ); }
  42         166  
439              
440             package XML::Bare;
441              
442             sub find_node {
443 1     1 1 2 my $node = shift;
444 1         3 my $name = shift;
445 1         3 my %match = @_;
446 1 50       20 return 0 if( ! defined $node );
447 1 50       4 $node = $node->{ $name } or return 0;
448 1 50       5 $node = [ $node ] if( ref( $node ) eq 'HASH' );
449 1 50       3 if( ref( $node ) eq 'ARRAY' ) {
450 1         4 for( my $i = 0; $i <= $#$node; $i++ ) {
451 1         1 my $one = $node->[ $i ];
452 1         3 for my $key ( keys %match ) {
453 1         1 my $val = $match{ $key };
454 1 50       2 croak('undefined value in find') unless defined $val;
455 1 50       4 if( $one->{ $key }{'value'} eq $val ) {
456 1         5 return $node->[ $i ];
457             }
458             }
459             }
460             }
461 0         0 return 0;
462             }
463              
464             sub xget {
465 0     0 1 0 my $hash = shift;
466 0         0 return map $_->{'value'}, @{$hash}{@_};
  0         0  
467             }
468              
469             sub forcearray {
470 0     0 1 0 my $ref = shift;
471 0 0       0 return [] if( !$ref );
472 0 0       0 return $ref if( ref( $ref ) eq 'ARRAY' );
473 0         0 return [ $ref ];
474             }
475              
476             sub merge {
477             # shift in the two array references as well as the field to merge on
478 0     0 1 0 my ( $a, $b, $id ) = @_;
479 0 0       0 my %hash = map { $_->{ $id } ? ( $_->{ $id }->{ 'value' } => $_ ) : ( 0 => 0 ) } @$a;
  0         0  
480 0         0 for my $one ( @$b ) {
481 0 0       0 next if( !$one->{ $id } );
482 0         0 my $short = $hash{ $one->{ $id }->{ 'value' } };
483 0 0       0 next if( !$short );
484 0         0 foreach my $key ( keys %$one ) {
485 0 0 0     0 next if( $key eq '_pos' || $key eq 'id' );
486 0         0 my $cur = $short->{ $key };
487 0         0 my $add = $one->{ $key };
488 0 0       0 if( !$cur ) { $short->{ $key } = $add; }
  0         0  
489             else {
490 0         0 my $type = ref( $cur );
491 0 0       0 if( $type eq 'HASH' ) {
492 0         0 my @arr;
493 0         0 $short->{ $key } = \@arr;
494 0         0 push( @arr, $cur );
495             }
496 0 0       0 if( ref( $add ) eq 'HASH' ) {
497 0         0 push( @{$short->{ $key }}, $add );
  0         0  
498             }
499             else { # we are merging an array
500 0         0 push( @{$short->{ $key }}, @$add );
  0         0  
501             }
502             }
503             # we need to deal with the case where this node
504             # is already there, either alone or as an array
505             }
506             }
507 0         0 return $a;
508             }
509              
510             sub clean {
511 0     0 1 0 my $ob = new XML::Bare( @_ );
512 0         0 my $root = $ob->parse();
513 0 0       0 if( $ob->{'save'} ) {
514 0 0       0 $ob->{'file'} = $ob->{'save'} if( "$ob->{'save'}" ne "1" );
515 0         0 $ob->save();
516 0         0 return;
517             }
518 0         0 return $ob->xml( $root );
519             }
520              
521             sub xmlin {
522 8     8 1 25 my $text = shift;
523 8         11 my %ops = ( @_ );
524 8         15 my $ob = new XML::Bare( text => $text );
525 8         18 my $simple = $ob->simple();
526 8 50       15 if( !$ops{'keeproot'} ) {
527 8         19 my @keys = keys %$simple;
528 8         9 my $first = $keys[0];
529 8 50       18 $simple = $simple->{ $first } if( $first );
530             }
531 8         16 return $simple;
532             }
533              
534             sub tohtml {
535 0     0 1 0 my %ops = ( @_ );
536 0         0 my $ob = new XML::Bare( %ops );
537 0   0     0 return $ob->html( $ob->parse(), $ops{'root'} || 'xml' );
538             }
539              
540             sub readxbs { # xbs = xml bare schema
541 0     0 1 0 my $node = shift;
542 0         0 my @demand;
543 0         0 for my $key ( keys %$node ) {
544 0 0 0     0 next if( substr( $key, 0, 1 ) eq '_' || $key eq '_att' || $key eq 'comment' );
      0        
545 0 0       0 if( $key eq 'value' ) {
546 0         0 my $val = $node->{'value'};
547 0 0       0 delete $node->{'value'} if( $val =~ m/^\W*$/ );
548 0         0 next;
549             }
550 0         0 my $sub = $node->{ $key };
551            
552 0 0       0 if( $key =~ m/([a-z_]+)([^a-z_]+)/ ) {
553 0         0 my $name = $1;
554 0         0 my $t = $2;
555 0         0 my $min;
556             my $max;
557 0 0       0 if( $t eq '+' ) {
    0          
    0          
    0          
    0          
558 0         0 $min = 1;
559 0         0 $max = 1000;
560             }
561             elsif( $t eq '*' ) {
562 0         0 $min = 0;
563 0         0 $max = 1000;
564             }
565             elsif( $t eq '?' ) {
566 0         0 $min = 0;
567 0         0 $max = 1;
568             }
569             elsif( $t eq '@' ) {
570 0         0 $name = 'multi_'.$name;
571 0         0 $min = 1;
572 0         0 $max = 1;
573             }
574             elsif( $t =~ m/\{([0-9]+),([0-9]+)\}/ ) {
575 0         0 $min = $1;
576 0         0 $max = $2;
577 0         0 $t = 'r'; # range
578             }
579            
580 0 0       0 if( ref( $sub ) eq 'HASH' ) {
581 0         0 my $res = readxbs( $sub );
582 0         0 $sub->{'_t'} = $t;
583 0         0 $sub->{'_min'} = $min;
584 0         0 $sub->{'_max'} = $max;
585             }
586 0 0       0 if( ref( $sub ) eq 'ARRAY' ) {
587 0         0 for my $item ( @$sub ) {
588 0         0 my $res = readxbs( $item );
589 0         0 $item->{'_t'} = $t;
590 0         0 $item->{'_min'} = $min;
591 0         0 $item->{'_max'} = $max;
592             }
593             }
594            
595 0 0       0 push( @demand, $name ) if( $min );
596 0         0 $node->{$name} = $node->{$key};
597 0         0 delete $node->{$key};
598             }
599             else {
600 0 0       0 if( ref( $sub ) eq 'HASH' ) {
601 0         0 readxbs( $sub );
602 0         0 $sub->{'_t'} = 'r';
603 0         0 $sub->{'_min'} = 1;
604 0         0 $sub->{'_max'} = 1;
605             }
606 0 0       0 if( ref( $sub ) eq 'ARRAY' ) {
607 0         0 for my $item ( @$sub ) {
608 0         0 readxbs( $item );
609 0         0 $item->{'_t'} = 'r';
610 0         0 $item->{'_min'} = 1;
611 0         0 $item->{'_max'} = 1;
612             }
613             }
614            
615 0         0 push( @demand, $key );
616             }
617             }
618 0 0       0 if( @demand ) { $node->{'_demand'} = \@demand; }
  0         0  
619             }
620              
621             sub find_by_perl {
622 1     1 1 2 my $arr = shift;
623 1         1 my $cond = shift;
624            
625 1         2 my @res;
626 1 50       22 if( ref( $arr ) eq 'ARRAY' ) {
627 0         0 $cond =~ s/-([a-z_]+)/\$ob->\{'$1'\}->\{'value'\}/gi;
628 0 0       0 foreach my $ob ( @$arr ) { push( @res, $ob ) if( eval( $cond ) ); }
  0         0  
629             }
630             else {
631 1         13 $cond =~ s/-([a-z_]+)/\$arr->\{'$1'\}->\{'value'\}/gi;
632 1 50       98 push( @res, $arr ) if( eval( $cond ) );
633             }
634 1         7 return \@res;
635             }
636              
637             sub del_by_perl {
638 0     0 1 0 my $arr = shift;
639 0         0 my $cond = shift;
640 0         0 $cond =~ s/-value/\$ob->\{'value'\}/g;
641 0         0 $cond =~ s/-([a-z]+)/\$ob->\{'$1'\}->\{'value'\}/g;
642 0         0 my @res;
643 0         0 for( my $i = 0; $i <= $#$arr; $i++ ) {
644 0         0 my $ob = $arr->[ $i ];
645 0 0       0 delete $arr->[ $i ] if( eval( $cond ) );
646             }
647 0         0 return \@res;
648             }
649              
650 0     0 1 0 sub newhash { shift; return { value => shift }; }
  0         0  
651              
652             sub xval {
653 0 0 0 0 1 0 return $_[0] ? $_[0]->{'value'} : ( $_[1] || '' );
654             }
655              
656             sub obj2xml {
657 96     96 1 180 my ( $objs, $name, $pad, $level, $pdex ) = @_;
658 96 100       271 $level = 0 if( !$level );
659 96 100       186 $pad = '' if( $level <= 2 );
660 96         106 my $xml = '';
661 96         88 my $att = '';
662 96         93 my $imm = 1;
663 96 50       150 return '' if( !$objs );
664             #return $objs->{'_raw'} if( $objs->{'_raw'} );
665 460         582 my @dex = sort {
666 96         470 my $oba = $objs->{ $a };
667 460         503 my $obb = $objs->{ $b };
668 460         411 my $posa = 0;
669 460         595 my $posb = 0;
670 460 100       782 $oba = $oba->[0] if( ref( $oba ) eq 'ARRAY' );
671 460 100       728 $obb = $obb->[0] if( ref( $obb ) eq 'ARRAY' );
672 460 100 100     726 if( ref( $oba ) eq 'HASH' ) { $posa = $oba->{'_pos'} || 0; }
  102         235  
673 460 100 100     706 if( ref( $obb ) eq 'HASH' ) { $posb = $obb->{'_pos'} || 0; }
  111         229  
674 460         1046 return $posa <=> $posb;
675             } keys %$objs;
676 96         156 for my $i ( @dex ) {
677 365   100     955 my $obj = $objs->{ $i } || '';
678 365         485 my $type = ref( $obj );
679 365 100 66     1077 if( $type eq 'ARRAY' ) {
    100          
680 4         5 $imm = 0;
681            
682             my @dex2 = sort {
683 4 50       9 if( !$a ) { return 0; }
  3         7  
  0         0  
684 3 50       8 if( !$b ) { return 0; }
  0         0  
685 3 50 33     14 if( ref( $a ) eq 'HASH' && ref( $b ) eq 'HASH' ) {
686 3         7 my $posa = $a->{'_pos'};
687 3         5 my $posb = $b->{'_pos'};
688 3 50       6 if( !$posa ) { $posa = 0; }
  0         0  
689 3 50       5 if( !$posb ) { $posb = 0; }
  0         0  
690 3         14 return $posa <=> $posb;
691             }
692 0         0 return 0;
693             } @$obj;
694            
695 4         8 for my $j ( @dex2 ) {
696 7         34 $xml .= obj2xml( $j, $i, $pad.' ', $level+1, $#dex );
697             }
698             }
699             elsif( $type eq 'HASH' && $i !~ /^_/ ) {
700 80 100       157 if( $obj->{ '_att' } ) {
701 9 50       40 $att .= ' ' . $i . '="' . $obj->{ 'value' } . '"' if( $i !~ /^_/ );;
702             }
703             else {
704 71         74 $imm = 0;
705 71         379 $xml .= obj2xml( $obj , $i, $pad.' ', $level+1, $#dex );
706             }
707             }
708             else {
709 281 100       1207 if( $i eq 'comment' ) { $xml .= '' . "\n"; }
  3 100       12  
    50          
710             elsif( $i eq 'value' ) {
711 42 100       86 if( $level > 1 ) { # $#dex < 4 &&
712 35 100 66     164 if( $obj && $obj =~ /[<>&;]/ ) { $xml .= ''; }
  1         4  
713 34 100       205 else { $xml .= $obj if( $obj =~ /\S/ ); }
714             }
715             }
716             elsif( $i =~ /^_/ ) {}
717 0         0 else { $xml .= '<' . $i . '>' . $obj . ''; }
718             }
719             }
720 96 100       178 my $pad2 = $imm ? '' : $pad;
721 96 100       143 my $cr = $imm ? '' : "\n";
722 96 50       215 if( substr( $name, 0, 1 ) ne '_' ) {
723 96 100       152 if( $name ) {
724 60 100       75 if( $xml ) {
725 47         186 $xml = $pad . '<' . $name . $att . '>' . $cr . $xml . $pad2 . '';
726             }
727             else {
728 13         31 $xml = $pad . '<' . $name . $att . ' />';
729             }
730             }
731 96 100       505 return $xml."\n" if( $level > 1 );
732 36         273 return $xml;
733             }
734 0           return '';
735             }
736              
737             sub obj2html {
738 0     0 1   my ( $objs, $name, $pad, $level, $pdex ) = @_;
739            
740 0           my $less = "<";
741 0           my $more = ">";
742 0           my $tn0 = "";
743 0           my $tn1 = "";
744 0           my $eq0 = "";
745 0           my $eq1 = "";
746 0           my $qo0 = "";
747 0           my $qo1 = "";
748 0           my $sp0 = "";
749 0           my $sp1 = "";
750 0           my $cd0 = "";
751 0           my $cd1 = "";
752            
753 0 0         $level = 0 if( !$level );
754 0 0         $pad = '' if( $level == 1 );
755 0           my $xml = '';
756 0           my $att = '';
757 0           my $imm = 1;
758 0 0         return '' if( !$objs );
759 0           my @dex = sort {
760 0           my $oba = $objs->{ $a };
761 0           my $obb = $objs->{ $b };
762 0           my $posa = 0;
763 0           my $posb = 0;
764 0 0         $oba = $oba->[0] if( ref( $oba ) eq 'ARRAY' );
765 0 0         $obb = $obb->[0] if( ref( $obb ) eq 'ARRAY' );
766 0 0 0       if( ref( $oba ) eq 'HASH' ) { $posa = $oba->{'_pos'} || 0; }
  0            
767 0 0 0       if( ref( $obb ) eq 'HASH' ) { $posb = $obb->{'_pos'} || 0; }
  0            
768 0           return $posa <=> $posb;
769             } keys %$objs;
770            
771 0 0         if( $objs->{'_cdata'} ) {
772 0           my $val = $objs->{'value'};
773 0           $val =~ s/^(\s*\n)+//;
774 0           $val =~ s/\s+$//;
775 0           $val =~ s/&/&/g;
776 0           $val =~ s/
777 0           $objs->{'value'} = $val;
778             #$xml = "$less![CDATA[
$val
]]$more";
779 0           $cd0 = "$less![CDATA[
";
780 0           $cd1 = "]]$more";
781             }
782 0           for my $i ( @dex ) {
783 0   0       my $obj = $objs->{ $i } || '';
784 0           my $type = ref( $obj );
785 0 0 0       if( $type eq 'ARRAY' ) {
    0          
786 0           $imm = 0;
787            
788             my @dex2 = sort {
789 0 0         if( !$a ) { return 0; }
  0            
  0            
790 0 0         if( !$b ) { return 0; }
  0            
791 0 0 0       if( ref( $a ) eq 'HASH' && ref( $b ) eq 'HASH' ) {
792 0           my $posa = $a->{'_pos'};
793 0           my $posb = $b->{'_pos'};
794 0 0         if( !$posa ) { $posa = 0; }
  0            
795 0 0         if( !$posb ) { $posb = 0; }
  0            
796 0           return $posa <=> $posb;
797             }
798 0           return 0;
799             } @$obj;
800            
801 0           for my $j ( @dex2 ) { $xml .= obj2html( $j, $i, $pad.'  ', $level+1, $#dex ); }
  0            
802             }
803             elsif( $type eq 'HASH' && $i !~ /^_/ ) {
804 0 0         if( $obj->{ '_att' } ) {
805 0           my $val = $obj->{ 'value' };
806 0           $val =~ s/
807 0 0         if( $val eq '' ) {
808 0 0         $att .= " $i" if( $i !~ /^_/ );
809             }
810             else {
811 0 0         $att .= " $i$eq0=$eq1$qo0\"$qo1$val$qo0\"$qo1" if( $i !~ /^_/ );
812             }
813             }
814             else {
815 0           $imm = 0;
816 0           $xml .= obj2html( $obj , $i, $pad.'  ', $level+1, $#dex );
817             }
818             }
819             else {
820 0 0         if( $i eq 'comment' ) { $xml .= "$less!--" . $obj . "--$more" . "
\n"; }
  0 0          
    0          
821             elsif( $i eq 'value' ) {
822 0 0         if( $level > 1 ) {
823 0 0 0       if( $obj && $obj =~ /[<>&;]/ && ! $objs->{'_cdata'} ) { $xml .= "$less![CDATA[$obj]]$more"; }
  0   0        
824 0 0         else { $xml .= $obj if( $obj =~ /\S/ ); }
825             }
826             }
827             elsif( $i =~ /^_/ ) {}
828 0           else { $xml .= "$less$tn0$i$tn1$more$obj$less/$tn0$i$tn1$more"; }
829             }
830             }
831 0 0         my $pad2 = $imm ? '' : $pad;
832 0 0         if( substr( $name, 0, 1 ) ne '_' ) {
833 0 0         if( $name ) {
834 0 0         if( $imm ) {
835 0 0         if( $xml =~ /\S/ ) {
836 0           $xml = "$sp0$pad$sp1$less$tn0$name$tn1$att$more$cd0$xml$cd1$less/$tn0$name$tn1$more";
837             }
838             else {
839 0           $xml = "$sp0$pad$sp1$less$tn0$name$tn1$att/$more";
840             }
841             }
842             else {
843 0 0         if( $xml =~ /\S/ ) {
844 0           $xml = "$sp0$pad$sp1$less$tn0$name$tn1$att$more
$xml
$sp0$pad$sp1$less/$tn0$name$tn1$more";
845             }
846 0           else { $xml = "$sp0$pad$sp1$less$tn0$name$tn1$att/$more"; }
847             }
848             }
849 0 0         $xml .= "
" if( $objs->{'_br'} );
850 0 0         if( $objs->{'_note'} ) {
851 0           $xml .= "
";
852 0           my $note = $objs->{'_note'}{'value'};
853 0           my @notes = split( /\|/, $note );
854 0           for( @notes ) {
855 0           $xml .= "
$sp0$pad$sp1<!-- $_ -->
";
856             }
857             }
858 0 0         return $xml."
\n" if( $level );
859 0           return $xml;
860             }
861 0           return '';
862             }
863              
864             1;
865              
866             __END__