File Coverage

blib/lib/XML/Builder.pm
Criterion Covered Total %
statement 281 309 90.9
branch 56 78 71.7
condition 25 50 50.0
subroutine 76 86 88.3
pod 0 32 0.0
total 438 555 78.9


line stmt bran cond sub pod time code
1 7     7   2827 use 5.008001;
  7         19  
  7         240  
2 7     7   27 use strict;
  7         8  
  7         164  
3 7     7   22 use warnings;
  7         8  
  7         143  
4              
5 7     7   24 use Scalar::Util ();
  7         7  
  7         75  
6 7     7   3856 use Encode ();
  7         60449  
  7         282  
7              
8             package XML::Builder;
9             $XML::Builder::VERSION = '0.906';
10             # ABSTRACT: programmatic XML generation, conveniently
11              
12 7     7   2981 use Object::Tiny::Lvalue qw( nsmap default_ns encoding );
  7         1957  
  7         39  
13              
14             # these aren't constants, they need to be overridable in subclasses
15             my %class = (
16             ns => 'XML::Builder::NS',
17             fragment => 'XML::Builder::Fragment',
18             qname => 'XML::Builder::Fragment::QName',
19             tag => 'XML::Builder::Fragment::Tag',
20             unsafe => 'XML::Builder::Fragment::Unsafe',
21             root => 'XML::Builder::Fragment::Root',
22             document => 'XML::Builder::Fragment::Document',
23             );
24              
25             my ( $name, $class );
26 1     1 0 10 eval XML::Builder::Util::factory_method( $name, $class )
  112     112 0 292  
  1     1 0 5  
  112     112 0 219  
  10     10 0 45  
  51     51 0 1150  
  0     0 0 0  
  57     57 0 120  
  1     1 0 5  
  10     10 0 69  
  51     51 0 182  
  3     3 0 68  
  58     58 0 224  
  1     1 0 8  
27             while ( $name, $class ) = each %class;
28              
29             sub new {
30 8     8 0 76 my $class = shift;
31 8         29 my $self = bless { @_ }, $class;
32 8   50     292 $self->encoding ||= 'us-ascii';
33 8   50     333 $self->nsmap ||= {};
34 8         72 return $self;
35             }
36              
37             sub register_ns {
38 43     43 0 47 my $self = shift;
39 43         56 my ( $uri, $pfx ) = @_;
40              
41 43         819 my $nsmap = $self->nsmap;
42              
43 43         175 $uri = $self->stringify( $uri );
44              
45 43 100       95 if ( exists $nsmap->{ $uri } ) {
46 32         41 my $ns = $nsmap->{ $uri };
47 32         541 my $registered_pfx = $ns->prefix;
48              
49 32 50 33     133 XML::Builder::Util::croak( "Namespace '$uri' being bound to '$pfx' is already bound to '$registered_pfx'" )
50             if defined $pfx and $pfx ne $registered_pfx;
51              
52 32         78 return $ns;
53             }
54              
55 11 100       35 if ( not defined $pfx ) {
56 4         13 my %pfxmap = map {; $_->prefix => $_ } values %$nsmap;
  3         52  
57              
58 4 100 66     57 if ( $uri eq '' and not exists $pfxmap{ '' } ) {
59 1         10 return $self->register_ns( '', '' );
60             }
61              
62 3         10 my $counter;
63 3 50       51 my $letter = ( $uri =~ m!([[:alpha:]])[^/]*/?\z! ) ? lc $1 : 'ns';
64 3         4 do { $pfx = $letter . ++$counter } while exists $pfxmap{ $pfx };
  3         18  
65             }
66              
67             # FIXME needs proper validity check per XML TR
68 10 50 66     63 XML::Builder::Util::croak( "Invalid namespace prefix '$pfx'" )
69             if length $pfx and $pfx !~ /[\w-]/;
70              
71 10         214 my $ns = $self->new_ns(
72             uri => $uri,
73             prefix => $pfx,
74             );
75              
76 10 100       168 $self->default_ns = $uri if '' eq $pfx;
77 10         72 return $nsmap->{ $uri } = $ns;
78             }
79              
80             sub get_namespaces {
81 3     3 0 132 my $self = shift;
82 3         5 return values %{ $self->nsmap };
  3         51  
83             }
84              
85 7     7 0 49 sub ns { shift->register_ns( @_ )->factory }
86 3     3 0 329 sub null_ns { shift->ns( '', '' ) }
87              
88             sub qname {
89 35     35 0 34 my $self = shift;
90 35         34 my $ns_uri = shift;
91 35         59 return $self->register_ns( $ns_uri )->qname( @_ );
92             }
93              
94             sub parse_qname {
95 34     34 0 36 my $self = shift;
96 34         33 my ( $name ) = @_;
97              
98 34         31 my $ns_uri = '';
99 34 50       77 $ns_uri = $1 if $name =~ s/\A\{([^}]+)\}//;
100              
101 34         53 return $self->qname( $ns_uri, $name );
102             }
103              
104             sub root {
105 2     2 0 4 my $self = shift;
106 2         4 my ( $tag ) = @_;
107 2         7 return $tag->root;
108             }
109              
110             sub document {
111 1     1 0 4 my $self = shift;
112 1         20 return $self->new_document( content => [ @_ ] );
113             }
114              
115             sub unsafe {
116 1     1 0 2 my $self = shift;
117 1         2 my ( $string ) = @_;
118 1         29 return $self->new_unsafe( content => $string );
119             }
120              
121             sub comment {
122 0     0 0 0 my $self = shift;
123 0         0 my ( $comment ) = $self->stringify( @_ );
124 0 0       0 XML::Builder::Util::croak( "Comment contains double dashes '$1...'" )
125             if $comment =~ /(.*?--)/;
126 0         0 return $self->new_unsafe( "" );
127             }
128              
129             sub pi {
130 0     0 0 0 my $self = shift;
131 0         0 my ( $name, $content ) = map $self->stringify( $_ ), @_;
132 0 0       0 XML::Builder::Util::croak( "PI contains terminator '$1...'" )
133             if $content =~ /(.*\?>)/;
134 0         0 return $self->new_unsafe( "" );
135             }
136              
137             sub render {
138 106     106 0 130 my $self = shift;
139 1         4 return 'SCALAR' eq ref $_[0]
140 106 100       2022 ? $self->qname( ${$_[0]}, @_[ 1 .. $#_ ] )
141             : $self->new_fragment( content => [ @_ ] );
142             }
143              
144             sub test_fragment {
145 157     157 0 138 my $self = shift;
146 157         141 my ( $obj ) = @_;
147 157         539 return $obj->isa( 'XML::Builder::Fragment::Role' );
148             }
149              
150             {
151 7     7   7405 no warnings 'qw';
  7         12  
  7         1680  
152              
153             my %XML_NCR = map eval "qq[$_]", qw(
154             \xA \xD
155             & & < < > >
156             " " ' '
157             );
158              
159             my %type = (
160             encode => undef,
161             escape_text => qr/([<>&'"])/,
162             escape_attr => qr/([<>&'"\x0A\x0D])/,
163             );
164              
165             # using eval instead of closures to avoid __ANON__
166             while ( my ( $subname, $specials_rx ) = each %type ) {
167             my $esc = '';
168              
169             $esc = sprintf '$str =~ s{ %s }{ $XML_NCR{$1} }gex', $specials_rx
170             if defined $specials_rx;
171              
172 1     1 0 20 eval sprintf 'sub %s {
  1     35 0 4  
  1     65 0 16  
  35         1786  
  35         72  
  35         71  
  7         17  
  35         552  
  65         95  
  65         119  
  65         120  
  13         39  
  65         1018  
173             my $self = shift;
174             my $str = $self->stringify( shift );
175             %s;
176             return Encode::encode $self->encoding, $str, Encode::HTMLCREF;
177             }', $subname, $esc;
178             }
179             }
180              
181             sub stringify {
182 149     149 0 1189 my $self = shift;
183 149         151 my ( $thing ) = @_;
184              
185 149 50       262 return if not defined $thing;
186              
187 149 100       1962 return $thing if not Scalar::Util::blessed $thing;
188              
189 4   100     36 my $conv = $thing->can( 'as_string' ) || overload::Method( $thing, '""' );
190 4 100       977 return $conv->( $thing ) if $conv;
191              
192 1         5 XML::Builder::Util::croak( 'Unstringifiable object ', $thing );
193             }
194              
195             #######################################################################
196              
197             package XML::Builder::NS;
198             $XML::Builder::NS::VERSION = '0.906';
199 7     7   38 use Object::Tiny::Lvalue qw( builder uri prefix qname_for_localname );
  7         8  
  7         26  
200 7     7   8724 use overload '""' => 'uri', fallback => 1;
  7         5885  
  7         33  
201              
202             sub new {
203 10     10   18 my $class = shift;
204 10         53 my $self = bless { @_ }, $class;
205 10   50     195 $self->qname_for_localname ||= {};
206 10         633 Scalar::Util::weaken $self->builder;
207 10         70 return $self;
208             }
209              
210             sub qname {
211 87     87   93 my $self = shift;
212 87         92 my $name = shift;
213              
214 87   33     1593 my $builder = $self->builder
215             || XML::Builder::Util::croak( 'XML::Builder for this NS object has gone out of scope' );
216              
217 87   66     1656 my $qname
218             = $self->qname_for_localname->{ $name }
219             ||= $builder->new_qname( name => $name, ns => $self );
220              
221 87 100       1769 return @_ ? $qname->tag( @_ ) : $qname;
222             }
223              
224             sub xmlns {
225 3     3   29 my $self = shift;
226 3         50 my $pfx = $self->prefix;
227 3 50       61 return ( ( '' ne $pfx ? "xmlns:$pfx" : 'xmlns' ), $self->uri );
228             }
229              
230 7     7   36 sub factory { bless \shift, 'XML::Builder::NS::QNameFactory' }
231              
232             #######################################################################
233              
234             package XML::Builder::NS::QNameFactory;
235             $XML::Builder::NS::QNameFactory::VERSION = '0.906';
236 51     51   967 sub AUTOLOAD { my $self = shift; $$self->qname( ( our $AUTOLOAD =~ /.*::(.*)/ ), @_ ) }
  51         273  
237 1     1   2 sub _qname { my $self = shift; $$self->qname( @_ ) }
  1         8  
238 0     0   0 sub DESTROY {}
239              
240             #######################################################################
241              
242             package XML::Builder::Fragment::Role;
243             $XML::Builder::Fragment::Role::VERSION = '0.906';
244 0     0   0 sub depends_ns_scope { 1 }
245              
246             #######################################################################
247              
248             package XML::Builder::Fragment;
249             $XML::Builder::Fragment::VERSION = '0.906';
250 7     7   5994 use parent -norequire => 'XML::Builder::Fragment::Role';
  7         1692  
  7         35  
251              
252 7     7   272 use Object::Tiny::Lvalue qw( builder content );
  7         11  
  7         48  
253              
254 0     0   0 sub depends_ns_scope { 0 }
255              
256             sub new {
257 170     170   165 my $class = shift;
258 170         504 my $self = bless { @_ }, $class;
259 170         2835 my $builder = $self->builder;
260 170         3059 my $content = $self->content;
261              
262 170         403 my ( @gather, @take );
263              
264 170 100       405 for my $r ( 'ARRAY' eq ref $content ? @$content : $content ) {
265 223         250 @take = $r;
266              
267 223 100       531 if ( not Scalar::Util::blessed $r ) {
268 66 100       113 @take = $builder->render( @$r ) if 'ARRAY' eq ref $r;
269 66         67 next;
270             }
271              
272 157 50       263 if ( not $builder->test_fragment( $r ) ) {
273 0         0 @take = $builder->stringify( $r );
274 0         0 next;
275             }
276              
277 157 100       2563 next if $builder == $r->builder;
278              
279 1 50       129 XML::Builder::Util::croak( 'Cannot merge XML::Builder fragments built with different namespace maps' )
280             if $r->depends_ns_scope;
281              
282 1         6 @take = $r->flatten;
283              
284 1         22 my ( $self_enc, $r_enc ) = map { lc $_->encoding } $builder, $r->builder;
  2         39  
285             next
286 0         0 if $self_enc eq $r_enc
287             # be more permissive: ASCII is one-way compatible with UTF-8 and Latin-1
288 1 50 0     9 or 'us-ascii' eq $r_enc and grep { $_ eq $self_enc } 'utf-8', 'iso-8859-1';
      33        
289              
290 0         0 XML::Builder::Util::croak(
291             'Cannot merge XML::Builder fragments with incompatible encodings'
292             . " (have $self_enc, fragment has $r_enc)"
293             );
294             }
295             continue {
296 223         841 push @gather, @take;
297             }
298              
299 170         2892 $self->content = \@gather;
300              
301 170         1913 return $self;
302             }
303              
304             sub as_string {
305 166     166   385 my $self = shift;
306 166         2743 my $builder = $self->builder;
307 166 100       412 return join '', map { ref $_ ? $_->as_string : $builder->escape_text( $_ ) } @{ $self->content };
  223         2471  
  166         2715  
308             }
309              
310             sub flatten {
311 0     0   0 my $self = shift;
312 0         0 return @{ $self->content };
  0         0  
313             }
314              
315             #######################################################################
316              
317             package XML::Builder::Fragment::Unsafe;
318             $XML::Builder::Fragment::Unsafe::VERSION = '0.906';
319 7     7   3586 use parent -norequire => 'XML::Builder::Fragment';
  7         20  
  7         33  
320              
321 0     0   0 sub depends_ns_scope { 0 }
322              
323             sub new {
324 1     1   2 my $class = shift;
325 1         3 my $self = bless { @_ }, $class;
326 1         23 $self->content = $self->builder->stringify( $self->content );
327 1         7 return $self;
328             }
329              
330             sub as_string {
331 1     1   2 my $self = shift;
332 1         16 return $self->builder->encode( $self->content );
333             }
334              
335 0     0   0 sub flatten { shift }
336              
337             #######################################################################
338              
339             package XML::Builder::Fragment::QName;
340             $XML::Builder::Fragment::QName::VERSION = '0.906';
341 7     7   1080 use Object::Tiny::Lvalue qw( builder ns name as_qname as_attr_qname as_clarkname as_string );
  7         12  
  7         35  
342              
343 7     7   1614 use parent -norequire => 'XML::Builder::Fragment';
  7         10  
  7         26  
344 7     7   248 use overload '""' => 'as_clarkname', fallback => 1;
  7         9  
  7         34  
345              
346             sub new {
347 51     51   59 my $class = shift;
348 51         215 my $self = bless { @_ }, $class;
349              
350 51         885 my $uri = $self->ns->uri;
351 51         2492 my $pfx = $self->ns->prefix;
352 51         1624 Scalar::Util::weaken $self->ns; # really don't even need this any more
353 51         1030 Scalar::Util::weaken $self->builder;
354              
355             # NB.: attributes without a prefix not in a namespace rather than in the
356             # default namespace, so attributes without a namespace never need a prefix
357              
358 51         1012 my $name = $self->name;
359 51 100       1035 $self->as_qname = ( '' eq $pfx ) ? $name : "$pfx:$name";
360 51 50 66     1106 $self->as_attr_qname = ( '' eq $pfx or '' eq $uri ) ? $name : "$pfx:$name";
361 51 100       1019 $self->as_clarkname = ( '' eq $uri ) ? $name : "{$uri}$name";
362 51         908 $self->as_string = '<' . $self->as_qname . '/>';
363              
364 51         1159 return $self;
365             }
366              
367             sub tag {
368 40     40   40 my $self = shift;
369              
370 40 50 33     124 if ( 'SCALAR' eq ref $_[0] and 'foreach' eq ${$_[0]} ) {
  0         0  
371 0         0 shift @_; # throw away
372 0         0 return $self->foreach( @_ );
373             }
374              
375             # has to be written this way so it'll drop undef attributes
376 40         54 my $attr = {};
377 40         96 XML::Builder::Util::merge_param_hash( $attr, \@_ );
378              
379 40   33     732 my $builder = $self->builder
380             || XML::Builder::Util::croak( 'XML::Builder for this QName object has gone out of scope' );
381              
382 40         312 return $builder->new_tag(
383             qname => $self,
384             attr => $attr,
385             content => [ map $builder->render( $_ ), @_ ],
386             );
387             }
388              
389             sub foreach {
390 8     8   10 my $self = shift;
391              
392 8         11 my $attr = {};
393 8         57 my @out = ();
394              
395 8   33     159 my $builder = $self->builder
396             || XML::Builder::Util::croak( 'XML::Builder for this QName object has gone out of scope' );
397              
398 8         36 do {
399 17         41 XML::Builder::Util::merge_param_hash( $attr, \@_ );
400 17 50       41 my $content = 'HASH' eq ref $_[0] ? undef : shift;
401 17         97 push @out, $builder->new_tag(
402             qname => $self,
403             attr => {%$attr},
404             content => $builder->render( $content ),
405             );
406             } while @_;
407              
408 8 100 66     133 return $builder->new_fragment( content => \@out )
409             if @out > 1 and not wantarray;
410              
411 3         36 return @out[ 0 .. $#out ];
412             }
413              
414             #######################################################################
415              
416             package XML::Builder::Fragment::Tag;
417             $XML::Builder::Fragment::Tag::VERSION = '0.906';
418 7     7   3300 use parent -norequire => 'XML::Builder::Fragment';
  7         9  
  7         33  
419 7     7   255 use Object::Tiny::Lvalue qw( qname attr );
  7         11  
  7         34  
420              
421 0     0   0 sub depends_ns_scope { 1 }
422              
423             sub as_string {
424 57     57   93 my $self = shift;
425              
426 57         929 my $builder = $self->builder;
427 57         963 my $qname = $self->qname->as_qname;
428 57   50     1859 my $attr = $self->attr || {};
429              
430 34         485 my $tag = join ' ', $qname,
431 57         381 map { sprintf '%s="%s"', $builder->parse_qname( $_ )->as_attr_qname, $builder->escape_attr( $attr->{ $_ } ) }
432             sort keys %$attr;
433              
434 57 100       2123 my $content = @{ $self->content } ? $self->SUPER::as_string : undef;
  57         1029  
435 57 100       2425 return defined $content
436             ? "<$tag>$content"
437             : "<$tag/>";
438             }
439              
440             sub append {
441 2     2   5 my $self = shift;
442 2         32 return $self->builder->new_fragment( content => [ $self, $self->builder->render( @_ ) ] );
443             }
444              
445             sub root {
446 3     3   5 my $self = shift;
447 3         55 bless $self, $self->builder->root_class;
448             }
449              
450 1     1   3 sub flatten { shift }
451              
452             #######################################################################
453              
454             package XML::Builder::Fragment::Root;
455             $XML::Builder::Fragment::Root::VERSION = '0.906';
456 7     7   2910 use parent -norequire => 'XML::Builder::Fragment::Tag';
  7         12  
  7         39  
457 7     7   256 use overload '""' => 'as_string', fallback => 1;
  7         10  
  7         23  
458              
459 1     1   4 sub depends_ns_scope { 0 }
460              
461             sub as_string {
462 3     3   6 my $self = shift;
463              
464 3         54 my %decl = map $_->xmlns, $self->builder->get_namespaces;
465              
466             # make sure to always declare the default NS (if not bound to a URI, by
467             # explicitly undefining it) to allow embedding the XML easily without
468             # having to parse the fragment
469 3 50       27 $decl{'xmlns'} = '' if not defined $decl{'xmlns'};
470              
471 3         11 local @{ $self->attr }{ keys %decl } = values %decl;
  3         61  
472              
473 3         29 return $self->SUPER::as_string( @_ );
474             }
475              
476             #######################################################################
477              
478             package XML::Builder::Fragment::Document;
479             $XML::Builder::Fragment::Document::VERSION = '0.906';
480 7     7   1220 use parent -norequire => 'XML::Builder::Fragment';
  7         10  
  7         31  
481 7     7   288 use overload '""' => 'as_string', fallback => 1;
  7         11  
  7         18  
482              
483             sub new {
484 1     1   2 my $class = shift;
485 1         8 my $self = $class->SUPER::new( @_ );
486 1         3 $self->validate;
487 1         3 return $self;
488             }
489              
490             sub validate {
491 1     1   2 my $self = shift;
492 1         8 my @root;
493              
494 1         2 for ( @{ $self->content } ) {
  1         20  
495 1 50       9 if ( Scalar::Util::blessed $_ ) {
496 1 50       17 if ( $_->isa( $self->builder->tag_class ) ) { push @root, $_; next }
  1         5  
  1         3  
497 0 0       0 if ( $_->isa( $self->builder->unsafe_class ) ) { next }
  0         0  
498             }
499 0         0 XML::Builder::Util::croak( 'Junk at top level of document' );
500             }
501              
502 1 50       4 XML::Builder::Util::croak( 'Document must have exactly one document element, not ' . @root )
503             if @root != 1;
504              
505 1         4 $root[0]->root;
506              
507 1         1 return;
508             }
509              
510             sub as_string {
511 1     1   176 my $self = shift;
512 1         2 my $preamble = qq(builder->encoding}"?>\n);
  1         18  
513 1         30 return $preamble . $self->SUPER::as_string( @_ );
514             }
515              
516             #######################################################################
517              
518             BEGIN {
519             package XML::Builder::Util;
520 7     7   2087 $XML::Builder::Util::VERSION = '0.906';
521 7     7   5509 use Carp::Clan '^XML::Builder(?:\z|::)';
  7         10635  
  7         59  
522              
523             sub merge_param_hash {
524 57     57   64 my ( $cur, $param ) = @_;
525              
526 57 100 66     444 return if not ( @$param and 'HASH' eq ref $param->[0] );
527              
528 17         22 my $new = shift @$param;
529              
530 17         52 @{ $cur }{ keys %$new } = values %$new;
  17         42  
531 17         68 while ( my ( $k, $v ) = each %$cur ) {
532 33 100       108 delete $cur->{ $k } if not defined $v;
533             }
534             }
535              
536             sub factory_method {
537 49     49   56 my ( $name, $class ) = @_;
538 49         75 my ( $class_method, $new_method ) = ( "$name\_class", "new_$name" );
539 49         2663 return <<";";
540             sub $class_method { "\Q$class\E" }
541             sub $new_method { \$_[0]->$class_method->new( builder => \@_ ) }
542             ;
543             }
544             }
545              
546             1;
547              
548             __END__