File Coverage

blib/lib/XML/Struct/Writer.pm
Criterion Covered Total %
statement 75 77 97.4
branch 35 40 87.5
condition 13 15 86.6
subroutine 16 16 100.0
pod 8 8 100.0
total 147 156 94.2


line stmt bran cond sub pod time code
1             package XML::Struct::Writer;
2 7     7   336487 use strict;
  7         23  
  7         218  
3              
4 7     7   1076 use Moo;
  7         22289  
  7         40  
5 7     7   7764 use XML::LibXML::SAX::Builder;
  7         124755  
  7         228  
6 7     7   3124 use XML::Struct::Writer::Stream;
  7         23  
  7         306  
7 7     7   53 use Scalar::Util qw(blessed reftype);
  7         14  
  7         433  
8 7     7   48 use Carp;
  7         16  
  7         8456  
9              
10             our $VERSION = '0.27';
11              
12             has attributes => (is => 'rw', default => sub { 1 });
13             has encoding => (is => 'rw', default => sub { 'UTF-8' });
14             has version => (is => 'rw', default => sub { '1.0' });
15             has standalone => (is => 'rw');
16             has pretty => (is => 'rw', default => sub { 0 }); # 0|1|2
17             has xmldecl => (is => 'rw', default => sub { 1 });
18             has handler => (is => 'lazy', builder => 1);
19              
20             has to => (
21             is => 'rw',
22             coerce => sub {
23             if (!ref $_[0]) {
24             return IO::File->new($_[0], 'w');
25             } elsif (reftype($_[0]) eq 'SCALAR') {
26 2     2   13 open my $io,">:utf8",$_[0];
  2         4  
  2         15  
27             return $io;
28             } else { # IO::Handle, GLOB, ...
29             return $_[0];
30             }
31             },
32             trigger => sub { delete $_[0]->{handler} }
33             );
34              
35             sub _build_handler {
36 14 100   14   303 $_[0]->to ? XML::Struct::Writer::Stream->new(
37             fh => $_[0]->to,
38             encoding => $_[0]->encoding,
39             version => $_[0]->version,
40             pretty => $_[0]->pretty,
41             ) : XML::LibXML::SAX::Builder->new( handler => $_[0] );
42             }
43              
44             sub write {
45 17     17 1 988 my ($self, $element, $name) = @_;
46              
47 17         51 $self->writeStart;
48 17   100     78 $self->writeElement(
49             $self->microXML($element, $name // 'root')
50             );
51 17         276 $self->writeEnd;
52            
53 17 100       408 $self->handler->can('result') ? $self->handler->result : 1;
54             }
55              
56             *writeDocument = \&write;
57              
58             # TODO: Make available as function in XML::Struct or XML::Struct::Simple
59             sub microXML {
60 48     48 1 94 my ($self, $element, $name) = @_;
61              
62 48         118 my $type = reftype($element);
63 48 50       96 if ($type) {
64             # MicroXML
65 48 100       103 if ($type eq 'ARRAY') {
    50          
66 34 100       86 if (@$element == 1) {
    100          
67 2         17 return $element;
68             } elsif (@$element == 2) {
69 10 100 50     49 if ( (reftype($element->[1]) // '') eq 'ARRAY') {
    100 66        
70 7         29 return [ $element->[0], {}, $element->[1] ];
71 1         5 } elsif (!$self->attributes and %{$element->[1]}) {
72 1         3 return [ $element->[0] ];
73             } else {
74 2         7 return $element;
75             }
76             } else {
77 22 100 100     82 if (!$self->attributes and %{$element->[1]}) {
  4         12  
78 1         4 return [ $element->[0], {}, $element->[2] ];
79             } else {
80 21         64 return $element;
81             }
82             }
83             # SimpleXML
84             } elsif ($type eq 'HASH') {
85             my $children = [
86             map {
87 14         30 my ($tag, $content) = ($_, $element->{$_});
88             # text
89 14 100       48 if (!ref $content) {
    100          
    50          
90 2         7 [ $tag, {}, [$content] ]
91             } elsif (reftype($content) eq 'ARRAY') {
92             @$content
93 6 100       18 ? map { [ $tag, {}, [$_] ] } @$content
  7         73  
94             : [ $tag ];
95             } elsif (reftype $content eq 'HASH' ) {
96 6         23 [ $tag, {}, [ $content ] ];
97             } else {
98 0         0 ();
99             }
100             }
101 14         46 grep { defined $element->{$_} }
  15         43  
102             sort keys %$element
103             ];
104 14 100       64 return $name ? [ $name, {}, $children ] : @$children;
105             }
106             }
107              
108 0         0 croak "expected XML as ARRAY or HASH reference";
109             }
110              
111             sub writeElement {
112 48     48 1 76 my $self = shift;
113            
114 48         89 foreach my $element (@_) {
115 46         107 $self->writeStartElement($element);
116 46   100     880 foreach my $child ( @{ $element->[2] // [] } ) {
  46         140  
117 56 100       394 if (ref $child) {
118 31         62 $self->writeElement( $self->microXML($child) );
119             } else {
120 25         59 $self->writeCharacters($child);
121             }
122             }
123              
124 46         362 $self->writeEndElement($element);
125             }
126             }
127              
128             sub writeStartElement {
129 46     46 1 82 my ($self, $element) = @_;
130              
131 46         102 my $args = { Name => $element->[0] };
132 46 100       117 $args->{Attributes} = $element->[1] if $element->[1];
133              
134 46         833 $self->handler->start_element($args);
135             }
136              
137             sub writeEndElement {
138 46     46 1 80 my ($self, $element) = @_;
139 46         731 $self->handler->end_element({ Name => $element->[0] });
140             }
141              
142             sub writeCharacters {
143 25     25 1 462 $_[0]->handler->characters({ Data => $_[1] });
144             }
145              
146             sub writeStart {
147 17     17 1 32 my $self = shift;
148 17         280 $self->handler->start_document;
149 17 100 100     580 if ($self->handler->can('xml_decl') && $self->xmldecl) {
150 7         199 $self->handler->xml_decl({
151             Version => $self->version,
152             Encoding => $self->encoding,
153             Standalone => $self->standalone,
154             });
155             }
156 17 50       275 $self->writeStartElement(@_) if @_;
157             }
158              
159             sub writeEnd {
160 17     17 1 28 my $self = shift;
161 17 50       39 $self->writeEndElement(@_) if @_;
162 17         319 $self->handler->end_document;
163             }
164              
165             1;
166             __END__
167              
168             =encoding UTF-8
169              
170             =head1 NAME
171              
172             XML::Struct::Writer - Write XML data structures to XML streams
173              
174             =head1 SYNOPSIS
175              
176             use XML::Struct::Writer;
177              
178             # serialize
179             XML::Struct::Writer->new(
180             to => \*STDOUT,
181             attributes => 0,
182             pretty => 1,
183             )->write( [
184             doc => [
185             [ name => [ "alice" ] ],
186             [ name => [ "bob" ] ],
187             ]
188             ] );
189              
190             # <?xml version="1.0" encoding="UTF-8"?>
191             # <doc>
192             # <name>alice</name>
193             # <name>bob</name>
194             # </doc>
195              
196             # create DOM
197             my $xml = XML::Struct::Writer->new->write( [
198             greet => { }, [
199             "Hello, ",
200             [ emph => { color => "blue" } , [ "World" ] ],
201             "!"
202             ]
203             ] );
204             $xml->toFile("greet.xml");
205              
206             # <?xml version="1.0" encoding="UTF-8"?>
207             # <greet>Hello, <emph color="blue">World</emph>!</greet>
208              
209             =head1 DESCRIPTION
210              
211             This module writes an XML document, given as L<XML::Struct> data structure, as
212             stream of L</"SAX EVENTS">. The default handler receives these events with
213             L<XML::LibXML::SAX::Builder> to build a DOM tree which can then be used to
214             serialize the XML document as string. The writer can also be used to directly
215             serialize XML with L<XML::Struct::Writer::Stream>.
216              
217             L<XML::Struct> provides the shortcut function C<writeXML> to this module.
218              
219             XML elements can be passed in any of these forms and its combinations:
220              
221             # MicroXML:
222              
223             [ $name => \%attributes, \@children ]
224              
225             [ $name => \%attributes ]
226              
227             [ $name ]
228              
229             # lax MicroXML also:
230              
231             [ $name => \@children ]
232              
233             # SimpleXML:
234              
235             { $name => \@children, $name => $content, ... }
236              
237             =head1 CONFIGURATION
238              
239             A XML::Struct::Writer can be configured with the following options:
240              
241             =over
242              
243             =item to
244              
245             Filename, L<IO::Handle>, string reference, or other kind of stream to directly
246             serialize XML to with L<XML::Struct::Writer::Stream>. This option is ignored
247             if C<handler> is explicitly set.
248              
249             =item handler
250              
251             A SAX handler to send L</"SAX EVENTS"> to. If neither this option nor C<to> is
252             explicitly set, an instance of L<XML::LibXML::SAX::Builder> is used to build a
253             DOM.
254              
255             =item attributes
256              
257             Ignore XML attributes if set to false. Set to true by default.
258              
259             =item xmldecl
260              
261             Include XML declaration on serialization. Enabled by default.
262              
263             =item encoding
264              
265             An encoding (for handlers that support an explicit encoding). Set to UTF-8
266             by default.
267              
268             =item version
269              
270             The XML version. Set to C<1.0> by default.
271              
272             =item standalone
273              
274             Add standalone flag in the XML declaration.
275              
276             =item pretty
277              
278             Pretty-print XML. Disabled by default.
279              
280             =back
281              
282             =head1 METHODS
283              
284             =head2 write( $root [, $name ] ) == writeDocument( $root [, $name ] )
285              
286             Write an XML document, given as array reference (lax MicroXML), hash reference
287             (SimpleXML), or both mixed. If given as hash reference, the name of a root tag
288             can be chosen or it is set to C<root>. This method is basically equivalent to:
289              
290             $writer->writeStart;
291             $writer->writeElement(
292             $writer->microXML($root, $name // 'root')
293             );
294             $writer->writeEnd;
295             $writer->result if $writer->can('result');
296              
297             The remaining methods expect XML in MicroXML format only.
298              
299             =head2 writeElement( $element [, @more_elements ] )
300              
301             Write one or more XML elements and their child elements to the handler.
302              
303             =head2 writeStart( [ $root [, $name ] ] )
304              
305             Call the handler's C<start_document> and C<xml_decl> methods. An optional root
306             element can be passed, so C<< $writer->writeStart($root) >> is equivalent to:
307              
308             $writer->writeStart;
309             $writer->writeStartElement($root);
310              
311             =head2 writeStartElement( $element )
312              
313             Directly call the handler's C<start_element> method.
314              
315             =head2 writeEndElement( $element )
316              
317             Directly call the handler's C<end_element> method.
318              
319             =head2 writeCharacters( $string )
320              
321             Directy call the handler's C<characters> method.
322              
323             =head2 writeEnd( [ $root ] )
324              
325             Directly call the handler's C<end_document> method. An optional root element
326             can be passed, so C<< $writer->writeEnd($root) >> is equivalent to:
327              
328             $writer->writeEndElement($root);
329             $writer->writeEnd;
330              
331             =head2 microXML( $element [, $name ] )
332              
333             Convert an XML element, given as array reference (lax MicroXML) or as hash
334             reference (SimpleXML) to a list of MicroXML elements and optionally remove
335             attributes. Does not affect child elements.
336              
337             =head1 SAX EVENTS
338              
339             A SAX handler, set with option C<handler>, is expected to implement the
340             following methods (two of them are optional):
341              
342             =over
343              
344             =item xml_decl( { Version => $version, Encoding => $encoding } )
345              
346             Optionally called once at the start of an XML document, if the handler supports
347             this method.
348              
349             =item start_document()
350              
351             Called once at the start of an XML document.
352              
353             =item start_element( { Name => $name, Attributes => \%attributes } )
354              
355             Called at the start of an XML element to emit an XML start tag.
356              
357             =item end_element( { Name => $name } )
358              
359             Called at the end of an XML element to emit an XML end tag.
360              
361             =item characters( { Data => $characters } )
362              
363             Called for character data. Character entities and CDATA section are expanded to
364             strings.
365              
366             =item end_document()
367              
368             Called once at the end of an XML document.
369              
370             =item result()
371              
372             Optionally called at the end of C<write>/C<writeDocument> to return a value
373             from this methods. Handlers do not need to implement this method.
374              
375             =back
376              
377             =head1 SEE ALSO
378              
379             Using a streaming SAX handler, such as L<XML::SAX::Writer>,
380             L<XML::Genx::SAXWriter>, L<XML::Handler::YAWriter>, and possibly L<XML::Writer>
381             should be more performant for serialization. Examples of other modules that
382             receive SAX events include L<XML::STX>, L<XML::SAX::SimpleDispatcher>, and
383             L<XML::SAX::Machines>,
384              
385             =cut