File Coverage

blib/lib/XML/Perl.pm
Criterion Covered Total %
statement 150 159 94.3
branch 53 64 82.8
condition 7 12 58.3
subroutine 19 19 100.0
pod 5 5 100.0
total 234 259 90.3


line stmt bran cond sub pod time code
1             package XML::Perl;
2              
3 4     4   72044 use strict;
  4         7  
  4         103  
4 4     4   14 use warnings;
  4         3  
  4         90  
5              
6 4     4   13 use Exporter;
  4         8  
  4         282  
7             our @ISA = qw(Exporter);
8             our @EXPORT = qw(perl2xml xmlformat xml2perlbase perlbase2xml xpath);
9              
10             our $VERSION = '0.07';
11              
12 4     4   2092 use HTML::Parser;
  4         19533  
  4         6639  
13              
14              
15              
16             sub perl2xml($;$$$) {
17 2     2 1 410 my ($d, $i, $s, $nl) = @_;
18 2 50       6 $i = 0 unless defined $i;
19 2 50       4 $s = "\t" unless defined $s;
20 2 50       3 $nl = "\n" unless defined $nl;
21 2 50       5 if (ref $d eq "HASH") {
22 2         8 return join "", map { _kv($_, $$d{$_}, $i, $s, $nl) } sort keys %$d;
  3         7  
23             } else {
24 0         0 warn "Must be HASH ref";
25 0         0 return;
26             }
27             }
28              
29              
30              
31             sub _kv($$$$$);
32             sub _kv($$$$$) {
33 24     24   25 my ($k, $v, $i, $s, $nl) = @_;
34 24         22 my $shift = join "", $s x $i;
35 24 100       34 if (ref $v eq "HASH") {
    100          
36 16         10 my @attrs = ();
37 16         15 my %nodes = ();
38 16         10 my $value;
39 16         32 while (my ($_k, $_v) = each %$v) {
40 32 100       53 if ( $_k =~ /^@/ ) {
    100          
41 14         43 push @attrs, "$'=\"$_v\"";
42             } elsif ($_k eq '') {
43 6         11 $value = $_v;
44             } else {
45 12         23 $nodes{$_k} = $_v;
46             }
47             }
48 16 100       30 my $attrs = @attrs ? (join " ", "", sort @attrs) : "";
49 16 100       26 if (keys %nodes) {
    100          
50             # nodes
51 4         3 ++$i;
52 4         9 my $foo = join "", map { _kv($_, $nodes{$_}, $i, $s, $nl) } sort keys %nodes;
  12         23  
53 4         25 return join "", $shift, "<$k$attrs>$nl", $foo, $shift, "$nl";
54             } elsif ($value) {
55             # value
56 6 50       11 if (ref $value eq "ARRAY") {
    100          
57 0         0 return join "", map { $shift, "<$k$attrs>$nl", _kv($k, $_, $i, $s, $nl), $shift, "$nl" } @$value;
  0         0  
58             } elsif (ref $value eq "HASH") {
59 2         2 ++$i;
60 2         3 my $foo = join "", map { _kv($_, $$value{$_}, $i, $s, $nl) } sort keys %$value;
  2         3  
61 2         8 return join "", $shift, "<$k$attrs>$nl", $foo, $shift, "$nl";
62             } else {
63 4         11 return join "", $shift, "<$k$attrs>", _char2entity($value), "$nl";
64             }
65             } else {
66             # Only attrs
67 6         20 return "$shift<$k$attrs/>$nl";
68             }
69             } elsif (ref $v eq "ARRAY") {
70 3         4 return join "", map { _kv($k, $_, $i, $s, $nl) } @$v;
  7         22  
71             } else {
72 5         8 return join "", "$shift<$k>", _char2entity($v), "$nl";
73             }
74             }
75              
76              
77             sub _char2entity {
78 18     18   18 my ($v) = @_;
79 18         20 foreach ($v) {
80 18         22 s/&/&/g;
81 18         15 s/>/>/g;
82 18         12 s/
83 18         12 s/"/"/g;
84 18         20 s/'/'/g;
85             }
86 18         51 return $v;
87             }
88              
89              
90              
91             # Форматирование XML - делаем отступы.
92             sub xmlformat($) {
93 1     1 1 9 my ($xml) = @_;
94 1         2 my $shift = 0;
95 1         1 my $last = "";
96             my $xmlf = sub {
97 20     20   25 my ($i, $j, $k) = @_;
98 20 100       20 if ($k) { # />
99 6         3 --$shift;
100 6         14 return $k
101             } else { # <...
102 14 100       17 if ($i eq "<") {
    50          
    0          
103 10         9 $last = $j;
104 10         47 return "\n" . ("\t" x $shift++) . "$i$j";
105             } elsif ($i eq "
106 4         2 --$shift;
107 4 100       6 if ($last eq $j) {
108 1         3 return "$i$j";
109             } else {
110 3         10 return "\n" . ("\t" x $shift) . "$i$j";
111             }
112             } elsif ($i eq "
113 0         0 return $i;
114             } else {
115 0         0 warn "Unknon element: $i";
116 0         0 return $i;
117             }
118             }
119 1         6 };
120              
121 1         11 $xml =~ s/
122             (?:
123             (?: (<|<\/|<(?!\/))((?:\w+:)?\w+) )
124             |
125             (\/\s*?>)
126             )
127 20         18 /$xmlf->($1, $2, $3)/xeg;
128 1         12 return $xml;
129             }
130              
131              
132              
133             sub xml2perlbase {
134 2     2 1 14 my ($xml) = @_;
135              
136 2         15 my $prs = HTML::Parser->new(api_version => 3);
137 2         68 $prs->xml_mode(1);
138 2         6 $prs->utf8_mode(1);
139              
140 2         3 my $t = {};
141             # {
142             # name => [
143             # { a => b, '' => v },
144             # {}
145             # ],
146             # ...
147             # }
148             # v - {} или scalar
149 2         4 my @p = ($t); # Текущая цепочка из ссылок на элементы в глубину.
150 2         2 my @n = (); # --//-- из имен элементов
151             $prs->handler(start => sub {
152 20     20   24 my ($prs, $tagname, $attr) = @_;
153 20         14 my $v = {};
154 20         10 push @{$p[-1]{$tagname}}, { %$attr, '' => $v };
  20         53  
155 20         18 push @p, $v;
156 20         51 push @n, $tagname;
157 2         22 }, "self,tagname,attr");
158              
159             $prs->handler(text => sub {
160 38     38   28 my ($prs, $text) = @_;
161 38 100       137 unless ($text =~ m/^\s*$/s) { # ToDo Возможно специфика HTML::Parser
162 14 50       49 $p[-2]{$n[-1]}[-1]{''} = $text if @p > 1;
163             }
164              
165 2         10 }, "self,dtext");
166              
167              
168             $prs->handler(end => sub {
169 20     20   18 my ($prs, $tagname) = @_;
170 20         22 my $v = $p[-2]{$n[-1]}[-1]{''};
171 20 50 66     100 if (ref $v eq "HASH" and keys %$v == 0 or $v eq "") {
      33        
172 0         0 delete $p[-2]{$n[-1]}[-1]{''};
173             }
174 20         12 pop @p;
175 20         43 pop @n;
176 2         10 }, "self,tagname");
177              
178 2         28 $prs->parse($xml);
179 2         26 return $t;
180             }
181              
182              
183              
184              
185              
186             sub perlbase2xml {
187 2     2 1 9 my ($t, $i, $s, $nl) = @_;
188 2 100       7 $i = 0 unless defined $i;
189 2 100       4 $s = "\t" unless defined $s;
190 2 100       5 $nl = "\n" unless defined $nl;
191 2         6 _perlbase2xml($t, $i, $s, $nl);
192             }
193              
194              
195              
196             sub _perlbase2xml {
197 5     5   6 my ($t, $i, $shift, $nl) = @_;
198 5         5 my @s = ();
199 5         17 foreach my $n (sort keys %$t) {
200 10         7 foreach my $e (@{$$t{$n}}) {
  10         14  
201             push @s, $shift x $i, join " ",
202             "<$n",
203 12         25 map { "$_=\"$$e{$_}\"" } sort grep { $_ } keys %$e;
  4         10  
  16         24  
204 12         12 my $v = $$e{''};
205 12 100 33     38 if (ref $v) {
    50          
206 3         8 push @s, ">$nl",
207             _perlbase2xml($v, $i + 1, $shift, $nl),
208             $shift x $i, "$nl";
209             } elsif (defined $v and $v ne "") {
210 9         24 push @s, join "", ">", _char2entity($v), "$nl";
211             } else {
212 0         0 push @s, "/>$nl";
213             }
214             }
215             };
216 5         19 return join "", @s;
217             }
218              
219              
220              
221              
222             sub xpath {
223 6     6 1 2029 my ($tree, $path) = @_;
224 6         16 my @path = split /\//, $path;
225 6 100       11 if ($path[0] eq '') {
226             # From root
227 5         5 shift @path;
228 5         10 _xpath($tree, @path);
229             } else {
230 1         3 _xpath_sub($tree, @path);
231             }
232             }
233              
234             sub _xpath {
235 13     13   13 my ($tree, $path, @path) = @_;
236 13         43 my ($k, $i) = $path =~ m/^(.+?)(?:\[(\d+)\])?$/;
237 13 100       22 if ($$tree{$k}) {
238 11         10 my @sub_tree = ();
239 11 100       12 if ($i) {
240 3         7 push @sub_tree, $$tree{$k}[$i - 1];
241             } else {
242 8         5 push @sub_tree, @{$$tree{$k}};
  8         12  
243             }
244 11         8 my @r = map { __xpath($_, @path) } @sub_tree;
  14         16  
245 11 100       35 return wantarray ? @r : $r[0];
246             } else {
247 2         3 return;
248             }
249             }
250              
251              
252             sub __xpath {
253 14     14   13 my ($tree, @path) = @_;
254 14 100 100     43 if (@path == 1 and $path[0] =~ m/^\@(.+)$/) {
    100          
255 1         4 return $$tree{$1};
256             } elsif (@path) {
257 5         23 return _xpath($$tree{''}, @path);
258             } else {
259 8         18 return $$tree{''};
260             }
261             }
262              
263              
264              
265             sub _xpath_sub {
266 4     4   6 my ($tree, @path) = @_;
267 4         6 my @sub_tree = grep { ref $_ } map { $$_{''} } map { @$_ } values %$tree;
  10         11  
  10         10  
  8         7  
268 4         5 my @r = grep { $_ } map { _xpath($_, @path) } @sub_tree;
  2         4  
  3         6  
269 4         3 push @r, map { _xpath_sub($_, @path) } @sub_tree;
  3         6  
270 4         7 return @r;
271             }
272              
273              
274              
275             1;
276              
277             __END__