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   4836554 use vars '$VERSION';
  50         113  
  50         3117  
11             $VERSION = '1.63';
12              
13 50     50   288 use base 'XML::Compile';
  50         91  
  50         16135  
14              
15 50     50   310 use warnings;
  50         73  
  50         1077  
16 50     50   193 use strict;
  50         70  
  50         848  
17              
18 50     50   204 use Log::Report 'xml-compile';
  50         71  
  50         184  
19              
20 50     50   10098 use List::Util qw/first/;
  50         79  
  50         2994  
21 50     50   262 use XML::LibXML ();
  50         72  
  50         743  
22 50     50   199 use File::Spec ();
  50         66  
  50         925  
23 50     50   208 use File::Basename qw/basename/;
  50         76  
  50         3565  
24 50     50   286 use Digest::MD5 qw/md5_hex/;
  50         98  
  50         2245  
25              
26 50     50   16135 use XML::Compile::Schema::Specs;
  50         142  
  50         1803  
27 50     50   20237 use XML::Compile::Schema::Instance;
  50         98  
  50         1574  
28 50     50   15750 use XML::Compile::Schema::NameSpaces;
  50         125  
  50         1748  
29 50     50   301 use XML::Compile::Util qw/SCHEMA2001 SCHEMA2001i unpack_type/;
  50         93  
  50         2252  
30              
31 50     50   22214 use XML::Compile::Translate ();
  50         127  
  50         131742  
32              
33              
34             sub init($)
35 52     52 0 162 { my ($self, $args) = @_;
36 52         404 $self->{namespaces} = XML::Compile::Schema::NameSpaces->new;
37 52         355 $self->SUPER::init($args);
38              
39             $self->importDefinitions($args->{top}, %$args)
40 52 50       557 if $args->{top};
41              
42 52         158 $self->{hooks} = [];
43 52 50       234 if(my $h1 = $args->{hook})
44 0 0       0 { $self->addHook(ref $h1 eq 'ARRAY' ? @$h1 : $h1);
45             }
46 52 50       205 if(my $h2 = $args->{hooks})
47 0 0       0 { $self->addHook($_) for ref $h2 eq 'ARRAY' ? @$h2 : $h2;
48             }
49            
50 52         180 $self->{key_rewrite} = [];
51 52 50       192 if(my $kr = $args->{key_rewrite})
52 0 0       0 { $self->addKeyRewrite(ref $kr eq 'ARRAY' ? @$kr : $kr);
53             }
54              
55 52         145 $self->{block_nss} = [];
56 52         339 $self->blockNamespace($args->{block_namespace});
57              
58 52   50     361 $self->{typemap} = $args->{typemap} || {};
59 52         170 $self->{unused_tags} = $args->{ignore_unused_tags};
60              
61 52         187 $self;
62             }
63              
64             #--------------------------------------
65              
66              
67             sub addHook(@)
68 1     1 1 810 { my $self = shift;
69 1 0       2 push @{$self->{hooks}}, @_>1 ? {@_} : defined $_[0] ? shift : ();
  1 50       7  
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 1143 { my $hooks = shift->{hooks};
83 752 50       1405 my $dir = shift or return @$hooks;
84 752   33     1413 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 209 { my ($self, $node, %opts) = @_;
101 54 50       171 defined $node or return ();
102              
103 54         99 my @nsopts;
104 54         151 foreach my $o (qw/source filename target_namespace
105             element_form_default attribute_form_default/)
106 270 100       614 { push @nsopts, $o => delete $opts{$o} if exists $opts{$o};
107             }
108              
109 54 50       305 UNIVERSAL::isa($node, __PACKAGE__)
110             and error __x"use useSchema(), not addSchemas() for a {got} object"
111             , got => ref $node;
112              
113 54 50       258 UNIVERSAL::isa($node, 'XML::LibXML::Node')
114             or error __x"addSchema() requires an XML::LibXML::Node";
115              
116 54 50       420 $node = $node->documentElement
117             if $node->isa('XML::LibXML::Document');
118              
119 54         196 my $nss = $self->namespaces;
120 54         103 my @schemas;
121              
122             $self->walkTree
123             ( $node,
124 76     76   116 sub { my $this = shift;
125 76 100 100     782 return 1 unless $this->isa('XML::LibXML::Element')
126             && $this->localName eq 'schema';
127              
128 58 50       486 my $schema = XML::Compile::Schema::Instance->new($this, @nsopts)
129             or next;
130              
131 58         356 $nss->add($schema);
132 58         109 push @schemas, $schema;
133 58         221 return 0;
134             }
135 54         523 );
136 54         362 @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   1007 { my $self = shift;
159 771 100       1201 my @more = map { ref $_ eq 'ARRAY' ? @$_ : defined $_ ? $_ : () } @_;
  771 100       2419  
160              
161 771         1076 my ($pref_all, %pref, @other);
162 771         910 foreach my $rule (@more, @{$self->{key_rewrite}})
  771         1574  
163 18 100       58 { if($rule eq 'PREFIXED') { $pref_all++ }
  4 50       10  
164 0         0 elsif($rule =~ m/^PREFIXED\((.*)\)/) { $pref{$_}++ for split /\,/, $1 }
165 14         32 else { push @other, $rule }
166             }
167              
168 771 50       2686 ( ( $pref_all ? 'PREFIXED'
    100          
169             : keys %pref ? 'PREFIXED('.join(',', sort keys %pref).')'
170             : ()), @other );
171             }
172              
173              
174             sub blockNamespace(@)
175 52     52 1 101 { my $self = shift;
176 52         94 push @{$self->{block_nss}}, @_;
  52         212  
177             }
178              
179             sub _block_nss(@)
180 771     771   948 { my $self = shift;
181 1542 50       3605 grep defined, map {ref $_ eq 'ARRAY' ? @$_ : $_}
182 771         945 @_, @{$self->{block_nss}};
  771         1204  
183             }
184              
185             #--------------------------------------
186              
187              
188             sub compile($$@)
189 752     752 1 1377501 { my ($self, $action, $type, %args) = @_;
190 752 50       2016 defined $type or return ();
191              
192 752 50       1657 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       1491 { exists $args{check_values} or $args{check_values} = 1;
199 752 100       1981 exists $args{check_occurs} or $args{check_occurs} = 1;
200             }
201              
202             my $iut = exists $args{ignore_unused_tags}
203 752 50       1571 ? $args{ignore_unused_tags} : $self->{unused_tags};
204              
205             $args{ignore_unused_tags}
206 752 0       1772 = !defined $iut ? undef : ref $iut eq 'Regexp' ? $iut : qr/^/;
    50          
207              
208             exists $args{include_namespaces}
209 752 100       1582 or $args{include_namespaces} = 1;
210              
211 752 100 100     3006 if($args{sloppy_integers} ||= 0)
212 94         4084 { eval "require Math::BigInt";
213 94 50       742 panic "require Math::BigInt or sloppy_integers:\n$@"
214             if $@;
215             }
216              
217 752 100 100     2790 if($args{sloppy_floats} ||= 0)
218 43         1135 { eval "require Math::BigFloat";
219 43 50       109 panic "require Math::BigFloat by sloppy_floats:\n$@" if $@;
220             }
221              
222 752 50 50     2614 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     5009 );
      66        
233              
234 752         2106 my $nss = $self->namespaces;
235              
236 752         1547 my ($h1, $h2) = (delete $args{hook}, delete $args{hooks});
237 752         1912 my @hooks = $self->hooks($action);
238 752 50       1326 push @hooks, ref $h1 eq 'ARRAY' ? @$h1 : $h1 if $h1;
    100          
239 752 0       1198 push @hooks, ref $h2 eq 'ARRAY' ? @$h2 : $h2 if $h2;
    50          
240              
241 752 100       892 my %map = ( %{$self->{typemap}}, %{$args{typemap} || {}} );
  752         1180  
  752         2550  
242 752         3685 trace "schema compile $action for $type";
243              
244 752         17273 my @rewrite = $self->_key_rewrite(delete $args{key_rewrite});
245 752         1740 my @blocked = $self->_block_nss(delete $args{block_namespace});
246              
247 752   50     2779 $args{abstract_types} ||= 'ERROR';
248 752   100     2336 $args{mixed_elements} ||= 'ATTRIBUTES';
249 752 100 66     2821 $args{default_values} ||= $action eq 'READER' ? 'EXTEND' : 'IGNORE';
250              
251             # Option rename in 0.88
252 752   66     2326 $args{any_element} ||= delete $args{anyElement};
253 752   66     2263 $args{any_attribute} ||= delete $args{anyAttribute};
254              
255 752 100       1460 if(my $xi = $args{xsi_type})
256 6         10 { my $nss = $self->namespaces;
257 6         18 foreach (keys %$xi)
258 6 100       26 { $xi->{$_} = $nss->autoexpand_xsi_type($_) if $xi->{$_} eq 'AUTO';
259             }
260             }
261              
262 752         1362 my $transl = XML::Compile::Translate->new
263             ( $action
264             , nss => $self->namespaces
265             );
266              
267 752         5109 $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   2352 { my ($self, $table, $reset_count, $block_default) = @_;
279 771 100       1853 $table = { reverse @$table }
280             if ref $table eq 'ARRAY';
281              
282             $table->{$_} = { uri => $_, prefix => $table->{$_} }
283 771         2430 for grep ref $table->{$_} ne 'HASH', keys %$table;
284              
285 771 50       1390 if($reset_count)
286 0         0 { $_->{used} = 0 for values %$table;
287             }
288              
289             $table->{''} = {uri => '', prefix => '', used => 0}
290 771 100 100     3414 if $block_default && !grep $_->{prefix} eq '', values %$table;
291              
292             # very strong preference for 'xsi'
293 771         3150 $table->{&SCHEMA2001i} = {uri => SCHEMA2001i, prefix => 'xsi', used => 0};
294              
295 771         1488 $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 47975 { my ($self, $action, $type, %args) = @_;
331              
332 19 50       81 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       93 : 'ALL';
    50          
343              
344 19 100       60 $show = 'struct,type,occur,facets' if $show eq 'ALL';
345 19 100       52 $show = '' if $show eq 'NONE';
346 19         73 my %show = map {("show_$_" => 1)} split m/\,/, $show;
  68         153  
347 19         66 my $nss = $self->namespaces;
348              
349 19   100     88 my $indent = $args{indent} || " ";
350 19         37 $args{check_occurs} = 1;
351 19   50     99 $args{mixed_elements} ||= 'ATTRIBUTES';
352 19   50     71 $args{default_values} ||= 'EXTEND';
353 19   100     80 $args{abstract_types} ||= 'ERROR';
354              
355             exists $args{include_namespaces}
356 19 100       41 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     61 if $to_xml && defined $args{typemap} && keys %{$args{typemap}};
  0   33     0  
361              
362 19         64 my @rewrite = $self->_key_rewrite(delete $args{key_rewrite});
363 19         65 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     133 );
370              
371 19   100     66 my $used = $to_xml && $show{show_type};
372 19   50     221 $table->{&SCHEMA2001}
373             ||= +{prefix => 'xs', uri => SCHEMA2001, used => $used};
374 19   50     59 $table->{&SCHEMA2001i}
375             ||= +{prefix => 'xsi', uri => SCHEMA2001i, used => $used};
376              
377 19         47 my $transl = XML::Compile::Translate->new
378             ( 'TEMPLATE'
379             , nss => $self->namespaces
380             );
381              
382 19         135 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       68 $compiled or return;
390              
391 19         44 my $ast = $compiled->();
392             #use Data::Dumper; $Data::Dumper::Indent = 1; warn Dumper $ast;
393              
394 19 100       58 if($to_perl)
395             { return $transl->toPerl($ast, %show, indent => $indent
396             , skip_header => $args{skip_header})
397 16         114 }
398              
399 3 100       7 if($to_xml)
400 2         23 { my $doc = XML::LibXML::Document->new('1.1', 'UTF-8');
401             my $node = $transl->toXML($doc, $ast, %show
402 2         12 , indent => $indent, skip_header => $args{skip_header});
403 2         116 return $node->toString(1);
404             }
405              
406             # return tree
407 1         35 $ast;
408             }
409              
410             #------------------------------------------
411              
412              
413 1629     1629 1 5133 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 9875 { my ($self, $frags, %options) = @_;
423 54 50       257 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         152 local $self->{_use_cache} = 1;
428              
429 54         86 my @schemas;
430 54         131 foreach my $data (@data)
431 54 50       167 { defined $data or next;
432 54         331 my ($xml, %details) = $self->dataToXML($data);
433 54 50       244 %details = %{delete $options{details}} if $options{details};
  0         0  
434              
435 54 50       160 if(defined $xml)
    0          
    0          
436 54         319 { my @added = $self->addSchemas($xml, %details, %options);
437 54 100       527 if(my $checksum = $details{checksum})
    50          
438 53         193 { $self->{_cache_checksum}{$checksum} = \@added;
439             }
440             elsif(my $filestamp = $details{filestamp})
441 0         0 { $self->{_cache_file}{$filestamp} = \@added;
442             }
443 54         365 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         3032 @schemas;
455             }
456              
457             sub _parseScalar($)
458 53     53   138 { my ($thing, $data) = @_;
459              
460             ref $thing && $thing->{_use_cache}
461 53 50 33     329 or return $thing->SUPER::_parseScalar($data);
462              
463 53         93 my $self = $thing;
464 53         397 my $checksum = md5_hex $$data;
465 53 50       213 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         398 trace "cache parsed scalar with checksum $checksum";
471              
472 53         2020 ( $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 1733 { my $nss = shift->namespaces;
502 4         14 sort map {$_->types}
503 3         22 map {$nss->schemas($_)}
  4         13  
504             $nss->list;
505             }
506              
507              
508             sub elements()
509 3     3 1 8 { my $nss = shift->namespaces;
510 4         15 sort map {$_->elements}
511 3         12 map {$nss->schemas($_)}
  4         11  
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 87 { my $self = shift;
524 20         37 $self->namespaces->doesExtend(@_);
525             }
526              
527              
528             1;