File Coverage

blib/lib/ODO/Parser/XML/Slow.pm
Criterion Covered Total %
statement 79 427 18.5
branch 1 186 0.5
condition 0 93 0.0
subroutine 23 42 54.7
pod 2 2 100.0
total 105 750 14.0


line stmt bran cond sub pod time code
1             #
2             # Copyright (c) 2004-2006 IBM Corporation.
3             #
4             # All rights reserved. This program and the accompanying materials
5             # are made available under the terms of the Eclipse Public License v1.0
6             # which accompanies this distribution, and is available at
7             # http://www.eclipse.org/legal/epl-v10.html
8             #
9             # File: $Source: /var/lib/cvs/ODO/lib/ODO/Parser/XML/Slow.pm,v $
10             # Created by: Stephen Evanchik( evanchik@us.ibm.com )
11             # Created on: 11/10/2004
12             # Revision: $Id: Slow.pm,v 1.48 2009-11-25 17:54:26 ubuntu Exp $
13             #
14             # Contributors:
15             # IBM Corporation - initial API and implementation
16             #
17             package ODO::Parser::XML::Slow;
18              
19 4     4   79542 use strict;
  4         11  
  4         188  
20 4     4   24 use warnings;
  4         13  
  4         149  
21              
22 4     4   24 use ODO::Exception;
  4         8  
  4         221  
23 4     4   2921 use ODO::Parser::XML::RDFAttributes;
  4         13  
  4         137  
24              
25 4     4   36 use XML::SAX qw/Namespaces Validation/;
  4         7  
  4         305  
26              
27 4     4   21 use vars qw /$VERSION/;
  4         10  
  4         310  
28             $VERSION = sprintf "%d.%02d", q$Revision: 1.50 $ =~ /: (\d+)\.(\d+)/;
29              
30 4     4   21 use base qw/ODO::Parser::XML/;
  4         8  
  4         2074  
31              
32             __PACKAGE__->mk_accessors(qw/base_uri/);
33              
34             our $VERBOSE = 0;
35              
36             =head1 NAME
37              
38             ODO::Parser::XML::Slow
39              
40             =head1 SYNOPSIS
41              
42             my $parser = ODO::Parser::XML->new();
43             my $statements = $parser->parse($RDF);
44              
45             foreach my $stmt (@{ $statements }) {
46             # Manipulate the ODO::Statement($stmt) here
47             }
48            
49             =head1 DESCRIPTION
50              
51             =head1 METHODS
52              
53             =over
54              
55             =item parse_rdf( $rdf | GLOB )
56              
57             Parameters:
58              
59             $rdf - Required.
60              
61             Returns:
62              
63             An array ref of ODO::Statement's or
64             undef if there is an error parsing the RDF
65              
66             =cut
67              
68             sub parse_rdf {
69 4     4 1 12 my ($self, $rdf) = @_;
70              
71 4         53 my $factory = XML::SAX::ParserFactory->new();
72 4         1094 $factory->require_feature(Namespaces);
73            
74 4         53 my $handler = ODO::Parser::XML::Slow::Handler->new(base_uri=> $self->base_uri());
75 4         17 $handler->verbose($VERBOSE);
76            
77 4         42 my $parser = $factory->parser(Handler=> $handler );
78            
79 0         0 my $method = 'parse_string';
80            
81 0 0       0 $method = 'parse_file'
82             if(ref $rdf eq 'GLOB');
83            
84 0         0 eval { $parser->$method($rdf); };
  0         0  
85            
86 0 0       0 throw ODO::Exception::RDF::Parse(error=> "Unable to parse RDF: $@")
87             if($@);
88 0 0       0 my $statements = (scalar( @{ $handler->statements() }) >= 0) ? $handler->statements() : undef;
  0         0  
89 0 0       0 my $imports = (scalar( @{ $handler->owl_imports() }) >= 0) ? $handler->owl_imports() : undef;
  0         0  
90 0         0 return ($statements, $imports);
91             }
92              
93             sub init {
94 4     4 1 155 my ($self, $config) = @_;
95 4         362 $self->params($config, qw/base_uri/);
96 4         205 return $self;
97             }
98              
99             =back
100              
101             =head1 COPYRIGHT
102              
103             Copyright (c) 2004-2006 IBM Corporation.
104              
105             All rights reserved. This program and the accompanying materials
106             are made available under the terms of the Eclipse Public License v1.0
107             which accompanies this distribution, and is available at
108             http://www.eclipse.org/legal/epl-v10.html
109              
110             =cut
111              
112              
113             package ODO::Parser::XML::Fragment;
114              
115 4     4   29 use strict;
  4         22  
  4         146  
116 4     4   21 use warnings;
  4         8  
  4         143  
117              
118 4     4   24 use base qw/ODO/;
  4         8  
  4         1002  
119              
120             our @METHODS = qw/base_uri subject language uri qname attributes parent children xtext text/;
121              
122             __PACKAGE__->mk_accessors(@METHODS);
123              
124             sub init {
125 0     0   0 my ($self, $config) = @_;
126            
127 0         0 $self->params($config, @METHODS);
128            
129 0 0       0 $self->uri( $config->{'namespace'} . $config->{'name'} )
130             unless(exists($config->{'uri'}));
131            
132 0 0       0 $self->qname( $config->{'namespace'} . ':' . $config->{'name'} )
133             unless(exists($config->{'qname'}));
134              
135 0         0 $self->xtext( [] );
136 0         0 $self->children( [] );
137            
138 0         0 return $self;
139             }
140              
141              
142             package ODO::Parser::XML::Slow::Handler;
143              
144 4     4   24 use strict;
  4         9  
  4         132  
145 4     4   20 use warnings;
  4         10  
  4         126  
146              
147 4     4   21 use ODO::Node;
  4         6  
  4         187  
148 4     4   671 use ODO::Statement;
  4         8  
  4         173  
149              
150 4     4   1330974 use URI;
  4         12243  
  4         155  
151 4     4   4889 use Encode qw/encode decode/; # For Unicode processing
  4         52600  
  4         464  
152              
153 4     4   3726 use XML::RegExp;
  4         3007  
  4         201  
154              
155             use XML::Namespace
156 4         44 xml=> 'http://www.w3.org/XML/1998/namespace#',
157             xsd=> 'http://www.w3.org/2001/XMLSchema#',
158             rdfs=> 'http://www.w3.org/2000/01/rdf-schema#',
159             rdf=> 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
160             owl => 'http://www.w3.org/2002/07/owl#'
161 4     4   257 ;
  4         11  
162              
163 4     4   2074 use Data::Dumper;
  4         8  
  4         329  
164              
165 4     4   27 use base qw/XML::SAX::Base ODO/;
  4         8  
  4         6451  
166              
167             __PACKAGE__->mk_accessors(qw/verbose base_uri blank_node_uri_prefix stack seen_id statements blank_nodes owl_imports gatherOwlImports/);
168              
169              
170             our $RESERVED = [
171             'RDF',
172             'ID',
173             'about',
174             'parseType',
175             'type',
176             'resource',
177             'nodeID',
178             'aboutEach',
179             'aboutEachPrefix',
180             'bagID',
181             'datatype',
182             'li',
183             ];
184              
185             our $FORBIDDEN_PROPERTY = [
186             'Description',
187             'RDF',
188             'ID',
189             'about',
190             'parseType',
191             'bagID',
192             'resource',
193             'nodeID',
194             'aboutEach',
195             'aboutEachPrefix',
196             ];
197              
198              
199             sub new {
200 4     4   51 my $self = shift;
201 4         48 $self = $self->SUPER::new(@_);
202              
203 4         421 $self->stack( [] );
204 4         39 $self->statements( [] );
205 4         32 $self->owl_imports( [] );
206 4         28 $self->gatherOwlImports([]);
207              
208 4         29 $self->seen_id( {} );
209 4         34 $self->blank_nodes( {} );
210 4 50       30 if ($self->base_uri()) {
211 0         0 my $uri = $self->base_uri();
212 0         0 $uri =~ s/#+$//;
213 0         0 $self->base_uri($uri);
214             }
215              
216 4         35 return $self;
217             }
218              
219              
220             sub add_statement {
221 0     0     my ($self, $s, $p, $o) = @_;
222            
223 0 0 0       unless(UNIVERSAL::isa($s, 'ODO::Node')
      0        
224             && UNIVERSAL::isa($p, 'ODO::Node')
225             && UNIVERSAL::isa($o, 'ODO::Node')) {
226            
227 0           my $str = '';
228            
229 0 0         $str = 'Subject is undefined, '
230             unless($s);
231            
232 0 0         $str .= 'Predicate is undefined, '
233             unless($p);
234            
235 0 0         $str .= 'Object is undefined '
236             unless($o);
237 0           throw XML::SAX::Exception::Parse(Message=> 'Fatal error in parsing, statement has undefined elements: ' . $str);
238             }
239            
240 0           my $statement = ODO::Statement->new(s=> $s, p=> $p, o=> $o);
241 0 0         throw XML::SAX::Exception::Parse(Message=> 'Cannot add an undefined statement.')
242             unless(UNIVERSAL::isa($statement, 'ODO::Statement'));
243 0           push @{ $self->statements() }, $statement;
  0            
244             }
245              
246              
247             sub is_reserved_uri {
248 0     0     my ($self, $uri) = @_;
249            
250 0           my $rdfNS = rdf->uri();
251            
252 0           foreach my $name (@{ $RESERVED } ) {
  0            
253 0 0         return 1
254             if($uri eq "${rdfNS}${name}");
255             }
256            
257 0           return 0;
258             }
259              
260              
261             sub is_forbidden_property {
262 0     0     my ($self, $uri) = @_;
263            
264 0           my $rdfNS = rdf->uri();
265            
266 0           foreach my $name (@{ $FORBIDDEN_PROPERTY } ) {
  0            
267 0 0         return 1
268             if($uri eq "${rdfNS}${name}");
269             }
270            
271 0           return 0;
272             }
273              
274              
275             sub validate_NodeElement_attributes {
276 0     0     my ($self, $attributes) = @_;
277            
278 0 0 0       if ( $attributes->{ rdf->uri('ID') } && $attributes->{ rdf->uri('nodeID') }) {
    0 0        
    0 0        
279 0           throw XML::SAX::Exception::Parse(Message=> 'Cannot have rdf:nodeID and rdf:ID.');
280             }
281             elsif ( $attributes->{ rdf->uri('about') } && $attributes->{ rdf->uri('nodeID') }) {
282 0           throw XML::SAX::Exception::Parse(Message=> 'Cannot have rdf:nodeID and rdf:about.');
283             }
284             elsif ( $attributes->{ rdf->uri('nodeID') } && $attributes->{ rdf->uri('resource') }) {
285 0           throw XML::SAX::Exception::Parse(Message=> 'Cannot have rdf:nodeID and rdf:resource.');
286             }
287             else {
288 0           return 1;
289             }
290             }
291              
292             sub validate_PropertyElt_attributes {
293 0     0     my ($self, $attributes) = @_;
294            
295 0 0 0       if( exists($attributes->{ rdf->uri('about') })
    0 0        
    0 0        
    0 0        
      0        
296             && exists($attributes->{ rdf->uri('nodeID') }) ) {
297 0           throw XML::SAX::Exception::Parse(Message=> 'Cannot have rdf:nodeID and rdf:about.');
298             }
299             elsif( exists($attributes->{ rdf->uri('nodeID') })
300             && exists($attributes->{ rdf->uri('resource') }) ) {
301 0           throw XML::SAX::Exception::Parse(Message=> 'Cannot have rdf:nodeID and rdf:resource.');
302             }
303             elsif( exists($attributes->{ rdf->uri('parseType') })
304             && $attributes->{ rdf->uri('parseType') } eq 'Literal'
305             && exists($attributes->{ rdf->uri('resource') }) ) {
306 0           throw XML::SAX::Exception::Parse(Message=> 'This is not legal RDF; specifying an rdf:parseType of "Literal" and an rdf:resource attribute at the same time is an error.');
307             }
308             elsif( exists($attributes->{ rdf->uri('parseType') })
309             && $attributes->{ rdf->uri('parseType') } eq 'Literal') {
310             # We can't allow parseType='Literal' and allow random properties which would make
311             # this _NOT_ a Literal
312             # This is a lot like the previous check
313 0           foreach my $attr (keys(%{ $attributes })) {
  0            
314 0           my $xml_uri = xml->uri();
315             next
316 0 0 0       if($attr eq rdf->uri('parseType') || $attr =~ /^ $xml_uri/);
317            
318 0           throw XML::SAX::Exception::Parse(Message=> 'Cannot have property attributes with attribute: rdf:parseType="Literal"');
319             }
320             }
321             else {
322             # Check for invalid parseType (ParseType, parsetype, Parsetype)
323             # This seems bizarre
324 0           foreach my $attr (keys(%{ $attributes })) {
  0            
325 0 0         throw XML::SAX::Exception::Parse(Message=> 'This RDF is not legal because the parseType attribute is mis-spelled.')
326             if($attr =~ m/^.*(parsetype|Parsetype|ParseType)$/);
327             }
328             }
329            
330 0           return 1;
331             }
332              
333              
334             sub blank_node {
335 0     0     my ($self, $nodeID) = @_;
336              
337             # We record new blank node IDs after we 'see' them, once we've seen
338             # an ID it must be returned every time we see it in the future
339 0 0 0       unless($nodeID && exists($self->blank_nodes()->{ $nodeID })) {
340             # Some nodes may not have a URI so we need to generate one
341 0 0         unless($nodeID) {
342             # The current time as well as a random number should be sufficient.
343             # I'm not sure what is in the spec as far as valid unique node IDs are
344             # concerned
345 0           do {
346 0           $nodeID = sprintf( "genid%08x%04x", time(), int(rand(0xFFFF)) );
347            
348 0 0         $nodeID = $self->blank_node_uri_prefix() . $nodeID
349             if($self->blank_node_uri_prefix());
350            
351             } while(exists($self->blank_nodes()->{ $nodeID }));
352             }
353            
354 0           $self->blank_nodes()->{ $nodeID } = "_:$nodeID";
355             }
356            
357 0           return $self->blank_nodes()->{ $nodeID };
358             }
359              
360              
361             sub reify_statement {
362 0     0     my ($self, $r, $s, $p, $o) = @_;
363            
364             # S, P, O of statement quad
365 0           $self->add_statement($r, ${ODO::Parser::REIFY_SUBJECT}, $s);
366 0           $self->add_statement($r, ${ODO::Parser::REIFY_PREDICATE}, $p);
367 0           $self->add_statement($r, ${ODO::Parser::REIFY_OBJECT}, $o);
368              
369             # Statement itself
370 0           $self->add_statement($r, ${ODO::Parser::RDF_TYPE}, ${ODO::Parser::REIFY_STATEMENT});
371             }
372              
373              
374             sub start_element {
375 0     0     my ($self, $sax) = @_;
376            
377 0           my $attributes = ODO::Parser::XML::RDF::Attributes->new(%{ $sax->{'Attributes'} });
  0            
378              
379 0           my $parent = undef;
380 0           my $base_uri = $self->base_uri();
381 0 0         if ( scalar(@{ $self->stack() }) > 0 ) {
  0            
382 0           $parent = $self->stack()->[-1];
383 0   0       $base_uri = ( $self->stack()->[-1]->base_uri() || $self->base_uri() );
384             }
385            
386 0           my $element = ODO::Parser::XML::Fragment->new(
387             {
388             namespace=> $sax->{'NamespaceURI'},
389             prefix=> $sax->{'Prefix'},
390             name=> $sax->{'LocalName'},
391             parent=> $parent,
392             attributes=> $attributes,
393             base_uri=> $base_uri,
394             }
395             );
396 0 0 0       if ($element->uri() eq rdf->uri('RDF')) {
    0          
    0          
397 0           my $baseURI = $element->attributes()->{xml->uri('base')};
398 0 0         if ($baseURI) {
399             # strip all trailing # from URI
400 0           $baseURI =~ s/#*$//i;
401             # append a hash unless uri ends with /
402 0 0         $baseURI .= '#' unless $baseURI =~ m/\/$/;
403 0           $element->base_uri( $baseURI );
404 0           $self->base_uri( $baseURI );
405             }
406 0           } elsif ($element->uri() eq owl->uri('Ontology')) {
407 0 0         if (scalar @{$self->gatherOwlImports()} >= 1) {
  0            
408 0           my $import = $element->attributes()->{rdf->uri('resource')};
409 0 0 0       $import = $element->attributes()->{rdf->uri('about')} unless defined $import and $import ne '';
410 0 0 0       push @{ $self->owl_imports() }, $import if defined $import and $import ne '';
  0            
411             }
412 0           push @{$self->gatherOwlImports()}, "ontology";
  0            
413             } elsif ($element->uri() eq owl->uri('imports') and scalar(@{$self->gatherOwlImports()}) > 0) {
414 0           my $import = $element->attributes()->{rdf->uri('resource')};
415 0 0 0       push @{ $self->owl_imports() }, $import if defined $import and $import ne '';
  0            
416             }
417            
418            
419            
420 0           push @{ $element->xtext() }, '<' .$sax->{'Prefix'} . ' ' . ODO::Parser::XML::RDF::Attributes->to_string($element->attributes()) . '>';
  0            
421 0           push @{ $self->stack() }, $element;
  0            
422             }
423              
424             sub characters {
425 0     0     my ($self, $chars) = @_;
426             # trim space from characters
427 0           $chars->{'Data'} =~ s/^\s+//gm;
428 0           $chars->{'Data'} =~ s/\s+$//gm;
429             # if there is no text, return
430 0 0         return if $chars->{'Data'} eq '';
431 0           $self->stack()->[-1]->text($chars->{'Data'});
432 0           push @{ $self->stack()->[-1]->xtext() }, $chars->{'Data'};
  0            
433             }
434              
435             sub end_element {
436 0     0     my ($self, $sax) = @_;
437            
438 0           my $element = pop @{ $self->stack() };
  0            
439 0           push @{ $element->xtext() }, '{'Name'} . '>';
  0            
440            
441             # stop processing owl imports - well at least pop array
442 0 0         if ($element->uri() eq owl->uri('Ontology')) {
443 0           pop @{$self->gatherOwlImports()};
  0            
444             }
445            
446 0 0         if ( scalar(@{ $self->stack() }) > 0 ) {
  0            
447 0           push @{ $self->stack()->[-1]->children() }, $element;
  0            
448 0           @{ $element->xtext() } = grep { defined($_) } @{ $element->xtext() };
  0            
  0            
  0            
449 0           $self->stack()->[-1]->xtext()->[1] = join('', @{ $element->xtext() } );
  0            
450             }
451             else {
452             # The root element might not be rdf:RDF so we locate it for further
453             # processing according to:
454             #
455             # doc =
456             # RDF | nodeElement
457             #
458             # Locate the RDF URI for production:
459             #
460             # RDF =
461             # element rdf:RDF {
462             # xmllang?, xmlbase?, nodeElementList
463             # }
464             # FIXME: This is TEMPORARY!
465 0           my $rdf_root_element = $element;
466            
467 0 0         unless($element->uri() eq rdf->uri('RDF')) {
468 0           foreach my $c (@{ $element->children() }) {
  0            
469 0 0         if($c->uri() eq rdf->uri('RDF')) {
470            
471             # Found the rdf:RDF root element
472 0           $rdf_root_element = $c;
473 0           last;
474             }
475             }
476             }
477            
478             # FIXME: Should we look for xml:base attributes here?
479             # If we didn't find the element process the node as a nodeElement
480 0 0         $self->nodeElement($element)
481             unless($rdf_root_element);
482            
483            
484             # Proceed down the RDF path of the grammar
485             return
486 0 0         unless (scalar(@{ $element->children() }) > 0 );
  0            
487            
488             # The rdf:RDF element may have an xml:base attribute setting the base
489             # namespace of a relative URI
490 0 0         $element->base_uri( $rdf_root_element->attributes()->{xml->uri('base')} )
491             if(exists($rdf_root_element->attributes()->{xml->uri('base')}));
492            
493             # TODO: Handle the xml:lang attribute
494              
495             # Now we begin the nodeElementList processing:
496             #
497             # nodeElementList =
498             # nodeElement*
499             #
500 0           foreach my $e (@{ $rdf_root_element->children() }) {
  0            
501             # Children inherit the baseURI of their parent
502 0           $e->base_uri( $rdf_root_element->base_uri() );
503              
504 0           eval { $self->nodeElement($e) };
  0            
505 0 0         if($@) {
506 0           $self->{'statements'} = [];
507 0           throw XML::SAX::Exception::Parse(Message=> $@);
508             }
509             }
510             }
511             }
512              
513              
514             # 7.2.11 Production nodeElement
515             #
516             # nodeElement =
517             # element * - ( local:* | rdf:RDF | rdf:ID | rdf:about | rdf:parseType |
518             # rdf:resource | rdf:nodeID | rdf:datatype | rdf:li |
519             # rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID ) {
520             # (idAttr | nodeIdAttr | aboutAttr )?, xmllang?, xmlbase?, propertyAttr*, propertyEltList
521             # }
522             #
523             sub nodeElement {
524 0     0     my ( $self, $e ) = @_;
525              
526 0 0 0       throw XML::SAX::Exception::Parse(Message=> 'URI: ' . $e->uri() . ' is reserved from nodeElement names')
527             if($e->uri() ne rdf->uri('type') && $self->is_reserved_uri( $e->uri() ));
528            
529 0   0       my $baseURI = ($e->base_uri() || $self->base_uri());
530              
531 0 0         $baseURI = $e->attributes()->{xml->uri('base')}
532             if(exists($e->attributes()->{xml->uri('base')}));
533 0   0       $baseURI = $baseURI || '';
534            
535 0           my $s;
536            
537             # Throws an XML::SAX::Exception
538 0           $self->validate_NodeElement_attributes( $e->attributes() );
539              
540             # These can be processed in any order:
541 0 0         if ( $e->attributes()->{ rdf->uri('ID') } ) {
    0          
    0          
    0          
542              
543             # If there is an attribute a with a.URI == rdf:ID,
544             # then e.subject := uri(identifier := resolve(e, concat("#", a.string-value))).
545 0 0         throw XML::SAX::Exception::Parse(Message=> 'The value of rdf:ID must match the XML Name production, (as modified by XML Namespaces).')
546             if(!$e->attributes()->{ rdf->uri('ID') } =~ m/$XML::RegExp::NCName/);
547             # use $baseURI if we dont already have a URI
548 0           my $idURI = $e->attributes->{rdf->uri('ID')};
549 0 0         if ($idURI =~ m|.*://|){
550 0           $s = ODO::Node::Resource->new( $idURI );
551             } else {
552 0 0         $idURI =~ s/^#*// if $idURI;
553 0 0 0       $idURI = $baseURI . $idURI if $baseURI =~ m/#$/ or $baseURI =~ m/\/$/;
554 0 0 0       $idURI = $baseURI . '#'. $idURI unless $baseURI =~ m/#$/ or $baseURI =~ m/\/$/;
555 0           $s = ODO::Node::Resource->new( $idURI);
556             }
557            
558            
559 0 0         throw XML::SAX::Exception::Parse(Message=> 'Duplicate rdf:ID specified: ' . $s->value())
560             if(exists($self->seen_id()->{ $s->value() }));
561              
562 0           $e->subject( $s );
563 0           $self->seen_id()->{ $s->value() } = 1;
564             }
565             elsif ( $e->attributes()->{ rdf->uri('nodeID') } ) {
566             # If there is an attribute a with a.URI == rdf:nodeID,
567             # then e.subject := bnodeid(identifier:=a.string-value).
568 0 0         throw XML::SAX::Exception::Parse(Message=> 'The value of rdf:nodeID must match the XML Name production, (as modified by XML Namespaces).')
569             if(!$e->attributes()->{ rdf->uri('nodeID') } =~ m/$XML::RegExp::NCName/);
570            
571 0           $s = ODO::Node::Blank->new();
572 0           $s->uri( $self->blank_node( $e->attributes()->{ rdf->uri('nodeID') } ) );
573            
574 0           $e->subject( $s );
575             }
576             elsif ( $e->attributes()->{ rdf->uri('about') } ) {
577             # If there is an attribute a with a.URI == rdf:about
578             # then e.subject := uri(identifier := resolve(e, a.string-value)).
579 0           my $aboutUri = $e->attributes()->{ rdf->uri('about') };
580 0 0         if ($aboutUri =~ m|.*://|) {
581 0           $s = ODO::Node::Resource->new( $aboutUri);
582             } else {
583 0           $aboutUri =~ s/^#*//;
584 0 0         if ($baseURI) {
585 0           $baseURI =~ s/#*$//g;
586 0 0         $baseURI .= '#' unless $baseURI =~ m/\/$/;
587             }
588 0   0       $s = ODO::Node::Resource->new( ($baseURI || '') . $aboutUri);
589             }
590 0           $e->subject( $s );
591             }
592             elsif ( !$e->subject() ) {
593             # If e.subject is empty, then e.subject := bnodeid(identifier := generated-blank-node-id()).
594 0           $s = ODO::Node::Blank->new($self->blank_node());
595            
596 0           $e->subject($s);
597             }
598             else {
599             # Nothing to do here in the spec
600             }
601              
602 0           my $p;
603             my $o;
604            
605             # If e.URI != rdf:Description then the following statement is added to the graph:
606 0 0         if ( $e->uri() ne rdf->uri('Description') ) {
607 0           $p = ODO::Node::Resource->new(rdf->uri('type'));
608 0           $o = ODO::Node::Resource->new($e->uri());
609              
610 0           $self->add_statement($e->subject(), $p, $o);
611             }
612              
613             # If there is an attribute a in propertyAttr with a.URI == rdf:type
614             # then u:=uri(identifier:=resolve(a.string-value)) and the following tiple is added to the graph:
615 0 0         if ( $e->attributes()->{ rdf->uri('type') } ) {
616 0           $p = ODO::Node::Resource->new( rdf->uri('type') );
617 0           $o = ODO::Node::Resource->new( $e->attributes()->{ rdf->uri('type') } );
618 0           $self->add_statement($e->subject(), $p, $o);
619             }
620            
621             # For each attribute a matching propertyAttr (and not rdf:type),
622             # the Unicode string a.string-value SHOULD be in Normal Form C[NFC],
623             # o := literal(literal-value := a.string-value, literal-language := e.language)
624             # and the following statement is added to the graph:
625 0           foreach my $k ( keys(%{ $e->attributes() }) ) {
  0            
626 0 0         if ( !$self->is_reserved_uri( $k ) ) {
627            
628 0           $p = ODO::Node::Resource->new( $k );
629              
630 0           $o = ODO::Node::Literal->new();
631 0           $o->value( $e->attributes->{ $k } );
632 0           $o->language( $e->language() );
633 0           $self->add_statement($e->subject(), $p ,$o);
634             }
635             }
636              
637             # Handle the propertyEltList children events in document order.
638             #
639             # propertyEltList =
640             # propertyElt*
641             #
642 0           foreach my $propertyElement (@{ $e->children() }) {
  0            
643             # Propagate the baseURI that was selected to the children
644 0 0         if ($baseURI) {
645 0           $baseURI =~ s/#*$//i;
646 0           $baseURI .= '#';
647 0           $propertyElement->base_uri( $baseURI );
648             }
649              
650 0           $self->propertyElt($propertyElement);
651             }
652             }
653              
654              
655             # 7.2.14 Production propertyElt
656             #
657             # propertyElt =
658             # resourcePropertyElt |
659             # literalPropertyElt |
660             # parseTypeLiteralPropertyElt |
661             # parseTypeResourcePropertyElt |
662             # parseTypeCollectionPropertyElt |
663             # parseTypeOtherPropertyElt |
664             # emptyPropertyElt
665             #
666             sub propertyElt {
667 0     0     my ($self, $e) = @_;
668              
669             # If element e has e.URI = rdf:li then apply the list expansion rules on
670             # element e.parent in section 7.4 to give a new URI u and e.URI := u.
671 0 0         if ( $e->uri() eq rdf->uri('li') ) {
672 0   0       $e->parent()->{'liCounter'} ||= 1;
673 0           $e->uri( rdf->uri($e->parent()->{'liCounter'}) );
674 0           $e->parent()->{'liCounter'}++;
675             }
676            
677 0 0         throw XML::SAX::Exception::Parse(Message=> 'URI ' . $e->uri() . ' is forbidden in propertyElement names')
678             if($self->is_forbidden_property($e->uri()));
679            
680             # Throws an XML::SAX::Exception
681 0           $self->validate_PropertyElt_attributes($e->attributes());
682              
683             # TODO: Add warnings for things like rdf:foo
684 0 0 0       if (( scalar(@{ $e->children() }) == 1 ) && ( !exists($e->attributes()->{ rdf->uri('parseType') } )) ) {
  0 0 0        
  0 0          
    0          
685 0           $self->resourcePropertyElt($e);
686             }
687             elsif ( ( scalar(@{ $e->children() }) == 0 ) && defined($e->text()) ) {
688 0           $self->literalPropertyElt($e);
689             }
690             elsif ( my $ptype = $e->attributes()->{ rdf->uri('parseType') } ) {
691 0 0         if ( $ptype eq 'Resource' ) {
    0          
692 0           $self->parseTypeResourcePropertyElt($e);
693             }
694             elsif ( $ptype eq 'Collection' ) {
695 0           $self->parseTypeCollectionPropertyElt($e);
696             }
697             else {
698             # parseType="Literal" parseType="Other items that are not Collection or Resource"
699 0           $self->parseTypeLiteralPropertyElt($e);
700             }
701             }
702             elsif ( ! defined($e->text()) ) {
703 0           $self->emptyPropertyElt($e);
704             }
705             else {
706             # TODO: XML Exception?
707             }
708             }
709              
710             # 7.2.15 Production resourcePropertyElt
711             #
712             # resourcePropertyElt =
713             # element * - ( local:* | rdf:RDF | rdf:ID | rdf:about | rdf:parseType |
714             # rdf:resource | rdf:nodeID | rdf:datatype |
715             # rdf:Description | rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID |
716             # xml:* ) {
717             # idAttr?, xmllang?, xmlbase?, nodeElement
718             # }
719             #
720             sub resourcePropertyElt {
721 0     0     my ($self, $e) = @_;
722              
723 0           my $n = $e->children()->[0];
724            
725             # For element e, and the single contained nodeElement n, first n must be processed
726             # using production nodeElement:
727 0           $self->nodeElement($n);
728              
729 0           my $p = ODO::Node::Resource->new( $e->uri() );
730            
731             # Then the following statement is added to the graph:
732 0 0         $self->add_statement( $e->parent()->subject(), $p, $n->subject() )
733             if ( $e->parent() );
734              
735             # If the rdf:ID attribute a is given, the above statement is reified with:
736 0 0         if ( $e->attributes->{ rdf->uri('ID') } ) {
737 0   0       my $baseURI = ($e->base_uri() || $self->base_uri() || '');
738            
739 0           my $i = ODO::Node::Resource->new( $baseURI . '#' . $e->attributes()->{ rdf->uri('ID') } );
740            
741 0           $self->reify_statement($i, $e->parent()->subject(), $p, $n->subject());
742             }
743             }
744              
745              
746             # 7.2.16 Production literalPropertyElt
747             #
748             # literalPropertyElt =
749             # element * - ( local:* | rdf:RDF | rdf:ID | rdf:about | rdf:parseType |
750             # rdf:resource | rdf:nodeID | rdf:datatype |
751             # rdf:Description | rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID |
752             # xml:* ) {
753             # (idAttr | datatypeAttr )?, xmllang?, xmlbase?, text
754             # }
755             #
756             sub literalPropertyElt {
757 0     0     my ($self, $e) = @_;
758            
759             # These are used in the reificatio so becareful with $p and $o
760 0           my $p = ODO::Node::Resource->new();
761 0           $p->uri($e->uri());
762            
763 0           my $o = ODO::Node::Literal->new();
764 0           $o->value( $e->text() );
765 0           $o->language( $e->language() );
766 0           $o->datatype( $e->attributes()->{ rdf->uri('datatype') } );
767              
768             # For element e, and the text event t. The Unicode string t.string-value SHOULD be in Normal Form C[NFC].
769             # If the rdf:datatype attribute d is given then o := typed-literal(literal-value := t.string-value,
770             # literal-datatype := d.string-value) otherwise o := literal(literal-value := t.string-value,
771             # literal-language := e.language) and the following statement is added to the graph:
772 0 0         if(!$e->parent()){
773 0           throw XML::SAX::Exception::Parse(Message=> 'Missing parent element');
774             }
775            
776 0           $self->add_statement( $e->parent()->subject(), $p, $o);
777              
778             # If the rdf:ID attribute a is given, the above statement is reified with:
779 0 0         if ( $e->attributes()->{ rdf->uri('ID') } ) {
780 0   0       my $baseURI = ($e->base_uri() || $self->base_uri() || '');
781            
782 0           my $i = ODO::Node::Resource->new( $baseURI . '#' . $e->attributes()->{ rdf->uri('ID') } );
783            
784 0           $self->reify_statement($i, $e->parent()->subject(), $p, $o);
785             }
786             }
787              
788              
789             # 7.2.17 Production parseTypeLiteralPropertyElt
790             #
791             # parseTypeLiteralPropertyElt =
792             # element * - ( local:* | rdf:RDF | rdf:ID | rdf:about | rdf:parseType |
793             # rdf:resource | rdf:nodeID | rdf:datatype |
794             # rdf:Description | rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID |
795             # xml:* ) {
796             # idAttr?, parseLiteral, xmllang?, xmlbase?, literal
797             # }
798             #
799             sub parseTypeLiteralPropertyElt {
800 0     0     my ($self, $e) = @_;
801            
802             # These are used below in the reification so don't modify $p or $o unless necesssary!
803 0           my $p = ODO::Node::Resource->new( $e->uri() );
804            
805 0           my $o = ODO::Node::Literal->new();
806 0           $o->value( $e->xtext()->[1] );
807 0           $o->language( $e->language() );
808 0           $o->datatype( rdf->uri('XMLLiteral'));
809            
810             # Then o := typed-literal(literal-value := x,
811             # literal-datatype := http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral ) and the
812             # following statement is added to the graph:
813 0           $self->add_statement( $e->parent()->subject(), $p, $o );
814            
815             # If the rdf:ID attribute a is given, the above statement is reified with:
816 0 0         if ( $e->attributes()->{ rdf->uri('ID') } ) {
817 0   0       my $baseURI = ($e->base_uri() || $self->base_uri() || '');
818              
819 0           my $i = ODO::Node::Resource->new( $baseURI . '#' . $e->attributes()->{ rdf->uri('ID') } );
820              
821 0           $e->subject($i);
822            
823 0           $self->reify_statement($i, $e->parent()->subject(), $p, $o);
824             }
825             }
826              
827              
828             # 7.2.18 Production parseTypeResourcePropertyElt
829             #
830             # parseTypeResourcePropertyElt =
831             # element * - ( local:* | rdf:RDF | rdf:ID | rdf:about | rdf:parseType |
832             # rdf:resource | rdf:nodeID | rdf:datatype |
833             # rdf:Description | rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID |
834             # xml:* ) {
835             # idAttr?, parseResource, xmllang?, xmlbase?, propertyEltList
836             # }
837             #
838             sub parseTypeResourcePropertyElt {
839 0     0     my ($self, $e) = @_;
840            
841             # For element e with possibly empty element content c:
842 0           my $p = ODO::Node::Resource->new( $e->uri() );
843              
844 0           my $o = ODO::Node::Resource->new( $self->blank_node() );
845            
846             # Add the following statement to the graph:
847 0           $self->add_statement( $e->parent()->subject(), $p, $o);
848            
849             # If the rdf:ID attribute a is given, the statement above is reified with:
850 0 0         if ( $e->attributes()->{ rdf->uri('ID') } ) {
851 0   0       my $baseURI = ($e->base_uri() || $self->base_uri() || '');
852              
853 0           my $i = ODO::Node::Resource->new( $baseURI . '#' . $e->attributes()->{ rdf->uri('ID') } );
854              
855 0           $e->subject($i);
856            
857 0           $self->reify_statement($i, $e->parent()->subject(), $p, $o);
858             }
859              
860             # If the element content c is not empty, then use event n to create a new sequence of events as follows:
861 0           my $c = ODO::Parser::XML::Fragment->new(
862             {
863             namespace=> rdf->uri(),
864             prefix=> 'rdf',
865             name=> 'Description',
866             parent=> $e->parent(),
867             attributes=> $e->attributes(),
868             base_uri=> $e->base_uri()
869             }
870             );
871            
872 0           $c->subject($o);
873            
874 0           my $children = [];
875            
876 0           foreach (@{ $e->children() } ) {
  0            
877            
878 0           $_->parent($c);
879 0           push @{ $children }, $_;
  0            
880             }
881              
882 0           $c->children( $children );
883              
884             # Then process the resulting sequence using production nodeElement:
885 0           $self->nodeElement($c);
886             }
887              
888              
889             # 7.2.19 Production parseTypeCollectionPropertyElt
890             #
891             # parseTypeCollectionPropertyElt =
892             # element * - ( local:* | rdf:RDF | rdf:ID | rdf:about | rdf:parseType |
893             # rdf:resource | rdf:nodeID | rdf:datatype |
894             # rdf:Description | rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID |
895             # xml:* ) {
896             # idAttr?, xmllang?, xmlbase?, parseCollection, nodeElementList
897             # }
898             #
899             sub parseTypeCollectionPropertyElt {
900 0     0     my ($self, $e) = @_;
901            
902 0           my @s;
903              
904 0           foreach (@{ $e->children() }) {
  0            
905 0           $self->nodeElement($_);
906            
907             # Generate blank nodes for all of the children
908 0           my $b = ODO::Node::Blank->new( $self->blank_node() );
909            
910 0           push @s, $b;
911             }
912            
913 0           my $p = ODO::Node::Resource->new( $e->uri() );
914            
915 0           my $reifyObject;
916            
917             # If s is not empty, n is the first event identifier in s and the following statement
918             # is added to the graph:
919 0 0         if ( scalar(@s) > 0 ) {
920 0           $self->add_statement($e->parent()->subject(), $p, $s[0]);
921              
922             # Used in the reification for later
923 0           $reifyObject = $s[0];
924              
925 0           foreach my $n (@s) {
926 0           $self->add_statement($n, ${ODO::Parser::RDF_TYPE}, ${ODO::Parser::RDF_LIST});
927             }
928            
929             # For each event n in s and the corresponding element event f in l, the following statement
930             # is added to the graph:
931 0           for ( 0 .. scalar(@s) - 1 ) {
932 0           $self->add_statement($s[$_], ${ODO::Parser::RDF_FIRST}, $e->children()->[$_]->subject());
933             }
934            
935             # For each consecutive and overlapping pair of events (n, o) in s, the following statement
936             # is added to the graph:
937 0           for ( 0 .. ( scalar(@s) - 2 ) ) {
938 0           $self->add_statement($s[$_], ${ODO::Parser::RDF_REST}, $s[ $_ + 1 ] );
939             }
940              
941             # If s is not empty, n is the last event identifier in s, the following statement
942             # is added to the graph:
943 0           $self->add_statement($s[-1], ${ODO::Parser::RDF_REST}, ${ODO::Parser::RDF_NIL});
944             }
945             else {
946             # otherwise the following statement is added to the graph:
947 0           $self->add_statement( $e->parent()->subject(), $p, ${ODO::Parser::RDF_NIL});
948              
949             # Reification for later on
950 0           $reifyObject = ${ODO::Parser::RDF_NIL};
951             }
952            
953             # If the rdf:ID attribute a is given, either of the the above statements is reified with:
954 0 0         if(exists($e->attributes()->{ rdf->uri('ID') })) {
955 0   0       my $baseURI = ($e->base_uri() || $self->base_uri() || '');
956              
957 0           my $i = ODO::Node::Resource->new( $baseURI . '#' . $e->attributes()->{ rdf->uri('ID') } );
958            
959 0           $e->subject($i);
960            
961 0           $self->reify_statement($i, $e->parent()->subject(), $p, $reifyObject);
962             }
963             }
964              
965              
966             # 7.2.21 Production emptyPropertyElt
967             #
968             # emptyPropertyElt =
969             # element * - ( local:* | rdf:RDF | rdf:ID | rdf:about | rdf:parseType |
970             # rdf:resource | rdf:nodeID | rdf:datatype |
971             # rdf:Description | rdf:aboutEach | rdf:aboutEachPrefix | rdf:bagID |
972             # xml:* ) {
973             # idAttr?, (resourceAttr | nodeIdAttr)?, xmllang?, xmlbase?, propertyAttr*
974             # }
975             #
976             sub emptyPropertyElt {
977 0     0     my ($self, $e) = @_;
978              
979 0   0       my $baseURI = ($e->base_uri() || $self->base_uri());
980 0           my $resource;
981            
982             # This is used in all code paths and potentially 2 simultaneously
983 0           my $elementURI = ODO::Node::Resource->new( $e->uri() );
984            
985             # If there are no attributes or only the optional rdf:ID attribute i then
986             # o := literal(literal-value:="", literal-language := e.language) and
987             # the following statement is added to the graph:
988 0 0 0       if (exists($e->attributes()->{ rdf->uri('ID') }) && values( %{ $e->attributes() }) == 1 ) {
  0            
989 0 0         throw XML::SAX::Exception::Parse(Message=> 'The value of rdf:ID must match the XML Name production, (as modified by XML Namespaces).')
990             if(!$e->attributes()->{ rdf->uri('ID') } =~ m/$XML::RegExp::NCName/);
991              
992 0           $resource = ODO::Node::Literal->new();
993 0           $resource->value('');
994 0           $resource->language( $e->language() );
995              
996 0           $self->add_statement($e->parent()->subject(), $elementURI, $resource);
997             }
998             else {
999             # Otherwise:
1000             # * If rdf:resource attribute i is present,
1001             # then r := uri(identifier := resolve(e, i.string-value))
1002             # * If rdf:nodeID attribute i is present, then r := bnodeid(identifier := i.string-value)
1003             # * If neither, r := bnodeid(identifier := generated-blank-node-id())
1004 0 0         if ( $e->attributes()->{ rdf->uri('resource') } ) {
    0          
1005 0           $resource = ODO::Node::Resource->new();
1006            
1007 0           my $uri;
1008              
1009 0 0         $uri = ($baseURI) ?
1010             URI->new_abs($e->attributes()->{ rdf->uri('resource') }, $baseURI) :
1011             URI->new($e->attributes()->{ rdf->uri('resource') });
1012              
1013 0           $resource->uri( $uri->as_string() );
1014              
1015 0 0         throw XML::SAX::Exception::Parse(Message=> 'Error creating URI object')
1016             unless($resource->uri());
1017             }
1018             elsif ( $e->attributes()->{ rdf->uri('nodeID') } ) {
1019 0 0         throw XML::SAX::Exception::Parse(Message=> 'The value of rdf:nodeID must match the XML Name production, (as modified by XML Namespaces).')
1020             if(!$e->attributes->{ rdf->uri('nodeID') } =~ m/$XML::RegExp::NCName/);
1021            
1022 0           $resource = ODO::Node::Blank->new();
1023 0           $resource->uri($self->blank_node( $e->attributes()->{ rdf->uri('nodeID') } ));
1024             }
1025             else {
1026 0           $resource = ODO::Node::Blank->new();
1027 0           $resource->uri($self->blank_node());
1028             }
1029              
1030             # The following are done in any order:
1031             # For all propertyAttr attributes a (in any order)
1032 0           foreach my $attr (keys(%{ $e->attributes() })) {
  0            
1033             # Skip RDF reserved URIs
1034             next
1035 0 0         if($self->is_reserved_uri($attr));
1036              
1037             # If a.URI == rdf:type then u:=uri(identifier:=resolve(a.string-value))
1038             # and the following statement is added to the graph:
1039 0 0         if ( $attr eq rdf->uri('type') ) {
1040 0           my $uri;
1041            
1042 0 0         $uri = ($baseURI) ?
1043             URI->new_abs($e->attributes()->{ $attr }, $baseURI) :
1044             URI->new($e->attributes()->{ $attr });
1045              
1046 0           my $o = ODO::Node::Resource->new($uri->as_string());
1047            
1048 0 0         throw XML::SAX::Exception::Parse(Message=> 'Error creating URI object')
1049             unless($o->uri());
1050            
1051 0           $self->add_statement($resource, ${ODO::Parser::RDF_TYPE}, $o);
1052             }
1053             else {
1054             # Otherwise Unicode string a.string-value SHOULD be in Normal Form C[NFC],
1055             # o := literal(literal-value := a.string-value, literal-language := e.language)
1056             # and the following statement is added to the graph:
1057 0           my $p = ODO::Node::Resource->new($attr);
1058            
1059 0           my $o = ODO::Node::Literal->new();
1060 0           $o->value($e->attributes()->{ $attr });
1061 0           $o->language( $e->language() );
1062              
1063 0           $self->add_statement($resource, $p, $o);
1064             }
1065             }
1066              
1067             # Add the following statement to the graph:
1068 0           $self->add_statement($e->parent()->subject(), $elementURI, $resource);
1069             }
1070              
1071             # ... and then if i is given, the above statement is reified
1072             # with uri(identifier := resolve(e, concat("#", i.string-value)))
1073             # using the reification rules in section 7.3.
1074 0 0         if ( $e->attributes()->{ rdf->uri('ID') } ) {
1075 0   0       my $i = ODO::Node::Resource->new( ($baseURI || '') . '#' . $e->attributes()->{ rdf->uri('ID') } );
1076 0           $self->reify_statement($i, $e->parent()->subject(), $elementURI, $resource);
1077             }
1078             }
1079              
1080             1;
1081              
1082             __END__