File Coverage

blib/lib/XML/Atom/OWL.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 XML::Atom::OWL;
2              
3 1     1   20296 use 5.010;
  1         3  
  1         32  
4 1     1   464 use common::sense;
  0            
  0            
5              
6             use Carp 1.00;
7             use DateTime 0;
8             use Encode 0 qw(encode_utf8);
9             use HTTP::Link::Parser 0.100;
10             use LWP::UserAgent 0;
11             use MIME::Base64 0 qw(decode_base64);
12             use RDF::Trine 0.135;
13             use Scalar::Util 0 qw(blessed);
14             use URI 1.30;
15             use XML::LibXML 1.70 qw(:all);
16              
17             use constant AAIR_NS => 'http://xmlns.notu.be/aair#';
18             use constant ATOM_NS => 'http://www.w3.org/2005/Atom';
19             use constant AWOL_NS => 'http://bblfish.net/work/atom-owl/2006-06-06/#';
20             use constant AS_NS => 'http://activitystrea.ms/spec/1.0/';
21             use constant AX_NS => 'http://buzzword.org.uk/rdf/atomix#';
22             use constant FH_NS => 'http://purl.org/syndication/history/1.0';
23             use constant FOAF_NS => 'http://xmlns.com/foaf/0.1/';
24             use constant IANA_NS => 'http://www.iana.org/assignments/relation/';
25             use constant RDF_NS => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
26             use constant RDF_TYPE => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type';
27             use constant THR_NS => 'http://purl.org/syndication/thread/1.0';
28             use constant XSD_NS => 'http://www.w3.org/2001/XMLSchema#';
29              
30             our $VERSION = '0.104';
31              
32             sub new
33             {
34             my $class = shift;
35             my $content = shift;
36             my $baseuri = shift;
37             my $options = shift || undef;
38             my $store = shift || undef;
39             my $domtree;
40            
41             unless (defined $content)
42             {
43             my $ua = LWP::UserAgent->new;
44             $ua->agent(sprintf('%s/%s ', __PACKAGE__, $VERSION));
45             $ua->default_header("Accept" => "application/atom+xml, application/xml;q=0.1, text/xml;q=0.1");
46             my $response = $ua->get($baseuri);
47             croak "HTTP response not successful\n"
48             unless $response->is_success;
49             croak "Non-Atom HTTP response\n"
50             unless $response->content_type =~ m`^(text/xml)|(application/(atom\+xml|xml))$`;
51             $content = $response->decoded_content;
52             }
53              
54             if (blessed($content) and $content->isa('XML::LibXML::Document'))
55             {
56             ($domtree, $content) = ($content, $content->toString);
57             }
58             else
59             {
60             my $xml_parser = XML::LibXML->new;
61             $domtree = $xml_parser->parse_string($content);
62             }
63              
64             $store = RDF::Trine::Store::DBI->temporary_store
65             unless defined $store;
66              
67             my $self = bless {
68             'content' => $content,
69             'baseuri' => $baseuri,
70             'options' => $options,
71             'DOM' => $domtree,
72             'sub' => {},
73             'RESULTS' => RDF::Trine::Model->new($store),
74             }, $class;
75              
76             return $self;
77             }
78              
79             sub uri
80             {
81             my $this = shift;
82             my $param = shift || '';
83             my $opts = shift || {};
84            
85             if ((ref $opts) =~ /^XML::LibXML/)
86             {
87             my $x = {'element' => $opts};
88             $opts = $x;
89             }
90            
91             if ($param =~ /^([a-z][a-z0-9\+\.\-]*)\:/i)
92             {
93             # seems to be an absolute URI, so can safely return "as is".
94             return $param;
95             }
96             elsif ($opts->{'require-absolute'})
97             {
98             return undef;
99             }
100            
101             my $base = $this->{baseuri};
102             if ($opts->{'element'})
103             {
104             $base = $this->get_node_base($opts->{'element'});
105             }
106            
107             my $url = URI->new($param);
108             my $rv = $url->abs($base)->as_string;
109              
110             while ($rv =~ m!^(http://.*)(\.\./|\.)+(\.\.|\.)?$!i)
111             {
112             $rv = $1;
113             }
114            
115             return $rv;
116             }
117              
118             sub dom
119             {
120             my $this = shift;
121             return $this->{DOM};
122             }
123              
124              
125             sub graph
126             {
127             my $this = shift;
128             $this->consume;
129             return $this->{RESULTS};
130             }
131              
132             sub graphs
133             {
134             my $this = shift;
135             $this->consume;
136             return { $this->{'baseuri'} => $this->{RESULTS} };
137             }
138              
139             sub root_identifier
140             {
141             my $self = shift;
142             $self->consume;
143             if ($self->{'root_identifier'} =~ /^_:(.*)/)
144             {
145             return RDF::Trine::Node::Blank->new($1);
146             }
147             else
148             {
149             return RDF::Trine::Node::Resource->new($self->{'root_identifier'});
150             }
151             }
152              
153             sub set_callbacks
154             # Set callback functions for handling RDF triples.
155             {
156             my $this = shift;
157              
158             if ('HASH' eq ref $_[0])
159             {
160             $this->{'sub'} = $_[0];
161             }
162             elsif (defined $_[0])
163             {
164             die("What kind of callback hashref was that??\n");
165             }
166             else
167             {
168             $this->{'sub'} = undef;
169             }
170            
171             return $this;
172             }
173              
174             sub consume
175             {
176             my $self = shift;
177              
178             return $self if $self->{'comsumed'};
179              
180             my $root = $self->dom->documentElement;
181            
182             if ($root->namespaceURI eq ATOM_NS and $root->localname eq 'feed')
183             {
184             $self->{'root_identifier'} = $self->consume_feed($root);
185             }
186             elsif ($root->namespaceURI eq ATOM_NS and $root->localname eq 'entry')
187             {
188             $self->{'root_identifier'} = $self->consume_entry($root);
189             }
190            
191             $self->{'comsumed'}++;
192            
193             return $self;
194             }
195              
196             sub consume_feed
197             {
198             my $self = shift;
199             my $feed = shift;
200             my $skip_entries = shift || 0;
201            
202             # Feed
203             my $feed_identifier = $self->bnode($feed);
204             $self->rdf_triple($feed, $feed_identifier, RDF_TYPE, AWOL_NS.'Feed');
205              
206             # Common stuff
207             $self->consume_feed_or_entry($feed, $feed_identifier);
208              
209             # fh:archive and fh:complete
210             if ($feed->getChildrenByTagNameNS(FH_NS, 'archive'))
211             {
212             $self->rdf_triple($feed, $feed_identifier, RDF_TYPE, AX_NS.'ArchiveFeed');
213             }
214             my $complete = 0;
215             if ($feed->getChildrenByTagNameNS(FH_NS, 'complete'))
216             {
217             $complete = 1;
218             $self->rdf_triple($feed, $feed_identifier, RDF_TYPE, AX_NS.'CompleteFeed');
219             }
220            
221             my $last_listid;
222              
223             # entry
224             unless ($skip_entries)
225             {
226             my @elems = $feed->getChildrenByTagNameNS(ATOM_NS, 'entry');
227             foreach my $e (@elems)
228             {
229             my $entry_identifier = $self->consume_entry($e);
230             $self->rdf_triple($e, $feed_identifier, AWOL_NS.'entry', $entry_identifier);
231              
232             # If this feed is known to be complete, include an rdf:List
233             # to assist in open-world reasoning.
234             if ($complete)
235             {
236             my $listid = $self->bnode;
237             if (defined $last_listid)
238             {
239             $self->rdf_triple($e, $last_listid, RDF_NS.'rest', $listid);
240             }
241             else
242             {
243             $self->rdf_triple($e, $feed_identifier, AX_NS.'entry-list', $listid);
244             }
245             $self->rdf_triple($e, $listid, RDF_TYPE, RDF_NS.'List');
246             $self->rdf_triple($e, $listid, RDF_NS.'first', $entry_identifier);
247             $last_listid = $listid;
248             }
249             }
250             }
251             if ($complete)
252             {
253             if (defined $last_listid)
254             {
255             $self->rdf_triple($feed, $last_listid, RDF_NS.'rest', RDF_NS.'nil');
256             }
257             else
258             {
259             $self->rdf_triple($feed, $feed_identifier, AX_NS.'entry-list', RDF_NS.'nil');
260             }
261             }
262            
263             # icon and logo
264             foreach my $role (qw(icon logo))
265             {
266             my @elems = $feed->getChildrenByTagNameNS(ATOM_NS, $role);
267             foreach my $e (@elems)
268             {
269             my $img = $self->uri($e->textContent, $e);
270             $self->rdf_triple($e, $feed_identifier, AWOL_NS.$role, $img);
271             $self->rdf_triple($e, $img, RDF_TYPE, FOAF_NS.'Image');
272             }
273             }
274              
275             # generator
276             {
277             my @elems = $feed->getChildrenByTagNameNS(ATOM_NS, 'generator');
278             foreach my $e (@elems)
279             {
280             my $gen_identifier = $self->consume_generator($e);
281             $self->rdf_triple($e, $feed_identifier, AWOL_NS.'generator', $gen_identifier);
282             }
283             }
284            
285             # subtitle
286             {
287             my @elems = $feed->getChildrenByTagNameNS(ATOM_NS, 'subtitle');
288             foreach my $e (@elems)
289             {
290             my $content_identifier = $self->consume_textconstruct($e);
291             $self->rdf_triple($e, $feed_identifier, AWOL_NS.'subtitle', $content_identifier);
292             }
293             }
294              
295             return $feed_identifier;
296             }
297              
298             sub consume_entry
299             {
300             my $self = shift;
301             my $entry = shift;
302            
303             # Entry
304             my $entry_identifier = $self->bnode($entry);
305             $self->rdf_triple($entry, $entry_identifier, RDF_TYPE, AWOL_NS.'Entry');
306              
307             # Common stuff
308             $self->consume_feed_or_entry($entry, $entry_identifier);
309            
310             # published
311             {
312             my @elems = $entry->getChildrenByTagNameNS(ATOM_NS, 'published');
313             foreach my $e (@elems)
314             {
315             $self->rdf_triple_literal($e, $entry_identifier, AWOL_NS.'published', $e->textContent, XSD_NS.'dateTime');
316             }
317             }
318              
319             # summary
320             {
321             my @elems = $entry->getChildrenByTagNameNS(ATOM_NS, 'content');
322             foreach my $e (@elems)
323             {
324             my $content_identifier = $self->consume_content($e);
325             $self->rdf_triple($e, $entry_identifier, AWOL_NS.'content', $content_identifier);
326             }
327             }
328            
329             # source
330             {
331             my @elems = $entry->getChildrenByTagNameNS(ATOM_NS, 'source');
332             foreach my $e (@elems)
333             {
334             my $feed_identifier = $self->consume_feed($e, 1);
335             $self->rdf_triple($e, $entry_identifier, AWOL_NS.'source', $feed_identifier);
336             }
337             }
338              
339             # summary
340             {
341             my @elems = $entry->getChildrenByTagNameNS(ATOM_NS, 'summary');
342             foreach my $e (@elems)
343             {
344             my $content_identifier = $self->consume_textconstruct($e);
345             $self->rdf_triple($e, $entry_identifier, AWOL_NS.'summary', $content_identifier);
346             }
347             }
348              
349             # thr:in-reply-to
350             {
351             my @elems = $entry->getChildrenByTagNameNS(THR_NS, 'in-reply-to');
352             foreach my $e (@elems)
353             {
354             my $irt_id = $self->consume_inreplyto($e);
355             $self->rdf_triple($e, $entry_identifier, AX_NS.'in-reply-to', $irt_id);
356             }
357             }
358              
359             # thr:total
360             {
361             my @elems = $entry->getChildrenByTagNameNS(THR_NS, 'total');
362             foreach my $e (@elems)
363             {
364             my $total = $e->textContent;
365             $self->rdf_triple_literal($e, $entry_identifier, AX_NS.'total', $total, XSD_NS.'integer');
366             }
367             }
368            
369             return $entry_identifier;
370             }
371              
372             sub consume_feed_or_entry
373             {
374             my $self = shift;
375             my $fore = shift;
376             my $id = shift;
377            
378             my @elems = $fore->getChildrenByTagNameNS(ATOM_NS, 'id');
379             foreach my $e (@elems)
380             {
381             my $_id = $self->uri($e->textContent, $e);
382             $self->rdf_triple_literal($e, $id, AWOL_NS.'id', $_id, XSD_NS.'anyURI');
383             }
384            
385             my $is_as = 0;
386            
387             # activitystreams:object, activitystreams:target
388             foreach my $role (qw(object target))
389             {
390             my @elems = $fore->getChildrenByTagNameNS(AS_NS, $role);
391             foreach my $e (@elems)
392             {
393             $is_as++;
394             my $obj_id = $self->consume_entry($e, $id);
395             $self->rdf_triple($e, $id, AAIR_NS.'activity'.ucfirst($role), $obj_id);
396             }
397             }
398              
399             # activitystreams:verb
400             {
401             my @elems = $fore->getChildrenByTagNameNS(AS_NS, 'verb');
402             foreach my $e (@elems)
403             {
404             $is_as++;
405             my $url = $e->textContent;
406             $url =~ s/(^\s*)|(\s*$)//g;
407             $self->rdf_triple($e, $id, AAIR_NS.'activityVerb', URI->new($url)->abs('http://activitystrea.ms/schema/1.0/')->as_string);
408             }
409             if ($is_as && !@elems)
410             {
411             $self->rdf_triple($fore, $id, AAIR_NS.'activityVerb', "http://activitystrea.ms/schema/1.0/post");
412             }
413             }
414              
415             # activitystreams:object-type
416             {
417             my @elems = $fore->getChildrenByTagNameNS(AS_NS, 'object-type');
418             foreach my $e (@elems)
419             {
420             my $url = $e->textContent;
421             $url =~ s/(^\s*)|(\s*$)//g;
422             $self->rdf_triple($e, $id, RDF_NS.'type', URI->new($url)->abs('http://activitystrea.ms/schema/1.0/')->as_string);
423             }
424             }
425              
426             # authors and contributors
427             foreach my $role (qw(author contributor))
428             {
429             my @elems = $fore->getChildrenByTagNameNS(ATOM_NS, $role);
430             foreach my $e (@elems)
431             {
432             my $person_identifier = $self->consume_person($e);
433             $self->rdf_triple($e, $id, AWOL_NS.$role, $person_identifier);
434            
435             if ($role eq 'author' and $is_as)
436             {
437             $self->rdf_triple($e, $person_identifier, RDF_NS.'type', AAIR_NS.'Actor');
438             $self->rdf_triple($e, $id, AAIR_NS.'activityActor', $person_identifier);
439             }
440             }
441             }
442              
443             # updated
444             {
445             my @elems = $fore->getChildrenByTagNameNS(ATOM_NS, 'updated');
446             foreach my $e (@elems)
447             {
448             $self->rdf_triple_literal($e, $id, AWOL_NS.'updated', $e->textContent, XSD_NS.'dateTime');
449             }
450             }
451              
452             # link
453             {
454             my @elems = $fore->getChildrenByTagNameNS(ATOM_NS, 'link');
455             foreach my $e (@elems)
456             {
457             my $link_identifier = $self->consume_link($e, $id);
458             $self->rdf_triple($e, $id, AWOL_NS.'link', $link_identifier);
459             }
460             }
461              
462             # title and rights
463             foreach my $role (qw(title rights))
464             {
465             my @elems = $fore->getChildrenByTagNameNS(ATOM_NS, $role);
466             foreach my $e (@elems)
467             {
468             my $content_identifier = $self->consume_textconstruct($e);
469             $self->rdf_triple($e, $id, AWOL_NS.$role, $content_identifier);
470             }
471             }
472            
473             # category
474             {
475             my @elems = $fore->getChildrenByTagNameNS(ATOM_NS, 'category');
476             foreach my $e (@elems)
477             {
478             my $cat_identifier = $self->consume_category($e, $id);
479             $self->rdf_triple($e, $id, AWOL_NS.'category', $cat_identifier);
480             }
481             }
482              
483             # Unknown Extensions!
484             {
485             my @elems = $fore->getChildrenByTagName('*');
486             foreach my $e (@elems)
487             {
488             next if $e->namespaceURI eq ATOM_NS;
489             next if $e->namespaceURI eq AS_NS;
490             next if $e->namespaceURI eq FH_NS;
491             next if $e->namespaceURI eq THR_NS;
492            
493             my $xml = $self->xmlify_inclusive($e);
494             $self->rdf_triple_literal($e, $id, AX_NS.'extension-element', $xml, RDF_NS.'XMLLiteral');
495             }
496             }
497              
498             return $id;
499             }
500              
501             sub consume_textconstruct
502             {
503             my $self = shift;
504             my $elem = shift;
505            
506             my $id = $self->bnode($elem);
507             $self->rdf_triple($elem, $id, RDF_TYPE, AWOL_NS.'TextContent');
508            
509             my $lang = $self->get_node_lang($elem);
510            
511             if (lc $elem->getAttribute('type') eq 'xhtml')
512             {
513             my $cnt = $self->xmlify($elem, $lang);
514             $self->rdf_triple_literal($elem, $id, AWOL_NS.'xhtml', $cnt, RDF_NS.'XMLLiteral');
515             }
516              
517             elsif (lc $elem->getAttribute('type') eq 'html')
518             {
519             my $cnt = $elem->textContent;
520             $self->rdf_triple_literal($elem, $id, AWOL_NS.'html', $cnt, undef, $lang);
521             }
522              
523             else
524             {
525             my $cnt = $elem->textContent;
526             $self->rdf_triple_literal($elem, $id, AWOL_NS.'text', $cnt, undef, $lang);
527             }
528            
529             return $id;
530             }
531              
532             sub consume_content
533             {
534             my $self = shift;
535             my $elem = shift;
536            
537             my $id = $self->bnode($elem);
538             $self->rdf_triple($elem, $id, RDF_TYPE, AWOL_NS.'Content');
539            
540             my $lang = $self->get_node_lang($elem);
541             my $base = $self->get_node_base($elem);
542            
543             if ($elem->hasAttribute('src'))
544             {
545             my $link = $self->uri($elem->getAttribute('src'), $elem);
546             $self->rdf_triple($elem, $id, AWOL_NS.'src', $link);
547            
548             if ($self->{'options'}->{'no_fetch_content_src'})
549             {
550             $self->rdf_triple_literal($elem, $id, AWOL_NS.'type', $elem->getAttribute('type'))
551             if $elem->hasAttribute('type');
552             }
553             else
554             {
555             my $ua = LWP::UserAgent->new;
556             $ua->agent(sprintf('%s/%s ', __PACKAGE__, $VERSION));
557             if ($elem->hasAttribute('type'))
558             {
559             $ua->default_header("Accept" => $elem->getAttribute('type').", */*;q=0.1");
560             }
561             else
562             {
563             $ua->default_header("Accept" => "application/xhtml+xml, text/html, text/plain, */*;q=0.1");
564             }
565             my $response = $ua->get($self->uri($elem->getAttribute('src'), $elem));
566             if ($response->is_success)
567             {
568             $self->rdf_triple_literal($elem, $id, AWOL_NS.'body', $response->decoded_content);
569            
570             if ($response->content_type)
571             { $self->rdf_triple_literal($elem, $id, AWOL_NS.'type', $response->content_type); }
572             elsif ($elem->hasAttribute('type'))
573             { $self->rdf_triple_literal($elem, $id, AWOL_NS.'type', $elem->getAttribute('type')); }
574              
575             if ($response->content_language =~ /^\s*([a-z]{2,3})\b/i)
576             { $self->rdf_triple_literal($elem, $id, AWOL_NS.'lang', lc $1, XSD_NS.'language'); }
577              
578             if ($response->base)
579             { $self->rdf_triple($elem, $id, AWOL_NS.'base', $response->base); }
580             }
581             else
582             {
583             $self->rdf_triple_literal($elem, $id, AWOL_NS.'type', $elem->getAttribute('type'))
584             if $elem->hasAttribute('type');
585             }
586             }
587             }
588            
589             elsif (lc $elem->getAttribute('type') eq 'text' or !$elem->hasAttribute('type'))
590             {
591             my $cnt = $elem->textContent;
592             $self->rdf_triple_literal($elem, $id, AWOL_NS.'body', $cnt, undef, $lang);
593             $self->rdf_triple_literal($elem, $id, AWOL_NS.'type', 'text/plain');
594             $self->rdf_triple_literal($elem, $id, AWOL_NS.'lang', $lang, XSD_NS.'language') if $lang;
595             $self->rdf_triple($elem, $id, AWOL_NS.'base', $base) if $base;
596             }
597            
598             elsif (lc $elem->getAttribute('type') eq 'xhtml')
599             {
600             my $cnt = $self->xmlify($elem, $lang);
601             $self->rdf_triple_literal($elem, $id, AWOL_NS.'body', $cnt, RDF_NS.'XMLLiteral');
602             $self->rdf_triple_literal($elem, $id, AWOL_NS.'type', 'application/xhtml+xml');
603             $self->rdf_triple_literal($elem, $id, AWOL_NS.'lang', $lang, XSD_NS.'language') if $lang;
604             $self->rdf_triple($elem, $id, AWOL_NS.'base', $base) if $base;
605             }
606              
607             elsif (lc $elem->getAttribute('type') eq 'html')
608             {
609             my $cnt = $elem->textContent;
610             $self->rdf_triple_literal($elem, $id, AWOL_NS.'body', $cnt, undef, $lang);
611             $self->rdf_triple_literal($elem, $id, AWOL_NS.'type', 'text/html');
612             $self->rdf_triple_literal($elem, $id, AWOL_NS.'lang', $lang, XSD_NS.'language') if $lang;
613             $self->rdf_triple($elem, $id, AWOL_NS.'base', $base) if $base;
614             }
615              
616             elsif ($elem->getAttribute('type') =~ m'([\+\/]xml)$'i)
617             {
618             my $cnt = $self->xmlify($elem, $lang);
619             $self->rdf_triple_literal($elem, $id, AWOL_NS.'body', $cnt, RDF_NS.'XMLLiteral');
620             $self->rdf_triple_literal($elem, $id, AWOL_NS.'type', $elem->getAttribute('type'));
621             $self->rdf_triple_literal($elem, $id, AWOL_NS.'lang', $lang, XSD_NS.'language') if $lang;
622             $self->rdf_triple($elem, $id, AWOL_NS.'base', $base) if $base;
623             }
624            
625             elsif ($elem->getAttribute('type') =~ m'^text\/'i)
626             {
627             my $cnt = $elem->textContent;
628             $self->rdf_triple_literal($elem, $id, AWOL_NS.'body', $cnt, undef, $lang);
629             $self->rdf_triple_literal($elem, $id, AWOL_NS.'type', $elem->getAttribute('type'));
630             $self->rdf_triple_literal($elem, $id, AWOL_NS.'lang', $lang, XSD_NS.'language') if $lang;
631             $self->rdf_triple($elem, $id, AWOL_NS.'base', $base) if $base;
632             }
633            
634             elsif ($elem->hasAttribute('type'))
635             {
636             my $cnt = $elem->textContent;
637             $self->rdf_triple_literal($elem, $id, AWOL_NS.'body', decode_base64($cnt));
638             $self->rdf_triple_literal($elem, $id, AWOL_NS.'type', $elem->getAttribute('type'));
639             $self->rdf_triple_literal($elem, $id, AWOL_NS.'lang', $lang, XSD_NS.'language') if $lang;
640             $self->rdf_triple($elem, $id, AWOL_NS.'base', $base) if $base;
641             }
642              
643             return $id;
644             }
645              
646             sub consume_person
647             {
648             my $self = shift;
649             my $person = shift;
650            
651             # Person
652             my $person_identifier = $self->bnode($person);
653             $self->rdf_triple($person, $person_identifier, RDF_TYPE, AWOL_NS.'Person');
654            
655             # name
656             {
657             my @elems = $person->getChildrenByTagNameNS(ATOM_NS, 'name');
658             foreach my $e (@elems)
659             {
660             $self->rdf_triple_literal($e, $person_identifier, AWOL_NS.'name', $e->textContent);
661             }
662             }
663              
664             # uri
665             {
666             my @elems = $person->getChildrenByTagNameNS(ATOM_NS, 'uri');
667             foreach my $e (@elems)
668             {
669             my $link = $self->uri($e->textContent, $e);
670             $self->rdf_triple($e, $person_identifier, AWOL_NS.'uri', $link);
671             }
672             }
673              
674             # email
675             {
676             my @elems = $person->getChildrenByTagNameNS(ATOM_NS, 'email');
677             foreach my $e (@elems)
678             {
679             $self->rdf_triple($e, $person_identifier, AWOL_NS.'email', 'mailto:'.$e->textContent);
680             }
681             }
682              
683             return $person_identifier;
684             }
685              
686             sub consume_generator
687             {
688             my $self = shift;
689             my $elem = shift;
690            
691             # Person
692             my $identifier = $self->bnode($elem);
693             $self->rdf_triple($elem, $identifier, RDF_TYPE, AWOL_NS.'Generator');
694            
695             # name
696             {
697             my $lang = $self->get_node_lang($elem);
698             $self->rdf_triple_literal($elem, $identifier, AWOL_NS.'name', $elem->textContent, undef, $lang);
699             }
700              
701             # uri
702             if ($elem->hasAttribute('uri'))
703             {
704             my $link = $self->uri($elem->getAttribute('uri'), $elem);
705             $self->rdf_triple($elem, $identifier, AWOL_NS.'uri', $link);
706             }
707              
708             # version
709             if ($elem->hasAttribute('uri'))
710             {
711             $self->rdf_triple($elem, $identifier, AWOL_NS.'version', $elem->getAttribute('version'));
712             }
713              
714             return $identifier;
715             }
716              
717             sub consume_inreplyto
718             {
719             my $self = shift;
720             my $link = shift;
721            
722             my $id = $self->bnode($link);
723             $self->rdf_triple($link, $id, RDF_TYPE, AWOL_NS.'Entry');
724            
725             if ($link->hasAttribute('ref'))
726             {
727             $self->rdf_triple_literal($link, $id, AWOL_NS.'id', $link->getAttribute('ref'), XSD_NS.'anyURI');
728             }
729            
730             if ($link->hasAttribute('href'))
731             {
732             my $href = $self->uri($link->getAttribute('href'), $link);
733             $self->rdf_triple($link, $id, IANA_NS.'self', $href);
734             }
735            
736             # TODO: "type".
737            
738             if ($link->hasAttribute('source'))
739             {
740             my $fid = $self->bnode;
741             my $href = $self->uri($link->getAttribute('href'), $link);
742             $self->rdf_triple($link, $id, AWOL_NS.'source', $fid);
743             $self->rdf_triple($link, $fid, RDF_TYPE, AWOL_NS.'Feed');
744             $self->rdf_triple($link, $fid, IANA_NS.'self', $href);
745             }
746            
747             return $id;
748             }
749              
750             sub consume_link
751             {
752             my $self = shift;
753             my $link = shift;
754             my $subject = shift || undef;
755            
756             # Link
757             my $link_identifier = $self->bnode($link);
758             $self->rdf_triple($link, $link_identifier, RDF_TYPE, AWOL_NS.'Link');
759              
760             # Destination
761             my $destination_identifier = $self->bnode;
762             $self->rdf_triple($link, $destination_identifier, RDF_TYPE, AWOL_NS.'Content');
763             $self->rdf_triple($link, $link_identifier, AWOL_NS.'to', $destination_identifier);
764              
765             # rel
766             {
767             my $rel = HTTP::Link::Parser::relationship_uri(
768             $link->hasAttribute('rel') ? $link->getAttribute('rel') : 'alternate');
769             $self->rdf_triple($link, $link_identifier, AWOL_NS.'rel', $rel);
770            
771             if ($link->hasAttribute('href') and defined $subject)
772             {
773             my $href = $self->uri($link->getAttribute('href'), $link);
774             $self->rdf_triple($link, $subject, $rel, $href);
775             }
776             }
777            
778             # href
779             if ($link->hasAttribute('href'))
780             {
781             my $href = $self->uri($link->getAttribute('href'), $link);
782             $self->rdf_triple($link, $destination_identifier, AWOL_NS.'src', $href);
783             }
784              
785             # hreflang
786             if ($link->hasAttribute('hreflang'))
787             {
788             my $hreflang = $link->getAttribute('hreflang');
789             $self->rdf_triple_literal($link, $destination_identifier, AWOL_NS.'lang', $hreflang);
790             }
791              
792             # length
793             if ($link->hasAttribute('length'))
794             {
795             my $length = $link->getAttribute('length');
796             $self->rdf_triple_literal($link, $destination_identifier, AWOL_NS.'length', $length, XSD_NS.'integer');
797             }
798              
799             # type
800             if ($link->hasAttribute('type'))
801             {
802             my $type = $link->getAttribute('type');
803             $self->rdf_triple_literal($link, $destination_identifier, AWOL_NS.'type', $type);
804             }
805              
806             # title: TODO - check this uses AWOL properly.
807             if ($link->hasAttribute('title'))
808             {
809             my $lang = $self->get_node_lang($link);
810             my $title = $link->getAttribute('title');
811             $self->rdf_triple_literal($link, $link_identifier, AWOL_NS.'title', $title, undef, $lang);
812             }
813              
814             # thr:count
815             if ($link->hasAttributeNS(THR_NS, 'count'))
816             {
817             my $count = $link->getAttributeNS(THR_NS, 'count');
818             $self->rdf_triple_literal($link, $link_identifier, AX_NS.'count', $count, XSD_NS.'integer');
819             }
820              
821             # thr:updated
822             if ($link->hasAttributeNS(THR_NS, 'updated'))
823             {
824             my $u = $link->getAttributeNS(THR_NS, 'updated');
825             $self->rdf_triple_literal($link, $link_identifier, AX_NS.'updated', $u, XSD_NS.'dateTime');
826             }
827              
828             return $link_identifier;
829             }
830              
831             sub consume_category
832             {
833             my $self = shift;
834             my $elem = shift;
835            
836             # Link
837             my $id = $self->bnode($elem);
838             $self->rdf_triple($elem, $id, RDF_TYPE, AWOL_NS.'Category');
839              
840             # term
841             if ($elem->hasAttribute('term'))
842             {
843             $self->rdf_triple_literal($elem, $id, AWOL_NS.'term', $elem->getAttribute('term'));
844             }
845            
846             # label
847             if ($elem->hasAttribute('label'))
848             {
849             my $lang = $self->get_node_lang($elem);
850             $self->rdf_triple_literal($elem, $id, AWOL_NS.'label', $elem->getAttribute('label'), undef, $lang);
851             }
852              
853             # scheme
854             if ($elem->hasAttribute('scheme'))
855             {
856             my $link = $self->uri($elem->getAttribute('scheme'), $elem);
857             $self->rdf_triple($elem, $id, AWOL_NS.'scheme', $link);
858             }
859              
860             return $id;
861             }
862              
863             sub xmlify
864             # Function only used internally.
865             {
866             my $this = shift;
867             my $dom = shift;
868             my $lang = shift;
869             my $rv;
870              
871             $lang = $this->get_node_lang($dom)
872             unless $lang;
873              
874             foreach my $kid ($dom->childNodes)
875             {
876             my $fakelang = 0;
877             if (($kid->nodeType == XML_ELEMENT_NODE) && defined $lang)
878             {
879             unless ($kid->hasAttributeNS(XML_XML_NS, 'lang'))
880             {
881             $kid->setAttributeNS(XML_XML_NS, 'lang', $lang);
882             $fakelang++;
883             }
884             }
885            
886             $rv .= $kid->toStringEC14N(1);
887            
888             if ($fakelang)
889             {
890             $kid->removeAttributeNS(XML_XML_NS, 'lang');
891             }
892             }
893            
894             return $rv;
895             }
896              
897              
898             sub xmlify_inclusive
899             # Function only used internally.
900             {
901             my $this = shift;
902             my $dom = shift;
903             my $lang = shift;
904             my $rv;
905            
906             $lang = $this->get_node_lang($dom)
907             unless $lang;
908            
909             my $fakelang = 0;
910             if (($dom->nodeType == XML_ELEMENT_NODE) && defined $lang)
911             {
912             unless ($dom->hasAttributeNS(XML_XML_NS, 'lang'))
913             {
914             $dom->setAttributeNS(XML_XML_NS, 'lang', $lang);
915             $fakelang++;
916             }
917             }
918            
919             $rv = $dom->toStringEC14N(1);
920            
921             if ($fakelang)
922             {
923             $dom->removeAttributeNS(XML_XML_NS, 'lang');
924             }
925            
926             return $rv;
927             }
928              
929             sub get_node_lang
930             {
931             my $this = shift;
932             my $node = shift;
933              
934             if ($node->hasAttributeNS(XML_XML_NS, 'lang'))
935             {
936             return valid_lang($node->getAttributeNS(XML_XML_NS, 'lang')) ?
937             $node->getAttributeNS(XML_XML_NS, 'lang'):
938             undef;
939             }
940              
941             if ($node != $this->{'DOM'}->documentElement
942             && defined $node->parentNode
943             && $node->parentNode->nodeType == XML_ELEMENT_NODE)
944             {
945             return $this->get_node_lang($node->parentNode);
946             }
947            
948             return undef;
949             }
950              
951             sub get_node_base
952             {
953             my $this = shift;
954             my $node = shift;
955              
956             my @base;
957              
958             while (1)
959             {
960             push @base, $node->getAttributeNS(XML_XML_NS, 'base')
961             if $node->hasAttributeNS(XML_XML_NS, 'base');
962            
963             $node = $node->parentNode;
964             last unless blessed($node) && $node->isa('XML::LibXML::Element');
965             }
966            
967             my $rv = URI->new($this->uri); # document URI.
968            
969             while (my $b = pop @base)
970             {
971             $rv = URI->new($b)->abs($rv);
972             }
973            
974             return $rv->as_string;
975             }
976              
977             sub rdf_triple
978             # Function only used internally.
979             {
980             my $this = shift;
981              
982             my $suppress_triple = 0;
983             $suppress_triple = $this->{'sub'}->{'pretriple_resource'}($this, @_)
984             if defined $this->{'sub'}->{'pretriple_resource'};
985             return if $suppress_triple;
986            
987             my $element = shift; # A reference to the XML::LibXML element being parsed
988             my $subject = shift; # Subject URI or bnode
989             my $predicate = shift; # Predicate URI
990             my $object = shift; # Resource URI or bnode
991             my $graph = shift; # Graph URI or bnode (if named graphs feature is enabled)
992              
993             # First make sure the object node type is ok.
994             my $to;
995             if ($object =~ m/^_:(.*)/)
996             {
997             $to = RDF::Trine::Node::Blank->new($1);
998             }
999             else
1000             {
1001             $to = RDF::Trine::Node::Resource->new($object);
1002             }
1003              
1004             # Run the common function
1005             return $this->rdf_triple_common($element, $subject, $predicate, $to, $graph);
1006             }
1007              
1008             sub rdf_triple_literal
1009             # Function only used internally.
1010             {
1011             my $this = shift;
1012              
1013             my $suppress_triple = 0;
1014             $suppress_triple = $this->{'sub'}->{'pretriple_literal'}($this, @_)
1015             if defined $this->{'sub'}->{'pretriple_literal'};
1016             return if $suppress_triple;
1017              
1018             my $element = shift; # A reference to the XML::LibXML element being parsed
1019             my $subject = shift; # Subject URI or bnode
1020             my $predicate = shift; # Predicate URI
1021             my $object = shift; # Resource Literal
1022             my $datatype = shift; # Datatype URI (possibly undef or '')
1023             my $language = shift; # Language (possibly undef or '')
1024             my $graph = shift; # Graph URI or bnode (if named graphs feature is enabled)
1025              
1026             # Now we know there's a literal
1027             my $to;
1028            
1029             # Work around bad Unicode handling in RDF::Trine.
1030             $object = encode_utf8($object);
1031              
1032             if (defined $datatype)
1033             {
1034             if ($datatype eq 'http://www.w3.org/1999/02/22-rdf-syntax-ns#XMLLiteral')
1035             {
1036             if ($this->{'options'}->{'use_rtnlx'})
1037             {
1038             eval
1039             {
1040             require RDF::Trine::Node::Literal::XML;
1041             $to = RDF::Trine::Node::Literal::XML->new($element->childNodes);
1042             };
1043             }
1044            
1045             if ( $@ || !defined $to)
1046             {
1047             my $orig = $RDF::Trine::Node::Literal::USE_XMLLITERALS;
1048             $RDF::Trine::Node::Literal::USE_XMLLITERALS = 0;
1049             $to = RDF::Trine::Node::Literal->new($object, undef, $datatype);
1050             $RDF::Trine::Node::Literal::USE_XMLLITERALS = $orig;
1051             }
1052             }
1053             else
1054             {
1055             $to = RDF::Trine::Node::Literal->new($object, undef, $datatype);
1056             }
1057             }
1058             else
1059             {
1060             $to = RDF::Trine::Node::Literal->new($object, $language, undef);
1061             }
1062              
1063             # Run the common function
1064             $this->rdf_triple_common($element, $subject, $predicate, $to, $graph);
1065             }
1066              
1067             sub rdf_triple_common
1068             # Function only used internally.
1069             {
1070             my $this = shift; # A reference to the Parser object
1071             my $element = shift; # A reference to the XML::LibXML element being parsed
1072             my $subject = shift; # Subject URI or bnode
1073             my $predicate = shift; # Predicate URI
1074             my $to = shift; # RDF::Trine::Node Resource URI or bnode
1075             my $graph = shift; # Graph URI or bnode (if named graphs feature is enabled)
1076              
1077             # First, make sure subject and predicates are the right kind of nodes
1078             my $tp = RDF::Trine::Node::Resource->new($predicate);
1079             my $ts;
1080             if ($subject =~ m/^_:(.*)/)
1081             {
1082             $ts = RDF::Trine::Node::Blank->new($1);
1083             }
1084             else
1085             {
1086             $ts = RDF::Trine::Node::Resource->new($subject);
1087             }
1088              
1089             my $statement;
1090              
1091             # If we are configured for it, and graph name can be found, add it.
1092             if (ref($this->{'options'}->{'named_graphs'}) && ($graph))
1093             {
1094             $this->{Graphs}->{$graph}++;
1095            
1096             my $tg;
1097             if ($graph =~ m/^_:(.*)/)
1098             {
1099             $tg = RDF::Trine::Node::Blank->new($1);
1100             }
1101             else
1102             {
1103             $tg = RDF::Trine::Node::Resource->new($graph);
1104             }
1105              
1106             $statement = RDF::Trine::Statement::Quad->new($ts, $tp, $to, $tg);
1107             }
1108             else
1109             {
1110             $statement = RDF::Trine::Statement->new($ts, $tp, $to);
1111             }
1112              
1113             my $suppress_triple = 0;
1114             $suppress_triple = $this->{'sub'}->{'ontriple'}($this, $element, $statement)
1115             if ($this->{'sub'}->{'ontriple'});
1116             return if $suppress_triple;
1117              
1118             $this->{RESULTS}->add_statement($statement);
1119             }
1120              
1121             sub bnode
1122             # Function only used internally.
1123             {
1124             my $this = shift;
1125             my $element = shift;
1126            
1127             if (defined $this->{'bnode_generator'})
1128             {
1129             return $this->{'bnode_generator'}->bnode($element);
1130             }
1131            
1132             return sprintf('_:AwolAutoNode%03d', $this->{bnodes}++);
1133             }
1134              
1135             sub valid_lang
1136             {
1137             my $value_to_test = shift;
1138              
1139             return 1 if (defined $value_to_test) && ($value_to_test eq '');
1140             return 0 unless defined $value_to_test;
1141            
1142             # Regex for recognizing RFC 4646 well-formed tags
1143             # http://www.rfc-editor.org/rfc/rfc4646.txt
1144             # http://tools.ietf.org/html/draft-ietf-ltru-4646bis-21
1145              
1146             # The structure requires no forward references, so it reverses the order.
1147             # It uses Java/Perl syntax instead of the old ABNF
1148             # The uppercase comments are fragments copied from RFC 4646
1149              
1150             # Note: the tool requires that any real "=" or "#" or ";" in the regex be escaped.
1151              
1152             my $alpha = '[a-z]'; # ALPHA
1153             my $digit = '[0-9]'; # DIGIT
1154             my $alphanum = '[a-z0-9]'; # ALPHA / DIGIT
1155             my $x = 'x'; # private use singleton
1156             my $singleton = '[a-wyz]'; # other singleton
1157             my $s = '[_-]'; # separator -- lenient parsers will use [_-] -- strict will use [-]
1158              
1159             # Now do the components. The structure is slightly different to allow for capturing the right components.
1160             # The notation (?:....) is a non-capturing version of (...): so the "?:" can be deleted if someone doesn't care about capturing.
1161              
1162             my $language = '([a-z]{2,8}) | ([a-z]{2,3} $s [a-z]{3})';
1163            
1164             # ABNF (2*3ALPHA) / 4ALPHA / 5*8ALPHA --- note: because of how | works in regex, don't use $alpha{2,3} | $alpha{4,8}
1165             # We don't have to have the general case of extlang, because there can be only one extlang (except for zh-min-nan).
1166              
1167             # Note: extlang invalid in Unicode language tags
1168              
1169             my $script = '[a-z]{4}' ; # 4ALPHA
1170              
1171             my $region = '(?: [a-z]{2}|[0-9]{3})' ; # 2ALPHA / 3DIGIT
1172              
1173             my $variant = '(?: [a-z0-9]{5,8} | [0-9] [a-z0-9]{3} )' ; # 5*8alphanum / (DIGIT 3alphanum)
1174              
1175             my $extension = '(?: [a-wyz] (?: [_-] [a-z0-9]{2,8} )+ )' ; # singleton 1*("-" (2*8alphanum))
1176              
1177             my $privateUse = '(?: x (?: [_-] [a-z0-9]{1,8} )+ )' ; # "x" 1*("-" (1*8alphanum))
1178              
1179             # Define certain grandfathered codes, since otherwise the regex is pretty useless.
1180             # Since these are limited, this is safe even later changes to the registry --
1181             # the only oddity is that it might change the type of the tag, and thus
1182             # the results from the capturing groups.
1183             # http://www.iana.org/assignments/language-subtag-registry
1184             # Note that these have to be compared case insensitively, requiring (?i) below.
1185              
1186             my $grandfathered = '(?:
1187             (en [_-] GB [_-] oed)
1188             | (i [_-] (?: ami | bnn | default | enochian | hak | klingon | lux | mingo | navajo | pwn | tao | tay | tsu ))
1189             | (no [_-] (?: bok | nyn ))
1190             | (sgn [_-] (?: BE [_-] (?: fr | nl) | CH [_-] de ))
1191             | (zh [_-] min [_-] nan)
1192             )';
1193              
1194             # old: | zh $s (?: cmn (?: $s Hans | $s Hant )? | gan | min (?: $s nan)? | wuu | yue );
1195             # For well-formedness, we don't need the ones that would otherwise pass.
1196             # For validity, they need to be checked.
1197              
1198             # $grandfatheredWellFormed = (?:
1199             # art $s lojban
1200             # | cel $s gaulish
1201             # | zh $s (?: guoyu | hakka | xiang )
1202             # );
1203              
1204             # Unicode locales: but we are shifting to a compatible form
1205             # $keyvalue = (?: $alphanum+ \= $alphanum+);
1206             # $keywords = ($keyvalue (?: \; $keyvalue)*);
1207              
1208             # We separate items that we want to capture as a single group
1209              
1210             my $variantList = $variant . '(?:' . $s . $variant . ')*' ; # special for multiples
1211             my $extensionList = $extension . '(?:' . $s . $extension . ')*' ; # special for multiples
1212              
1213             my $langtag = "
1214             ($language)
1215             ($s ( $script ) )?
1216             ($s ( $region ) )?
1217             ($s ( $variantList ) )?
1218             ($s ( $extensionList ) )?
1219             ($s ( $privateUse ) )?
1220             ";
1221              
1222             # Here is the final breakdown, with capturing groups for each of these components
1223             # The variants, extensions, grandfathered, and private-use may have interior '-'
1224            
1225             my $r = ($value_to_test =~
1226             /^(
1227             ($langtag)
1228             | ($privateUse)
1229             | ($grandfathered)
1230             )$/xi);
1231             return $r;
1232             }
1233              
1234             'A man, a plan, a canal: Panama'; # E, r u true?
1235              
1236             __END__