File Coverage

blib/lib/W3C/SOAP/XSD/Document.pm
Criterion Covered Total %
statement 28 30 93.3
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 38 40 95.0


line stmt bran cond sub pod time code
1             package W3C::SOAP::XSD::Document;
2              
3             # Created on: 2012-05-26 15:46:31
4             # Create by: Ivan Wills
5             # $Id$
6             # $Revision$, $HeadURL$, $Date$
7             # $Revision$, $Source$, $Date$
8              
9 7     7   31 use Moose;
  7         10  
  7         42  
10 7     7   39292 use warnings;
  7         16  
  7         236  
11 7     7   29 use version;
  7         12  
  7         39  
12 7     7   425 use Carp qw/carp croak cluck confess longmess/;
  7         13  
  7         498  
13 7     7   35 use Scalar::Util;
  7         10  
  7         229  
14 7     7   181 use List::Util;
  7         13  
  7         351  
15             #use List::MoreUtils;
16 7     7   32 use Data::Dumper qw/Dumper/;
  7         9  
  7         300  
17 7     7   31 use English qw/ -no_match_vars /;
  7         11  
  7         42  
18 7     7   2444 use Path::Class;
  7         10  
  7         329  
19 7     7   957645 use XML::LibXML;
  0            
  0            
20             use WWW::Mechanize;
21             use Try::Tiny;
22             use URI;
23             use W3C::SOAP::Exception;
24             use W3C::SOAP::XSD::Document::Element;
25             use W3C::SOAP::XSD::Document::ComplexType;
26             use W3C::SOAP::XSD::Document::SimpleType;
27             use W3C::SOAP::Utils qw/normalise_ns ns2module/;
28              
29             extends 'W3C::SOAP::Document';
30              
31             our $VERSION = version->new('0.11');
32              
33             has element_form_default => (
34             is => 'rw',
35             isa => 'Str',
36             builder => '_element_form_default',
37             lazy => 1,
38             );
39             has imports => (
40             is => 'rw',
41             isa => 'ArrayRef[W3C::SOAP::XSD::Document]',
42             builder => '_imports',
43             lazy => 1,
44             );
45             has includes => (
46             is => 'rw',
47             isa => 'ArrayRef[W3C::SOAP::XSD::Document]',
48             builder => '_includes',
49             lazy => 1,
50             );
51             has simple_types => (
52             is => 'rw',
53             isa => 'ArrayRef[W3C::SOAP::XSD::Document::SimpleType]',
54             builder => '_simple_types',
55             lazy => 1,
56             );
57             has simple_type => (
58             is => 'rw',
59             isa => 'HashRef[W3C::SOAP::XSD::Document::SimpleType]',
60             builder => '_simple_type',
61             lazy => 1,
62             );
63             has anon_simple_type_count => (
64             is => 'ro',
65             isa => 'Int',
66             traits => [qw/Counter/],
67             default => -1,
68             handles => { simple_type_count => 'inc' },
69             );
70             has complex_types => (
71             is => 'rw',
72             isa => 'ArrayRef[W3C::SOAP::XSD::Document::ComplexType]',
73             builder => '_complex_types',
74             lazy => 1,
75             );
76             has complex_type => (
77             is => 'rw',
78             isa => 'HashRef[W3C::SOAP::XSD::Document::ComplexType]',
79             builder => '_complex_type',
80             lazy => 1,
81             );
82             has anon_complex_type_count => (
83             is => 'ro',
84             isa => 'Int',
85             traits => [qw/Counter/],
86             default => -1,
87             handles => { complex_type_count => 'inc' },
88             clearer => 'reset_complex_type_count',
89             lazy => 1,
90             );
91             has elements => (
92             is => 'rw',
93             isa => 'ArrayRef[W3C::SOAP::XSD::Document::Element]',
94             builder => '_elements',
95             lazy => 1,
96             );
97             has element => (
98             is => 'rw',
99             isa => 'HashRef[W3C::SOAP::XSD::Document::Element]',
100             builder => '_element',
101             lazy => 1,
102             );
103             has module => (
104             is => 'rw',
105             isa => 'Str',
106             builder => '_module',
107             lazy => 1,
108             );
109             has ns_name => (
110             is => 'rw',
111             isa => 'Str',
112             builder => '_ns_name',
113             lazy => 1,
114             );
115             has ns_map => (
116             is => 'rw',
117             isa => 'HashRef[Str]',
118             predicate => 'has_ns_map',
119             builder => '_ns_map',
120             lazy => 1,
121             );
122              
123             sub _element_form_default {
124             my ($self) = @_;
125             my @imports;
126             my @nodes = $self->xpc->findnodes('//@elementFormDefault');
127              
128             if (@nodes) {
129             return $nodes[0]->value;
130             }
131              
132             return 'unqualified';
133             }
134              
135             sub _imports {
136             my ($self) = @_;
137             my @imports;
138             my @nodes = $self->xpc->findnodes('//xsd:import');
139              
140             for my $import (@nodes) {
141             next if $import->getAttribute('namespace') && $import->getAttribute('namespace') eq 'http://www.w3.org/2001/XMLSchema';
142              
143             my $location = $import->getAttribute('schemaLocation');
144             if ($location) {
145              
146             if ( $self->location && (
147             $self->location =~ m{^(?:https?|ftp)://}xms
148             || (
149             -f $self->location
150             && !-f $location
151             )
152             )
153             ) {
154             my $current_location
155             = -f $self->location
156             ? file($self->location)->absolute . ''
157             : $self->location;
158              
159             $location = URI->new_abs($location, $current_location)->as_string;
160             }
161              
162             push @imports, __PACKAGE__->new(
163             location => $location,
164             ns_module_map => $self->ns_module_map,
165             module_base => $self->module_base,
166             );
167             }
168             else {
169             warn "Found import but no schemaLocation so no schema imported!\n\t" . $import->toString . "\n\t";
170             }
171             }
172              
173             return \@imports;
174             }
175              
176             sub _includes {
177             my ($self) = @_;
178             my @includes;
179             my @nodes = $self->xpc->findnodes('//xsd:include');
180              
181             for my $include (@nodes) {
182             next if $include->getAttribute('namespace') && $include->getAttribute('namespace') eq 'http://www.w3.org/2001/XMLSchema';
183              
184             my $location = $include->getAttribute('schemaLocation');
185             if ($location) {
186              
187             if ( $self->location && $self->location =~ m{^(?:https?|ftp)://}xms ) {
188             $location = URI->new_abs($location, $self->location)->as_string;
189             }
190              
191             push @includes, __PACKAGE__->new(
192             location => $location,
193             ns_module_map => $self->ns_module_map,
194             module_base => $self->module_base,
195             );
196             }
197             else {
198             warn "Found include but no schemaLocation so no schema included!\n\t" . $include->toString . "\n\t";
199             }
200             }
201              
202             return \@includes;
203             }
204              
205             sub _simple_types {
206             my ($self) = @_;
207             my @simple_types;
208             my @nodes = $self->xpc->findnodes('//xsd:simpleType');
209              
210             for my $node (@nodes) {
211             push @simple_types, W3C::SOAP::XSD::Document::SimpleType->new(
212             document => $self,
213             node => $node,
214             );
215             }
216              
217             return \@simple_types;
218             }
219              
220             sub _simple_type {
221             my ($self) = @_;
222             my %simple_type;
223              
224             for my $type (@{ $self->simple_types }) {
225             my $name = $type->name;
226             if ( !$name ) {
227             my $parent = $type->node->parentNode;
228             $name = $parent->getAttribute('name');
229             $name = $name ? $name . '_type' : 'anon'.$self->simple_type_count;
230             $type->name($name);
231             }
232             confess "No name for simple type ".$type->node->parentNode->toString if !$name;
233             $simple_type{$name} = $type;
234             }
235              
236             return \%simple_type;
237             }
238              
239             sub _complex_types {
240             my ($self) = @_;
241             my @complex_types;
242             my @nodes = $self->xpc->findnodes('/*/xsd:complexType');
243             push @nodes, $self->xpc->findnodes('/*/xsd:complexContent');
244              
245             for my $node (@nodes) {
246             # get all top level complex types
247             try {
248             push @complex_types, W3C::SOAP::XSD::Document::ComplexType->new(
249             document => $self,
250             node => $node,
251             );
252             }
253             catch {
254             warn Dumper {
255             document => $self,
256             node => $node,
257             };
258             die $_;
259             };
260              
261             }
262              
263             # now itterate over all document level elements and elements of complex types
264             my @elements = ( @{ $self->elements }, map {@{ $_->sequence }} @complex_types );
265              
266             while ( my $element = shift @elements ) {
267             # Get the elements first sub complex type (if any)
268             my ($node) = $self->xpc->findnodes('xsd:complexType', $element->node);
269             if (!$node) {
270             ($node) = $self->xpc->findnodes('xsd:complexContent', $element->node);
271             }
272             next unless $node;
273              
274             try {
275             push @complex_types, W3C::SOAP::XSD::Document::ComplexType->new(
276             parent_node => $element,
277             document => $self,
278             node => $node,
279             );
280             push @elements, @{ $complex_types[-1]->sequence };
281             }
282             catch {
283             warn Dumper {
284             parent_node => $element->node->toString,
285             document => $self,
286             node => $node,
287             };
288             die $_;
289             };
290             }
291              
292             # Moved the typification of the names in here from
293             # the complex_type builder as I can't see why you
294             # wouldn't want the name fixed up front.
295             for my $type (@complex_types) {
296             my $name = $type->name;
297             if ( !$name ) {
298             my $parent = $type->node->parentNode;
299             $name = $parent->getAttribute('name');
300             $name = $name ? $name . 'Type' : 'Anon'.$self->complex_type_count;
301             $type->name($name);
302             }
303             confess "No name for complex type ".$type->node->parentNode->toString if !$name;
304             }
305              
306             return \@complex_types;
307             }
308              
309             sub _complex_type {
310             my ($self) = @_;
311             my %complex_type;
312             for my $type (@{ $self->complex_types }) {
313             my $name = $type->name;
314             $complex_type{$name} = $type;
315             }
316              
317             return \%complex_type;
318             }
319              
320             sub _elements {
321             my ($self) = @_;
322             my @elements;
323             my @nodes = $self->xpc->findnodes('/*/xsd:element');
324              
325             for my $node (@nodes) {
326             push @elements, W3C::SOAP::XSD::Document::Element->new(
327             document => $self,
328             node => $node,
329             );
330             }
331              
332             return \@elements;
333             }
334              
335             sub _element {
336             my ($self) = @_;
337             my %element;
338             for my $element (@{ $self->elements }) {
339             $element{$element->name} = $element;
340             }
341             return \%element;
342             }
343              
344             sub _ns_name {
345             my ($self) = @_;
346             my %rev = reverse %{ $self->ns_map };
347             if ( !defined $rev{$self->target_namespace} ) {
348             delete $self->ns_map->{''};
349             my $ns = $self->target_namespace;
350             $ns =~ s/:/_/gxms;
351             $rev{$self->target_namespace} = $ns;
352             $self->ns_map->{$ns} = $self->target_namespace;
353             }
354             return $rev{$self->target_namespace};
355             }
356              
357             sub _ns_map {
358             my ($self) = @_;
359              
360             my %map
361             = map {
362             ( $_->name =~ /^xmlns:?(.*)$/xms => $_->value )
363             }
364             grep {
365             $_->name =~ /^xmlns/xms
366             }
367             $self->xml->getDocumentElement->getAttributes;
368              
369             my %rev;
370             for my $name ( keys %map ) {
371             $rev{$map{$name}} = defined $rev{$map{$name}} ? $rev{$map{$name}} : $name;
372             }
373             if ( $rev{$self->target_namespace} && $map{''} && $map{''} eq $self->target_namespace ) {
374             delete $map{''};
375             }
376              
377             my $ns = $self->target_namespace;
378             $ns =~ s/:/_/gxms;
379             $map{$ns} = $self->target_namespace if !$rev{$self->target_namespace};
380             $map{''} = '';
381              
382             return \%map;
383             }
384              
385             sub get_ns_uri {
386             my ($self, $ns_name, $node) = @_;
387             confess "No namespace passed when trying to map a namespace uri!\n" if !defined $ns_name;
388              
389             return $self->ns_map->{$ns_name} if $self->ns_map->{$ns_name};
390              
391             if ( $ns_name =~ /:/xms ) {
392             my $tmp_ns_name = $ns_name;
393             $tmp_ns_name =~ s/:/_/gxms;
394             return $self->ns_map->{$tmp_ns_name} if $self->ns_map->{$tmp_ns_name};
395             }
396              
397             while ($node) {
398             my $ns = $node->getAttribute("xmlns:$ns_name");
399             return $ns if $ns;
400             $ns = $node->getAttribute("targetNamespace");
401             return $ns if $ns;
402             $node = $node->parentNode;
403             last if ref $node eq 'XML::LibXML::Document';
404             }
405              
406             confess "Couldn't find the namespace '$ns_name' to map\nMap has:\n", Dumper $self->ns_map if !defined $self->ns_map->{$ns_name};
407              
408             return $self->ns_map->{$ns_name};
409             }
410              
411             1;
412              
413             __END__
414              
415             =head1 NAME
416              
417             W3C::SOAP::XSD::Document - Represents a XMLSchema Document
418              
419             =head1 VERSION
420              
421             This documentation refers to W3C::SOAP::XSD::Document version 0.11.
422              
423             =head1 SYNOPSIS
424              
425             use W3C::SOAP::XSD::Document;
426              
427             my $xsd = W3C::SOAP::XSD::Document->new(
428             location => 'my.xsd',
429             ns_base => {
430             'http://xml.namespace.com/SomeTing.html' => 'MyApp::SomeTing',
431             },
432             );
433              
434             =head1 DESCRIPTION
435              
436             Takes a XMLSchema Document and makes the contents available in a convenient
437             interface.
438              
439             =head1 SUBROUTINES/METHODS
440              
441             =over 4
442              
443             =item C<get_ns_uri ()>
444              
445             =back
446              
447             =head1 ATTRIBUTES
448              
449             =over 4
450              
451             =item C<imports>
452              
453             =item C<includes>
454              
455             =item C<simple_types>
456              
457             =item C<simple_type>
458              
459             =item C<complex_types>
460              
461             =item C<complex_type>
462              
463             =item C<elements>
464              
465             =item C<element>
466              
467             =item C<module>
468              
469             =item C<ns_map>
470              
471             =item C<ns_module_map>
472              
473             =back
474              
475             =head1 DIAGNOSTICS
476              
477             =head1 CONFIGURATION AND ENVIRONMENT
478              
479             =head1 DEPENDENCIES
480              
481             =head1 INCOMPATIBILITIES
482              
483             =head1 BUGS AND LIMITATIONS
484              
485             Please report problems to Ivan Wills (ivan.wills@gmail.com).
486              
487             Patches are welcome.
488              
489             =head1 AUTHOR
490              
491             Ivan Wills - (ivan.wills@gmail.com)
492              
493             =head1 LICENSE AND COPYRIGHT
494              
495             Copyright (c) 2012 Ivan Wills (14 Mullion Close Hornsby Heights NSW Australia).
496             All rights reserved.
497              
498             This module is free software; you can redistribute it and/or modify it under
499             the same terms as Perl itself. See L<perlartistic>. This program is
500             distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
501             without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
502             PARTICULAR PURPOSE.
503              
504             =cut