File Coverage

blib/lib/XML/Atom/FromOWL.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package XML::Atom::FromOWL;
2              
3 1     1   27390 use 5.010;
  1         5  
  1         40  
4 1     1   5 use strict;
  1         2  
  1         31  
5 1     1   4 no warnings;
  1         6  
  1         42  
6              
7 1     1   791 use Data::UUID;
  1         9714  
  1         88  
8 1     1   388 use RDF::TrineX::Functions -shortcuts_nodes;
  0            
  0            
9             use Scalar::Util qw[blessed];
10             use XML::Atom::Content;
11             use XML::Atom::Entry;
12             use XML::Atom::Feed;
13             use XML::Atom::Link;
14             use XML::Atom::Person;
15              
16             use constant AS => 'http://activitystrea.ms/spec/1.0/';
17             use constant ATOM => 'http://www.w3.org/2005/Atom';
18             use constant FH => 'http://purl.org/syndication/history/1.0';
19             use constant THR => 'http://purl.org/syndication/thread/1.0';
20             use constant XHTML => 'http://www.w3.org/1999/xhtml';
21              
22             sub AWOL { return 'http://bblfish.net/work/atom-owl/2006-06-06/#' . shift; }
23             sub AAIR { return 'http://xmlns.notu.be/aair#' . shift; }
24             sub AX { return 'http://buzzword.org.uk/rdf/atomix#' . shift; }
25             sub FOAF { return 'http://xmlns.com/foaf/0.1/' . shift; }
26             sub HNEWS { return 'http://ontologi.es/hnews#' . shift; }
27             sub LINK { return 'http://www.iana.org/assignments/relation/' . shift; }
28             sub RDF { return 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' . shift; }
29             sub XSD { return 'http://www.w3.org/2001/XMLSchema#' . shift; }
30              
31             use namespace::clean;
32              
33             our ($AUTHORITY, $VERSION);
34             our (%feed_dispatch, %entry_dispatch);
35              
36             BEGIN
37             {
38             $AUTHORITY = 'cpan:TOBYINK';
39             $VERSION = '0.102';
40              
41             %feed_dispatch = (
42             AWOL('Feed') => sub {},
43             AWOL('entry') => \&_export_feed_entry,
44             AWOL('id') => \&_export_thing_id,
45             AWOL('title') => \&_export_thing_TextConstruct,
46             AWOL('subtitle') => \&_export_thing_TextConstruct,
47             AWOL('rights') => \&_export_thing_TextConstruct,
48             AWOL('updated') => \&_export_thing_DateConstruct,
49             AWOL('icon') => \&_export_thing_ImageConstruct,
50             AWOL('logo') => \&_export_thing_ImageConstruct,
51             AWOL('link') => \&_export_thing_link,
52             AWOL('author') => \&_export_thing_PersonConstruct,
53             AWOL('contributor') => \&_export_thing_PersonConstruct,
54             AWOL('category') => \&_export_thing_category,
55             AX('ArchiveFeed') => \&_export_feed_fh_archive,
56             AX('CompleteFeed') => \&_export_feed_fh_complete,
57             );
58             %entry_dispatch = (
59             AWOL('Entry') => sub {},
60             AWOL('id') => \&_export_thing_id,
61             AWOL('title') => \&_export_thing_TextConstruct,
62             AWOL('summary') => \&_export_thing_TextConstruct,
63             AWOL('rights') => \&_export_thing_TextConstruct,
64             AWOL('published') => \&_export_thing_DateConstruct,
65             AWOL('updated') => \&_export_thing_DateConstruct,
66             AWOL('link') => \&_export_thing_link,
67             AWOL('author') => \&_export_thing_PersonConstruct,
68             AWOL('contributor') => \&_export_thing_PersonConstruct,
69             AWOL('category') => \&_export_thing_category,
70             AWOL('content') => \&_export_entry_content,
71             AX('total') => \&_export_entry_thr_total,
72             AX('in-reply-to') => \&_export_entry_thr_in_reply_to,
73             AAIR('activityVerb') => \&_export_entry_as_verb,
74             AAIR('activityObject') => \&_export_entry_as_ObjectConstruct,
75             AAIR('activityTarget') => \&_export_entry_as_ObjectConstruct,
76             'http://activitystrea.ms/schema/1.0/*' => \&_export_entry_as_object_type,
77             # TODO:- atom:source
78             );
79             }
80              
81             sub new
82             {
83             my ($class, %options) = @_;
84             bless { %options }, $class;
85             }
86              
87             sub export_feeds
88             {
89             my ($self, $model, %options) = @_;
90             $model = rdf_parse($model)
91             unless blessed($model) && $model->isa('RDF::Trine::Model');
92            
93             my @subjects = $model->subjects(rdf_resource(RDF('type')), rdf_resource(AWOL('Feed')));
94              
95             my @feeds;
96             foreach my $s (@subjects)
97             {
98             push @feeds, $self->export_feed($model, $s, %options);
99             }
100            
101             if ($options{sort} eq 'id')
102             {
103             return sort { $a->id cmp $b->id } @feeds;
104             }
105             return @feeds;
106             }
107              
108             sub export_entries
109             {
110             my ($self, $model, %options) = @_;
111             $model = rdf_parse($model)
112             unless blessed($model) && $model->isa('RDF::Trine::Model');
113            
114             my @subjects = $model->subjects(rdf_resource(RDF('type')), rdf_resource(AWOL('Entry')));
115              
116             my @entries;
117             foreach my $s (@subjects)
118             {
119             push @entries, $self->export_feed($model, $s, %options);
120             }
121            
122             if ($options{sort} eq 'id')
123             {
124             return sort { $a->id cmp $b->id } @entries;
125             }
126             return @entries;
127             }
128              
129             sub export_feed
130             {
131             my ($self, $model, $subject, %options) = @_;
132             $model = rdf_parse($model)
133             unless blessed($model) && $model->isa('RDF::Trine::Model');
134            
135             my $feed = XML::Atom::Feed->new(Version => 1.0);
136              
137             my $attr = {
138             version => $VERSION,
139             uri => 'https://metacpan.org/release/'.__PACKAGE__,
140             };
141             $attr->{uri} =~ s/::/-/g;
142             $feed->set(ATOM(), 'generator', __PACKAGE__, $attr, 1);
143              
144             my $extra_links = {};
145             my $triples = $model->get_statements($subject, undef, undef);
146             while (my $triple = $triples->next)
147             {
148             next unless $triple->rdf_compatible;
149            
150             if (defined $feed_dispatch{$triple->predicate->uri}
151             and ref($feed_dispatch{$triple->predicate->uri}) eq 'CODE')
152             {
153             my $code = $feed_dispatch{$triple->predicate->uri};
154             $code->($self, $feed, $model, $triple, %options);
155             }
156             elsif ($triple->predicate->uri eq RDF('type')
157             and $triple->object->is_resource
158             and defined $feed_dispatch{$triple->object->uri}
159             and ref($feed_dispatch{$triple->object->uri}) eq 'CODE')
160             {
161             my $code = $feed_dispatch{$triple->object->uri};
162             $code->($self, $feed, $model, $triple, %options);
163             }
164             elsif ($triple->object->is_resource)
165             {
166             my $rel = $triple->predicate->uri;
167             $rel =~ s'^http://www\.iana\.org/assignments/relation/'';
168             $extra_links->{$rel} ||= {};
169             $extra_links->{$rel}{$triple->object->uri}++;
170             }
171             elsif ($triple->object->is_literal)
172             {
173             $self->_export_thing_LiteralValue($feed, $model, $triple, %options);
174             }
175             }
176              
177             $self->_process_extra_links($feed, $model, $extra_links, %options);
178             $feed->id( $self->_make_id ) unless $feed->id;
179              
180             return $feed;
181             }
182              
183             sub export_entry
184             {
185             my ($self, $model, $subject, %options) = @_;
186             $model = rdf_parse($model)
187             unless blessed($model) && $model->isa('RDF::Trine::Model');
188            
189             my $entry = XML::Atom::Entry->new(Version => 1.0);
190              
191             my $extra_links;
192             my $triples = $model->get_statements($subject, undef, undef);
193             while (my $triple = $triples->next)
194             {
195             next unless $triple->rdf_compatible;
196            
197             if (defined $entry_dispatch{$triple->predicate->uri}
198             and ref($entry_dispatch{$triple->predicate->uri}) eq 'CODE')
199             {
200             my $code = $entry_dispatch{$triple->predicate->uri};
201             $code->($self, $entry, $model, $triple, %options);
202             }
203             elsif ($triple->predicate->uri eq RDF('type')
204             and $triple->object->is_resource
205             and defined $entry_dispatch{$triple->object->uri}
206             and ref($entry_dispatch{$triple->object->uri}) eq 'CODE')
207             {
208             my $code = $entry_dispatch{$triple->object->uri};
209             $code->($self, $entry, $model, $triple, %options);
210             }
211             elsif ($triple->predicate->uri eq RDF('type')
212             and $triple->object->is_resource
213             and defined $entry_dispatch{ ($triple->object->qname)[0] . '*' }
214             and ref($entry_dispatch{ ($triple->object->qname)[0] . '*' }) eq 'CODE')
215             {
216             my $code = $entry_dispatch{ ($triple->object->qname)[0] . '*' };
217             $code->($self, $entry, $model, $triple, %options);
218             }
219             elsif ($triple->object->is_resource)
220             {
221             my $rel = $triple->predicate->uri;
222             $rel =~ s'^http://www\.iana\.org/assignments/relation/'';
223             $extra_links->{$rel} ||= {};
224             $extra_links->{$rel}{$triple->object->uri}++;
225             }
226             elsif ($triple->object->is_literal)
227             {
228             $self->_export_thing_LiteralValue($entry, $model, $triple, %options);
229             }
230             }
231              
232             $self->_process_extra_links($entry, $model, $extra_links, %options);
233             $entry->id( $self->_make_id ) unless $entry->id;
234              
235             return $entry;
236             }
237              
238             sub _process_extra_links
239             {
240             my ($self, $thing, $model, $extras, %options) = @_;
241             return unless keys %$extras;
242            
243             my $already = {};
244             foreach my $link ($thing->links)
245             {
246             $already->{$link->rel} ||= {};
247             $already->{$link->rel}{$link->href}++;
248             }
249            
250             PRED: foreach my $predicate (keys %$extras)
251             {
252             OBJ: foreach my $object (keys %{ $extras->{$predicate} })
253             {
254             next OBJ if $already->{$predicate}{$object};
255             my $link = XML::Atom::Link->new(Version => 1.0);
256             $link->rel($predicate);
257             $link->href($object);
258             $thing->add_link($link);
259             }
260             }
261             }
262              
263             sub _export_thing_LiteralValue
264             {
265             my ($self, $thing, $model, $triple, %options) = @_;
266             my $ns = XML::Atom::Namespace->new(xhtml => XHTML);
267              
268             if ($triple->object->is_literal)
269             {
270             my $attr = {
271             content => $triple->object->literal_value,
272             property => $triple->predicate->uri,
273             };
274             $attr->{'xml:lang'} = $triple->object->literal_value_language
275             if $triple->object->has_language;
276             $attr->{'datatype'} = $triple->object->literal_datatype
277             if $triple->object->has_datatype;
278             $thing->set_attr(typeof => '');
279             return $thing->set($ns, 'meta', undef, $attr, 1);
280             }
281             }
282              
283             sub _export_feed_entry
284             {
285             my ($self, $feed, $model, $triple, %options) = @_;
286             my $entry = $self->export_entry($model, $triple->object, %options);
287             $feed->add_entry($entry);
288             }
289              
290             sub _export_feed_fh_archive
291             {
292             my ($self, $feed, $model, $triple, %options) = @_;
293             return $feed->set(FH, 'archive');
294             }
295              
296             sub _export_feed_fh_complete
297             {
298             my ($self, $feed, $model, $triple, %options) = @_;
299             return $feed->set(FH, 'complete');
300             }
301              
302             sub _export_entry_thr_total
303             {
304             my ($self, $entry, $model, $triple, %options) = @_;
305             return $entry->set(THR, 'total', $triple->object->literal_value)
306             if $triple->object->is_literal;
307             }
308              
309             sub _export_entry_thr_in_reply_to
310             {
311             my ($self, $entry, $model, $triple, %options) = @_;
312             my $attr;
313            
314             my $iter = $model->get_statements($triple->object);
315             while (my $st = $iter->next)
316             {
317             if ($st->predicate->uri eq LINK('self')
318             and $st->object->is_resource)
319             {
320             $attr->{href} = $st->object->uri;
321             }
322             elsif ($st->predicate->uri eq AWOL('id')
323             and !$st->object->is_blank)
324             {
325             $attr->{ref} = flatten_node($st->object);
326             }
327             elsif ($st->predicate->uri eq AWOL('source'))
328             {
329             my $iter2 = $model->get_statements($st->object, rdf_resource(LINK('self')));
330             while (my $st2 = $iter2->next)
331             {
332             if ($st2->object->is_resource)
333             {
334             $attr->{source} = $st2->object->uri;
335             }
336             }
337             }
338             }
339            
340             return $entry->set(THR, 'in-reply-to', undef, $attr);
341             }
342              
343             sub _export_thing_id
344             {
345             my ($self, $thing, $model, $triple, %options) = @_;
346             unless ($triple->object->is_blank)
347             {
348             return $thing->id( flatten_node($triple->object) );
349             }
350             }
351              
352             sub _export_thing_link
353             {
354             my ($self, $thing, $model, $triple, %options) = @_;
355            
356             my $link = XML::Atom::Link->new(Version => 1.0);
357            
358             my $iter = $model->get_statements($triple->object);
359             while (my $st = $iter->next)
360             {
361             if ($st->predicate->uri eq AWOL('rel')
362             and $st->object->is_resource)
363             {
364             (my $rel = $st->object->uri)
365             =~ s'^http://www\.iana\.org/assignments/relation/'';
366             $link->rel($rel);
367             }
368             elsif ($st->predicate->uri eq AWOL('to'))
369             {
370             my $iter2 = $model->get_statements($st->object);
371             while (my $st2 = $iter2->next)
372             {
373             if ($st2->predicate->uri eq AWOL('type')
374             and $st2->object->is_literal)
375             {
376             $link->type(flatten_node($st2->object));
377             }
378             elsif ($st2->predicate->uri eq AWOL('src')
379             and !$st2->object->is_blank)
380             {
381             $link->href(flatten_node($st2->object));
382             }
383             elsif ($st2->predicate->uri eq AWOL('lang')
384             and $st2->object->is_literal)
385             {
386             $link->hreflang(flatten_node($st2->object));
387             }
388             }
389             }
390             }
391            
392             return $thing->add_link($link);
393             }
394              
395             sub _export_thing_PersonConstruct
396             {
397             my ($self, $thing, $model, $triple, %options) = @_;
398            
399             my $person = XML::Atom::Person->new(Version => 1.0);
400            
401             my $iter = $model->get_statements($triple->object);
402             while (my $st = $iter->next)
403             {
404             if ($st->predicate->uri eq AWOL('email')
405             and !$st->object->is_blank)
406             {
407             (my $e = flatten_node($st->object)) =~ s'^mailto:'';
408             $person->email($e);
409             }
410             elsif ($st->predicate->uri eq AWOL('uri')
411             and !$st->object->is_blank)
412             {
413             $person->url(flatten_node($st->object));
414             }
415             elsif ($st->predicate->uri eq AWOL('name')
416             and $st->object->is_literal)
417             {
418             $person->name(flatten_node($st->object));
419             }
420             }
421            
422             if ($triple->predicate->uri eq AWOL('contributor'))
423             {
424             return $thing->add_contributor($person);
425             }
426             if ($triple->predicate->uri eq AWOL('author'))
427             {
428             return $thing->add_author($person);
429             }
430             }
431              
432             sub _export_entry_content
433             {
434             my ($self, $entry, $model, $triple, %options) = @_;
435            
436             my $content = XML::Atom::Content->new(Version => 1.0);
437             if ($triple->object->is_literal)
438             {
439             $content->body(flatten_node($triple->object));
440             $content->lang($triple->object->literal_value_language)
441             if $triple->object->has_language;
442             }
443             else
444             {
445             my $iter = $model->get_statements($triple->object);
446             while (my $st = $iter->next)
447             {
448             if ($st->predicate->uri eq AWOL('base')
449             and !$st->object->is_blank)
450             {
451             $content->base(flatten_node($st->object));
452             }
453             elsif ($st->predicate->uri eq AWOL('type')
454             and $st->object->is_literal)
455             {
456             $content->type(flatten_node($st->object));
457             }
458             elsif ($st->predicate->uri eq AWOL('lang')
459             and $st->object->is_literal)
460             {
461             $content->lang(flatten_node($st->object));
462             }
463             elsif ($st->predicate->uri eq AWOL('body')
464             and $st->object->is_literal)
465             {
466             $content->body(flatten_node($st->object));
467             }
468             elsif ($st->predicate->uri eq AWOL('src')
469             and !$st->object->is_blank)
470             {
471             $content->set_attr(src => flatten_node($st->object));
472             }
473             }
474             }
475            
476             return $entry->content($content);
477             }
478              
479             sub _export_thing_category
480             {
481             my ($self, $thing, $model, $triple, %options) = @_;
482            
483             my $category = XML::Atom::Category->new(Version => 1.0);
484              
485             if ($triple->object->is_literal)
486             {
487             $category->term(flatten_node($triple->object));
488             }
489             else
490             {
491             my $iter = $model->get_statements($triple->object);
492             while (my $st = $iter->next)
493             {
494             if ($st->predicate->uri eq AWOL('term')
495             and $st->object->is_literal)
496             {
497             $category->term(flatten_node($st->object));
498             }
499             elsif ($st->predicate->uri eq AWOL('scheme')
500             and !$st->object->is_blank)
501             {
502             $category->scheme(flatten_node($st->object));
503             }
504             elsif ($st->predicate->uri eq AWOL('label')
505             and $st->object->is_literal)
506             {
507             $category->label(flatten_node($st->object));
508             $category->set_attr('xml:lang', $st->object->literal_value_language)
509             if $st->object->has_language;
510             }
511             }
512             }
513            
514             return $thing->add_category($category);
515             }
516              
517             sub _export_thing_TextConstruct
518             {
519             my ($self, $thing, $model, $triple, %options) = @_;
520              
521             my $tag = {
522             AWOL('title') => 'title',
523             AWOL('subtitle') => 'subtitle',
524             AWOL('summary') => 'summary',
525             AWOL('rights') => 'rights',
526             }->{$triple->predicate->uri};
527            
528             if ($triple->object->is_literal)
529             {
530             my $attr = { type=>'text' };
531             $attr->{'xml:lang'} = $triple->object->literal_value_language
532             if $triple->object->has_language;
533             return $thing->set(ATOM(), $tag, flatten_node($triple->object), $attr, 1);
534             }
535             else
536             {
537             foreach my $fmt (qw(text html xhtml)) # TODO: does 'xhtml' need special handling??
538             {
539             my $iter = $model->get_statements(
540             $triple->object,
541             rdf_resource(AWOL($fmt)),
542             undef,
543             );
544             while (my $st = $iter->next)
545             {
546             if ($st->object->is_literal)
547             {
548             my $attr = { type=>$fmt };
549             $attr->{'xml:lang'} = $st->object->literal_value_language
550             if $st->object->has_language;
551             return $thing->set(ATOM(), $tag, flatten_node($st->object), $attr, 1);
552             }
553             }
554             }
555             }
556             }
557              
558             sub _export_thing_DateConstruct
559             {
560             my ($self, $thing, $model, $triple, %options) = @_;
561              
562             my $tag = {
563             AWOL('published') => 'published',
564             AWOL('updated') => 'updated',
565             }->{$triple->predicate->uri};
566            
567             if ($triple->object->is_literal)
568             {
569             my $attr = {};
570             return $thing->set(ATOM(), $tag, flatten_node($triple->object), $attr, 1);
571             }
572             }
573              
574             sub _export_thing_ImageConstruct
575             {
576             my ($self, $thing, $model, $triple, %options) = @_;
577              
578             my $tag = {
579             AWOL('logo') => 'logo',
580             AWOL('icon') => 'icon',
581             }->{$triple->predicate->uri};
582            
583             if ($triple->object->is_resource)
584             {
585             my $attr = {};
586             return $thing->set(ATOM(), $tag, flatten_node($triple->object), $attr, 1);
587             }
588             }
589              
590             sub _export_entry_as_verb
591             {
592             my ($self, $thing, $model, $triple, %options) = @_;
593              
594             if ($triple->object->is_resource)
595             {
596             my $attr = {};
597             my $verb = flatten_node($triple->object);
598             $verb =~ s#^http://activitystrea\.ms/schema/1\.0/#./#;
599             return $thing->elem->addNewChild(AS(), 'as:verb')->appendText($verb);
600             }
601             }
602              
603             sub _export_entry_as_object_type
604             {
605             my ($self, $thing, $model, $triple, %options) = @_;
606              
607             if ($triple->object->is_resource)
608             {
609             my $attr = {};
610             my $type = flatten_node($triple->object);
611             $type =~ s#^http://activitystrea\.ms/schema/1\.0/#./#;
612             return $thing->elem->addNewChild(AS(), 'as:object-type')->appendText($type);
613             }
614             }
615              
616             sub _export_entry_as_ObjectConstruct
617             {
618             my ($self, $thing, $model, $triple, %options) = @_;
619              
620             my $tag = {
621             AAIR('activityObject') => 'object',
622             AAIR('activityTarget') => 'target',
623             }->{$triple->predicate->uri};
624            
625             if ($triple->object->is_resource or $triple->object->is_blank)
626             {
627             my $object_entry = $self->export_entry($model, $triple->object, %options);
628             my $node = $thing->elem->addNewChild(AS(), "as:$tag");
629             $node->appendChild($_->cloneNode(1)) for $object_entry->elem->childNodes;
630             return $node;
631             }
632             }
633              
634             sub _make_id
635             {
636             my ($self) = @_;
637             $self->{uuid} ||= Data::UUID->new;
638             return 'urn:uuid:'.$self->{uuid}->create_str;
639             }
640              
641             1;
642              
643             __END__
644              
645             =head1 NAME
646              
647             XML::Atom::FromOWL - export RDF data to Atom
648              
649             =head1 SYNOPSIS
650              
651             use LWP::UserAgent;
652             use XML::Atom::OWL;
653             use XML::Atom::FromOWL;
654            
655             my $ua = LWP::UserAgent->new;
656             my $r = $ua->get('http://intertwingly.net/blog/index.atom');
657             my $atomowl = XML::Atom::OWL->new($r->decoded_content, $r->base);
658             my $model = $atomowl->consume->graph; ## an RDF::Trine::Model
659            
660             my $exporter = XML::Atom::FromOWL->new;
661             print $_->as_xml
662             foreach $exporter->export_feeds($model);
663              
664             =head1 DESCRIPTION
665              
666             This module reads RDF and writes Atom feeds. It does the reverse
667             of L<XML::Atom::OWL>.
668              
669             =head2 Constructor
670              
671             =over
672              
673             =item * C<< new(%options) >>
674              
675             Returns a new XML::Atom::FromOWL object.
676              
677             There are no valid options at the moment - the hash is reserved
678             for future use.
679              
680             =back
681              
682             =head2 Methods
683              
684             =over
685              
686             =item * C<< export_feeds($input, %options) >>
687              
688             Returns a list of feeds found in the input, in no particular order.
689              
690             The input may be a URI, file name, L<RDF::Trine::Model> or anything else
691             that can be handled by the C<parse> function of L<RDF::TrineX::Functions>.
692              
693             Each item in the list returned is an L<XML::Atom::Feed>.
694              
695             =item * C<< export_feed($input, $subject, %options) >>
696              
697             As per C<export_feeds> but exports just a single feed.
698              
699             The subject provided must be an RDF::Trine::Node::Blank or
700             RDF::Trine::Node::Resource of type awol:Feed.
701              
702             =item * C<< export_entries($input, %options) >>
703              
704             Returns a list of entries found in the input, in no particular order.
705              
706             The input may be a URI, file name, L<RDF::Trine::Model> or anything else
707             that can be handled by the C<parse> function of L<RDF::TrineX::Functions>.
708              
709             Each item in the list returned is an L<XML::Atom::Entry>.
710              
711             =item * C<< export_entry($input, $subject, %options) >>
712              
713             As per C<export_entry> but exports just a single entry.
714              
715             The subject provided must be an RDF::Trine::Node::Blank or
716             RDF::Trine::Node::Resource of type awol:Entry.
717              
718             =back
719              
720             =head2 RDF Input
721              
722             Input is expected to use AtomOwl
723             L<http://bblfish.net/work/atom-owl/2006-06-06/#>.
724              
725             =head2 Feed Output
726              
727             This module doesn't attempt to enforce many of OWL's semantic
728             constraints (e.g. it doesn't enforce that an entry has only one
729             title). It relies on L<XML::Atom::Feed> and L<XML::Atom::Entry>
730             for that sort of thing, but if your input is sensible that
731             shouldn't be a problem.
732              
733             =head1 SEE ALSO
734              
735             L<XML::Atom::OWL>, L<HTML::Microformats>, L<RDF::TrineX::Functions>,
736             L<XML::Atom::Feed>, L<XML::Atom::Entry>.
737              
738             L<http://bblfish.net/work/atom-owl/2006-06-06/>.
739              
740             L<http://www.perlrdf.org/>.
741              
742             =head1 AUTHOR
743              
744             Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
745              
746             =head1 COPYRIGHT AND LICENCE
747              
748             This software is copyright (c) 2011-2012 by Toby Inkster.
749              
750             This is free software; you can redistribute it and/or modify it under
751             the same terms as the Perl 5 programming language system itself.
752              
753             =head1 DISCLAIMER OF WARRANTIES
754              
755             THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED
756             WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
757             MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
758