File Coverage

blib/lib/HTML/Bare.pm
Criterion Covered Total %
statement 193 695 27.7
branch 78 390 20.0
condition 14 81 17.2
subroutine 25 52 48.0
pod 23 23 100.0
total 333 1241 26.8


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