File Coverage

blib/lib/W3C/SOAP/XSD/Parser.pm
Criterion Covered Total %
statement 31 33 93.9
branch n/a
condition n/a
subroutine 11 11 100.0
pod n/a
total 42 44 95.4


line stmt bran cond sub pod time code
1             package W3C::SOAP::XSD::Parser;
2              
3             # Created on: 2012-05-28 08:11:37
4             # Create by: Ivan Wills
5             # $Id$
6             # $Revision$, $HeadURL$, $Date$
7             # $Revision$, $Source$, $Date$
8              
9 7     7   188266 use Moose;
  7         742576  
  7         52  
10 7     7   40396 use warnings;
  7         14  
  7         203  
11 7     7   1275 use version;
  7         2925  
  7         60  
12 7     7   439 use Carp;
  7         15  
  7         563  
13 7     7   35 use Scalar::Util;
  7         11  
  7         232  
14 7     7   30 use List::Util;
  7         11  
  7         349  
15 7     7   32 use List::MoreUtils qw/all/;
  7         12  
  7         77  
16 7     7   2819 use Data::Dumper qw/Dumper/;
  7         5425  
  7         347  
17 7     7   948 use English qw/ -no_match_vars /;
  7         3163  
  7         47  
18 7     7   2977 use Path::Class;
  7         33057  
  7         355  
19 7     7   3179 use W3C::SOAP::XSD::Document;
  0            
  0            
20             use File::ShareDir qw/dist_dir/;
21             use Moose::Util::TypeConstraints;
22             use W3C::SOAP::Utils qw/split_ns/;
23             use W3C::SOAP::XSD;
24              
25             Moose::Exporter->setup_import_methods(
26             as_is => ['load_xsd'],
27             );
28              
29             extends 'W3C::SOAP::Parser';
30              
31             our $VERSION = version->new('0.11');
32              
33             subtype xsd_documents =>
34             as 'ArrayRef[W3C::SOAP::XSD::Document]';
35             coerce xsd_documents =>
36             from 'W3C::SOAP::XSD::Document',
37             via {[$_]};
38             has '+document' => (
39             isa => 'xsd_documents',
40             coerce => 1,
41             );
42             has ns_module_map => (
43             is => 'rw',
44             isa => 'HashRef[Str]',
45             required => 1,
46             );
47              
48             sub write_modules {
49             my ($self) = @_;
50             confess "No lib directory setup" if !$self->has_lib;
51             confess "No template object set" if !$self->has_template;
52              
53             my @xsds = $self->get_schemas;
54             my $template = $self->template;
55             my @schemas;
56             my $self_module;
57             my @parents;
58             my @xsd_modules;
59              
60             # process the schemas
61             for my $xsd (@xsds) {
62             my $module = $xsd->module;
63             push @xsd_modules, $module;
64             $self_module ||= $module;
65             my $file = $self->lib . '/' . $module;
66             $file =~ s{::}{/}gxms;
67             $file = file $file;
68             my $parent = $file->parent;
69             my @missing;
70             while ( !-d $parent ) {
71             push @missing, $parent;
72             $parent = $parent->parent;
73             }
74             mkdir $_ for reverse @missing;
75              
76             for my $type ( @{ $xsd->complex_types } ) {
77             my $type_name = $type->name || $type->parent_node->name;
78             warn "me = ".(ref $type).
79             "\nnode = ".($type->node->nodeName).
80             "\nparent = ".(ref $type->parent_node).
81             "\nparent node = ".($type->node->parentNode->nodeName).
82             "\ndocument = ".(ref $type->document)."\n"
83             if !$type_name;
84             confess "No name found for ",
85             $type->node->toString,
86             "\nin :\n",
87             $type->document->string,"\n"
88             if !$type_name;
89             my $type_module = $module . '::' . $type_name;
90             push @parents, $type_module;
91             my $type_file = $self->lib . '/' . $type_module;
92             $type_file =~ s{::}{/}gxms;
93             $type_file = file $type_file;
94             mkdir $type_file->parent if !-d $type_file->parent;
95              
96             my %modules;
97             for my $el (@{ $type->sequence }) {
98             $modules{ $el->type_module }++
99             if ! $el->simple_type && $el->module ne $module
100             }
101             for my $element (@{ $type->sequence }) {
102             next if $element->simple_type;
103             my ($ns) = split_ns($element->type);
104             $ns ||= $element->document->target_namespace;
105             my $ns_uri = $element->document->get_ns_uri($ns, $element->node);
106             $modules{ $type->document->get_module_name($ns_uri) }++
107             if $ns_uri && $ns_uri ne $type->document->target_namespace;
108             }
109              
110             # write the complex type module
111             $self->write_module(
112             'xsd/complex_type.pm.tt',
113             {
114             xsd => $xsd,
115             module => $type_module,
116             modules => [ keys %modules ],
117             node => $type
118             },
119             "$type_file.pm"
120             );
121             }
122              
123             # write the simple types library
124             $self->write_module(
125             'xsd/base.pm.tt',
126             {
127             xsd => $xsd,
128             },
129             "$file/Base.pm"
130             );
131              
132             # write the "XSD" elements module
133             $self->write_module(
134             'xsd/pm.tt',
135             {
136             xsd => $xsd,
137             parents => \@parents,
138             w3c_version => $VERSION,
139             config => { xsd => { parent_module => $xsd->module . '::Base'}},
140             },
141             "$file.pm"
142             );
143              
144             }
145              
146             #warn Dumper \@xsd_modules, $self_module;
147             return wantarray ? @xsd_modules : $self_module;
148             }
149              
150             my %written;
151             sub write_module {
152             my ($self, $tt, $template_data, $file) = @_;
153             my $template = $self->template;
154              
155             if ($written{$file}++) {
156             #warn "Already written $file!\n";
157             return;
158             }
159              
160             $template->process($tt, $template_data, "$file");
161             confess "Error in creating $file (via $tt): ". $template->error."\n"
162             if $template->error;
163              
164             return;
165             }
166              
167             sub written_modules {
168             return keys %written;
169             }
170              
171             sub get_schemas {
172             my ($self) = @_;
173             my @xsds = @{ $self->document };
174             my %xsd;
175              
176             # import all schemas
177             while ( my $xsd = shift @xsds ) {
178             my $target_namespace = $xsd->target_namespace;
179             push @{ $xsd{$target_namespace} }, $xsd;
180              
181             for my $import ( @{ $xsd->imports } ) {
182             push @xsds, $import;
183             }
184             for my $include ( @{ $xsd->includes } ) {
185             push @xsds, $include;
186             }
187             }
188              
189             # flatten schemas specified more than once
190             for my $ns ( keys %xsd ) {
191             my $xsd = pop @{ $xsd{$ns} };
192             if ( @{ $xsd{$ns} } ) {
193             for my $xsd_repeat ( @{ $xsd{$ns} } ) {
194             push @{ $xsd->simple_types }, @{ $xsd_repeat->simple_types };
195             push @{ $xsd->complex_types }, @{ $xsd_repeat->complex_types };
196             push @{ $xsd->elements }, @{ $xsd_repeat->elements };
197             }
198             }
199              
200             push @xsds, $xsd;
201             }
202              
203             return @xsds;
204             }
205              
206             sub load_xsd {
207             my ($location) = @_;
208             my $parser = __PACKAGE__->new(
209             location => $location,
210             ns_module_map => {},
211             );
212              
213             return $parser->dynamic_classes;
214             }
215              
216             sub dynamic_classes {
217             my ($self) = @_;
218             my @xsds = $self->get_schemas;
219             my @packages;
220              
221             # construct the in memory module names
222             for my $xsd (@xsds) {
223             $xsd->module_base('Dynamic::XSD');
224             $xsd->module;
225             }
226              
227             my %seen;
228             my @ordered_xsds;
229             XSD:
230             while ( my $xsd = shift @xsds ) {
231             my $module = $xsd->module;
232              
233             # Complex types
234             my @types = @{ $xsd->complex_types };
235             my %local_seen;
236             TYPE:
237             while ( my $type = shift @types ) {
238             my $type_name = $type->name || $type->parent_node->name;
239             my $type_module = $module . '::' . $type_name;
240              
241             if ( $type->extension && !$seen{ $type->extension }++ ) {
242             push @xsds, $xsd;
243             next XSD;
244             }
245             $local_seen{ $type_module }++;
246             }
247              
248             %seen = ( %seen, %local_seen );
249             push @ordered_xsds, $xsd;
250             }
251              
252             my %complex_seen = ( 'W3C::SOAP::XSD' => 1 );
253             for my $xsd (@ordered_xsds) {
254             my $module = $xsd->module;
255              
256             # Create simple types
257             $self->simple_type_package($xsd);
258              
259             # Complex types
260             my @complex_types = @{ $xsd->complex_types };
261             my %types;
262             while ( my $type = shift @complex_types ) {
263             my $type_name = $type->name || $type->parent_node->name;
264             my $type_module = $module . '::' . $type_name;
265             next if $types{$type_module}++;
266              
267             my %modules = ( 'W3C::SOAP::XSD' => 1 );
268             for my $el (@{ $type->sequence }) {
269             $modules{ $el->type_module }++
270             if ! $el->simple_type && $el->module ne $module
271             }
272             if ( $type->extension ) {
273             $modules{ $type->extension }++
274             }
275              
276             if ( !all {$complex_seen{$_}} keys %modules ) {
277             push @complex_types, $type;
278             next;
279             }
280              
281             $complex_seen{$type_module}++;
282             $self->complex_type_package($xsd, $type, $type_module, [ keys %modules ]);
283             }
284              
285             # elements package
286             $self->elements_package($xsd, $module);
287              
288             push @packages, $module;
289             }
290              
291             return @packages;
292             }
293              
294             sub simple_type_package {
295             my ($self, $xsd) = @_;
296              
297             for my $subtype (@{ $xsd->simple_types }) {
298             next if !$subtype->name;
299              
300             # Setup base simple types
301             if ( @{ $subtype->enumeration } ) {
302             enum(
303             $subtype->moose_type
304             => $subtype->enumeration
305             );
306             }
307             else {
308             subtype $subtype->moose_type =>
309             as $subtype->moose_base_type;
310             }
311              
312             # Add coercion from XML::LibXML nodes
313             coerce $subtype->moose_type =>
314             from 'XML::LibXML::Node' =>
315             via { $_->textContent };
316              
317             if ($subtype->list) {
318             coerce $subtype->moose_type =>
319             from 'ArrayRef' =>
320             via { join ' ', @$_ };
321             }
322             }
323              
324             return;
325             }
326              
327             sub complex_type_package {
328             my ($self, $xsd, $type, $class_name, $super) = @_;
329              
330             my $class = Moose::Meta::Class->create(
331             $class_name,
332             superclasses => $super,
333             );
334              
335             $class->add_attribute(
336             '+xsd_ns',
337             default => $xsd->target_namespace,
338             required => 1,
339             );
340              
341             for my $node (@{ $type->sequence }) {
342             $self->element_attributes($class, $class_name, $node, $xsd, 1);
343             }
344              
345             return $class;
346             }
347              
348             sub elements_package {
349             my ($self, $xsd, $class_name) = @_;
350              
351             my $class = Moose::Meta::Class->create(
352             $class_name,
353             superclasses => [ 'W3C::SOAP::XSD' ],
354             );
355              
356             $class->add_attribute(
357             '+xsd_ns',
358             default => $xsd->target_namespace,
359             required => 1,
360             );
361              
362             for my $node (@{ $xsd->elements }) {
363             $self->element_attributes($class, $class_name, $node, $xsd);
364             }
365              
366             return $class;
367             }
368              
369             sub element_attributes {
370             my ($self, $class, $class_name, $element, $xsd, $complex) = @_;
371              
372             my $simple = $element->simple_type;
373             my $very_simple = $element->very_simple_type;
374             my $is_array = $element->max_occurs eq 'unbounded'
375             || ( $element->max_occurs && $element->max_occurs > 1 )
376             || ( $element->min_occurs && $element->min_occurs > 1 );
377             my $type_name = $simple || $element->type_module;
378             my $serialize = '';
379              
380             if ( $very_simple ) {
381             if ( $very_simple eq 'xs:boolean' ) {
382             $serialize = sub { $_ ? 'true' : 'false' };
383             }
384             elsif ( $very_simple eq 'xs:date' ) {
385             $serialize = sub {
386             return $_->ymd if $_->time_zone->isa('DateTime::TimeZone::Floating');
387             my $d = DateTime::Format::Strptime::strftime('%F%z', $_);
388             $d =~ s/([+-]\d\d)(\d\d)$/$1:$2/xms;
389             return $d
390             };
391             }
392             elsif ( $very_simple eq 'xs:time' ) {
393             $serialize = sub { $_->hms };
394             }
395             }
396              
397             my @extra;
398             push @extra, ( xs_perl_module => $element->type_module ) if !$simple;
399             push @extra, ( xs_choice_group => $element->choice_group ) if $element->choice_group;
400             push @extra, ( xs_serialize => $serialize ) if $serialize;
401              
402             confess "No perl name!\n".$element->node->parentNode->toString if !$element->perl_name;
403             $class->add_attribute(
404             $element->perl_name,
405             is => 'rw',
406             isa => $class_name->xsd_subtype(
407             ($simple ? 'parent' : 'module') => $type_name,
408             list => $is_array,
409             nillable => $element->nillable,
410             ),
411             predicate => 'has_'. $element->perl_name,
412             # TODO handle nillable correctly should be a Maybe type
413             #required => !$element->nillable,
414             coerce => 1,
415             #[%- IF config->alias && element->name.replace('^\w+:', '') != element->perl_name %]
416             #alias => '[% element->name.replace('^\w+:', '') %]',
417             #[%- END %]
418             traits => [qw{ W3C::SOAP::XSD }],
419             xs_name => $element->name,
420             xs_ns => !$complex || $xsd->element_form_default eq 'qualified' ? $xsd->target_namespace : '',
421             xs_type => $element->type,
422             xs_min_occurs => $element->min_occurs,
423             xs_max_occurs => $element->max_occurs eq 'unbounded' ? 0 : $element->max_occurs,
424             @extra,
425             );
426              
427             if ( $ENV{W3C_SOAP_NAME_STYLE} eq 'both' && $element->name ne $element->perl_name ) {
428             my $name = $element->perl_name;
429             $class->add_method(
430             $element->name => sub { shift->$name(@_) }
431             );
432             }
433              
434             return;
435             }
436             1;
437              
438             __END__
439              
440             =head1 NAME
441              
442             W3C::SOAP::XSD::Parser - Parser for XSD documents that generates Perl modules
443             implementing the object defined.
444              
445             =head1 VERSION
446              
447             This documentation refers to W3C::SOAP::XSD::Parser version 0.11.
448              
449             =head1 SYNOPSIS
450              
451             use W3C::SOAP::XSD::Parser;
452              
453             # Brief but working code example(s) here showing the most common usage(s)
454             # This section will be as far as many users bother reading, so make it as
455             # educational and exemplary as possible.
456              
457             =head1 DESCRIPTION
458              
459             =head1 SUBROUTINES/METHODS
460              
461             =over 4
462              
463             =item C<load_xsd ($schema_location)>
464              
465             Loads the schema and dynamically generates the Perl/Moose packages that
466             represent the schema.
467              
468             =item C<write_modules ()>
469              
470             Uses the supplied documents to write out perl modules to disk that represent
471             the XSDs in the documents.
472              
473             =item C<write_module ($tt, $data, $file)>
474              
475             Write the template to disk
476              
477             =item C<written_modules ()>
478              
479             Returns a list of all XSD modules written by the parser.
480              
481             =item C<get_schemas ()>
482              
483             Gets a list of the schemas imported/included from the base XML Schema(s)
484              
485             =item C<complex_type_package ( $xsd, $type, $class_name, $super)>
486              
487             Creates the complex types
488              
489             =item C<<$wsdl->dynamic_classes ()>>
490              
491             Creates a dynamic XSD objects that represent the XML Schema files imported.
492              
493             =item C<element_attributes ( $class, $class_name, $element )>
494              
495             Sets up all the attributes for a single element
496              
497             =item C<elements_package ( $xsd, $class_name )>
498              
499             Creates the package that represents top level elements in the XSD
500              
501             =item C<simple_type_package ( $xsd )>
502              
503             Creates all the simple types for the C<$xsd>
504              
505             =back
506              
507             =head1 DIAGNOSTICS
508              
509             =head1 CONFIGURATION AND ENVIRONMENT
510              
511             =head1 DEPENDENCIES
512              
513             =head1 INCOMPATIBILITIES
514              
515             =head1 BUGS AND LIMITATIONS
516              
517             There are no known bugs in this module.
518              
519             Please report problems to Ivan Wills (ivan.wills@gmail.com).
520              
521             Patches are welcome.
522              
523             =head1 AUTHOR
524              
525             Ivan Wills - (ivan.wills@gmail.com)
526              
527             =head1 LICENSE AND COPYRIGHT
528              
529             Copyright (c) 2012 Ivan Wills (14 Mullion Close, Hornsby Heights, NSW Australia 2077).
530             All rights reserved.
531              
532             This module is free software; you can redistribute it and/or modify it under
533             the same terms as Perl itself. See L<perlartistic>. This program is
534             distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
535             without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
536             PARTICULAR PURPOSE.
537              
538             =cut