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 .= "$name>"; | ||||
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 . '' . $i . '>'; } | ||||
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 . '' . $name . '>'; | ||||
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/</g; | |||||
786 | 0 | $objs->{'value'} = $val; | |||||
787 | #$html = "$less![CDATA[ $val |
||||||
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/</g; | |||||
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__ |