File Coverage

blib/lib/XML/Perl.pm
Criterion Covered Total %
statement 152 161 94.4
branch 54 66 81.8
condition 8 15 53.3
subroutine 19 19 100.0
pod 5 5 100.0
total 238 266 89.4


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