File Coverage

blib/lib/XML/Builder.pm
Criterion Covered Total %
statement 281 309 90.9
branch 55 78 70.5
condition 25 50 50.0
subroutine 76 86 88.3
pod 0 32 0.0
total 437 555 78.7


line stmt bran cond sub pod time code
1 7     7   2987 use 5.008001;
  7         17  
  7         234  
2 7     7   27 use strict;
  7         5  
  7         166  
3 7     7   25 use warnings;
  7         11  
  7         148  
4              
5 7     7   25 use Scalar::Util ();
  7         8  
  7         76  
6 7     7   5979 use Encode ();
  7         67547  
  7         326  
7              
8             package XML::Builder;
9             $XML::Builder::VERSION = '0.905';
10             # ABSTRACT: programmatic XML generation, conveniently
11              
12 7     7   3451 use Object::Tiny::Lvalue qw( nsmap default_ns encoding );
  7         2251  
  7         36  
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 7 eval XML::Builder::Util::factory_method( $name, $class )
  107     107 0 306  
  1     1 0 5  
  107     107 0 199  
  10     10 0 47  
  49     49 0 1163  
  0     0 0 0  
  55     55 0 121  
  1     1 0 4  
  10     10 0 70  
  49     49 0 198  
  3     3 0 68  
  56     56 0 215  
  1     1 0 7  
27             while ( $name, $class ) = each %class;
28              
29             sub new {
30 8     8 0 74 my $class = shift;
31 8         28 my $self = bless { @_ }, $class;
32 8   50     287 $self->encoding ||= 'us-ascii';
33 8   50     315 $self->nsmap ||= {};
34 8         66 return $self;
35             }
36              
37             sub register_ns {
38 43     43 0 56 my $self = shift;
39 43         56 my ( $uri, $pfx ) = @_;
40              
41 43         1145 my $nsmap = $self->nsmap;
42              
43 43         209 $uri = $self->stringify( $uri );
44              
45 43 100       140 if ( exists $nsmap->{ $uri } ) {
46 32         49 my $ns = $nsmap->{ $uri };
47 32         710 my $registered_pfx = $ns->prefix;
48              
49 32 50 33     176 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         161 return $ns;
53             }
54              
55 11 100       34 if ( not defined $pfx ) {
56 4         17 my %pfxmap = map {; $_->prefix => $_ } values %$nsmap;
  3         96  
57              
58 4 100 66     61 if ( $uri eq '' and not exists $pfxmap{ '' } ) {
59 1         16 return $self->register_ns( '', '' );
60             }
61              
62 3         5 my $counter;
63 3 50       24 my $letter = ( $uri =~ m!([[:alpha:]])[^/]*/?\z! ) ? lc $1 : 'ns';
64 3         6 do { $pfx = $letter . ++$counter } while exists $pfxmap{ $pfx };
  3         20  
65             }
66              
67             # FIXME needs proper validity check per XML TR
68 10 50 66     68 XML::Builder::Util::croak( "Invalid namespace prefix '$pfx'" )
69             if length $pfx and $pfx !~ /[\w-]/;
70              
71 10         332 my $ns = $self->new_ns(
72             uri => $uri,
73             prefix => $pfx,
74             );
75              
76 10 100       172 $self->default_ns = $uri if '' eq $pfx;
77 10         75 return $nsmap->{ $uri } = $ns;
78             }
79              
80             sub get_namespaces {
81 3     3 0 141 my $self = shift;
82 3         3 return values %{ $self->nsmap };
  3         68  
83             }
84              
85 7     7 0 51 sub ns { shift->register_ns( @_ )->factory }
86 3     3 0 338 sub null_ns { shift->ns( '', '' ) }
87              
88             sub qname {
89 35     35 0 38 my $self = shift;
90 35         41 my $ns_uri = shift;
91 35         66 return $self->register_ns( $ns_uri )->qname( @_ );
92             }
93              
94             sub parse_qname {
95 34     34 0 42 my $self = shift;
96 34         45 my ( $name ) = @_;
97              
98 34         37 my $ns_uri = '';
99 34 50       89 $ns_uri = $1 if $name =~ s/\A\{([^}]+)\}//;
100              
101 34         66 return $self->qname( $ns_uri, $name );
102             }
103              
104             sub root {
105 2     2 0 3 my $self = shift;
106 2         4 my ( $tag ) = @_;
107 2         12 return $tag->root;
108             }
109              
110             sub document {
111 1     1 0 2 my $self = shift;
112 1         18 return $self->new_document( content => [ @_ ] );
113             }
114              
115             sub unsafe {
116 1     1 0 2 my $self = shift;
117 1         1 my ( $string ) = @_;
118 1         25 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 102     102 0 145 my $self = shift;
139 1         8 return 'SCALAR' eq ref $_[0]
140 102 100       1913 ? $self->qname( ${$_[0]}, @_[ 1 .. $#_ ] )
141             : $self->new_fragment( content => [ @_ ] );
142             }
143              
144             sub test_fragment {
145 149     149 0 236 my $self = shift;
146 149         113 my ( $obj ) = @_;
147 149         524 return $obj->isa( 'XML::Builder::Fragment::Role' );
148             }
149              
150             {
151 7     7   8377 no warnings 'qw';
  7         14  
  7         2213  
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 18 eval sprintf 'sub %s {
  1     35 0 2  
  1     65 0 15  
  35         1616  
  35         98  
  35         85  
  7         14  
  35         689  
  65         122  
  65         162  
  65         142  
  13         39  
  65         1265  
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 1466 my $self = shift;
183 149         174 my ( $thing ) = @_;
184              
185 149 50       312 return if not defined $thing;
186              
187 149 100       2331 return $thing if not Scalar::Util::blessed $thing;
188              
189 4   100     46 my $conv = $thing->can( 'as_string' ) || overload::Method( $thing, '""' );
190 4 100       1250 return $conv->( $thing ) if $conv;
191              
192 1         6 XML::Builder::Util::croak( 'Unstringifiable object ', $thing );
193             }
194              
195             #######################################################################
196              
197             package XML::Builder::NS;
198             $XML::Builder::NS::VERSION = '0.905';
199 7     7   48 use Object::Tiny::Lvalue qw( builder uri prefix qname_for_localname );
  7         10  
  7         31  
200 7     7   10525 use overload '""' => 'uri', fallback => 1;
  7         6419  
  7         41  
201              
202             sub new {
203 10     10   19 my $class = shift;
204 10         63 my $self = bless { @_ }, $class;
205 10   50     228 $self->qname_for_localname ||= {};
206 10         614 Scalar::Util::weaken $self->builder;
207 10         67 return $self;
208             }
209              
210             sub qname {
211 82     82   92 my $self = shift;
212 82         84 my $name = shift;
213              
214 82   33     1619 my $builder = $self->builder
215             || XML::Builder::Util::croak( 'XML::Builder for this NS object has gone out of scope' );
216              
217 82   66     1787 my $qname
218             = $self->qname_for_localname->{ $name }
219             ||= $builder->new_qname( name => $name, ns => $self );
220              
221 82 100       2317 return @_ ? $qname->tag( @_ ) : $qname;
222             }
223              
224             sub xmlns {
225 3     3   33 my $self = shift;
226 3         62 my $pfx = $self->prefix;
227 3 50       78 return ( ( '' ne $pfx ? "xmlns:$pfx" : 'xmlns' ), $self->uri );
228             }
229              
230 7     7   34 sub factory { bless \shift, 'XML::Builder::NS::QNameFactory' }
231              
232             #######################################################################
233              
234             package XML::Builder::NS::QNameFactory;
235             $XML::Builder::NS::QNameFactory::VERSION = '0.905';
236 46     46   880 sub AUTOLOAD { my $self = shift; $$self->qname( ( our $AUTOLOAD =~ /.*::(.*)/ ), @_ ) }
  46         213  
237 1     1   3 sub _qname { my $self = shift; $$self->qname( @_ ) }
  1         11  
238 0     0   0 sub DESTROY {}
239              
240             #######################################################################
241              
242             package XML::Builder::Fragment::Role;
243             $XML::Builder::Fragment::Role::VERSION = '0.905';
244 0     0   0 sub depends_ns_scope { 1 }
245              
246             #######################################################################
247              
248             package XML::Builder::Fragment;
249             $XML::Builder::Fragment::VERSION = '0.905';
250 7     7   6446 use parent -norequire => 'XML::Builder::Fragment::Role';
  7         2022  
  7         43  
251              
252 7     7   310 use Object::Tiny::Lvalue qw( builder content );
  7         11  
  7         51  
253              
254 0     0   0 sub depends_ns_scope { 0 }
255              
256             sub new {
257 163     163   146 my $class = shift;
258 163         511 my $self = bless { @_ }, $class;
259 163         2571 my $builder = $self->builder;
260 163         3205 my $content = $self->content;
261              
262 163         346 my ( @gather, @take );
263              
264 163 100       366 for my $r ( 'ARRAY' eq ref $content ? @$content : $content ) {
265 213         317 @take = $r;
266              
267 213 100       485 if ( not Scalar::Util::blessed $r ) {
268 64 50       96 @take = $builder->render( @_ ) if 'ARRAY' eq ref $r;
269 64         63 next;
270             }
271              
272 149 50       218 if ( not $builder->test_fragment( $r ) ) {
273 0         0 @take = $builder->stringify( $r );
274 0         0 next;
275             }
276              
277 149 100       2294 next if $builder == $r->builder;
278              
279 1 50       128 XML::Builder::Util::croak( 'Cannot merge XML::Builder fragments built with different namespace maps' )
280             if $r->depends_ns_scope;
281              
282 1         16 @take = $r->flatten;
283              
284 1         34 my ( $self_enc, $r_enc ) = map { lc $_->encoding } $builder, $r->builder;
  2         72  
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     11 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 213         887 push @gather, @take;
297             }
298              
299 163         2669 $self->content = \@gather;
300              
301 163         1819 return $self;
302             }
303              
304             sub as_string {
305 159     159   444 my $self = shift;
306 159         3428 my $builder = $self->builder;
307 159 100       504 return join '', map { ref $_ ? $_->as_string : $builder->escape_text( $_ ) } @{ $self->content };
  213         3124  
  159         3018  
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.905';
319 7     7   3957 use parent -norequire => 'XML::Builder::Fragment';
  7         23  
  7         34  
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         6 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.905';
341 7     7   1189 use Object::Tiny::Lvalue qw( builder ns name as_qname as_attr_qname as_clarkname as_string );
  7         10  
  7         40  
342              
343 7     7   1999 use parent -norequire => 'XML::Builder::Fragment';
  7         12  
  7         32  
344 7     7   327 use overload '""' => 'as_clarkname', fallback => 1;
  7         10  
  7         37  
345              
346             sub new {
347 49     49   65 my $class = shift;
348 49         226 my $self = bless { @_ }, $class;
349              
350 49         1055 my $uri = $self->ns->uri;
351 49         2611 my $pfx = $self->ns->prefix;
352 49         1827 Scalar::Util::weaken $self->ns; # really don't even need this any more
353 49         1201 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 49         955 my $name = $self->name;
359 49 100       1002 $self->as_qname = ( '' eq $pfx ) ? $name : "$pfx:$name";
360 49 50 66     1179 $self->as_attr_qname = ( '' eq $pfx or '' eq $uri ) ? $name : "$pfx:$name";
361 49 100       1053 $self->as_clarkname = ( '' eq $uri ) ? $name : "{$uri}$name";
362 49         995 $self->as_string = '<' . $self->as_qname . '/>';
363              
364 49         1164 return $self;
365             }
366              
367             sub tag {
368 40     40   45 my $self = shift;
369              
370 40 50 33     117 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         46 my $attr = {};
377 40         95 XML::Builder::Util::merge_param_hash( $attr, \@_ );
378              
379 40   33     720 my $builder = $self->builder
380             || XML::Builder::Util::croak( 'XML::Builder for this QName object has gone out of scope' );
381              
382 40         277 return $builder->new_tag(
383             qname => $self,
384             attr => $attr,
385             content => [ map $builder->render( $_ ), @_ ],
386             );
387             }
388              
389             sub foreach {
390 7     7   9 my $self = shift;
391              
392 7         7 my $attr = {};
393 7         10 my @out = ();
394              
395 7   33     110 my $builder = $self->builder
396             || XML::Builder::Util::croak( 'XML::Builder for this QName object has gone out of scope' );
397              
398 7         58 do {
399 15         97 XML::Builder::Util::merge_param_hash( $attr, \@_ );
400 15 50       32 my $content = 'HASH' eq ref $_[0] ? undef : shift;
401 15         41 push @out, $builder->new_tag(
402             qname => $self,
403             attr => {%$attr},
404             content => $builder->render( $content ),
405             );
406             } while @_;
407              
408 7 100 66     94 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.905';
418 7     7   4028 use parent -norequire => 'XML::Builder::Fragment';
  7         13  
  7         35  
419 7     7   281 use Object::Tiny::Lvalue qw( qname attr );
  7         11  
  7         32  
420              
421 0     0   0 sub depends_ns_scope { 1 }
422              
423             sub as_string {
424 55     55   65 my $self = shift;
425              
426 55         1092 my $builder = $self->builder;
427 55         1266 my $qname = $self->qname->as_qname;
428 55   50     2204 my $attr = $self->attr || {};
429              
430 34         641 my $tag = join ' ', $qname,
431 55         373 map { sprintf '%s="%s"', $builder->parse_qname( $_ )->as_attr_qname, $builder->escape_attr( $attr->{ $_ } ) }
432             sort keys %$attr;
433              
434 55 100       2483 my $content = @{ $self->content } ? $self->SUPER::as_string : undef;
  55         1295  
435 55 100       2585 return defined $content
436             ? "<$tag>$content"
437             : "<$tag/>";
438             }
439              
440             sub append {
441 2     2   3 my $self = shift;
442 2         52 return $self->builder->new_fragment( content => [ $self, $self->builder->render( @_ ) ] );
443             }
444              
445             sub root {
446 3     3   5 my $self = shift;
447 3         49 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.905';
456 7     7   7855 use parent -norequire => 'XML::Builder::Fragment::Tag';
  7         14  
  7         58  
457 7     7   305 use overload '""' => 'as_string', fallback => 1;
  7         10  
  7         26  
458              
459 1     1   5 sub depends_ns_scope { 0 }
460              
461             sub as_string {
462 3     3   4 my $self = shift;
463              
464 3         69 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       32 $decl{'xmlns'} = '' if not defined $decl{'xmlns'};
470              
471 3         10 local @{ $self->attr }{ keys %decl } = values %decl;
  3         77  
472              
473 3         47 return $self->SUPER::as_string( @_ );
474             }
475              
476             #######################################################################
477              
478             package XML::Builder::Fragment::Document;
479             $XML::Builder::Fragment::Document::VERSION = '0.905';
480 7     7   1389 use parent -norequire => 'XML::Builder::Fragment';
  7         14  
  7         33  
481 7     7   342 use overload '""' => 'as_string', fallback => 1;
  7         10  
  7         27  
482              
483             sub new {
484 1     1   2 my $class = shift;
485 1         7 my $self = $class->SUPER::new( @_ );
486 1         4 $self->validate;
487 1         3 return $self;
488             }
489              
490             sub validate {
491 1     1   3 my $self = shift;
492 1         1 my @root;
493              
494 1         2 for ( @{ $self->content } ) {
  1         23  
495 1 50       8 if ( Scalar::Util::blessed $_ ) {
496 1 50       20 if ( $_->isa( $self->builder->tag_class ) ) { push @root, $_; next }
  1         1  
  1         2  
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       7 XML::Builder::Util::croak( 'Document must have exactly one document element, not ' . @root )
503             if @root != 1;
504              
505 1         5 $root[0]->root;
506              
507 1         2 return;
508             }
509              
510             sub as_string {
511 1     1   225 my $self = shift;
512 1         1 my $preamble = qq(builder->encoding}"?>\n);
  1         19  
513 1         32 return $preamble . $self->SUPER::as_string( @_ );
514             }
515              
516             #######################################################################
517              
518             BEGIN {
519             package XML::Builder::Util;
520 7     7   2657 $XML::Builder::Util::VERSION = '0.905';
521 7     7   6755 use Carp::Clan '^XML::Builder(?:\z|::)';
  7         12227  
  7         47  
522              
523             sub merge_param_hash {
524 55     55   67 my ( $cur, $param ) = @_;
525              
526 55 100 66     261 return if not ( @$param and 'HASH' eq ref $param->[0] );
527              
528 17         24 my $new = shift @$param;
529              
530 17         50 @{ $cur }{ keys %$new } = values %$new;
  17         170  
531 17         79 while ( my ( $k, $v ) = each %$cur ) {
532 33 100       103 delete $cur->{ $k } if not defined $v;
533             }
534             }
535              
536             sub factory_method {
537 49     49   65 my ( $name, $class ) = @_;
538 49         96 my ( $class_method, $new_method ) = ( "$name\_class", "new_$name" );
539 49         3487 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__