File Coverage

blib/lib/Data/Validate/XSD/ParseXML.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package Data::Validate::XSD::ParseXML;
2              
3 1     1   5 use strict;
  1         2  
  1         39  
4              
5             =head1 NAME
6              
7             Data::Validate::XSD::ParseXML - Parse an XML file into a data structure for validation
8              
9             =head1 DESCRIPTION
10              
11             Please install XML::SAX to use this module.
12              
13             Used internally by Data::Validate::XSD to load xml files for both xsd definitions
14             and xml data. For the xml data we use a simple conversion metric which treats each
15             tag level as an hash reference and multiple tags witht he same name as an array reference.
16              
17             For the xsd defininitions we use the same method as the data to aquire the data but then
18             It's converted into a simpler format and any features which arn't available will produce
19             warnings.
20              
21             =cut
22              
23 1     1   413 use XML::SAX::ParserFactory;
  0            
  0            
24              
25             =head2 I<$parser>->new( $xml_string )
26              
27             Create a new parser object to parse xml files.
28              
29             =cut
30             sub new {
31             my ($class, $xml) = @_;
32             return bless { xml => $xml }, $class;
33             }
34              
35             =head2 I<$parser>->data( )
36              
37             Return the parsed data structure.
38              
39             =cut
40             sub data {
41             my ($self) = @_;
42              
43             my $handler = Data::Validate::XSD::ParseXML::Parser->new();
44             my $parser = XML::SAX::ParserFactory->parser( Handler => $handler );
45             $parser->parse_string( $self->{'xml'} );
46              
47             return $handler->{'root'};
48             }
49              
50             =head2 I<$parser>->definition( )
51              
52             Convert the data into a definition, assume it's in xsd format.
53              
54             =cut
55             sub definition {
56             my ($self) = @_;
57             my $data = $self->data();
58             my $result = {};
59              
60             if($data) {
61             $data = $data->{'schema'};
62             $self->_decode_complexes( $data->{'complexType'}, $result );
63             $self->_decode_simples( $data->{'simpleType'}, $result );
64              
65             $result->{'root'} = $self->_decode_elements($data->{'element'}, $result);
66             }
67              
68             return $result;
69             }
70              
71             sub _decode_complexes {
72             my ($self, $data, $result) = @_;
73              
74             $data = [ $data ] if ref($data) ne 'ARRAY';
75              
76             foreach my $d (@{$data}) {
77             my $name = $d->{'_name'};
78             $self->_decode_complex( $name, $d, $result );
79             }
80             }
81              
82             sub _decode_complex {
83             my ($self, $name, $data, $result) = @_;
84              
85             my $elements;
86             if($data->{'element'}) {
87             $elements = $self->_decode_elements( $data->{'element'}, $result );
88             }
89              
90             if($data->{'or'}) {
91            
92             }
93              
94             $result->{'complexTypes'}->{$name} = $elements;
95              
96             }
97              
98             sub _decode_elements {
99             my ($self, $data, $result) = @_;
100              
101             $data = [ $data ] if ref($data) ne 'ARRAY';
102             my @els;
103              
104             foreach my $element (@{$data}) {
105             push @els, $self->_decode_element( $element, $result );
106             }
107              
108             return \@els;
109             }
110              
111             sub _decode_simples {
112             my ($self, $data, $result) = @_;
113             foreach my $d (@{$data}) {
114             #my $name = '';
115             #$self->_decode_simple( $name, $d, $data );
116             }
117             }
118              
119             sub _decode_element {
120             my ($self, $data, $result) = @_;
121             my $element = {};
122              
123             if($data->{'complexType'}) {
124             my $name = $self->_random_name;
125             $element->{'type'} = $name;
126             $self->_decode_complex( $name, delete($data->{'complexType'}), $result );
127             } elsif($data->{'_type'}) {
128             my ($ns, $type) = split(':', delete($data->{'_type'}));
129             $element->{'type'} = $type ? $type : $ns;
130             }
131              
132             foreach my $key (keys(%{$data})) {
133             if($key =~ /^\_(.+)$/) {
134             $element->{$1} = $data->{$key};
135             }
136             }
137              
138             return $element;
139             }
140              
141             sub _random_name {
142             my ($self) = @_;
143             my @charset = ( 0 .. 9, 'a' .. 'z', 'A' .. 'Z' );
144              
145             my $result = '';
146              
147             $result .= $charset[int(rand() * @charset)] for(1..10);
148              
149             return $result;
150             }
151              
152             package Data::Validate::XSD::ParseXML::Parser;
153              
154             use base qw(XML::SAX::Base);
155              
156             =head2 $parser->new( )
157              
158             Create a new parser object.
159              
160             =cut
161             sub new
162             {
163             my ($class) = @_;
164             my $root = {};
165             my $self = bless {
166             root => $root,
167             current => $root,
168             parents => [ ],
169             count => 0,
170             }, $class;
171             return $self;
172             }
173              
174             =head1 SAX PARSING
175              
176             =head2 $parser->start_element( $node )
177              
178             Start a new xml element
179              
180             =cut
181             sub start_element
182             {
183             my ($self, $node) = @_;
184              
185             my $name = $node->{'LocalName'};
186             my $atrs = $node->{'Attributes'};
187             my $ns = $node->{'Prefix'};
188             my $c = $self->{'current'};
189             my $new = {};
190              
191             if(not $c->{$name}) {
192             $c->{$name} = $new;
193             } else {
194             if(ref($c->{$name}) eq 'ARRAY') {
195             push @{$c->{$name}}, $new;
196             } else {
197             $c->{$name} = [ $c->{$name}, $new ];
198             }
199             }
200             push @{$self->{'parents'}}, $c;
201             $self->{'count'}++;
202             $self->{'name'} = $name;
203             $self->{'parent'} = $c;
204             $self->{'current'} = $new;
205              
206             foreach my $a (keys(%{$atrs})) {
207             my $attribute = $atrs->{$a};
208             if($attribute->{'Name'} ne 'xmlns') {
209             $self->{'current'}->{'_'.$attribute->{'LocalName'}} = $attribute->{'Value'};
210             }
211             }
212              
213             }
214              
215             =head2 $parser->end_element( $element )
216              
217             Ends an xml element
218              
219             =cut
220             sub end_element
221             {
222             my ($self, $element) = @_;
223             $self->{'count'}++;
224             $self->{'current'} = $self->{'parent'};
225             pop @{$self->{'parents'}};
226             $self->{'parent'} = $self->{'parents'}->[$#{$self->{'parents'}}];
227             }
228              
229             =head2 $parser->characters()
230              
231             Handle part of a cdata by concatination
232              
233             =cut
234             sub characters
235             {
236             my ($self, $text) = @_;
237             my $t = $text->{'Data'};
238             if($t =~ /\S/) {
239             my $p = $self->{'parent'};
240             my $c = $p->{$self->{'name'}};
241             if(ref($c) eq 'HASH') {
242             if(%{$c}) {
243             if($c->{'+data'}) {
244             $c->{'+data'} .= $t;
245             } else {
246             $c->{'+data'} = $t;
247             }
248             } else {
249             $p->{$self->{'name'}} = $t;
250             }
251             } elsif(ref($c) eq 'ARRAY') {
252             pop @{$c} if ref($c->[$#{$c}]) eq 'HASH' and not %{$c->[$#{$c}]};
253             push @{$c}, $t;
254             } else {
255             $p->{$self->{'name'}} .= $t;
256             }
257             }
258             }
259              
260              
261             =head1 COPYRIGHT
262              
263             Copyright, Martin Owens 2007-2008, Affero General Public License (AGPL)
264              
265             http://www.fsf.org/licensing/licenses/agpl-3.0.html
266              
267             =head1 SEE ALSO
268              
269             L,L
270              
271             =cut
272             1;