File Coverage

blib/lib/XML/Perl.pm
Criterion Covered Total %
statement 152 161 94.4
branch 56 70 80.0
condition 9 15 60.0
subroutine 19 19 100.0
pod 5 5 100.0
total 241 270 89.2


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