File Coverage

blib/lib/XML/Quick.pm
Criterion Covered Total %
statement 58 62 93.5
branch 28 38 73.6
condition 8 10 80.0
subroutine 8 8 100.0
pod 1 1 100.0
total 103 119 86.5


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