File Coverage

lib/XML/Compile/Translate/Template.pm
Criterion Covered Total %
statement 361 487 74.1
branch 187 304 61.5
condition 107 174 61.4
subroutine 60 87 68.9
pod 1 40 2.5
total 716 1092 65.5


line stmt bran cond sub pod time code
1             # Copyrights 2006-2019 by [Mark Overmeer ].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.02.
5             # This code is part of distribution XML-Compile. Meta-POD processed with
6             # OODoc into POD and HTML manual-pages. See README.md
7             # Copyright Mark Overmeer. Licensed under the same terms as Perl itself.
8              
9             package XML::Compile::Translate::Template;
10 11     11   2236 use vars '$VERSION';
  11         24  
  11         728  
11             $VERSION = '1.62';
12              
13 11     11   66 use base 'XML::Compile::Translate';
  11         20  
  11         1412  
14              
15 11     11   82 use strict;
  11         21  
  11         352  
16 11     11   58 use warnings;
  11         21  
  11         417  
17 11     11   61 no warnings 'once', 'recursion';
  11         17  
  11         530  
18              
19 11     11   66 use Log::Report 'xml-compile';
  11         17  
  11         92  
20              
21             use XML::Compile::Util
22 11     11   3834 qw/odd_elements even_elements SCHEMA2001i pack_type unpack_type/;
  11         31  
  11         969  
23 11     11   71 use List::Util qw/max first/;
  11         27  
  11         748  
24              
25 11     11   67 use vars '$VERSION'; # OODoc adds $VERSION to the script
  11         19  
  11         89315  
26             $VERSION ||= 'undef';
27              
28              
29             sub makeTagQualified
30 31     31 0 97 { my ($self, $path, $node, $local, $ns) = @_;
31 31         169 my $prefix = $self->_registerNSprefix('', $ns, 1);
32              
33             # it is certainly not correct to do a keyRewrite here, but it works :(
34 31 100       187 $self->{_output} eq 'PERL' ? $self->keyRewrite($ns, $local)
    100          
35             : length $prefix ? "$prefix:$local"
36             : $local;
37             }
38              
39             sub makeTagUnqualified
40 95     95 0 210 { my ($self, $path, $node, $local, $ns) = @_;
41             # $name =~ s/.*\://;
42             return $self->keyRewrite($ns, $local)
43 95 100       313 if $self->{_output} eq 'PERL';
44              
45 35         85 my $prefix = $self->_registerNSprefix('', $ns, 1);
46 35 100       105 length $prefix ? "$prefix:$local" : $local;
47             }
48              
49             # Detect recursion. Based on type is best, but some schema's do not
50             # have named types, so tags are indexed as well.
51             my (%recurse_type, %reuse_type, %recurse_tag, %reuse_tag);
52              
53             sub compile($@)
54 19     19 1 149 { my ($self, $type, %args) = @_;
55 19         57 $self->{_output} = $args{output};
56 19   50     105 $self->{_style} = $args{output_style} || 1;
57 19         58 (%recurse_type, %reuse_type, %recurse_tag, %reuse_tag) = ();
58 19         241 $self->SUPER::compile($type, %args);
59             }
60              
61             sub actsAs($)
62 213     213 0 360 { my ($self, $as) = @_;
63             ($as eq 'READER' && $self->{_output} eq 'PERL')
64 213 100 100     1624 || ($as eq 'WRITER' && $self->{_output} eq 'XML')
      100        
65             }
66              
67             sub makeWrapperNs($$$$$)
68 4     4 0 11 { my ($self, $path, $processor, $index, $filter) = @_;
69              
70 4         7 my @entries;
71 4 50   8   22 $filter = sub {1} if ref $filter ne 'CODE';
  8         17  
72              
73 4         33 foreach my $entry (sort {$a->{prefix} cmp $b->{prefix}} values %$index)
  20         42  
74 16 100       34 { $entry->{used} or next;
75 8         13 my ($prefix, $uri) = @{$entry}{'prefix', 'uri'};
  8         19  
76 8 50       16 $filter->($uri, $prefix) or next;
77 8         16 push @entries, [ $uri, $prefix ];
78 8         18 $entry->{used} = 0;
79             }
80              
81 4 50   4   11 sub { my $data = $processor->(@_) or return ();
82 4 50       14 if($self->{include_namespaces})
83 4         34 { $data->{"xmlns:$_->[1]"} = $_->[0] for @entries;
84             }
85 4         16 $data;
86 4         57 };
87             }
88              
89             sub typemapToHooks($$)
90 19     19 0 55 { my ($self, $hooks, $typemap) = @_;
91              
92 19         102 while(my($type, $action) = each %$typemap)
93 3 50       9 { defined $action or next;
94              
95 3 100       34 my ($struct, $example)
    100          
96             = $action =~ s/^[\\]?\&/\$/
97             ? ( "call on converter function with object"
98             , "$action->('WRITER', \$object, '$type', \$doc)")
99             : $action =~ m/^\$/
100             ? ( "call on converter with object"
101             , "$action->toXML(\$object, '$type', \$doc)")
102             : ( [ "calls toXML() on $action objects", " with $type and doc" ]
103             , "bless({}, '$action')" );
104              
105 3         13 my $details =
106             { struct => $struct
107             , example => $example
108             };
109              
110 3     3   28 push @$hooks, { type => $type, replace => sub { $details} };
  3         7  
111             }
112              
113 19         48 $hooks;
114             }
115              
116             sub makeElementWrapper
117 19     19 0 51 { my ($self, $path, $processor) = @_;
118 19     19   87 sub { $processor->() };
  19         43  
119             }
120             *makeAttributeWrapper = \&makeElementWrapper;
121              
122             sub _block($@)
123 45     45   143 { my ($self, $block, $path, @pairs) = @_;
124             bless
125 40     40   106 sub { my @elems = map { $_->() } odd_elements @pairs;
  85         137  
126 40         85 my @tags = map { $_->{tag} } @elems;
  85         170  
127              
128 40         89 local $" = ', ';
129 40 50       171 my $struct = @tags ? "$block of @tags"
130             : "empty $block from ".join(" ", even_elements @pairs);
131              
132 40         63 my @lines;
133 40         131 while(length $struct > 65)
134 0         0 { $struct =~ s/(.{1,60}|\S+)(?:\s+|$)//;
135 0         0 push @lines, $1;
136             }
137 40 50       108 push @lines, $struct
138             if length $struct;
139 40         107 $lines[$_] =~ s/^/ / for 1..$#lines;
140              
141 40         212 { tag => $block
142             , elems => \@elems
143             , struct => \@lines
144             };
145 45         373 }, 'BLOCK';
146             }
147              
148 39     39 0 107 sub makeSequence { my $self = shift; $self->_block(sequence => @_) }
  39         106  
149 6     6 0 12 sub makeChoice { my $self = shift; $self->_block(choice => @_) }
  6         28  
150 0     0 0 0 sub makeAll { my $self = shift; $self->_block(all => @_) }
  0         0  
151              
152             sub makeBlockHandler
153 45     45 0 138 { my ($self, $path, $label, $min, $max, $proc, $kind, $multi) = @_;
154              
155             my $code =
156 40     40   76 sub { my $data = $proc->();
157 40 100 66     446 my $occur
    50 66        
    50 66        
158             = $max eq 'unbounded' && $min==0 ? 'occurs any number of times'
159             : $max ne 'unbounded' && $max==1 && $min==0 ? 'is optional'
160             : $max ne 'unbounded' && $max==1 && $min==1 ? '' # the usual case
161             : "occurs $min <= # <= $max times";
162              
163 40 100 33     118 $data->{occur} ||= $occur if $occur;
164 40 100 100     173 if($max ne 'unbounded' && $max==1)
165 35         60 { bless $data, 'BLOCK';
166             }
167             else
168 5         10 { $data->{tag} = $multi;
169 5         7 $data->{is_array} = 1;
170 5         16 bless $data, 'REP-BLOCK';
171             }
172 40         109 $data;
173 45         195 };
174 45         210 ($label => $code);
175             }
176              
177             sub makeElementHandler
178 85     85 0 227 { my ($self, $path, $label, $min, $max, $req, $opt) = @_;
179 80 50   80   131 sub { my $data = $opt->() or return;
180 80 100 100     698 my $occur
    100 100        
    100 66        
181             = $max eq 'unbounded' && $min==0 ? 'occurs any number of times'
182             : $max ne 'unbounded' && $max==1 && $min==0 ? 'is optional'
183             : $max ne 'unbounded' && $max==1 && $min==1 ? '' # the usual case
184             : "occurs $min <= # <= $max times";
185 80 100 33     209 $data->{occur} ||= $occur if $occur;
186 80   100     294 $data->{is_array} = $max eq 'unbounded' || $max > 1;
187 80         191 $data;
188 85         738 };
189             }
190              
191             sub makeRequired
192 78     78 0 160 { my ($self, $path, $label, $do) = @_;
193 78         145 $do;
194             }
195              
196             sub makeElementHref
197 0     0 0 0 { my ($self, $path, $ns, $childname, $do) = @_;
198 0         0 $do;
199             }
200              
201             sub makeElement
202 99     99 0 237 { my ($self, $path, $ns, $childname, $do) = @_;
203 97     97   173 sub { my $h = $do->(@_);
204 97         172 $h->{_NAME} = $childname;
205 97         209 $h;
206 99         383 };
207             }
208              
209             sub makeElementDefault
210 0     0 0 0 { my ($self, $path, $ns, $childname, $do, $default) = @_;
211 0     0   0 sub { my $h = $do->(@_);
212 0         0 $h->{occur} = "defaults to '$default'";
213 0         0 $h->{example} = $default;
214 0         0 $h;
215 0         0 };
216             }
217              
218             sub makeElementFixed
219 0     0 0 0 { my ($self, $path, $ns, $childname, $do, $fixed) = @_;
220 0     0   0 sub { my $h = $do->(@_);
221 0         0 $h->{occur} = "fixed to '$fixed'";
222 0         0 $h->{example} = $fixed;
223 0         0 $h;
224 0         0 };
225             }
226              
227             sub makeElementAbstract
228 2     2 0 6 { my ($self, $path, $ns, $childname, $do) = @_;
229             # sub { () };
230             sub {
231 2     2   7 my $h = $do->(@_);
232 2         4 $h->{_NAME} = $childname;
233 2         4 $h->{occur} = "ABSTRACT";
234 2         5 $h;
235 2         9 };
236             }
237              
238             sub makeComplexElement
239 35     35 0 113 { my ($self, $path, $tag, $elems, $attrs, $any_attr, $type, $is_nillable)=@_;
240 35         159 my @elem_parts = odd_elements @$elems;
241 35         80 my @attr_parts = (odd_elements(@$attrs), @$any_attr);
242              
243 30     30   48 sub { my (@attrs, @elems);
244 30         98 my $is_pseudo_type = $type !~ m/^{/; # like "unnamed complex"
245              
246 30 50 66     202 if((!$is_pseudo_type && $recurse_type{$type}) || $recurse_tag{$tag})
      33        
247             { return
248 0         0 +{ kind => 'complex'
249             , struct => 'probably a recursive complex'
250             , tag => $tag
251             , _TYPE => $type
252             };
253             }
254              
255 30 50 66     158 if((!$is_pseudo_type && $reuse_type{$type}) || $reuse_tag{$tag})
      33        
256             { return
257 0         0 +{ kind => 'complex'
258             , struct => 'complex structure shown above'
259             , tag => $tag
260             , _TYPE => $type
261             };
262             }
263              
264 30         73 $recurse_type{$type}++; $recurse_tag{$tag}++;
  30         59  
265 30         51 $reuse_type{$type}++; $reuse_tag{$tag}++;
  30         46  
266 30         83 push @elems, $_->() for @elem_parts;
267 30         71 push @attrs, $_->() for @attr_parts;
268              
269 30         62 $recurse_type{$type}--; $recurse_tag{$tag}--;
  30         54  
270              
271 30 100       197 +{ kind => 'complex'
272             , struct => ($is_nillable ? "is nillable, as: $tag => NIL" : undef)
273             , tag => $tag
274             , attrs => \@attrs
275             , elems => \@elems
276             , _TYPE => $type
277             };
278 35         250 };
279             }
280              
281             sub makeTaggedElement
282 1     1 0 5 { my ($self, $path, $tag, $st, $attrs, $attrs_any, $type, $is_nillable) = @_;
283 1         3 my @parts = (odd_elements(@$attrs), @$attrs_any);
284              
285 1     1   4 sub { my @attrs = map $_->(), @parts;
286 1         5 my %simple = $st->();
287              
288 1         2 my @struct = 'string content of the container';
289 1 50       4 push @struct, $simple{struct} if $simple{struct};
290 1 50       3 push @struct, 'is nillable, hence value or NIL' if $is_nillable;
291              
292             my %content =
293             ( tag => '_'
294             , struct => \@struct
295 1   50     5 , example => ($simple{example} || 'Hello, World!')
296             );
297 1 50       3 $content{_TYPE} = $simple{_TYPE} if $simple{_TYPE};
298              
299 1         7 +{ kind => 'tagged'
300             , struct => "$tag is simple value with attributes"
301             , tag => $tag
302             , attrs => \@attrs
303             , elems => [ \%content ]
304             , _TYPE => $type
305             };
306 1         7 };
307             }
308              
309             sub makeMixedElement
310 1     1 0 4 { my ($self, $path, $tag, $elems, $attrs, $attrs_any, $type, $is_nillable)=@_;
311 1         5 my @parts = (odd_elements(@$attrs), @$attrs_any);
312              
313 1         2 my @struct = 'mixed content cannot be processed automatically';
314 1 50       3 push @struct, 'is nillable' if $is_nillable;
315              
316 1         6 my %mixed =
317             ( tag => '_'
318             , struct => \@struct
319             , example => "XML::LibXML::Element->new('$tag')"
320             );
321              
322 1 50       3 unless(@parts) # show simpler alternative
323 0         0 { $mixed{tag} = $tag;
324 0         0 $mixed{type} = $type;
325 0     0   0 return sub { \%mixed };
  0         0  
326             }
327              
328 1     1   3 sub { my @attrs = map $_->(), @parts;
329 1         7 +{ kind => 'mixed'
330             , struct => "$tag has a mixed content"
331             , tag => $tag
332             , elems => [ \%mixed ]
333             , attrs => \@attrs
334             , _TYPE => $type
335             };
336 1         5 };
337             }
338              
339             sub makeSimpleElement
340 64     64 0 184 { my ($self, $path, $tag, $st, undef, undef, $type, $is_nillable) = @_;
341 67     67   74 sub { my @struct;
342 67 100       140 push @struct, 'is nillable, hence value or NIL' if $is_nillable;
343 67         123 +{ kind => 'simple'
344             , struct => \@struct
345             , tag => $tag
346             , $st->()
347             };
348 64         298 };
349             }
350              
351             sub makeBuiltin
352 90     90 0 276 { my ($self, $path, $node, $type, $def, $check_values) = @_;
353 90     87   420 sub { (_TYPE=> $type, example => $def->{example}) };
  87         445  
354             }
355              
356             sub makeList
357 0     0 0 0 { my ($self, $path, $st) = @_;
358 0     0   0 sub { my %d = $st->();
359 0         0 $d{struct} = 'a list of values, where each';
360 0         0 my $example = $d{example};
361 0 0       0 if($self->{_output} eq 'PERL')
362 0 0       0 { $example = qq("$example") if $example =~ m/[^0-9.]/;
363 0         0 $d{example} = "[ $example , ... ]";
364             }
365             else
366 0         0 { $d{example} = "$example $example ...";
367             }
368 0         0 %d };
  0         0  
369             }
370              
371             sub makeFacetsList
372 0     0 0 0 { my ($self, $path, $st, $info) = @_;
373 0         0 $self->makeFacets($path, $st, $info);
374             }
375              
376             sub _ff($@)
377 0     0   0 { my ($self,$type) = (shift, shift);
378 0         0 my @lines = $type.':';
379 0         0 while(@_)
380 0         0 { my $facet = shift;
381 0         0 $facet =~ s/\t/\t/g;
382 0 0       0 $facet = qq{"$facet"} if $facet =~ m/\s/;
383 0 0       0 push @lines, ' ' if length($lines[-1]) + length($facet) > 55;
384 0         0 $lines[-1] .= ' '.$facet;
385             }
386 0         0 @lines;
387             }
388              
389             sub makeFacets
390 6     6 0 17 { my ($self, $path, $st, $info) = @_;
391 6         20 my @comment;
392 6         45 foreach my $k (sort keys %$info)
393 12         27 { my $v = $info->{$k};
394 12 0       100 push @comment
    0          
    0          
    50          
    100          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
395             , $k eq 'enumeration' ? $self->_ff('Enum', sort @$v)
396             : $k eq 'pattern' ? $self->_ff('Pattern', @$v)
397             : $k eq 'length' ? "fixed length of $v"
398             : $k eq 'maxLength' ? "length <= $v"
399             : $k eq 'minLength' ? "length >= $v"
400             : $k eq 'totalDigits' ? "total digits is $v"
401             : $k eq 'maxScale' ? "scale <= $v"
402             : $k eq 'minScale' ? "scale >= $v"
403             : $k eq 'maxInclusive' ? "value <= $v"
404             : $k eq 'maxExclusive' ? "value < $v"
405             : $k eq 'minInclusive' ? "value >= $v"
406             : $k eq 'minExclusive' ? "value > $v"
407             : $k eq 'fractionDigits' ? "faction digits is $v"
408             : $k eq 'whiteSpace' ? "white-space $v"
409             : "restriction? $k = $v";
410             }
411              
412 6         21 my %facet = (facets => \@comment, $st->());
413              
414 6 50       35 if(my $enum = $info->{enumeration})
415 0         0 { $facet{example} = $enum->[0];
416             }
417              
418 6     10   56 sub { %facet };
  10         59  
419             }
420              
421             sub makeUnion
422 0     0 0 0 { my ($self, $path, @types) = @_;
423 0     0   0 sub { my @choices = map { +{$_->()} } @types;
  0         0  
424             +( kind => 'union'
425             , struct => "one of the following (union)"
426             , choice => \@choices
427             , example => $choices[0]->{example}
428 0         0 );
429 0         0 };
430             }
431              
432             sub makeAttributeRequired
433 6     6 0 23 { my ($self, $path, $ns, $tag, $label, $do) = @_;
434              
435 6     6   29 sub { +{ kind => 'attr'
436             , tag => $label
437             , occur => "attribute $tag is required"
438             , $do->()
439             };
440 6         29 };
441             }
442              
443             sub makeAttributeProhibited
444 0     0 0 0 { my ($self, $path, $ns, $tag, $label, $do) = @_;
445 0         0 ();
446             }
447              
448             sub makeAttribute
449 19     19 0 59 { my ($self, $path, $ns, $tag, $label, $do) = @_;
450 17     17   36 sub { +{ kind => 'attr'
451             , tag => $tag
452             , occur => "becomes an attribute"
453             , $do->()
454             };
455 19         76 };
456             }
457              
458             sub makeAttributeDefault
459 0     0 0 0 { my ($self, $path, $ns, $tag, $label, $do) = @_;
460             sub {
461 0     0   0 +{ kind => 'attr'
462             , tag => $tag
463             , occur => "attribute $tag has default"
464             , $do->()
465             };
466 0         0 };
467             }
468              
469             sub makeAttributeFixed
470 0     0 0 0 { my ($self, $path, $ns, $tag, $label, $do, $fixed) = @_;
471 0         0 my $value = $fixed->value;
472              
473 0     0   0 sub { +{ kind => 'attr'
474             , tag => $tag
475             , occur => "attribute $tag is fixed"
476             , example => $value
477             };
478 0         0 };
479             }
480              
481             sub makeSubstgroup
482 2     2 0 9 { my ($self, $path, $type, @todo) = @_;
483              
484             sub {
485 2     2   5 my (@example_tags, $example_nest, %tags);
486 2         6 my @do = @todo;
487 2         5 my $group = $do[1][0];
488              
489 2         6 while(@do)
490 6         13 { my ($type, $info) = (shift @do, shift @do);
491 6         13 my ($label, $call) = @$info;
492 6         23 my $processed = $call->();
493 6         9 my $show = '';
494 6 50       23 if($processed->{kind} eq 'substitution group')
    50          
495             { # substr extended by subst, which already is formatted.
496             # need to extract only the indicated type info.
497 0   0     0 my $s = $processed->{struct} || [];
498 0   0     0 /^ $label (.*)/ and $show = $1 for @$s;
499             }
500             elsif(my $type = $processed->{_TYPE})
501 6         18 { $show = $self->prefixed($type);
502             }
503              
504 6 100 66     21 if($processed->{occur} && $processed->{occur} eq 'ABSTRACT')
505 1         3 { $show .= ' (abstract)';
506             }
507             else
508             { # some complication to always produce the same tag for
509             # regression tests... Instance uses a HASH...
510 5         11 push @example_tags, $label;
511             $example_nest ||= $processed->{kind} eq 'simple'
512 5 100 50     37 ? ($processed->{example} || '...') : '{...}';
      66        
513             }
514            
515 6         32 $tags{$label} = $show;
516             }
517              
518 2         21 my $longest = max map length, keys %tags;
519 2         37 my @lines = map sprintf(" %-${longest}s %s", $_, $tags{$_}),
520             sort keys %tags;
521              
522 2         10 my $example_tag = (sort @example_tags)[0];
523 2 50       10 my $example = $example_tag ? "{ $example_tag => $example_nest }"
524             : "undef # only abstract types known";
525              
526 2         7 my $name = $self->prefixed($type);
527              
528 2         18 +{ kind => 'substitution group'
529             , tag => $group
530             , struct => [ "substitutionGroup $name", @lines ]
531             , example => $example
532             };
533 2         18 };
534             }
535              
536             sub makeXsiTypeSwitch($$$$)
537 1     1 0 4 { my ($self, $where, $elem, $default_type, $types) = @_;
538 1         10 my @types = map " ".$self->prefixed($_), sort keys %$types;
539 1         3 my $deftype = $self->prefixed($default_type);
540              
541 1     1   7 sub { +{ kind => 'xsi:type switch'
542             , tag => $elem
543             , struct => [ 'xsi:type alternatives:', @types ]
544             , example => "{ XSI_TYPE => '$deftype', %data }"
545             }
546 1         6 };
547             }
548              
549             sub makeAnyAttribute
550 0     0 0 0 { my ($self, $path, $handler, $yes, $no, $process) = @_;
551 0   0     0 $yes ||= []; $no ||= [];
  0   0     0  
552 0 0       0 $yes = [ map {$self->prefixed("{$_}") || $_} @$yes];
  0         0  
553 0 0       0 $no = [ map {$self->prefixed("{$_}") || $_} @$no];
  0         0  
554 0 0       0 my $occurs = @$yes ? "in @$yes" : @$no ? "not in @$no" : 'in any namespace';
    0          
555 0     0   0 bless sub { +{kind => 'attr' , struct => "any attribute $occurs"
556 0         0 , tag => 'ANYATTR', example => 'AnySimple'} }, 'ANY';
557             }
558              
559             sub makeAnyElement
560 0     0 0 0 { my ($self, $path, $handler, $yes, $no, $process, $min, $max) = @_;
561 0   0     0 $yes ||= []; $no ||= [];
  0   0     0  
562 0 0       0 $yes = [ map {$self->prefixed("{$_}") || $_} @$yes];
  0         0  
563 0 0       0 $no = [ map {$self->prefixed("{$_}") || $_} @$no];
  0         0  
564 0 0       0 my $where = @$yes ? "in @$yes" : @$no ? "not in @$no" : 'in any namespace';
    0          
565              
566 0         0 my $data = +{ kind => 'element', struct => "any element $where"
567             , tag => "ANY", example => 'Anything' };
568 0 0 0     0 my $occur
    0 0        
    0 0        
569             = $max eq 'unbounded' && $min==0 ? 'occurs any number of times'
570             : $max ne 'unbounded' && $max==1 && $min==0 ? 'is optional'
571             : $max ne 'unbounded' && $max==1 && $min==1 ? '' # the usual case
572             : "occurs $min <= # <= $max times";
573 0 0 0     0 $data->{occur} ||= $occur if $occur;
574 0   0     0 $data->{is_array} = $max eq 'unbounded' || $max > 1;
575              
576 0     0   0 bless sub { +$data }, 'ANY';
  0         0  
577             }
578              
579             sub makeHook($$$$$$$)
580 3     3 0 12 { my ($self, $path, $r, $tag, $before, $replace, $after, $fulltype) = @_;
581              
582 3 50 33     16 return $r unless $before || $replace || $after;
      33        
583              
584 3 50 33     16 error __x"template only supports one production (replace) hook"
585             if $replace && @$replace > 1;
586              
587 3 50 33 0   13 return sub {()} if $replace && grep {$_ eq 'SKIP'} @$replace;
  0         0  
  3         17  
588              
589 3 50       15 my @replace = $replace ? map {$self->_decodeReplace($path,$_)} @$replace:();
  3         12  
590 3 50       9 my @before = $before ? map {$self->_decodeBefore($path,$_) } @$before :();
  0         0  
591 3 50       10 my @after = $after ? map {$self->_decodeAfter($path,$_) } @$after :();
  0         0  
592              
593             sub
594 3     3   23 { my $doc = XML::LibXML::Document->new;
595 3 0       11 for(@before) { $_->($doc, $path, undef) or return }
  0         0  
596              
597 3 50       12 my $xml = @replace ? $replace[0]->($doc, $path, $r) : $r->();
598 3 50       9 defined $xml or return ();
599              
600 3 0       7 for(@after) { $xml = $_->($doc, $path, $xml) or return }
  0         0  
601 3         15 $xml;
602             }
603 3         18 }
604              
605             sub _decodeBefore($$)
606 0     0   0 { my ($self, $path, $call) = @_;
607 0 0       0 return $call if ref $call eq 'CODE';
608 0         0 error __x"labeled before hook `{name}' undefined", name => $call;
609             }
610              
611             sub _decodeReplace($$)
612 3     3   8 { my ($self, $path, $call) = @_;
613 3 50       14 return $call if ref $call eq 'CODE';
614              
615 0 0       0 if($call eq 'COLLAPSE')
616             { return sub
617 0     0   0 { my ($tag, $path, $do) = @_;
618 0         0 my $h = $do->();
619 0         0 $h->{elems} = [ { struct => [ 'content collapsed' ]
620             , kind => 'collapsed' } ];
621 0         0 delete $h->{attrs};
622 0         0 $h;
623 0         0 };
624             }
625              
626             # SKIP already handled
627 0         0 error __x"labeled replace hook `{name}' undefined", name => $call;
628             }
629              
630             sub _decodeAfter($$)
631 0     0   0 { my ($self, $path, $call) = @_;
632 0 0       0 return $call if ref $call eq 'CODE';
633 0         0 error __x"labeled after hook `{name}' undefined", name => $call;
634             }
635              
636              
637             ###
638             ### toPerl
639             ###
640              
641             sub toPerl($%)
642 16     16 0 106 { my ($self, $ast, %args) = @_;
643 16 50       53 $ast or return undef;
644              
645 16         27 my @lines;
646 16 100       59 if($ast->{kind})
647 13   33     40 { my $name = $ast->{_NAME} || $ast->{tag};
648 13         49 my $pref = $self->prefixed($name);
649 13 50       85 push @lines, defined $pref
650             ? ("# Describing $ast->{kind} $pref", "# $name")
651             : "# Describing $ast->{kind} $name";
652             }
653              
654             push @lines
655             , "#"
656             , "# Produced by ".__PACKAGE__." version $VERSION"
657             , "# on ".localtime()
658             , "#"
659             , "# BE WARNED: in most cases, the example below cannot be used without"
660             , "# interpretation. The comments will guide you."
661             , "#"
662 16 50       49 unless $args{skip_header};
663              
664             # add info about name-spaces
665 16         147 foreach my $nsdecl (grep /^xmlns\:/, sort keys %$ast)
666 1   50     10 { push @lines, sprintf "# %-15s %s", $nsdecl, $ast->{$nsdecl} || '(none)';
667             }
668 16 100       62 push @lines, '' if @lines;
669            
670             # produce data tree
671 16         56 push @lines, $self->_perlAny($ast, \%args);
672              
673             # remove leading 'type =>'
674 16         85 for(my $linenr = 0; $linenr < @lines; $linenr++)
675 73 100       281 { next if $lines[$linenr] =~ m/^\s*\#/;
676 29 100       147 next unless $lines[$linenr] =~ s/.*? \=\>\s*//;
677 16 100       81 $lines[$linenr] =~ m/\S/ or splice @lines, $linenr, 1;
678 16         33 last;
679             }
680              
681 16         95 my $lines = join "\n", @lines;
682 16         1023 $lines =~ s/\,?\s*$/\n/;
683 16         1153 $lines;
684             }
685              
686             my %seen;
687             sub _perlAny($$);
688             sub _perlAny($$)
689 105     105   194 { my ($self, $ast, $args) = @_;
690              
691 105         142 my ($pref, @lines);
692 105 100 100     340 if($ast->{_TYPE} && $args->{show_type})
693 54 50       148 { if($pref = $self->prefixed($ast->{_TYPE}))
694 54 100 66     289 { push @lines # not perfect, but a good attempt
695             , $pref =~ m/^[aiou]/i && $pref !~ m/^(uni|eu)/i
696             ? "# is an $pref" : "# is a $pref";
697             }
698             }
699              
700 105 100 100     336 if($ast->{struct} && $args->{show_struct})
701 56         83 { my $struct = $ast->{struct};
702 56 100       182 my @struct = ref $struct ? @$struct : $struct;
703 56         238 s/^/# /gm for @struct;
704 56         113 push @lines, @struct;
705             }
706              
707             push @lines, "# $ast->{occur}"
708 105 100 100     315 if $ast->{occur} && $args->{show_occur};
709              
710 105 100 100     212 if($ast->{facets} && $args->{show_facets})
711 3         3 { my $facets = $ast->{facets};
712 3 50       10 my @facets = ref $facets ? @$facets : $facets;
713 3         23 s/^/# /gm for @facets;
714 3         7 push @lines, @facets;
715             }
716              
717 105         138 my @childs;
718 105 100       192 push @childs, @{$ast->{attrs}} if $ast->{attrs};
  21         50  
719 105 100       189 push @childs, @{$ast->{elems}} if $ast->{elems};
  46         73  
720 105 50       214 push @childs, $ast->{body} if $ast->{body};
721              
722 105         137 my @subs;
723 105         160 foreach my $child (@childs)
724 89         473 { my @sub = $self->_perlAny($child, $args);
725 89 50       210 @sub or next;
726              
727             # last line is code and gets comma
728 89 50       706 $sub[-1] =~ s/\,?\s*$/,/
729             if $sub[-1] !~ m/\#\s/;
730              
731 89 100       286 if(ref $ast ne 'BLOCK')
732 46         765 { s/^(.)/$args->{indent}$1/ for @sub;
733             }
734              
735             # seperator blank, sometimes
736 89 100 100     745 unshift @sub, ''
      100        
737             if $sub[0] =~ m/^\s*[#{]/ # }
738             || (@subs && $subs[-1] =~ m/[}\]]\,\s*$/);
739              
740 89         403 push @subs, @sub;
741             }
742              
743 105 100       247 if(ref $ast eq 'REP-BLOCK')
744             { # repeated block
745 3 50       9 @subs or @subs = '';
746 3 100       17 $subs[0] =~ s/^ /{ / or $subs[0] =~ s/^\s*$/{/;
747 3 50       11 if($subs[-1] =~ m/\#\s/) { push @subs, "}," }
  0         0  
748 3         24 else { $subs[-1] =~ s/$/ },/ }
749             }
750              
751             # XML does not permit difficult tags, but we still check.
752 105   100     234 my $tag = $ast->{tag} || '';
753 105 100 66     525 if(defined $tag && $tag !~ m/^[\w_][\w\d_]*$/)
754 3         9 { $tag =~ s/\\/\\\\/g;
755 3         6 $tag =~ s/'/\\'/g;
756 3         7 $tag = qq{'$tag'};
757             }
758              
759 105   100     281 my $kind = $ast->{kind} || '';
760 105 100 33     687 if(ref $ast eq 'REP-BLOCK')
    100          
    100          
    50          
    50          
    50          
    50          
761 3         78 { s/^(.)/ $1/ for @subs;
762 3         13 $subs[0] =~ s/^ ?/[/;
763 3         15 push @lines, "$tag => ", @subs , ']';
764             }
765             elsif(ref $ast eq 'BLOCK')
766 22         73 { push @lines, @subs;
767             }
768             elsif(@subs)
769 21 100       62 { length $subs[0] or shift @subs;
770 21 100       63 if($ast->{is_array})
771 3         71 { s/^(.)/ $1/ for @subs;
772 3         13 $subs[0] =~ s/^[ ]{0,3}/[ {/;
773 3 50 33     20 if($subs[-1] =~ m/\#\s/ || $self->{_style}==2)
774 0         0 { push @subs, "}, ], " }
775 3         20 else { $subs[-1] .= ' }, ], ' }
776 3         18 push @lines, "$tag =>", @subs;
777             }
778             else
779 18         117 { $subs[0] =~ s/^ /{ /;
780 18 50       112 if($self->{_style}==2)
    50          
781 0         0 { push @subs, "}, ";
782 0 0       0 $subs[-1] .= "# $pref" if $pref;
783             }
784 0         0 elsif($subs[-1] =~ m/\#\s/) { push @subs, "}, " }
785 18         50 else { $subs[-1] .= ' },' }
786 18         102 push @lines, "$tag =>", @subs;
787             }
788             }
789             elsif($kind eq 'complex' || $kind eq 'mixed') # empty complex-type
790             { # if there is an "occurs", then there can always be more than one
791 0 0       0 push @lines, $tag.' => '.($ast->{occur} ? '[{},]' : '{}');
792             }
793             elsif($kind eq 'collapsed') {;}
794             elsif($kind eq 'union') # union type
795 0         0 { foreach my $union ( @{$ast->{choice}} )
  0         0  
796             { # remove examples
797 0         0 my @l = grep { m/^#/ } $self->_perlAny($union, $args);
  0         0  
798 0         0 s/^\#/# -/ for $l[0];
799 0         0 s/^\#/# / for @l[1..$#l];
800 0         0 push @lines, @l;
801             }
802             }
803             elsif(!exists $ast->{example})
804 0         0 { push @lines, "$tag => 'TEMPLATE-ERROR $ast->{kind}'";
805             }
806              
807 105         193 my $example = $ast->{example};
808 105 100       179 if(defined $example)
809 59 50 100     476 { $example = qq{"$example"} # in quotes unless
      100        
      100        
      100        
      66        
810             if $example !~ m/^[+-]?\d+(?:\.\d+)?$/ # numeric or
811             && $example !~ m/^\$/ # variable or
812             && $example !~ m/^bless\b/ # constructor or
813             && $example !~ m/^\$?[\w:]*\-\>/ # method call example
814             && $example !~ m/^\{.*\}$/ # anon HASH example
815             && $example !~ m/^\[.*\]$/; # anon ARRAY example
816              
817             push @lines, "$tag => "
818 59 100       263 . ($ast->{is_array} ? "[ $example, ]" : $example);
819             }
820 105         572 @lines;
821             }
822              
823             ###
824             ### toXML
825             ###
826              
827             sub toXML($$%)
828 2     2 0 16 { my ($self, $doc, $ast, %args) = @_;
829 2         16 my $xml = $self->_xmlAny($doc, $ast, "\n$args{indent}", \%args);
830              
831 2 50       116 UNIVERSAL::isa($xml, 'XML::LibXML::Element')
832             or return $xml;
833              
834             # add comment
835 2         4 my $pkg = __PACKAGE__;
836 2         183 my $now = localtime();
837              
838 2         43 my $header = $doc->createComment( <<_HEADER . ' ' );
839              
840             BE WARNED: in most cases, the example below cannot be used without
841             interpretation. The comments will guide you.
842             Produced by $pkg version $VERSION
843             on $now
844             _HEADER
845              
846 2 50       12 unless($args{skip_header})
847 0         0 { $xml->insertBefore($header, $xml->firstChild);
848 0         0 $xml->insertBefore($doc->createTextNode("\n "), $header);
849             }
850              
851             # I use xsi:type myself, too late for the usual "used" counter
852             $ast->{'xmlns:xsi'} ||= SCHEMA2001i
853 2 100 50     13 if $args{show_type};
854              
855             # add info about name-spaces
856 2         20 foreach (sort keys %$ast)
857 20 100       108 { if( m/^xmlns\:(.*)/ )
858 6         20 { $xml->setNamespace($ast->{$_}, $1, 0);
859             }
860             }
861              
862 2         36 $xml;
863             }
864              
865             sub _xmlAny($$$$);
866             sub _xmlAny($$$$)
867 42     42   76 { my ($self, $doc, $ast, $indent, $args) = @_;
868 42         50 my @res;
869             my $xsi = $self->_registerNSprefix('xsi', SCHEMA2001i, 1)
870 42 100       111 if $args->{show_type};
871              
872 42         49 my @comment;
873 42 100 100     118 if($ast->{struct} && $args->{show_struct})
874 17         19 { my $struct = $ast->{struct};
875 17 50       41 push @comment, ref $struct ? @$struct : $struct;
876             }
877              
878             push @comment, $ast->{occur}
879 42 100 100     85 if $ast->{occur} && $args->{show_occur};
880              
881 42 100 100     76 if($ast->{facets} && $args->{show_facets})
882 2         4 { my $facets = $ast->{facets};
883 2 50       9 push @comment, ref $facets eq 'ARRAY' ? @$facets : $facets;
884             }
885              
886 42 50 66     108 if(defined $ast->{kind} && $ast->{kind} eq 'union')
887 0         0 { push @comment, map " $_->{type}", @{$ast->{choice}};
  0         0  
888             }
889              
890 42 100       62 my @attrs = @{$ast->{attrs} || []};
  42         114  
891 42         73 foreach my $attr (@attrs)
892 8         70 { push @res, $doc->createAttribute($attr->{tag}, $attr->{example});
893 8         25 my ($ns, $local) = unpack_type $attr->{_TYPE};
894 8         25 my $prefix = $self->_registerNSprefix('', $ns, 1);
895             push @comment, "attr $attr->{tag} has type $prefix:$local"
896 8 100       35 if $args->{show_type};
897             }
898              
899 42         71 my $nest_indent = $indent.$args->{indent};
900 42 100       83 if(@comment)
901 13         45 { my $comment = ' '.join("$nest_indent ", @comment) .' ';
902 13         105 push @res
903             , $doc->createTextNode($indent)
904             , $doc->createComment($comment);
905             }
906              
907 42 100       55 my @elems = @{$ast->{elems} || []};
  42         110  
908 42         63 foreach my $elem (@elems)
909 40 100 100     166 { if(ref $elem eq 'BLOCK' || ref $elem eq 'REP-BLOCK')
    50          
910 12         56 { push @res, $self->_xmlAny($doc, $elem, $indent, $args);
911             }
912             elsif($elem->{tag} eq '_')
913 0         0 { push @res, $doc->createTextNode($indent.$elem->{example});
914             }
915             else
916 28         88 { my $node = $self->_xmlAny($doc, $elem, $nest_indent, $args);
917 28 50       246 push @res, $doc->createTextNode($indent)
918             if $node->isa('XML::LibXML::Element');
919 28         55 push @res, $node;
920             }
921             }
922              
923 42         212 (my $outdent = $indent) =~ s/$args->{indent}$//; # sorry
924              
925 42 100       103 if(my $example = $ast->{example})
926 22 100       116 { push @res, $doc->createTextNode
927             (@comment ? "$indent$example$outdent" : $example)
928             }
929              
930 42 100 100     115 if($ast->{_TYPE} && $args->{show_type})
931 15         40 { my $pref = $self->prefixed($ast->{_TYPE});
932 15         175 push @res, $doc->createAttribute("$xsi:type" => $pref);
933             }
934              
935             return @res
936 42 100       123 if wantarray;
937              
938 30         127 my $node = $doc->createElement($ast->{tag});
939 30         266 $node->addChild($_) for @res;
940 30 100       411 $node->appendText($outdent) if @elems;
941 30         115 $node;
942             }
943              
944             sub makeBlocked($$$)
945 0     0 0   { my ($self, $where, $class, $type) = @_;
946 0           panic "namespace blocking not yet supported for Templates";
947             }
948              
949              
950             1;