File Coverage

blib/lib/XML/API.pm
Criterion Covered Total %
statement 345 480 71.8
branch 134 224 59.8
condition 28 67 41.7
subroutine 43 57 75.4
pod 1 1 100.0
total 551 829 66.4


line stmt bran cond sub pod time code
1             # Private package (not to be used outside XML::API)
2             package XML::API::Element;
3 9     9   134913 use strict;
  9         10  
  9         207  
4 9     9   25 use warnings;
  9         9  
  9         191  
5 9     9   25 use Carp qw(croak);
  9         11  
  9         412  
6 9     9   27 use Scalar::Util qw(weaken refaddr);
  9         10  
  9         5909  
7              
8             our $VERSION = '0.30';
9              
10             sub new {
11 64     64   650 my $proto = shift;
12 64   33     166 my $class = ref($proto) || $proto;
13 64         200 my $self = {
14             attrs => {},
15             contents => [],
16             @_
17             };
18              
19 64 100       126 if ( $self->{comment} ) {
20 2         8 $self->{comment} =~ s/--/- -/go;
21             }
22              
23 64 100       151 weaken( $self->{parent} ) if ( exists $self->{parent} );
24              
25 64         60 bless( $self, $class );
26 64         77 return $self;
27             }
28              
29             sub parent {
30 85     85   64 my $self = shift;
31 85         88 return $self->{parent};
32             }
33              
34             sub inline {
35 48     48   35 my $self = shift;
36 48         123 return $self->{inline};
37             }
38              
39             sub attrs_as_string {
40 79     79   51 my $self = shift;
41 79         57 my @strings;
42              
43 79         44 foreach my $key ( sort keys %{ $self->{attrs} } ) {
  79         163  
44 26         24 my $val = $self->{attrs}->{$key};
45 26 50       36 if ( !defined($val) ) {
46 0         0 warn "Attribute '$key' (element '$self->{element}') is undefined";
47 0         0 $val = '*undef*';
48             }
49 26         53 push( @strings, $key . '="' . $val . '"' );
50             }
51              
52 79 100       165 return '' unless (@strings);
53 21         53 return ' ' . join( ' ', @strings );
54             }
55              
56             sub add {
57 84     84   65 my $self = shift;
58 84         48 push( @{ $self->{contents} }, @_ );
  84         198  
59             }
60              
61             sub as_string {
62 81     81   1001 my $self = shift;
63 81   100     158 my $indent = shift || '';
64 81   100     122 my $growindent = shift || '';
65              
66 81 100       107 if ( $self->{comment} ) {
67 2         9 return $indent . '';
68             }
69              
70 79 100       96 if ( $self->{cdata} ) {
71 2         9 return $indent . '{cdata} . ']]>';
72             }
73              
74 77 100       45 if ( !@{ $self->{contents} } ) {
  77         125  
75             return
76             $indent . '<'
77             . ( $self->{ns} ? $self->{ns} . ':' : '' )
78             . $self->{element}
79 8 50       23 . $self->attrs_as_string . ' />';
80             }
81              
82             my $str =
83             $indent . '<'
84             . ( $self->{ns} ? $self->{ns} . ':' : '' )
85             . $self->{element}
86 69 100       163 . $self->attrs_as_string . '>';
87 69         58 my $complex = 0;
88              
89 69         41 foreach my $c ( @{ $self->{contents} } ) {
  69         102  
90 104 100       71 if ( eval { $c->isa(__PACKAGE__) and !$c->inline } ) {
  104 100       365  
    100          
91 48         40 $complex = 1;
92 48         108 $str .= "\n" . $c->as_string( $indent . $growindent, $growindent );
93             }
94 56         155 elsif ( eval { $c->isa('XML::API') } ) { # assume it is complex?
95             $str .= "\n"
96             . join( "\n",
97 3         6 map { $_->as_string( $indent . $growindent, $growindent ) }
  3         6  
98             $c->_elements );
99             }
100             else {
101 53 50       116 $str .= $c if ( defined($c) );
102             }
103             }
104              
105 69 100       96 if ($complex) {
106 28         26 $str .= "\n" . $indent;
107             }
108             $str .=
109 69 100       118 '{ns} ? $self->{ns} . ':' : '' ) . $self->{element} . '>';
110 69         176 return $str;
111             }
112              
113             sub fast_string {
114 2     2   3 my $self = shift;
115              
116 2 50       4 $self->{comment} && return '';
117 2 50       5 $self->{cdata} && return '{cdata} . ']]>';
118              
119             return
120             '<'
121             . ( $self->{ns} ? $self->{ns} . ':' : '' )
122             . $self->{element}
123             . $self->attrs_as_string . ' />'
124 2 50       2 unless @{ $self->{contents} };
  2 100       9  
125              
126             return
127             '<'
128             . ( $self->{ns} ? $self->{ns} . ':' : '' )
129             . $self->{element}
130             . $self->attrs_as_string . '>'
131             . join(
132             '',
133             map {
134 1         5 eval { $_->isa(__PACKAGE__) } ? $_->fast_string
135             : (
136 1         8 eval { $_->isa('XML::API') }
137 1 50       1 ? join( '', map { $_->fast_string } $_->_elements )
  0 50       0  
138             : $_
139             )
140 1         1 } @{ $self->{contents} }
141             )
142             . '
143             . ( $self->{ns} ? $self->{ns} . ':' : '' )
144 1 50       3 . $self->{element} . '>';
    50          
145             }
146              
147             # Private package (not to be used outside XML::API)
148             package XML::API::SAXHandler;
149 9     9   40 use strict;
  9         8  
  9         127  
150 9     9   21 use warnings;
  9         7  
  9         222  
151 9     9   26 use base qw(XML::SAX::Base);
  9         9  
  9         7110  
152              
153             sub new {
154 2     2   3 my $proto = shift;
155 2   33     10 my $class = ref($proto) || $proto;
156 2         6 my $self = {
157             xmlapi => undef,
158             @_,
159             };
160 2         3 bless( $self, $class );
161 2         13 return $self;
162             }
163              
164             sub start_element {
165 0     0   0 my $self = shift;
166 0         0 my $hash = shift;
167 0 0       0 if ( $hash->{Name} eq '_xml_api_ignore' ) {
168 0         0 $self->{xml_api_ignore} = 1;
169 0         0 return;
170             }
171 0         0 $self->{xml_api_ignore} = 0;
172              
173 0         0 my $attrs = {};
174 0         0 foreach my $val ( values %{ $hash->{Attributes} } ) {
  0         0  
175 0         0 $attrs->{ $val->{Name} } = $val->{Value};
176             }
177              
178 0         0 $self->{xmlapi}->_open( $hash->{Name}, $attrs );
179 0         0 return;
180             }
181              
182             sub characters {
183 0     0   0 my $self = shift;
184 0         0 my $hash = shift;
185 0 0       0 $self->{xml_api_ignore} && return;
186 0         0 $self->{xmlapi}->_add( $hash->{Data} );
187 0         0 return;
188             }
189              
190             sub end_element {
191 0     0   0 my $self = shift;
192 0         0 my $hash = shift;
193 0 0       0 if ( $hash->{Name} eq '_xml_api_ignore' ) {
194 0         0 return;
195             }
196              
197 0         0 $self->{xmlapi}->_close( $hash->{Name} );
198 0         0 return;
199             }
200              
201             # ----------------------------------------------------------------------
202             # XML::API - Perl extension for creating XML documents
203             # ----------------------------------------------------------------------
204             package XML::API;
205 9     9   124610 use strict;
  9         20  
  9         194  
206 9     9   27 use warnings;
  9         8  
  9         331  
207 9     9   78 use overload '""' => \&_as_string, 'fallback' => 1;
  9         9  
  9         48  
208 9     9   510 use Carp qw(carp croak confess);
  9         11  
  9         499  
209 9     9   32 use Scalar::Util qw(weaken refaddr);
  9         10  
  9         346  
210 9     9   4374 use XML::SAX;
  9         27350  
  9         28868  
211              
212             our $VERSION = '0.30';
213             our $DEFAULT_ENCODING = 'UTF-8';
214             our $ENCODING = undef;
215             our $Indent = ' ';
216             our $AUTOLOAD;
217              
218             # Not implemented yet:
219             # strict => 0|1 # Optional, defaults to 0
220             #By default strict checking is performed to make sure that the structure
221             #of the document matches the Schema. This can be turned off by setting
222             #'strict' to false (0 or undef).
223              
224             sub new {
225 16     16 1 2232 my $proto = shift;
226 16   33     69 my $class = ref($proto) || $proto;
227              
228 16         54 my $self = {
229             doctype => undef,
230             encoding => undef,
231             debug => undef,
232             @_,
233             };
234              
235             #
236             # Derived classes
237             #
238 16 50       56 if ( $class ne __PACKAGE__ ) {
    100          
239 0 0       0 if ( $self->{doctype} ) {
240 0         0 confess("Must not specify doctype when instantiating $class");
241             }
242             }
243             elsif ( $self->{doctype} ) {
244 5         16 $class = $class . '::' . uc( $self->{doctype} );
245 5 100       246 if ( !eval "require $class;1;" ) {
246 1         9 die "Could not load module '$class'";
247             }
248             }
249 15         23 delete $self->{doctype};
250 15         21 bless( $self, $class );
251              
252 15   33     302 $self->{encoding} = $self->{encoding} || $ENCODING || $DEFAULT_ENCODING;
253 15         23 $self->{elements} = [];
254 15         32 $self->{current} = undef;
255 15         16 $self->{string} = undef;
256 15         18 $self->{ids} = {};
257 15         18 $self->{langs} = {};
258              
259 15         72 return $self;
260             }
261              
262             sub _root_element {
263 54     54   98 return '';
264             }
265              
266             sub _root_attrs {
267 0     0   0 return {};
268             }
269              
270             sub _doctype {
271 19     19   39 return '';
272             }
273              
274             sub _elements {
275 3     3   3 my $self = shift;
276 3         3 return @{ $self->{elements} };
  3         4  
277             }
278              
279             sub _open {
280 58     58   955 my $self = shift;
281 58   33     107 my $element = shift || croak '_open($element,...)';
282              
283 58         49 my $namespace = $self->{namespace};
284              
285             # reset the output string in case it has been cached
286 58         56 $self->{string} = undef;
287              
288 58 100       82 if ( $element eq $self->_root_element ) {
289 4         8 $self->{has_root_element} = 1;
290             }
291              
292 58         63 my $attrs = {};
293 58         41 my @content;
294              
295 58         56 my $total = scalar(@_) - 1;
296 58         37 my $next;
297              
298 58         110 foreach my $i ( 0 .. $total ) {
299 42 100       66 if ($next) {
300 6         5 $next = undef;
301 6         5 next;
302             }
303              
304 36         37 my $arg = $_[$i];
305 36 100 66     151 if ( ref($arg) eq 'HASH' ) {
    100          
306 1         5 while ( my ( $key, $val ) = each %$arg ) {
307 0         0 $attrs->{$key} = _escapeXML($val);
308 0 0       0 if ( !defined($val) ) {
309 0         0 carp "attribute '$key' undefined (element '$element')";
310 0         0 $attrs->{$key} = '';
311             }
312             }
313             }
314             elsif ( defined($arg) and $arg =~ m/^-[^0-9\.]+/o ) {
315 6         12 $arg =~ s/^-//o;
316 6         13 $attrs->{$arg} = _escapeXML( $_[ ++$i ] );
317 6 50       13 if ( !defined( $attrs->{$arg} ) ) {
318 0         0 carp "attribute '$arg' undefined (element '$element') ";
319 0         0 $attrs->{$arg} = '';
320             }
321 6         8 $next = 1;
322 6         7 next;
323             }
324             else {
325 29         49 push( @content, $arg );
326             }
327             }
328              
329             #
330             # Start with the default root element attributes and add those
331             # given if this is the root element
332             #
333 58 100       78 if ( $element eq $self->_root_element ) {
334 4         8 my $rootattrs = $self->_root_attrs;
335 4         18 while ( my ( $key, $val ) = each %$attrs ) {
336 0         0 $rootattrs->{$key} = $val;
337             }
338 4         5 $attrs = $rootattrs;
339             }
340              
341 58 50       88 my ( $file, $line ) = (caller)[ 1, 2 ] if ( $self->{debug} );
342              
343 58 100       77 if ( $self->{langnext} ) {
344 4         8 $attrs->{'xml:lang'} = delete $self->{langnext};
345             }
346 58 100       93 if ( $self->{dirnext} ) {
347 1         1 $attrs->{'dir'} = delete $self->{dirnext};
348             }
349              
350 58         35 my $e;
351 58 100       77 if ( $self->{current} ) {
352             $e = XML::API::Element->new(
353             element => $element,
354             attrs => $attrs,
355             ns => $namespace,
356             parent => $self->{current},
357 44         75 );
358 44         60 $self->_add($e);
359             }
360             else {
361 14         47 $e = XML::API::Element->new(
362             element => $element,
363             attrs => $attrs,
364             ns => $namespace,
365             );
366 14         13 push( @{ $self->{elements} }, $e );
  14         31  
367             }
368              
369 58         54 $self->{current} = $e;
370 58 100       69 if ( $self->{_raw} ) {
371 1         2 $self->_raw(@content);
372             }
373             else {
374 57         79 $self->_add(@content);
375             }
376              
377             $self->_comment("DEBUG: '$element' (open) at $file:$line")
378 58 50       94 if ( $self->{debug} );
379              
380 58         85 return $e;
381             }
382              
383             sub _add {
384 107     107   74 my $self = shift;
385 107         89 $self->{string} = undef;
386 107 50       198 if ( !$self->{current} ) {
387 0         0 croak 'Cannot use _add with no current element';
388             }
389              
390 107         115 foreach my $item (@_) {
391 78 50       102 carp "undefined input" unless ( defined($item) );
392 78 100       63 if ( eval { $item->isa(__PACKAGE__) } ) {
  78         295  
393 1 50       6 if ( refaddr($item) == refaddr($self) ) {
394 0         0 croak 'Cannot _add object to itself';
395             }
396 1 50       4 if ( !$self->{current} ) {
397 0         0 push( @{ $self->{elements} }, $item );
  0         0  
398             }
399             else {
400 1         3 $self->{current}->add($item);
401             }
402 1         2 bless( $item, ref($self) );
403 1         2 $item->{parent} = $self;
404 1         3 weaken( $item->{parent} );
405              
406 1         1 foreach my $lang ( keys %{ $item->{langs} } ) {
  1         4  
407 0         0 $self->{langs}->{$lang} = 1;
408             }
409             }
410             else {
411 77 100       61 if ( eval { $item->isa('XML::API::Element') } ) {
  77 100       186  
412 44         66 $self->{current}->add($item);
413             }
414 33         109 elsif ( eval { $item->isa('XML::API::Cache') } ) {
415 1         3 foreach my $lang ( $item->langs ) {
416 1         2 $self->{langs}->{$lang} = 1;
417             }
418 1         3 $self->{current}->add($item);
419             }
420             else {
421 32         49 $self->{current}->add( _escapeXML($item) );
422             }
423             }
424             }
425             }
426              
427             sub _raw {
428 4     4   5 my $self = shift;
429 4         5 $self->{string} = undef;
430 4         5 foreach my $item (@_) {
431 4 50       9 carp "undefined input" unless ( defined($item) );
432 4 50 66     23 if ( ref($item) and $item->isa(__PACKAGE__) ) {
433 0         0 croak 'Cannot add XML::API objects as raw';
434             }
435 4 100       10 if ( $self->{current} ) {
436 3         4 $self->{current}->add($item);
437             }
438             else {
439 1         0 push( @{ $self->{elements} }, $item );
  1         3  
440             }
441             }
442             }
443              
444             sub _close {
445 46     46   34 my $self = shift;
446 46   33     80 my $element = shift || croak '_close($element)';
447              
448 46 50       82 my ( $file, $line ) = (caller)[ 1, 2 ] if ( $self->{debug} );
449              
450 46 50       65 if ( !$self->{current} ) {
451 0         0 carp 'attempt to close non-existent element "' . $element . '"';
452 0         0 return;
453             }
454              
455 46 50       65 if ( $element eq $self->{current}->{element} ) {
456 46 100       58 if ( $self->{current}->parent ) {
457 39         44 $self->{current} = $self->{current}->parent;
458             $self->_comment("DEBUG: '$element' close at $file:$line")
459 39 50       63 if ( $self->{debug} );
460             }
461             else {
462 7         13 $self->{current} = undef;
463             }
464             }
465             else {
466             carp 'attempted to close element "'
467             . $element
468             . '" when current '
469             . 'element is "'
470 0         0 . $self->{current}->{element} . '"';
471             }
472 46         124 return;
473             }
474              
475             sub _element {
476 3     3   8 my $self = shift;
477 3   33     6 my $element = shift || croak '_element($element)';
478 3         7 my $e = $self->_open( $element, @_ );
479 3         7 $self->_close($element);
480 3         3 return $e;
481             }
482              
483             #
484             # The implementation for element, element_open and element_close
485             #
486              
487             sub AUTOLOAD {
488 60     60   253 my $self = shift;
489 60         50 my $element = $AUTOLOAD;
490              
491 60         57 my ( $open, $close ) = ( 0, 0 );
492              
493 60 100       264 if ( $element =~ s/.*::(.+)_open$/$1/o ) {
    100          
    100          
494 23         26 my $old_ns = $self->{namespace};
495              
496 23 50       40 if ( $element =~ s/(.+)__(.+)/$2/o ) {
497 0         0 $self->{namespace} = $1;
498             }
499              
500 23         45 my $e = $self->_open( $element, @_ );
501 23         29 $self->{namespace} = $old_ns;
502 23         90 return $e;
503             }
504             elsif ( $element =~ s/.*::(.+)_close$/$1/o ) {
505 11         13 $element =~ s/(.+)__(.+)/$2/o;
506 11         18 return $self->_close($element);
507             }
508             elsif ( $element =~ s/.*::(.+)_raw$/$1/o ) {
509 1         1 $element =~ s/(.+)__(.+)/$2/o;
510 1         3 $self->{_raw} = 1;
511 1         1 $self->_open( $element, @_ );
512 1         1 $self->{_raw} = 0;
513 1         2 return $self->_close($element);
514             }
515              
516 25         68 $element =~ s/.*:://o;
517 25 50       37 croak 'element not defined' unless ($element);
518              
519 25 50       41 if ( $element =~ /^_/o ) {
520 0         0 croak 'Undefined subroutine &' . ref($self) . "::$element called";
521             }
522              
523 25         28 my $old_ns = $self->{namespace};
524              
525 25 100       39 if ( $element =~ s/(.+)__(.+)/$2/o ) {
526 1         1 $self->{namespace} = $1;
527             }
528 25         37 my $e = $self->_open( $element, @_ );
529 25         20 $self->{namespace} = $old_ns;
530 25         38 $self->_close($element);
531 25         170 return $e;
532             }
533              
534             sub _ast {
535 1     1   4 my $self = shift;
536              
537 1         5 foreach my $i ( 1 .. int( scalar(@_) / 2 ) ) {
538 1         3 my ( $e, $val ) = splice( @_, 0, 2 );
539              
540 1 50       3 if ( !ref($val) ) {
541 0         0 $self->_element( $e, $val );
542 0         0 next;
543             }
544              
545 1         1 my $attr = {};
546 1         2 my @contents = ();
547              
548 1 50 33     5 if ( ref($val) and ref($val) eq 'ARRAY' ) {
549 1         2 my @val = @$val;
550              
551 1         2 foreach my $i ( 1 .. int( scalar(@val) / 2 ) ) {
552 1         2 my ( $arg, $arg2 ) = splice( @val, 0, 2 );
553              
554 1 50 33     5 if ( $arg =~ s/^-(.+)/$1/o ) {
    50          
555 0         0 $attr->{$arg} = $arg2;
556             }
557             elsif ( ref($arg2) and ref($arg2) eq 'ARRAY' ) {
558 0         0 push( @contents, [ $arg, $arg2 ] );
559             }
560             else {
561 1         3 push( @contents, { $arg => $arg2 } );
562             }
563             }
564              
565 1 50       3 push( @contents, @val ) if (@val);
566             }
567             else {
568 0         0 push( @contents, $val );
569             }
570 1         3 $self->_open( $e, $attr );
571              
572 1         2 foreach my $c (@contents) {
573 1 50 33     12 if ( ref($c) and ref($c) eq 'ARRAY' ) {
    50 33        
574 0         0 $self->_ast(@$c);
575             }
576             elsif ( ref($c) and ref($c) eq 'HASH' ) {
577 1         3 my ( $k, $v ) = each %$c;
578 1         5 $self->_open($k);
579 1         2 $self->_add($v);
580 1         2 $self->_close($k);
581             }
582             else {
583 0         0 $self->_add($c);
584             }
585             }
586              
587 1         1 $self->_close($e);
588             }
589              
590 1         1 return;
591             }
592              
593             sub _comment {
594 1     1   5 my $self = shift;
595              
596             # FIXME: should escape?
597 1         4 $self->_raw( XML::API::Element->new( comment => join( '', @_ ) ) );
598 1         9 return;
599             }
600              
601             sub _cdata {
602 1     1   2 my $self = shift;
603 1         4 $self->_raw( XML::API::Element->new( cdata => join( '', @_ ) ) );
604 1         2 return;
605             }
606              
607             sub _css {
608 0     0   0 my $self = shift;
609 0         0 my $content = shift;
610 0 0       0 if ( $content =~ /\n/s ) {
611 0         0 $self->_raw( '/**/' );
612             }
613             else {
614 0         0 $self->_raw( '/**/' );
615             }
616 0         0 return;
617             }
618              
619             sub _javascript {
620 0     0   0 my $self = shift;
621 0         0 $self->script_open( -type => 'text/javascript' );
622 0         0 $self->_raw( '// -------- JavaScript Begin --------
623 0         0 $self->_raw(@_);
624 0         0 $self->_raw('// --------- JavaScript End --------- ]]>');
625 0         0 $self->script_close;
626 0         0 return;
627             }
628              
629             sub _parse {
630 2     2   316 my $self = shift;
631 2         3 my $current = $self->{current};
632              
633 2         5 foreach (@_) {
634 2 50 33     13 next unless ( defined($_) and $_ ne '' );
635 2         3 local $XML::SAX::ParserPackage = 'XML::LibXML::SAX';
636 2         11 my $parser =
637             XML::SAX::ParserFactory->parser(
638             Handler => XML::API::SAXHandler->new( xmlapi => $self ), );
639              
640             # remove leading and trailing space, otherwise SAX barfs at us.
641 0         0 ( my $t = $_ ) =~ s/(^\s+)|(\s+$)//go;
642              
643             # escape '&' as well
644 0         0 $t =~ s/\&(\w+\;)/__AMP__$1/go;
645              
646             # escape '&' in urls
647 0         0 $t =~ s/\&(\w+=)/__AMP__amp;$1/go;
648 0         0 $parser->parse_string(
649             '<_xml_api_ignore>' . $t . '' );
650             }
651              
652             # always make sure that we finish where we started
653 0         0 $self->{current} = $current;
654             }
655              
656             sub _parse_chunk {
657 0     0   0 my $self = shift;
658 0         0 my $current = $self->{current};
659              
660 0         0 foreach (@_) {
661 0 0 0     0 next unless ( defined($_) and $_ ne '' );
662 0         0 local $XML::SAX::ParserPackage = 'XML::LibXML::SAX';
663 0         0 my $parser =
664             XML::SAX::ParserFactory->parser(
665             Handler => XML::API::SAXHandler->new( xmlapi => $self ), );
666              
667             # remove leading and trailing space, otherwise SAX barfs at us.
668 0         0 ( my $t = $_ ) =~ s/(^\s+)|(\s+$)//go;
669              
670             # escape '&' as well
671 0         0 $t =~ s/\&(\w+\;)/__AMP__$1/go;
672              
673             # escape '&' in urls
674 0         0 $t =~ s/\&(\w+=)/__AMP__amp;$1/go;
675              
676             # escape '&' on their own.
677 0         0 $t =~ s/(\W)\&(\W)/$1__AMP__amp;$2/go;
678 0         0 $parser->parse_chunk($t);
679             }
680              
681             # always make sure that we finish where we started
682 0         0 $self->{current} = $current;
683             }
684              
685             sub _attrs {
686 5     5   6 my $self = shift;
687              
688 5 100       8 if (@_) {
689 2         2 my $attrs = shift;
690 2 50 33     9 if ( !$attrs or ref($attrs) ne 'HASH' ) {
691 0         0 croak 'usage: _attrs($hashref)';
692             }
693 2         2 $self->{current}->{attrs} = $attrs;
694             }
695 5         19 return $self->{current}->{attrs};
696             }
697              
698             sub _encoding {
699 0     0   0 my $self = shift;
700 0 0       0 if (@_) {
701 0         0 $self->{encoding} = shift;
702             }
703 0         0 return $self->{encoding};
704             }
705              
706             sub _set_lang {
707 5     5   13 my $self = shift;
708 5   33     9 my $lang = shift || croak 'usage: set_lang($lang)';
709 5         5 my $dir = shift;
710              
711 5 100 100     15 if ( $self->{has_root_element} and !$self->_lang ) {
712 1         2 $self->{elements}->[0]->{attrs}->{'xml:lang'} = $lang;
713 1 50       2 if ($dir) {
714 0         0 $self->{elements}->[0]->{attrs}->{'dir'} = $dir;
715             }
716             }
717             else {
718 4         6 $self->{langnext} = $lang;
719 4 100       7 $self->{dirnext} = $dir if ($dir);
720             }
721 5         9 $self->{langs}->{$lang} = 1;
722              
723 5         5 return;
724             }
725              
726             sub _lang {
727 8     8   13 my $self = shift;
728              
729 8 100       16 if ( $self->{current} ) {
730             return $self->{current}->{attrs}->{'xml:lang'}
731 7 100       28 if ( exists( $self->{current}->{attrs}->{'xml:lang'} ) );
732              
733 2         2 my $e = $self->{current};
734 2         8 while ( $e = $e->{parent} ) {
735             return $e->{attrs}->{'xml:lang'}
736 3 100       9 if ( exists( $e->{attrs}->{'xml:lang'} ) );
737             }
738             }
739 2 50       3 return $self->{langnext} if ( $self->{langnext} );
740 2 50       4 return $self->{parent}->_lang if ( $self->{parent} );
741 2         8 return;
742             }
743              
744             sub _langs {
745 8     8   309 my $self = shift;
746 8         5 return keys %{ $self->{langs} };
  8         33  
747             }
748              
749             sub _dir {
750 5     5   5 my $self = shift;
751              
752 5 100       11 if ( $self->{current} ) {
753             return $self->{current}->{attrs}->{'dir'}
754 4 100       12 if ( exists( $self->{current}->{attrs}->{'dir'} ) );
755              
756 3         20 my $e = $self->{current};
757 3         8 while ( $e = $e->{parent} ) {
758             return $e->{attrs}->{'dir'}
759 0 0       0 if ( exists( $e->{attrs}->{'dir'} ) );
760             }
761             }
762 4 50       5 return $self->{dirnext} if ( $self->{dirnext} );
763 4 50       6 return $self->{parent}->_dir if ( $self->{parent} );
764 4         11 return;
765             }
766              
767             sub _ns {
768 0     0   0 my $self = shift;
769 0 0       0 if (@_) {
770 0         0 $self->{namespace} = shift;
771             }
772 0         0 return $self->{namespace};
773             }
774              
775             sub _debug {
776 0     0   0 my $self = shift;
777 0 0       0 if (@_) {
778 0         0 $self->{debug} = shift;
779             }
780 0         0 return $self->{debug};
781             }
782              
783             sub _current {
784 0     0   0 my $self = shift;
785 0         0 return $self->{current};
786             }
787              
788             sub _set_id {
789 0     0   0 my $self = shift;
790 0         0 my $id = shift;
791              
792 0 0 0     0 if ( !defined($id) or $id eq '' ) {
793 0         0 carp '_set_id called without a valid id';
794 0         0 return;
795             }
796 0 0       0 if ( defined( $self->{ids}->{$id} ) ) {
797 0         0 carp 'id ' . $id . ' already defined - overwriting';
798             }
799 0         0 $self->{ids}->{$id} = $self->{current};
800             }
801              
802             sub _goto {
803 0     0   0 my $self = shift;
804              
805 0 0       0 if (@_) {
806 0         0 my $id = shift;
807 0 0       0 if ( !defined $id ) {
808 0         0 $self->{current} = undef;
809 0         0 return;
810             }
811 0 0       0 if ( $id->isa('XML::API::Element') ) {
    0          
812 0         0 $self->{current} = $id;
813             }
814             elsif ( defined( $self->{ids}->{$id} ) ) {
815 0         0 $self->{current} = $self->{ids}->{$id};
816             }
817             else {
818             carp "Nonexistent ID given to _goto: '$id'. ",
819 0         0 '(Known IDs: ', join( ',', keys( %{ $self->{ids} } ) ), ')';
  0         0  
820 0         0 $self->{current} = undef;
821             }
822             }
823 0         0 return $self->{current};
824             }
825              
826             sub _as_string {
827 23     23   1564 my $self = shift;
828 23         18 my $file = shift;
829              
830 23         18 my $string = '';
831              
832 23 50 66     92 if ( ref($self) eq __PACKAGE__ or $self->{has_root_element} ) {
833 23         46 $string = qq{{encoding}" ?>\n};
834 23 100       41 $string .= $self->_doctype . "\n" if ( $self->_doctype );
835             }
836              
837             $string .= join(
838             "\n",
839             map {
840             $_->isa(__PACKAGE__)
841 24 50       104 ? join( "\n", map { $_->as_string } $_->_elements )
  0         0  
842             : $_->as_string( '', ' ' )
843 23         25 } @{ $self->{elements} }
  23         54  
844             );
845              
846 23 50       45 if ($file) {
847 0 0       0 open( FH, '>' . $file ) || die "open '$file': $!";
848 0 0       0 if ( $self->_encoding eq 'UTF-8' ) {
849 0         0 binmode( FH, ':utf8' );
850             }
851 0         0 print FH $string;
852 0         0 close(FH);
853 0         0 return;
854             }
855 23         106 return $string;
856             }
857              
858             sub _fast_string {
859 2     2   3 my $self = shift;
860 2         2 my $file = shift;
861              
862 2         3 my $string = '';
863              
864 2 50 33     10 if ( ref($self) eq __PACKAGE__ or $self->{has_root_element} ) {
865 2         6 $string = qq{{encoding}" ?>};
866 2 50       4 $string .= $self->_doctype if ( $self->_doctype );
867             }
868              
869             $string .=
870 2         3 join( "\n", map { $_->fast_string( '', ' ' ) } @{ $self->{elements} } );
  2         4  
  2         5  
871              
872 2 50       4 if ($file) {
873 0 0       0 open( FH, '>' . $file ) || die "open '$file': $!";
874 0 0       0 if ( $self->_encoding eq 'UTF-8' ) {
875 0         0 binmode( FH, ':utf8' );
876             }
877 0         0 print FH $string;
878 0         0 close(FH);
879 0         0 return;
880             }
881 2         5 return $string;
882             }
883              
884             sub _escapeXML {
885 41     41   45 my $data = $_[0];
886 41 50       56 return '' unless ( defined($data) );
887 41 100       97 if ( $data =~ /[\&\<\>\"(__AMP__)\']/o ) {
888 10         19 $data =~ s/\&(?!\w+\;)/\&\;/go;
889 10         13 $data =~ s/\
890 10         10 $data =~ s/\>/\>\;/go;
891 10         10 $data =~ s/\"/\"\;/go;
892 10         10 $data =~ s/__AMP__/\&/go;
893 10         10 $data =~ s/\'/\&apos\;/go;
894             }
895 41         65 return $data;
896             }
897              
898             #
899             # We must specify the DESTROY function explicitly otherwise our AUTOLOAD
900             # function gets called at object death.
901             #
902       0     DESTROY { }
903              
904             1;
905             __END__