File Coverage

lib/XML/Compile/Schema.pm
Criterion Covered Total %
statement 210 266 78.9
branch 89 150 59.3
condition 43 72 59.7
subroutine 32 39 82.0
pod 17 18 94.4
total 391 545 71.7


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::Schema;
10 50     50   5729431 use vars '$VERSION';
  50         133  
  50         2966  
11             $VERSION = '1.62';
12              
13 50     50   303 use base 'XML::Compile';
  50         99  
  50         19283  
14              
15 50     50   413 use warnings;
  50         100  
  50         1274  
16 50     50   238 use strict;
  50         96  
  50         1006  
17              
18 50     50   231 use Log::Report 'xml-compile';
  50         96  
  50         235  
19              
20 50     50   11723 use List::Util qw/first/;
  50         92  
  50         3529  
21 50     50   301 use XML::LibXML ();
  50         91  
  50         901  
22 50     50   220 use File::Spec ();
  50         87  
  50         1029  
23 50     50   264 use File::Basename qw/basename/;
  50         91  
  50         4202  
24 50     50   319 use Digest::MD5 qw/md5_hex/;
  50         93  
  50         2651  
25              
26 50     50   19083 use XML::Compile::Schema::Specs;
  50         139  
  50         2429  
27 50     50   21866 use XML::Compile::Schema::Instance;
  50         118  
  50         1781  
28 50     50   18595 use XML::Compile::Schema::NameSpaces;
  50         135  
  50         2098  
29 50     50   346 use XML::Compile::Util qw/SCHEMA2001 SCHEMA2001i unpack_type/;
  50         101  
  50         2628  
30              
31 50     50   26415 use XML::Compile::Translate ();
  50         137  
  50         153847  
32              
33              
34             sub init($)
35 52     52 0 188 { my ($self, $args) = @_;
36 52         431 $self->{namespaces} = XML::Compile::Schema::NameSpaces->new;
37 52         388 $self->SUPER::init($args);
38              
39             $self->importDefinitions($args->{top}, %$args)
40 52 50       612 if $args->{top};
41              
42 52         199 $self->{hooks} = [];
43 52 50       258 if(my $h1 = $args->{hook})
44 0 0       0 { $self->addHook(ref $h1 eq 'ARRAY' ? @$h1 : $h1);
45             }
46 52 50       245 if(my $h2 = $args->{hooks})
47 0 0       0 { $self->addHook($_) for ref $h2 eq 'ARRAY' ? @$h2 : $h2;
48             }
49            
50 52         211 $self->{key_rewrite} = [];
51 52 50       218 if(my $kr = $args->{key_rewrite})
52 0 0       0 { $self->addKeyRewrite(ref $kr eq 'ARRAY' ? @$kr : $kr);
53             }
54              
55 52         170 $self->{block_nss} = [];
56 52         429 $self->blockNamespace($args->{block_namespace});
57              
58 52   50     402 $self->{typemap} = $args->{typemap} || {};
59 52         204 $self->{unused_tags} = $args->{ignore_unused_tags};
60              
61 52         207 $self;
62             }
63              
64             #--------------------------------------
65              
66              
67             sub addHook(@)
68 1     1 1 641 { my $self = shift;
69 1 0       2 push @{$self->{hooks}}, @_>1 ? {@_} : defined $_[0] ? shift : ();
  1 50       9  
70 1         2 $self;
71             }
72              
73              
74             sub addHooks(@)
75 0     0 1 0 { my $self = shift;
76 0         0 $self->addHook($_) for @_;
77 0         0 $self;
78             }
79              
80              
81             sub hooks(;$)
82 752     752 1 1218 { my $hooks = shift->{hooks};
83 752 50       1565 my $dir = shift or return @$hooks;
84 752   33     1614 grep +(!$_->{action} || $_->{action} eq $dir), @$hooks;
85             }
86              
87              
88             sub addTypemaps(@)
89 0     0 1 0 { my $map = shift->{typemap};
90 0         0 while(@_ > 1)
91 0         0 { my $k = shift;
92 0         0 $map->{$k} = shift;
93             }
94 0         0 $map;
95             }
96             *addTypemap = \&addTypemaps;
97              
98              
99             sub addSchemas($@)
100 54     54 1 223 { my ($self, $node, %opts) = @_;
101 54 50       200 defined $node or return ();
102              
103 54         153 my @nsopts;
104 54         170 foreach my $o (qw/source filename target_namespace
105             element_form_default attribute_form_default/)
106 270 100       854 { push @nsopts, $o => delete $opts{$o} if exists $opts{$o};
107             }
108              
109 54 50       376 UNIVERSAL::isa($node, __PACKAGE__)
110             and error __x"use useSchema(), not addSchemas() for a {got} object"
111             , got => ref $node;
112              
113 54 50       264 UNIVERSAL::isa($node, 'XML::LibXML::Node')
114             or error __x"addSchema() requires an XML::LibXML::Node";
115              
116 54 50       468 $node = $node->documentElement
117             if $node->isa('XML::LibXML::Document');
118              
119 54         224 my $nss = $self->namespaces;
120 54         105 my @schemas;
121              
122             $self->walkTree
123             ( $node,
124 76     76   130 sub { my $this = shift;
125 76 100 100     824 return 1 unless $this->isa('XML::LibXML::Element')
126             && $this->localName eq 'schema';
127              
128 58 50       536 my $schema = XML::Compile::Schema::Instance->new($this, @nsopts)
129             or next;
130              
131 58         410 $nss->add($schema);
132 58         127 push @schemas, $schema;
133 58         256 return 0;
134             }
135 54         596 );
136 54         422 @schemas;
137             }
138              
139              
140             sub useSchema(@)
141 0     0 1 0 { my $self = shift;
142 0         0 foreach my $schema (@_)
143 0   0     0 { error __x"useSchema() accepts only {pkg} extensions, not {got}"
144             , pkg => __PACKAGE__, got => (ref $schema || $schema);
145 0         0 $self->namespaces->use($schema);
146             }
147 0         0 $self;
148             }
149              
150              
151             sub addKeyRewrite(@)
152 0     0 1 0 { my $self = shift;
153 0         0 unshift @{$self->{key_rewrite}}, @_;
  0         0  
154 0 0       0 defined wantarray ? $self->_key_rewrite(undef) : ();
155             }
156              
157             sub _key_rewrite($)
158 771     771   1102 { my $self = shift;
159 771 100       1364 my @more = map { ref $_ eq 'ARRAY' ? @$_ : defined $_ ? $_ : () } @_;
  771 100       2607  
160              
161 771         1177 my ($pref_all, %pref, @other);
162 771         1075 foreach my $rule (@more, @{$self->{key_rewrite}})
  771         1760  
163 18 100       58 { if($rule eq 'PREFIXED') { $pref_all++ }
  4 50       9  
164 0         0 elsif($rule =~ m/^PREFIXED\((.*)\)/) { $pref{$_}++ for split /\,/, $1 }
165 14         40 else { push @other, $rule }
166             }
167              
168 771 50       2928 ( ( $pref_all ? 'PREFIXED'
    100          
169             : keys %pref ? 'PREFIXED('.join(',', sort keys %pref).')'
170             : ()), @other );
171             }
172              
173              
174             sub blockNamespace(@)
175 52     52 1 128 { my $self = shift;
176 52         112 push @{$self->{block_nss}}, @_;
  52         242  
177             }
178              
179             sub _block_nss(@)
180 771     771   1108 { my $self = shift;
181 1542 50       4197 grep defined, map {ref $_ eq 'ARRAY' ? @$_ : $_}
182 771         1105 @_, @{$self->{block_nss}};
  771         1672  
183             }
184              
185             #--------------------------------------
186              
187              
188             sub compile($$@)
189 752     752 1 1591948 { my ($self, $action, $type, %args) = @_;
190 752 50       2276 defined $type or return ();
191              
192 752 50       1890 if(exists $args{validation})
193 0         0 { $args{check_values} = $args{validation};
194 0         0 $args{check_occurs} = $args{validation};
195 0         0 $args{ignore_facets} = ! $args{validation};
196             }
197             else
198 752 100       1949 { exists $args{check_values} or $args{check_values} = 1;
199 752 100       2095 exists $args{check_occurs} or $args{check_occurs} = 1;
200             }
201              
202             my $iut = exists $args{ignore_unused_tags}
203 752 50       1668 ? $args{ignore_unused_tags} : $self->{unused_tags};
204              
205             $args{ignore_unused_tags}
206 752 0       1781 = !defined $iut ? undef : ref $iut eq 'Regexp' ? $iut : qr/^/;
    50          
207              
208             exists $args{include_namespaces}
209 752 100       1785 or $args{include_namespaces} = 1;
210              
211 752 100 100     3248 if($args{sloppy_integers} ||= 0)
212 94         4734 { eval "require Math::BigInt";
213 94 50       870 panic "require Math::BigInt or sloppy_integers:\n$@"
214             if $@;
215             }
216              
217 752 100 100     2864 if($args{sloppy_floats} ||= 0)
218 43         1493 { eval "require Math::BigFloat";
219 43 50       174 panic "require Math::BigFloat by sloppy_floats:\n$@" if $@;
220             }
221              
222 752 50 50     2701 if($args{json_friendly} ||= 0)
223 0         0 { eval "require Types::Serialiser";
224 0 0       0 panic "require Types::Serialiser by json_friendly:\n$@" if $@;
225             }
226              
227             $args{prefixes} = $self->_namespaceTable
228             (($args{prefixes} || $args{output_namespaces})
229             , $args{namespace_reset}
230             , !($args{use_default_namespace} || $args{use_default_prefix})
231             # use_default_prefix renamed in 0.90
232 752   66     5476 );
      66        
233              
234 752         2187 my $nss = $self->namespaces;
235              
236 752         1693 my ($h1, $h2) = (delete $args{hook}, delete $args{hooks});
237 752         2061 my @hooks = $self->hooks($action);
238 752 50       1473 push @hooks, ref $h1 eq 'ARRAY' ? @$h1 : $h1 if $h1;
    100          
239 752 0       1362 push @hooks, ref $h2 eq 'ARRAY' ? @$h2 : $h2 if $h2;
    50          
240              
241 752 100       912 my %map = ( %{$self->{typemap}}, %{$args{typemap} || {}} );
  752         1324  
  752         2915  
242 752         4151 trace "schema compile $action for $type";
243              
244 752         19823 my @rewrite = $self->_key_rewrite(delete $args{key_rewrite});
245 752         1910 my @blocked = $self->_block_nss(delete $args{block_namespace});
246              
247 752   50     3592 $args{abstract_types} ||= 'ERROR';
248 752   100     2730 $args{mixed_elements} ||= 'ATTRIBUTES';
249 752 100 66     3195 $args{default_values} ||= $action eq 'READER' ? 'EXTEND' : 'IGNORE';
250              
251             # Option rename in 0.88
252 752   66     2625 $args{any_element} ||= delete $args{anyElement};
253 752   66     2632 $args{any_attribute} ||= delete $args{anyAttribute};
254              
255 752 100       1582 if(my $xi = $args{xsi_type})
256 6         16 { my $nss = $self->namespaces;
257 6         20 foreach (keys %$xi)
258 6 100       35 { $xi->{$_} = $nss->autoexpand_xsi_type($_) if $xi->{$_} eq 'AUTO';
259             }
260             }
261              
262 752         1564 my $transl = XML::Compile::Translate->new
263             ( $action
264             , nss => $self->namespaces
265             );
266              
267 752         5678 $transl->compile
268             ( $type, %args
269             , hooks => \@hooks
270             , typemap => \%map
271             , rewrite => \@rewrite
272             , block_namespace => \@blocked
273             );
274             }
275              
276             # also used in ::Cache init()
277             sub _namespaceTable($;$$)
278 771     771   2702 { my ($self, $table, $reset_count, $block_default) = @_;
279 771 100       2103 $table = { reverse @$table }
280             if ref $table eq 'ARRAY';
281              
282             $table->{$_} = { uri => $_, prefix => $table->{$_} }
283 771         2778 for grep ref $table->{$_} ne 'HASH', keys %$table;
284              
285 771 50       1574 if($reset_count)
286 0         0 { $_->{used} = 0 for values %$table;
287             }
288              
289             $table->{''} = {uri => '', prefix => '', used => 0}
290 771 100 100     3659 if $block_default && !grep $_->{prefix} eq '', values %$table;
291              
292             # very strong preference for 'xsi'
293 771         3456 $table->{&SCHEMA2001i} = {uri => SCHEMA2001i, prefix => 'xsi', used => 0};
294              
295 771         1693 $table;
296             }
297              
298              
299             sub compileType($$@)
300 0     0 1 0 { my ($self, $action, $type, %args) = @_;
301              
302             # translator can only create elements, not types.
303 0   0     0 my $elem = delete $args{element} || $type;
304 0         0 my ($ens, $elocal) = unpack_type $elem;
305 0         0 my ($ns, $local) = unpack_type $type;
306              
307 0         0 my $SchemaNS = SCHEMA2001;
308              
309 0 0       0 my $defs = $ns ? <<_DIRTY_TRICK1 : <<_DIRTY_TRICK2;
310            
311             targetNamespace="$ens"
312             xmlns:tns="$ns">
313            
314            
315             _DIRTY_TRICK1
316            
317             targetNamespace="$ens"
318             elementFormDefault="unqualified"
319             >
320            
321            
322             _DIRTY_TRICK2
323              
324 0         0 $self->importDefinitions($defs);
325 0         0 $self->compile($action, $elem, %args);
326             }
327              
328              
329             sub template($@)
330 19     19 1 55522 { my ($self, $action, $type, %args) = @_;
331              
332 19 50       109 my ($to_perl, $to_xml)
    100          
    100          
333             = $action eq 'PERL' ? (1, 0)
334             : $action eq 'XML' ? (0, 1)
335             : $action eq 'TREE' ? (0, 0)
336             : error __x"template output is either in XML or PERL layout, not '{action}'"
337             , action => $action;
338              
339             my $show
340             = exists $args{show_comments} ? $args{show_comments}
341             : exists $args{show} ? $args{show} # pre-0.79 option name
342 19 100       107 : 'ALL';
    50          
343              
344 19 100       74 $show = 'struct,type,occur,facets' if $show eq 'ALL';
345 19 100       59 $show = '' if $show eq 'NONE';
346 19         93 my %show = map {("show_$_" => 1)} split m/\,/, $show;
  68         192  
347 19         77 my $nss = $self->namespaces;
348              
349 19   100     103 my $indent = $args{indent} || " ";
350 19         48 $args{check_occurs} = 1;
351 19   50     119 $args{mixed_elements} ||= 'ATTRIBUTES';
352 19   50     92 $args{default_values} ||= 'EXTEND';
353 19   100     102 $args{abstract_types} ||= 'ERROR';
354              
355             exists $args{include_namespaces}
356 19 100       54 or $args{include_namespaces} = 1;
357              
358             # it could be used to add extra comment lines
359             error __x"typemaps not implemented for XML template examples"
360 19 50 66     77 if $to_xml && defined $args{typemap} && keys %{$args{typemap}};
  0   33     0  
361              
362 19         79 my @rewrite = $self->_key_rewrite(delete $args{key_rewrite});
363 19         87 my @blocked = $self->_block_nss(delete $args{block_namespace});
364              
365             my $table = $args{prefixes} = $self->_namespaceTable
366             (($args{prefixes} || $args{output_namespaces})
367             , $args{namespace_reset}
368             , !$args{use_default_namespace}
369 19   66     159 );
370              
371 19   100     71 my $used = $to_xml && $show{show_type};
372 19   50     232 $table->{&SCHEMA2001}
373             ||= +{prefix => 'xs', uri => SCHEMA2001, used => $used};
374 19   50     77 $table->{&SCHEMA2001i}
375             ||= +{prefix => 'xsi', uri => SCHEMA2001i, used => $used};
376              
377 19         68 my $transl = XML::Compile::Translate->new
378             ( 'TEMPLATE'
379             , nss => $self->namespaces
380             );
381              
382 19         176 my $compiled = $transl->compile
383             ( $type
384             , %args
385             , rewrite => \@rewrite
386             , block_namespace => \@blocked # not yet supported
387             , output => $action
388             );
389 19 50       88 $compiled or return;
390              
391 19         51 my $ast = $compiled->();
392             #use Data::Dumper; $Data::Dumper::Indent = 1; warn Dumper $ast;
393              
394 19 100       74 if($to_perl)
395             { return $transl->toPerl($ast, %show, indent => $indent
396             , skip_header => $args{skip_header})
397 16         122 }
398              
399 3 100       9 if($to_xml)
400 2         31 { my $doc = XML::LibXML::Document->new('1.1', 'UTF-8');
401             my $node = $transl->toXML($doc, $ast, %show
402 2         18 , indent => $indent, skip_header => $args{skip_header});
403 2         114 return $node->toString(1);
404             }
405              
406             # return tree
407 1         42 $ast;
408             }
409              
410             #------------------------------------------
411              
412              
413 1629     1629 1 5447 sub namespaces() { shift->{namespaces} }
414              
415              
416             # The cache will certainly avoid penalties by the average module user,
417             # which does not understand the sharing schema definitions between objects
418             # especially in SOAP implementations.
419             my (%schemaByFilestamp, %schemaByChecksum);
420              
421             sub importDefinitions($@)
422 54     54 1 11089 { my ($self, $frags, %options) = @_;
423 54 50       303 my @data = ref $frags eq 'ARRAY' ? @$frags : $frags;
424              
425             # this is a horrible hack, but by far the simpelest solution to
426             # avoid dataToXML process the same info twice.
427 54         174 local $self->{_use_cache} = 1;
428              
429 54         94 my @schemas;
430 54         138 foreach my $data (@data)
431 54 50       191 { defined $data or next;
432 54         344 my ($xml, %details) = $self->dataToXML($data);
433 54 50       253 %details = %{delete $options{details}} if $options{details};
  0         0  
434              
435 54 50       215 if(defined $xml)
    0          
    0          
436 54         346 { my @added = $self->addSchemas($xml, %details, %options);
437 54 100       572 if(my $checksum = $details{checksum})
    50          
438 53         206 { $self->{_cache_checksum}{$checksum} = \@added;
439             }
440             elsif(my $filestamp = $details{filestamp})
441 0         0 { $self->{_cache_file}{$filestamp} = \@added;
442             }
443 54         418 push @schemas, @added;
444             }
445             elsif(my $filestamp = $details{filestamp})
446 0         0 { my $cached = $self->{_cache_file}{$filestamp};
447 0         0 $self->namespaces->add(@$cached);
448             }
449             elsif(my $checksum = $details{checksum})
450 0         0 { my $cached = $self->{_cache_checksum}{$checksum};
451 0         0 $self->namespaces->add(@$cached);
452             }
453             }
454 54         3518 @schemas;
455             }
456              
457             sub _parseScalar($)
458 53     53   158 { my ($thing, $data) = @_;
459              
460             ref $thing && $thing->{_use_cache}
461 53 50 33     372 or return $thing->SUPER::_parseScalar($data);
462              
463 53         111 my $self = $thing;
464 53         462 my $checksum = md5_hex $$data;
465 53 50       255 if($self->{_cache_checksum}{$checksum})
466 0         0 { trace "reusing string data with checksum $checksum";
467 0         0 return (undef, checksum => $checksum);
468             }
469              
470 53         414 trace "cache parsed scalar with checksum $checksum";
471              
472 53         2439 ( $self->SUPER::_parseScalar($data)
473             , checksum => $checksum
474             );
475             }
476              
477             sub _parseFile($)
478 0     0   0 { my ($thing, $fn) = @_;
479              
480             ref $thing && $thing->{_use_cache}
481 0 0 0     0 or return $thing->SUPER::_parseFile($fn);
482 0         0 my $self = $thing;
483              
484 0         0 my ($mtime, $size) = (stat $fn)[9,7];
485 0         0 my $filestamp = File::Spec->rel2abs($fn) . '-'. $mtime . '-' . $size;
486              
487 0 0       0 if($self->{_cache_file}{$filestamp})
488 0         0 { trace "reusing schemas from file $filestamp";
489 0         0 return (undef, filestamp => $filestamp);
490             }
491              
492 0         0 trace "cache parsed file $filestamp";
493              
494 0         0 ( $self->SUPER::_parseFile($fn)
495             , filestamp => $filestamp
496             );
497             }
498              
499              
500             sub types()
501 3     3 1 1732 { my $nss = shift->namespaces;
502 4         20 sort map {$_->types}
503 3         12 map {$nss->schemas($_)}
  4         12  
504             $nss->list;
505             }
506              
507              
508             sub elements()
509 3     3 1 9 { my $nss = shift->namespaces;
510 4         14 sort map {$_->elements}
511 3         11 map {$nss->schemas($_)}
  4         9  
512             $nss->list;
513             }
514              
515              
516             sub printIndex(@)
517 0     0 1 0 { my $self = shift;
518 0         0 $self->namespaces->printIndex(@_);
519             }
520              
521              
522             sub doesExtend($$)
523 20     20 1 102 { my $self = shift;
524 20         39 $self->namespaces->doesExtend(@_);
525             }
526              
527              
528             1;