File Coverage

lib/XML/Compile/Schema.pm
Criterion Covered Total %
statement 207 263 78.7
branch 88 150 58.6
condition 43 72 59.7
subroutine 31 38 81.5
pod 17 18 94.4
total 386 541 71.3


line stmt bran cond sub pod time code
1             # Copyrights 2006-2024 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.03.
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             our $VERSION = '1.64';
11             }
12              
13 50     50   6641498 use base 'XML::Compile';
  50         127  
  50         25185  
14              
15 50     50   373 use warnings;
  50         83  
  50         2528  
16 50     50   246 use strict;
  50         75  
  50         1431  
17              
18 50     50   272 use Log::Report 'xml-compile';
  50         87  
  50         287  
19              
20 50     50   15981 use List::Util qw/first/;
  50         136  
  50         3973  
21 50     50   313 use XML::LibXML ();
  50         104  
  50         1101  
22 50     50   223 use File::Spec ();
  50         77  
  50         1393  
23 50     50   354 use File::Basename qw/basename/;
  50         95  
  50         4673  
24 50     50   333 use Digest::MD5 qw/md5_hex/;
  50         90  
  50         2969  
25              
26 50     50   26115 use XML::Compile::Schema::Specs;
  50         231  
  50         2831  
27 50     50   32134 use XML::Compile::Schema::Instance;
  50         156  
  50         2413  
28 50     50   25703 use XML::Compile::Schema::NameSpaces;
  50         323  
  50         2832  
29 50     50   404 use XML::Compile::Util qw/SCHEMA2001 SCHEMA2001i unpack_type/;
  50         102  
  50         3691  
30              
31 50     50   34406 use XML::Compile::Translate ();
  50         283  
  50         212814  
32              
33              
34             sub init($)
35 52     52 0 274 { my ($self, $args) = @_;
36 52         640 $self->{namespaces} = XML::Compile::Schema::NameSpaces->new;
37 52         477 $self->SUPER::init($args);
38              
39             $self->importDefinitions($args->{top}, %$args)
40 52 50       665 if $args->{top};
41              
42 52         215 $self->{hooks} = [];
43 52 50       340 if(my $h1 = $args->{hook})
44 0 0       0 { $self->addHook(ref $h1 eq 'ARRAY' ? @$h1 : $h1);
45             }
46 52 50       249 if(my $h2 = $args->{hooks})
47 0 0       0 { $self->addHook($_) for ref $h2 eq 'ARRAY' ? @$h2 : $h2;
48             }
49            
50 52         201 $self->{key_rewrite} = [];
51 52 50       281 if(my $kr = $args->{key_rewrite})
52 0 0       0 { $self->addKeyRewrite(ref $kr eq 'ARRAY' ? @$kr : $kr);
53             }
54              
55 52         221 $self->{block_nss} = [];
56 52         473 $self->blockNamespace($args->{block_namespace});
57              
58 52   50     545 $self->{typemap} = $args->{typemap} || {};
59 52         223 $self->{unused_tags} = $args->{ignore_unused_tags};
60              
61 52         270 $self;
62             }
63              
64             #--------------------------------------
65              
66              
67             sub addHook(@)
68 1     1 1 915 { my $self = shift;
69 1 0       1 push @{$self->{hooks}}, @_>1 ? {@_} : defined $_[0] ? shift : ();
  1 50       8  
70 1         1 $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 760     760 1 3101 { my $hooks = shift->{hooks};
83 760 50       4097 my $dir = shift or return @$hooks;
84 760   33     2687 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 298 { my ($self, $node, %opts) = @_;
101 54 50       309 defined $node or return ();
102              
103 54         125 my @nsopts;
104 54         195 foreach my $o (qw/source filename target_namespace
105             element_form_default attribute_form_default/)
106 270 100       1000 { push @nsopts, $o => delete $opts{$o} if exists $opts{$o};
107             }
108              
109 54 50       512 UNIVERSAL::isa($node, __PACKAGE__)
110             and error __x"use useSchema(), not addSchemas() for a {got} object"
111             , got => ref $node;
112              
113 54 50       332 UNIVERSAL::isa($node, 'XML::LibXML::Node')
114             or error __x"addSchema() requires an XML::LibXML::Node";
115              
116 54 50       626 $node = $node->documentElement
117             if $node->isa('XML::LibXML::Document');
118              
119 54         262 my $nss = $self->namespaces;
120 54         124 my @schemas;
121              
122             $self->walkTree
123             ( $node,
124 76     76   160 sub { my $this = shift;
125 76 100 100     1153 return 1 unless $this->isa('XML::LibXML::Element')
126             && $this->localName eq 'schema';
127              
128 58 50       753 my $schema = XML::Compile::Schema::Instance->new($this, @nsopts)
129             or next;
130              
131 58         632 $nss->add($schema);
132 58         135 push @schemas, $schema;
133 58         428 return 0;
134             }
135 54         769 );
136 54         529 @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 779     779   1420 { my $self = shift;
159 779 100       1910 my @more = map { ref $_ eq 'ARRAY' ? @$_ : defined $_ ? $_ : () } @_;
  779 100       3683  
160              
161 779         1524 my ($pref_all, %pref, @other);
162 779         1159 foreach my $rule (@more, @{$self->{key_rewrite}})
  779         2699  
163 18 100       74 { if($rule eq 'PREFIXED') { $pref_all++ }
  4 50       11  
164 0         0 elsif($rule =~ m/^PREFIXED\((.*)\)/) { $pref{$_}++ for split /\,/, $1 }
165 14         33 else { push @other, $rule }
166             }
167              
168 779 50       4944 ( ( $pref_all ? 'PREFIXED'
    100          
169             : keys %pref ? 'PREFIXED('.join(',', sort keys %pref).')'
170             : ()), @other );
171             }
172              
173              
174             sub blockNamespace(@)
175 52     52 1 136 { my $self = shift;
176 52         160 push @{$self->{block_nss}}, @_;
  52         284  
177             }
178              
179             sub _block_nss(@)
180 779     779   1301 { my $self = shift;
181 1558 50       5448 grep defined, map {ref $_ eq 'ARRAY' ? @$_ : $_}
182 779         1484 @_, @{$self->{block_nss}};
  779         2155  
183             }
184              
185             #--------------------------------------
186              
187              
188             sub compile($$@)
189 760     760 1 2276930 { my ($self, $action, $type, %args) = @_;
190 760 50       3161 defined $type or return ();
191              
192 760 50       2692 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 760 100       2290 { exists $args{check_values} or $args{check_values} = 1;
199 760 100       3393 exists $args{check_occurs} or $args{check_occurs} = 1;
200             }
201              
202             my $iut = exists $args{ignore_unused_tags}
203 760 50       2732 ? $args{ignore_unused_tags} : $self->{unused_tags};
204              
205             $args{ignore_unused_tags}
206 760 0       2570 = !defined $iut ? undef : ref $iut eq 'Regexp' ? $iut : qr/^/;
    50          
207              
208             exists $args{include_namespaces}
209 760 100       2185 or $args{include_namespaces} = 1;
210              
211 760 100 100     4674 if($args{sloppy_integers} ||= 0)
212 94         11899 { eval "require Math::BigInt";
213 94 50       1200 panic "require Math::BigInt or sloppy_integers:\n$@"
214             if $@;
215             }
216              
217 760 100 100     4222 if($args{sloppy_floats} ||= 0)
218 43         2331 { eval "require Math::BigFloat";
219 43 50       268 panic "require Math::BigFloat by sloppy_floats:\n$@" if $@;
220             }
221              
222 760 50 50     3676 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 760   66     8151 );
      66        
233              
234 760         3236 my $nss = $self->namespaces;
235              
236 760         2343 my ($h1, $h2) = (delete $args{hook}, delete $args{hooks});
237 760         3589 my @hooks = $self->hooks($action);
238 760 50       2043 push @hooks, ref $h1 eq 'ARRAY' ? @$h1 : $h1 if $h1;
    100          
239 760 0       2175 push @hooks, ref $h2 eq 'ARRAY' ? @$h2 : $h2 if $h2;
    50          
240              
241 760 100       1364 my %map = ( %{$self->{typemap}}, %{$args{typemap} || {}} );
  760         4074  
  760         4010  
242 760         5467 trace "schema compile $action for $type";
243              
244 760         32951 my @rewrite = $self->_key_rewrite(delete $args{key_rewrite});
245 760         2821 my @blocked = $self->_block_nss(delete $args{block_namespace});
246              
247 760   50     4398 $args{abstract_types} ||= 'ERROR';
248 760   100     3720 $args{mixed_elements} ||= 'ATTRIBUTES';
249 760 100 66     4526 $args{default_values} ||= $action eq 'READER' ? 'EXTEND' : 'IGNORE';
250              
251             # Option rename in 0.88
252 760   66     3798 $args{any_element} ||= delete $args{anyElement};
253 760   66     3511 $args{any_attribute} ||= delete $args{anyAttribute};
254              
255 760 100       2161 if(my $xi = $args{xsi_type})
256 6         29 { my $nss = $self->namespaces;
257 6         25 foreach (keys %$xi)
258 6 100       44 { $xi->{$_} = $nss->autoexpand_xsi_type($_) if $xi->{$_} eq 'AUTO';
259             }
260             }
261              
262 760         2330 my $transl = XML::Compile::Translate->new
263             ( $action
264             , nss => $self->namespaces
265             );
266              
267 760         9066 $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 779     779   3335 { my ($self, $table, $reset_count, $block_default) = @_;
279 779 100       2849 $table = +{ reverse @$table }
280             if ref $table eq 'ARRAY';
281              
282             $table->{$_} = +{ uri => $_, prefix => $table->{$_} }
283 779         4326 for grep ref $table->{$_} ne 'HASH', keys %$table;
284              
285 779 50       2302 if($reset_count)
286 0         0 { $_->{used} = 0 for values %$table;
287             }
288              
289             $table->{''} = +{ uri => '', prefix => '', used => 0 }
290 779 100 100     5265 if $block_default && !grep $_->{prefix} eq '', values %$table;
291              
292             # very strong preference for 'xsi'
293 779         5723 $table->{&SCHEMA2001i} = +{ uri => SCHEMA2001i, prefix => 'xsi', used => 0 };
294              
295 779         2288 $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 76639 { my ($self, $action, $type, %args) = @_;
331              
332 19 50       362 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       111 : 'ALL';
    50          
343              
344 19 100       168 $show = 'struct,type,occur,facets' if $show eq 'ALL';
345 19 100       63 $show = '' if $show eq 'NONE';
346 19         94 my %show = map {("show_$_" => 1)} split m/\,/, $show;
  68         198  
347 19         89 my $nss = $self->namespaces;
348              
349 19   100     108 my $indent = $args{indent} || " ";
350 19         49 $args{check_occurs} = 1;
351 19   50     130 $args{mixed_elements} ||= 'ATTRIBUTES';
352 19   50     112 $args{default_values} ||= 'EXTEND';
353 19   100     85 $args{abstract_types} ||= 'ERROR';
354              
355             exists $args{include_namespaces}
356 19 100       52 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 0 66     212 if $to_xml && defined $args{typemap} && keys %{$args{typemap}};
  0   33     0  
361              
362 19         97 my @rewrite = $self->_key_rewrite(delete $args{key_rewrite});
363 19         80 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     163 );
370              
371 19   100     76 my $used = $to_xml && $show{show_type};
372 19   50     171 $table->{&SCHEMA2001}
373             ||= +{prefix => 'xs', uri => SCHEMA2001, used => $used};
374 19   50     74 $table->{&SCHEMA2001i}
375             ||= +{prefix => 'xsi', uri => SCHEMA2001i, used => $used};
376              
377 19         77 my $transl = XML::Compile::Translate->new
378             ( 'TEMPLATE'
379             , nss => $self->namespaces
380             );
381              
382 19         192 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       120 $compiled or return;
390              
391 19         59 my $ast = $compiled->();
392             #use Data::Dumper; $Data::Dumper::Indent = 1; warn Dumper $ast;
393              
394 19 100       104 if($to_perl)
395             { return $transl->toPerl($ast, %show, indent => $indent
396             , skip_header => $args{skip_header})
397 16         157 }
398              
399 3 100       7 if($to_xml)
400 2         28 { 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         113 return $node->toString(1);
404             }
405              
406             # return tree
407 1         59 $ast;
408             }
409              
410             #------------------------------------------
411              
412              
413 1645     1645 1 8694 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 16389 { 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         183 local $self->{_use_cache} = 1;
428              
429 54         141 my @schemas;
430 54         147 foreach my $data (@data)
431 54 50       220 { defined $data or next;
432 54         443 my ($xml, %details) = $self->dataToXML($data);
433 54 50       273 %details = %{delete $options{details}} if $options{details};
  0         0  
434              
435 54 50       222 if(defined $xml)
    0          
    0          
436 54         482 { my @added = $self->addSchemas($xml, %details, %options);
437 54 100       615 if(my $checksum = $details{checksum})
    50          
438 53         225 { $self->{_cache_checksum}{$checksum} = \@added;
439             }
440             elsif(my $filestamp = $details{filestamp})
441 0         0 { $self->{_cache_file}{$filestamp} = \@added;
442             }
443 54         414 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         3926 @schemas;
455             }
456              
457             sub _parseScalar($)
458 53     53   147 { my ($thing, $data) = @_;
459              
460             ref $thing && $thing->{_use_cache}
461 53 50 33     626 or return $thing->SUPER::_parseScalar($data);
462              
463 53         122 my $self = $thing;
464 53         595 my $checksum = md5_hex $$data;
465 53 50       338 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         649 trace "cache parsed scalar with checksum $checksum";
471              
472 53         2833 ( $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 2432 { my $nss = shift->namespaces;
502 4         15 sort map {$_->types}
503 3         17 map {$nss->schemas($_)}
  4         15  
504             $nss->list;
505             }
506              
507              
508             sub elements()
509 3     3 1 85 { my $nss = shift->namespaces;
510 4         16 sort map {$_->elements}
511 3         15 map {$nss->schemas($_)}
  4         12  
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 119 { my $self = shift;
524 20         41 $self->namespaces->doesExtend(@_);
525             }
526              
527              
528             1;