File Coverage

blib/lib/XML/Quick.pm
Criterion Covered Total %
statement 56 60 93.3
branch 28 38 73.6
condition 8 10 80.0
subroutine 7 7 100.0
pod 1 1 100.0
total 100 116 86.2


line stmt bran cond sub pod time code
1             package XML::Quick;
2             $XML::Quick::VERSION = '0.06';
3             # ABSTRACT: Generate XML from hashes (and other data)
4              
5 3     3   65917 use warnings;
  3         7  
  3         90  
6 3     3   14 use strict;
  3         5  
  3         72  
7              
8 3     3   15 use Scalar::Util qw(reftype);
  3         9  
  3         230  
9 3     3   14 use Exporter;
  3         4  
  3         173  
10              
11 3     3   16 use base qw(Exporter);
  3         5  
  3         2564  
12              
13             our @EXPORT = qw(xml);
14              
15             # cdata escaping
16             sub _escape($) {
17 11     11   19 my ($cdata) = @_;
18              
19 11         21 $cdata =~ s/&/&/g;
20 11         23 $cdata =~ s/
21 11         20 $cdata =~ s/>/>/g;
22 11         17 $cdata =~ s/"/"/g;
23              
24 11         22 $cdata =~ s/([^\x20-\x7E])/'&#' . ord($1) . ';'/ge;
  0         0  
25              
26 11         31 return $cdata;
27             };
28              
29             sub xml {
30 16     16 1 5121 my ($data, $opts) = @_;
31              
32             # handle undef properly
33 16 50       48 $data = '' if not defined $data;
34            
35 16 100 66     72 if (not defined $opts or reftype $opts ne 'HASH') {
36             # empty options hash if they didn't provide one
37 10         17 $opts = {};
38             }
39             else {
40             # shallow copy the opts so we don't modify the callers
41 6         21 $opts = {%$opts};
42             }
43              
44             # escape by default
45 16 50       63 $opts->{escape} = 1 if not exists $opts->{escape};
46              
47 16         26 my $xml = '';
48              
49             # stringify anything thats not a hash
50 16 100 66     93 if(not defined reftype $data or reftype $data ne 'HASH') {
51 10 50       37 $xml = $opts->{escape} ? _escape($data) : $data;
52             }
53              
54             # dig down into hashes
55             else {
56             # move attrs/cdata into opts as necessary
57 6 100       18 if(exists $data->{_attrs}) {
58 2 50       7 $opts->{attrs} = $data->{_attrs} if not exists $opts->{attrs};
59             }
60              
61 6 100       16 if(exists $data->{_cdata}) {
62 1 50       5 $opts->{cdata} = $data->{_cdata} if not exists $opts->{cdata};
63             }
64            
65             # loop over the keys
66 6         8 for my $key (keys %{$data}) {
  6         18  
67             # skip meta
68 8 100       29 next if $key =~ m/^_/;
69              
70             # undef
71 5 50       28 if(not defined $data->{$key}) {
    100          
    50          
    0          
72 0         0 $xml .= xml('', { root => $key });
73             }
74              
75             # plain scalar
76             elsif(not ref $data->{$key}) {
77 2         10 $xml .= xml($data->{$key}, { root => $key });
78             }
79              
80             # hash
81             elsif(reftype $data->{$key} eq 'HASH') {
82             $xml .= xml($data->{$key}, { root => $key,
83             attrs => $data->{$key}->{_attrs} || {},
84 3   100     34 cdata => $data->{$key}->{_cdata} || '' })
      100        
85             }
86              
87             # array
88             elsif(reftype $data->{$key} eq 'ARRAY') {
89 0         0 $xml .= xml($_, { root => $key }) for @{$data->{$key}};
  0         0  
90             }
91             }
92             }
93              
94             # wrap it up
95 16 100       48 if($opts->{root}) {
96             # open the tag
97 5         11 my $wrap = "<$opts->{root}";
98              
99             # attribute list
100 5 100       13 if($opts->{attrs}) {
101 3         4 for my $key (keys %{$opts->{attrs}}) {
  3         10  
102 2         5 my $val = $opts->{attrs}->{$key};
103 2         3 $val =~ s/'/'/g;
104              
105 2         9 $wrap .= " $key='$val'";
106             }
107             }
108              
109             # character data
110 5 100       14 if($opts->{cdata}) {
111 1 50       20 $xml = ($opts->{escape} ? _escape($opts->{cdata}) : $opts->{cdata}) . $xml;
112             }
113              
114             # if there's no content, then close it up right now
115 5 100       11 if($xml eq '') {
116 2         4 $wrap .= '/>';
117             }
118              
119             # otherwise dump in the contents and close
120             else {
121 3         10 $wrap .= ">$xml{root}>";
122             }
123              
124 5         9 $xml = $wrap;
125             }
126              
127             # all done
128 16         71 return $xml;
129             }
130              
131             1;
132              
133             =pod
134              
135             =encoding UTF-8
136              
137             =head1 NAME
138              
139             XML::Quick - Generate XML from hashes (and other data)
140              
141             =head1 SYNOPSIS
142              
143             use XML::Quick;
144              
145             $xml = xml($data);
146            
147             $xml = xml($data, { ... });
148              
149             =head1 DESCRIPTION
150              
151             This module generates XML from Perl data (typically a hash). It tries hard to
152             produce something sane no matter what you pass it. It probably fails.
153              
154             When you use this module, it will export the C function into your
155             namespace. This function does everything.
156              
157             =head2 xml
158              
159             The simplest thing you can do is call C a basic string. It will be
160             XML-escaped for you:
161              
162             xml('v&lue');
163              
164             # produces: v&lue
165            
166             To create a simple tag, you'll need to pass a hash instead:
167              
168             xml({
169             'tag' => 'value'
170             });
171            
172             # produces: value
173              
174             Of course you can have several tags in the same hash:
175              
176             xml({
177             'tag' => 'value',
178             'tag2' => 'value2'
179             });
180            
181             # produces: value2
182             # value
183              
184             Arrays will be turned into multiple tags with the same name:
185            
186             xml({
187             'tag' => [
188             'one',
189             'two',
190             'three'
191             ]
192             });
193            
194             # produces: one
195             # two
196             # three
197            
198             Use nested hashes to produce nested tags:
199            
200             xml({
201             'tag' => {
202             'subtag' => 'value'
203             }
204             });
205            
206             # produces:
207             # value
208             #
209            
210             A hash key with a value of C or an empty hash or array will produce a
211             "bare" tag:
212              
213             xml({
214             'tag' => undef
215             });
216              
217             # produces:
218              
219             Adding attributes to tags is slightly more involved. To add attributes to a
220             tag, include its attributes in a hash stored in the C<_attrs> key of the tag:
221            
222             xml({
223             'tag' => {
224             '_attrs' => {
225             'foo' => 'bar'
226             }
227             }
228             });
229              
230             # produces:
231            
232             Of course, you're probably going to want to include a value or other tags
233             inside this tag. For a value, use the C<_cdata> key:
234              
235             xml({
236             'tag' => {
237             '_attrs' => {
238             'foo' => 'bar'
239             },
240             '_cdata' => 'value'
241             }
242             });
243              
244             # produces: value
245              
246             For nested tags, just include them like normal:
247            
248             xml({
249             'tag' => {
250             '_attrs' => {
251             'foo' => 'bar'
252             },
253             'subtag' => 'value'
254             }
255             });
256            
257             # produces:
258             # subvalue
259             #
260              
261             If you wanted to, you could include both values and nested tags, but you almost
262             certainly shouldn't. See L for more details.
263            
264             There are also a number of processing options available, which can be specified
265             by passing a hash reference as a second argument to C:
266              
267             =over
268              
269             =item * root
270              
271             Setting this will cause the returned XML to be wrapped in a single toplevel
272             tag.
273              
274             xml({ tag => 'value' });
275             # produces: value
276            
277             xml({ tag => 'value' }, { root => 'wrap' });
278             # produces: value
279              
280             =item * attrs
281              
282             Used in conjuction with the C option to add attributes to the root tag.
283              
284             xml({ tag => 'value' }, { root => 'wrap', attrs => { style => 'shiny' }});
285             # produces: value
286              
287             =item * cdata
288              
289             Used in conjunction with the C option to add character data to the root
290             tag.
291              
292             xml({ tag => 'value' }, { root => 'wrap', cdata => 'just along for the ride' });
293             # produces: just along for the ridevalue
294              
295             You probably don't need to use this. If you just want to create a basic tag
296             from nothing do this:
297              
298             xml({ tag => 'value' });
299              
300             Rather than this:
301              
302             xml('', { root => 'tag', cdata => 'value' });
303              
304             You almost certainly don't want to add character data to a root tag with nested
305             tags inside. See L for more details.
306              
307             =item * escape
308              
309             A flag, enabled by default. When enabled, character data values will be escaped
310             with XML entities as appropriate. Disabling this is useful when you want to
311             wrap an XML string with another tag.
312              
313             xml("foo", { root => 'wrap' })
314             # produces: <xml>foo</xml>
315              
316             xml("foo", { root => 'wrap', escape => 0 })
317             # produces: foo
318              
319             =back
320              
321             =head1 BUGS AND LIMITATIONS
322              
323             Because Perl hash keys get randomised, there's really no guarantee the
324             generated XML tags will be in the same order as they were when you put them in
325             the hash. This generally won't be a problem as the vast majority of XML-based
326             datatypes don't care about order. I don't recommend you use this module to
327             create XML when order is important (eg XHTML, XSL, etc).
328              
329             Things are even more hairy when including character data alongside tags via the
330             C or C<_cdata> options. The C options only really exist to allow
331             attributes and values to be specified for a single tag. The rich support
332             necessary to support multiple character data sections interspersed alongside
333             tags is entirely outside the scope of what the module is designed for.
334              
335             There are probably bugs. This kind of thing is an inexact science. Feedback
336             welcome.
337              
338             =head1 SUPPORT
339              
340             =head2 Bugs / Feature Requests
341              
342             Please report any bugs or feature requests through the issue tracker
343             at L.
344             You will be notified automatically of any progress on your issue.
345              
346             =head2 Source Code
347              
348             This is open source software. The code repository is available for
349             public review and contribution under the terms of the license.
350              
351             L
352              
353             git clone https://github.com/robn/XML-Quick.git
354              
355             =head1 AUTHOR
356              
357             Robert Norris
358              
359             =head1 CONTRIBUTORS
360              
361             =over 4
362              
363             =item *
364              
365             YAMASHINA Hio fixed a bug where C would modify the caller's data
366              
367             =item *
368              
369             Dawid Joubert suggested escaping non-ASCII characters and provided a patch
370             (though I did it a little bit differently to how he suggested)
371              
372             =item *
373              
374             Peter Eichman fixed a bug where single quotes in attribute values were not
375             being escaped.
376              
377             =back
378              
379             =head1 COPYRIGHT AND LICENSE
380              
381             This software is copyright (c) 2005-2006 Monash University, (c) 2008-2015 by Robert Norris.
382              
383             This is free software; you can redistribute it and/or modify it under
384             the same terms as the Perl 5 programming language system itself.