File Coverage

blib/lib/XML/Trivial.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package XML::Trivial;
2              
3 2     2   44827 use XML::Parser::Expat;
  0            
  0            
4              
5             use strict;
6             use warnings;
7              
8             our $VERSION = '0.06';
9              
10             my @stack;
11             my @nsstack = ({''=>'',
12             xml=>'http://www.w3.org/XML/1998/namespace',
13             xmlns=>'http://www.w3.org/2000/xmlns/'});
14             my $cur;
15              
16             sub parseFile {
17             my $filename = shift;
18             local *FH;
19             open(FH,$filename);
20             my $ret = XML::Trivial::parse(*FH);
21             close(FH);
22             return $ret;
23             }
24              
25             sub parse {
26             @stack = undef;
27             $cur = 0;
28             my $expat = new XML::Parser::Expat;
29             $expat->setHandlers(XMLDecl => \&XML::Trivial::_xmldecl,
30             Doctype => \&XML::Trivial::_doctype,
31             Start => \&XML::Trivial::_startElement,
32             End => \&XML::Trivial::_endElement,
33             Char => \&XML::Trivial::_char,
34             Comment => \&XML::Trivial::_comment,
35             Proc => \&XML::Trivial::_proc,
36             CdataStart => \&XML::Trivial::_startCDATA,
37             CdataEnd => \&XML::Trivial::_endCDATA
38             );
39             $expat->parse(@_);
40             $expat->release;
41             my $ret = XML::Trivial::Element->new($stack[0]);
42             return $ret;
43             }
44              
45             #handlers
46              
47             sub _xmldecl {
48             my ($p, $ver, $enc, $std) = @_;
49             push @{$stack[$cur]},('-xml',[$ver,$enc,$std]);
50             }
51              
52             sub _doctype {
53             my ($p, $nam, $sid, $pid, $int) = @_;
54             push @{$stack[$cur]},('-doc',[$nam, $sid, $pid, $int]);
55             }
56              
57             sub _startElement {
58             my ($p, $el, %atts) = @_;
59             $cur++;
60             push @stack, [$el,\%atts];
61             my %ns;
62             my ($name, $value);
63             while (($name, $value) = each %atts) {
64             $name =~ /^xmlns(:|)(.*)$/ and $ns{$2} = $value;
65             }
66             push @nsstack, \%ns;
67             }
68              
69             sub _endElement {
70             my ($p, $el) = @_;
71             my $n = XML::Trivial::Element->new($stack[$cur], \@nsstack);
72             $cur--;
73             pop @stack;
74             push @{$stack[$cur]},('-elm',$n);
75             pop @nsstack;
76             }
77              
78             sub _char {
79             my ($p, $str) = @_;
80             if ($stack[$cur][@{$stack[$cur]}-2] eq '-txt' or
81             $stack[$cur][@{$stack[$cur]}-2] eq '-cds') {
82             $stack[$cur][@{$stack[$cur]}-1] .= $str;
83             } elsif ($stack[$cur][@{$stack[$cur]}-1] eq '-cds') {
84             push @{$stack[$cur]},$str;
85             } else {
86             push @{$stack[$cur]},('-txt',$str);
87             }
88             }
89              
90             sub _comment {
91             my ($p, $str) = @_;
92             push @{$stack[$cur]},('-not',$str);
93             }
94              
95             sub _proc {
96             my ($p, $tgt, $data) = @_;
97             push @{$stack[$cur]},('-pro',[$tgt, $data]);
98             }
99              
100             sub _startCDATA {
101             my ($p) = @_;
102             push @{$stack[$cur]},'-cds';
103             }
104              
105             sub _endCDATA {
106             my ($p) = @_;
107             if ($stack[$cur][@{$stack[$cur]}-1] eq '-cds') {
108             push @{$stack[$cur]},undef;
109             }
110             $stack[$cur][@{$stack[$cur]}-2] = '-cdt';
111             }
112              
113              
114             package XML::Trivial::Element;
115             use Scalar::Util 'weaken';
116             use strict;
117             use warnings;
118              
119             sub new {
120             my ($class, $aref, $nsstack) = @_;
121             tie my %h, $class, $aref || [], $nsstack;
122             my $self = bless \%h, $class;
123             my %ehns;
124             my $key;
125             my $s = tied(%$self);
126             foreach (@{$s->{ea}}) {
127             tied(%$_)->{parent} = $self;
128             weaken(tied(%$_)->{parent});#because it is circular ref
129             $key = $_->ns(undef).'*'.$_->ln();
130             $ehns{$key} = $_ unless exists $ehns{$key};
131             }
132             $s->{ehns} = \%ehns;
133             return $self;
134             }
135              
136             sub TIEHASH {
137             my ($class, $a, $nsstack) = @_;
138             #$a is arrayref like [name, atts, type1, data1, type2, data2, ...]
139             my @ea; my %eh;#elements
140             my @ta; #texts
141             my @ca; #cdatas
142             my @pa; my %ph;#process instructions
143             my @na; #notes
144             my $firstkey;
145             my $lastkey;
146             my %next;
147             my %nh; #hash of namespaces in scope
148             foreach (@$nsstack) {
149             while (my ($name, $value) = each %$_) {
150             $nh{$name} = $value;
151             }
152             }
153             for (my $i = 0; $i < @$a; $i += 2) {
154             if ($$a[$i] =~ /^-(.*)$/) {
155             if ($1 eq 'elm') {
156             push @ea, $$a[$i+1];
157             unless ($eh{$$a[$i+1]->a(0)}) {
158             $eh{$$a[$i+1]->a(0)} = $$a[$i+1];
159             if ($lastkey) {
160             $next{$lastkey} = $$a[$i+1]->a(0);
161             }
162             $lastkey = $$a[$i+1]->a(0);
163             }
164             $firstkey ||= $$a[$i+1]->a(0);
165             } elsif ($1 eq 'txt') {
166             push @ta, $$a[$i+1];
167             } elsif ($1 eq 'cdt') {
168             push @ta, $$a[$i+1];
169             push @ca, $$a[$i+1];
170             } elsif ($1 eq 'pro') {
171             push @pa, $$a[$i+1];
172             unless ($ph{$$a[$i+1][0]}) {
173             $ph{$$a[$i+1][0]} = $$a[$i+1][1];
174             }
175             } elsif ($1 eq 'not') {
176             push @na, $$a[$i+1];
177             }
178             }
179             }
180             return bless {a=>$a,
181             ea=>\@ea, eh=>\%eh,
182             ta=>\@ta,
183             ca=>\@ca,
184             pa=>\@pa, ph=>\%ph,
185             na=>\@na,
186             nh=>\%nh,
187             parent=>undef,
188             firstkey=>$firstkey,
189             next=>\%next
190             }, $class;
191             }
192              
193             sub FETCH {
194             my ($self, $key) = @_;
195             $key =~ /^\d+$/ and return $$self{ea}[$key];
196             $key =~ /\*/ and return $$self{ehns}{$key};
197             return $$self{eh}{$key};
198             }
199              
200             sub EXISTS {
201             my ($self, $key) = @_;
202             $key =~ /\*/ and return exists $$self{ehns}{$key};
203             return exists $$self{eh}{$key};
204             }
205              
206             sub FIRSTKEY {
207             return $$_[0]{firstkey};
208             }
209              
210             sub NEXTKEY {
211             return $$_[0]{next}{$$_[1]};
212             }
213              
214             sub SCALAR {
215             return $$_[0]{a}[0];
216             }
217              
218             sub p { #parent
219             my ($self) = @_;
220             return tied(%$self)->{parent};
221             }
222              
223             sub xv { #xml version
224             my ($self) = @_;
225             my $s = tied(%$self);
226             defined $s->{parent} and return $s->{parent}->xv();
227             $s->{a}[0] eq '-xml' and return $s->{a}[1][0];
228             return '1.0';
229             }
230              
231             sub xe { #xml encoding
232             my ($self) = @_;
233             my $s = tied(%$self);
234             defined $s->{parent} and return $s->{parent}->xe();
235             $s->{a}[0] eq '-xml' and defined $s->{a}[1][1] and
236             return $s->{a}[1][1];
237             return 'UTF-8';
238             }
239              
240             sub xs { #xml standalone
241             my ($self) = @_;
242             my $s = tied(%$self);
243             defined $s->{parent} and return $s->{parent}->xs();
244             $s->{a}[0] eq '-xml' and defined $s->{a}[1][2] and
245             return $s->{a}[1][2]?1:0;
246             return undef;
247             }
248              
249             sub dn { #doctype name
250             my ($self) = @_;
251             my $s = tied(%$self);
252             defined $s->{parent} and return $s->{parent}->dn();
253             my $i = 0;
254             while ($s->{a}[$i] =~ /^-/) {
255             $s->{a}[$i] eq '-doc' and return $s->{a}[$i+1][0];
256             $i += 2;
257             }
258             return undef;
259             }
260              
261             sub ds { #doctype system
262             my ($self) = @_;
263             my $s = tied(%$self);
264             defined $s->{parent} and return $s->{parent}->ds();
265             my $i = 0;
266             while ($s->{a}[$i] =~ /^-/) {
267             $s->{a}[$i] eq '-doc' and return $s->{a}[$i+1][1];
268             $i += 2;
269             }
270             return undef;
271             }
272              
273             sub dp { #doctype public
274             my ($self) = @_;
275             my $s = tied(%$self);
276             defined $s->{parent} and return $s->{parent}->dp();
277             my $i = 0;
278             while ($s->{a}[$i] =~ /^-/) {
279             $s->{a}[$i] eq '-doc' and return $s->{a}[$i+1][2];
280             $i += 2;
281             }
282             return undef;
283             }
284              
285             sub en { #element (qualified) name
286             my ($self) = @_;
287             return tied(%$self)->{a}[0];
288             }
289              
290             sub ep { #element prefix
291             my ($self) = @_;
292             tied(%$self)->{a}[0] =~ /^([^:]*):.*$/ and return $1;
293             return '';
294             }
295              
296             sub ln { #local (unqualified) name
297             my ($self) = @_;
298             tied(%$self)->{a}[0] =~ /^([^:]*:)?([^:]*)$/ and return $2;
299             }
300              
301             sub ns { #namespace
302             my ($self, $p) = @_;
303             1 == @_ and return wantarray ? %{tied(%$self)->{nh}} : tied(%$self)->{nh};
304             defined $p and return tied(%$self)->{nh}{$p};
305             return tied(%$self)->{nh}{$self->ep()};
306             }
307              
308             sub ah { #attribute hash
309             my ($self, $key, $ns) = @_;
310             1 == @_ and return wantarray ? %{tied(%$self)->{a}[1]} : tied(%$self)->{a}[1];
311             2 == @_ and return tied(%$self)->{a}[1]{$key};
312             my $s = tied(%$self);
313             my ($ret, $name, $value);
314             if (defined $key) {
315             if (defined $ns) {
316             $key =~ /:/ and return undef;
317             while (($name, $value) = each %{$s->{a}[1]}) {
318             unless (defined $ret) {
319             if ($name eq $key) {
320             $ns eq '' and $ret = $value;
321             } elsif ($name =~ /^([^:]+):$key$/) {
322             exists $s->{nh}{$1} and $s->{nh}{$1} eq $ns and
323             $ret = $value;
324             }
325             }
326             }
327             return $ret;
328             } else {
329             $ret = {};
330             $key =~ /:/ and return $ret;
331             while (($name, $value) = each %{$s->{a}[1]}) {
332             if ($name eq $key) {
333             $$ret{''} = $value;
334             } elsif ($name =~ /^((([^:]+):)|)$key$/) {
335             $$ret{$s->{nh}{$3}} = $value;
336             }
337             }
338             }
339             } else {
340             $ns = $self->ns(undef) unless defined $ns;
341             $ret = {};
342             if ($ns eq '') {
343             while (($name, $value) = each %{$s->{a}[1]}) {
344             $name !~ /:/ and $$ret{$name} = $value;
345             }
346             } else {
347             while (($name, $value) = each %{$s->{a}[1]}) {
348             $name =~ /^([^:]+):([^:]+)$/ and $s->{nh}{$1} eq $ns and
349             $$ret{$2} = $value;
350             }
351             }
352             }
353             return wantarray ? %$ret : $ret;
354             }
355              
356             sub eh { #element hash
357             my ($self, $key) = @_;
358             1 == @_ and return wantarray ? %{tied(%$self)->{eh}} : tied(%$self)->{eh};
359             $key =~ /\*/ and return tied(%$self)->{ehns}{$key};
360             return tied(%$self)->{eh}{$key};
361             }
362              
363             sub ea { #element array
364             my ($self, $index) = @_;
365             1 == @_ and return wantarray ? @{tied(%$self)->{ea}} : tied(%$self)->{ea};
366             return tied(%$self)->{ea}[$index];
367             }
368              
369             sub ta { #text array (ca included)
370             my ($self, $index) = @_;
371             (1 == @_ or not defined $index)
372             and return wantarray ? @{tied(%$self)->{ta}} : tied(%$self)->{ta};
373             return tied(%$self)->{ta}[$index];
374             }
375              
376             sub ca { #cdata array
377             my ($self, $index) = @_;
378             (1 == @_ or not defined $index)
379             and return wantarray ? @{tied(%$self)->{ca}} : tied(%$self)->{ca};
380             return tied(%$self)->{ca}[$index];
381             }
382              
383             sub ts { #text serialized
384             my ($self) = @_;
385             return join '', @{tied(%$self)->{ta}};
386             }
387              
388             sub pa { #process instr. array
389             my ($self, $index) = @_;
390             (1 == @_ or not defined $index)
391             and return wantarray ? @{tied(%$self)->{pa}} : tied(%$self)->{pa};
392             return tied(%$self)->{pa}[$index];
393             }
394              
395             sub ph { #process instr. hash
396             my ($self, $key) = @_;
397             1 == @_ and return wantarray ? %{tied(%$self)->{ph}} : tied(%$self)->{ph};
398             return tied(%$self)->{ph}{$key};
399             }
400              
401             sub na { #notes array
402             my ($self, $index) = @_;
403             (1 == @_ or not defined $index)
404             and return wantarray ? @{tied(%$self)->{na}} : tied(%$self)->{na};
405             return tied(%$self)->{na}[$index];
406             }
407              
408             sub a { #all in the document order
409             my ($self, $index) = @_;
410             (1 == @_ or not defined $index)
411             and return wantarray ? @{tied(%$self)->{a}} : tied(%$self)->{a};
412             return tied(%$self)->{a}[$index];
413             }
414              
415              
416              
417             sub sr { #serialize
418             my ($self) = @_;
419             my $s = tied(%$self);
420             my $ret = '';
421             my $val;
422             my $i = 0;
423             my $en;
424             my $pfix = "\n";
425             while ($s->{a}[$i]) {
426             if ($s->{a}[$i] =~ /^-(.*)$/) {
427             if ($1 eq 'elm') {
428             $ret .= $s->{a}[$i+1]->sr;
429             } elsif ($1 eq 'txt') {
430             $val = $s->{a}[$i+1];
431             $val =~ s/\&/\&/g;
432             $val =~ s/
433             $val =~ s/\]\]>/]]\>/g;
434             $ret .= $val;
435             } elsif ($1 eq 'cdt') {
436             $ret .= '{a}[$i+1].']]>';
437             } elsif ($1 eq 'pro') {
438             $ret .= '{a}[$i+1][0].' '.$s->{a}[$i+1][1].'?>'.$pfix;
439             } elsif ($1 eq 'not') {
440             $ret .= ''.$pfix;
441             } elsif ($1 eq 'xml') {
442             $ret .= "{a}[$i+1][0]."'";
443             $s->{a}[$i+1][1] and $ret .= " encoding='".$s->{a}[$i+1][1]."'";
444             defined $s->{a}[$i+1][2] and $ret .= " standalone='".($s->{a}[$i+1][2]?'yes':'no')."'";
445             $ret .= "?>".$pfix;
446             } elsif ($1 eq 'doc') {
447             $ret .= '{a}[$i+1][0];
448             $s->{a}[$i+1][2] and $ret .= ' PUBLIC \''.$s->{a}[$i+1][2].'\'';
449             $s->{a}[$i+1][1] and not $s->{a}[$i+1][2] and $ret .= ' SYSTEM';
450             $s->{a}[$i+1][1] and $ret .= ' \''.$s->{a}[$i+1][1].'\'';
451             $ret .= ">".$pfix;
452             }
453             } else {
454             $pfix = '';
455             $en = $s->{a}[$i];
456             $ret .= '<'.$en;
457             foreach (keys %{$s->{a}[$i+1]}) {
458             $val = $s->{a}[$i+1]{$_};
459             $val =~ s/\&/\&/g;
460             $val =~ s/\'/\'/g;
461             $val =~ s/
462             $ret .= ' '.$_."='".$val."'";
463             }
464             $ret .= '>';
465             }
466             $i += 2;
467             }
468             defined $en or return $ret;
469             return $ret.'';
470             }
471              
472             1;
473              
474             __END__