Commits

Toby Inkster committed 8e2cbde

Release AWOL2Atom code as XML-Atom-FromOWL-0.001.

Comments (0)

Files changed (13)

+############################################################################
+## Changes #################################################################
+############################################################################
+
+XML-Atom-FromOWL
+================
+
+Created:      2011-01-12
+Home page:    <http://search.cpan.org/dist/XML-Atom-FromOWL/>
+Bug tracker:  <http://rt.cpan.org/Dist/Display.html?Queue=XML-Atom-FromOWL>
+Maintainer:   Toby Inkster <mailto:tobyink@cpan.org>
+
+0.001 [2011-01-13] # Initial release
+
+#############################################################
+
+@prefix :        <http://usefulinc.com/ns/doap#> .
+@prefix dcs:     <http://ontologi.es/doap-changeset#> .
+@prefix dc:      <http://purl.org/dc/terms/> .
+@prefix foaf:    <http://xmlns.com/foaf/0.1/> .
+@prefix my:      <http://purl.org/NET/cpan-uri/dist/XML-Atom-FromOWL/> .
+@prefix rdfs:    <http://www.w3.org/2000/01/rdf-schema#> .
+@prefix toby:    <http://tobyinkster.co.uk/#> .
+@prefix xsd:     <http://www.w3.org/2001/XMLSchema#> .
+
+#############################################################
+
+<>
+
+	dc:title         "Changes" ;
+	dc:description   "Revision history for Perl extension XML::Atom::FromOWL."@en ;
+	dc:subject       my:project ;
+	dc:creator       toby:i .
+
+#############################################################
+
+my:v_0-001
+
+	a               :Version ;
+	dc:issued       "2011-01-13"^^xsd:date ;
+	:revision       "0.001"^^xsd:string ;
+	:file-release   <http://backpan.cpan.org/authors/id/T/TO/TOBYINK/XML-Atom-FromOWL-0.001.tar.gz> ;
+	rdfs:label      "Initial release"@en .
+
+#############################################################
+
+my:project
+
+	a               :Project ;
+	:name           "XML-Atom-FromOWL" ;
+	:shortdesc      "export RDF data to Atom"@en ;
+	:programming-language  "Perl" ;
+	:homepage       <http://search.cpan.org/dist/XML-Atom-FromOWL/> ;
+	:download-page  <http://search.cpan.org/dist/XML-Atom-FromOWL/> ;
+	:bug-database   <http://rt.cpan.org/Dist/Display.html?Queue=XML-Atom-FromOWL> ;
+	:repository     [ a :SVNRepository ; :browse <http://goddamn.co.uk/viewvc/perlmods/XML-Atom-FromOWL/> ] ;
+	:maintainer     toby:i ;
+	:developer      toby:i ;
+	:documenter     toby:i ;
+	:tester         toby:i ;
+	:created        "2011-01-12"^^xsd:date ;
+	:license        <http://dev.perl.org/licenses/> ;
+	:release        my:v_0-001 .
+
+#############################################################
+
+toby:i
+
+	a               foaf:Person ;
+	foaf:name       "Toby Inkster" ;
+	foaf:homepage   <http://tobyinkster.co.uk/> ;
+	foaf:page       <http://search.cpan.org/~tobyink/> ;
+	foaf:mbox       <mailto:tobyink@cpan.org> ;
+	<http://www.w3.org/2002/07/owl#sameAs> <http://purl.org/NET/cpan-uri/person/tobyink> .
+
+#############################################################
+<?xml version="1.0" encoding="utf-8"?>
+<rdf:RDF xmlns:dbug="http://ontologi.es/doap-bugs#" xmlns:dc="http://purl.org/dc/terms/" xmlns:dcs="http://ontologi.es/doap-changeset#" xmlns:doap="http://usefulinc.com/ns/doap#" xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#">
+<rdf:Description rdf:nodeID="rE36C88321F6211E090D1FB4E32C985D7r0">
+	<doap:browse rdf:resource="http://goddamn.co.uk/viewvc/perlmods/XML-Atom-FromOWL/"/>
+	<rdf:type rdf:resource="http://usefulinc.com/ns/doap#SVNRepository"/>
+</rdf:Description>
+<rdf:Description rdf:about="file:///home/tai/src/perlmods/XML-Atom-FromOWL/Changes.ttl">
+	<dc:creator rdf:resource="http://tobyinkster.co.uk/#i"/>
+	<dc:description xml:lang="en">Revision history for Perl extension XML::Atom::FromOWL.</dc:description>
+	<dc:subject rdf:resource="http://purl.org/NET/cpan-uri/dist/XML-Atom-FromOWL/project"/>
+	<dc:title>Changes</dc:title>
+</rdf:Description>
+<rdf:Description rdf:about="http://purl.org/NET/cpan-uri/dist/XML-Atom-FromOWL/project">
+	<doap:bug-database rdf:resource="http://rt.cpan.org/Dist/Display.html?Queue=XML-Atom-FromOWL"/>
+	<doap:created rdf:datatype="http://www.w3.org/2001/XMLSchema#date">2011-01-12</doap:created>
+	<doap:developer rdf:resource="http://tobyinkster.co.uk/#i"/>
+	<doap:documenter rdf:resource="http://tobyinkster.co.uk/#i"/>
+	<doap:download-page rdf:resource="http://search.cpan.org/dist/XML-Atom-FromOWL/"/>
+	<doap:homepage rdf:resource="http://search.cpan.org/dist/XML-Atom-FromOWL/"/>
+	<doap:license rdf:resource="http://dev.perl.org/licenses/"/>
+	<doap:maintainer rdf:resource="http://tobyinkster.co.uk/#i"/>
+	<doap:name>XML-Atom-FromOWL</doap:name>
+	<doap:programming-language>Perl</doap:programming-language>
+	<doap:release rdf:resource="http://purl.org/NET/cpan-uri/dist/XML-Atom-FromOWL/v_0-001"/>
+	<doap:repository rdf:nodeID="rE36C88321F6211E090D1FB4E32C985D7r0"/>
+	<doap:shortdesc xml:lang="en">export RDF data to Atom</doap:shortdesc>
+	<doap:tester rdf:resource="http://tobyinkster.co.uk/#i"/>
+	<rdf:type rdf:resource="http://usefulinc.com/ns/doap#Project"/>
+</rdf:Description>
+<rdf:Description rdf:about="http://purl.org/NET/cpan-uri/dist/XML-Atom-FromOWL/v_0-001">
+	<dc:issued rdf:datatype="http://www.w3.org/2001/XMLSchema#date">2011-01-13</dc:issued>
+	<doap:file-release rdf:resource="http://backpan.cpan.org/authors/id/T/TO/TOBYINK/XML-Atom-FromOWL-0.001.tar.gz"/>
+	<doap:revision rdf:datatype="http://www.w3.org/2001/XMLSchema#string">0.001</doap:revision>
+	<rdf:type rdf:resource="http://usefulinc.com/ns/doap#Version"/>
+	<rdfs:label xml:lang="en">Initial release</rdfs:label>
+</rdf:Description>
+<rdf:Description xmlns:ns1="http://www.w3.org/2002/07/owl#" rdf:about="http://tobyinkster.co.uk/#i">
+	<rdf:type rdf:resource="http://xmlns.com/foaf/0.1/Person"/>
+	<ns1:sameAs rdf:resource="http://purl.org/NET/cpan-uri/person/tobyink"/>
+	<foaf:homepage rdf:resource="http://tobyinkster.co.uk/"/>
+	<foaf:mbox rdf:resource="mailto:tobyink@cpan.org"/>
+	<foaf:name>Toby Inkster</foaf:name>
+	<foaf:page rdf:resource="http://search.cpan.org/~tobyink/"/>
+</rdf:Description>
+</rdf:RDF>
+Changes
+Changes.ttl
+Changes.xml
+Makefile.PL
+MANIFEST
+MANIFEST.SKIP
+README
+META.yml
+SIGNATURE
+
+t/00sig.t
+t/01basic.t
+
+inc/Module/AutoInstall.pm
+inc/Module/Install/AutoInstall.pm
+inc/Module/Install/Base.pm
+inc/Module/Install/Can.pm
+inc/Module/Install/DOAPChangeSets.pm
+inc/Module/Install/Fetch.pm
+inc/Module/Install/Include.pm
+inc/Module/Install/Makefile.pm
+inc/Module/Install/Metadata.pm
+inc/Module/Install.pm
+inc/Module/Install/ReadmeFromPod.pm
+inc/Module/Install/Win32.pm
+inc/Module/Install/WriteAll.pm
+inc/Test/Signature.pm
+
+lib/XML/Atom/FromOWL.pm
+^Makefile$
+^blib/
+^pm_to_blib
+^blibdirs
+\.svn
+^example.*\.pl$
+^[^/]+\.(tar\.gz|tar\.bz2|tgz|tbz2|tbz|zip|tar)$
+^MYMETA.yml
+---
+abstract: 'export RDF data to Atom'
+author:
+  - 'Toby Inkster <tobyink@cpan.org>'
+build_requires:
+  ExtUtils::MakeMaker: 6.42
+  Module::Signature: 0.66
+  Test::More: 0.61
+configure_requires:
+  ExtUtils::MakeMaker: 6.42
+distribution_type: module
+generated_by: 'Module::Install version 1.00'
+keywords:
+  - Atom
+  - RDF
+  - 'Semantic Web'
+  - OWL
+  - Feed
+license: perl
+meta-spec:
+  url: http://module-build.sourceforge.net/META-spec-v1.4.html
+  version: 1.4
+name: XML-Atom-FromOWL
+no_index:
+  directory:
+    - inc
+    - t
+requires:
+  RDF::TrineShortcuts: 0.100
+  Scalar::Util: 0
+  XML::Atom::Content: 0
+  XML::Atom::Entry: 0
+  XML::Atom::Feed: 0
+  XML::Atom::Link: 0
+  XML::Atom::Person: 0
+  common::sense: 0
+  namespace::clean: 0
+  perl: 5.8.0
+resources:
+  bugtracker: http://rt.cpan.org/Dist/Display.html?Queue=XML-Atom-FromOWL
+  homepage: http://search.cpan.org/dist/XML-Atom-FromOWL/
+  license: http://dev.perl.org/licenses/
+  repository: http://goddamn.co.uk/viewvc/perlmods/XML-Atom-FromOWL/
+version: 0.001
+use strict;
+use warnings;
+
+use inc::Module::Install;
+
+my $dist = 'XML-Atom-FromOWL';
+my $fn   = "lib/$dist.pm"; $fn =~ s#-#/#g;
+
+name                $dist;
+perl_version_from   $fn;
+version_from        $fn;
+abstract_from       $fn;
+readme_from         $fn;
+author              'Toby Inkster <tobyink@cpan.org>';
+license             'perl';
+
+requires            'common::sense'       => 0;
+test_requires       'Module::Signature'   => '0.66';
+requires            'namespace::clean'    => 0;
+requires            'RDF::TrineShortcuts' => '0.100';
+requires            'Scalar::Util'        => 0;
+test_requires       'Test::More'          => '0.61';
+requires            'XML::Atom::Content'  => 0;
+requires            'XML::Atom::Entry'    => 0;
+requires            'XML::Atom::Feed'     => 0;
+requires            'XML::Atom::Link'     => 0;
+requires            'XML::Atom::Person'   => 0;
+
+resources(
+	'homepage'   => "http://search.cpan.org/dist/$dist/",
+	'repository' => "http://goddamn.co.uk/viewvc/perlmods/$dist/",
+	'bugtracker' => "http://rt.cpan.org/Dist/Display.html?Queue=$dist",
+	);
+
+keywords("Atom", "RDF", "Semantic Web", "OWL", "Feed");
+	
+write_doap_changes;
+write_doap_changes_xml;
+
+include 'Test::Signature';
+auto_install;
+WriteAll(
+	'meta' => 1,
+	'sign' => 1,
+	);
+NAME
+    XML::Atom::FromOWL - export RDF data to Atom
+
+SYNOPSIS
+     use LWP::UserAgent;
+     use XML::Atom::OWL;
+     use XML::Atom::FromOWL;
+ 
+     my $ua       = LWP::UserAgent->new;
+     my $r        = $ua->get('http://intertwingly.net/blog/index.atom');
+     my $atomowl  = XML::Atom::OWL->new($r->decoded_content, $r->base);
+     my $model    = $atomowl->consume->graph;  ## an RDF::Trine::Model
+ 
+     my $exporter = XML::Atom::FromOWL->new;
+     print $_->as_xml
+            foreach $exporter->export_feeds($model);
+
+DESCRIPTION
+    This module reads RDF and writes Atom feeds. It does the reverse of
+    XML::Atom::OWL.
+
+  Constructor
+    *   "new(%options)"
+
+        Returns a new XML::Atom::FromOWL object.
+
+        There are no valid options at the moment - the hash is reserved for
+        future use.
+
+  Methods
+    *   "export_feeds($input, %options)"
+
+        Returns a list of feeds found in the input, in no particular order.
+
+        The input may be a URI, file name, RDF::Trine::Model or anything
+        else that can be handled by the "rdf_parse" method of
+        RDF::TrineShortcuts.
+
+        Each item in the list returned is an XML::Atom::Feed.
+
+    *   "export_feed($input, $subject, %options)"
+
+        As per "export_feeds" but exports just a single feed.
+
+        The subject provided must be an RDF::Trine::Node::Blank or
+        RDF::Trine::Node::Resource of type awol:Feed.
+
+    *   "export_entries($input, %options)"
+
+        Returns a list of entries found in the input, in no particular
+        order.
+
+        The input may be a URI, file name, RDF::Trine::Model or anything
+        else that can be handled by the "rdf_parse" method of
+        RDF::TrineShortcuts.
+
+        Each item in the list returned is an XML::Atom::Entry.
+
+    *   "export_entry($input, $subject, %options)"
+
+        As per "export_entry" but exports just a single entry.
+
+        The subject provided must be an RDF::Trine::Node::Blank or
+        RDF::Trine::Node::Resource of type awol:Entry.
+
+  RDF Input
+    Input is expected to use AtomOwl
+    <http://bblfish.net/work/atom-owl/2006-06-06/#>.
+
+  Feed Output
+    This module doesn't attempt to enforce many of OWL's semantic
+    constraints (e.g. it doesn't enforce that an entry has only one title).
+    It relies on XML::Atom::Feed and XML::Atom::Entry for that sort of
+    thing, but if your input is sensible that shouldn't be a problem.
+
+SEE ALSO
+    XML::Atom::OWL, HTML::Microformats, RDF::TrineShortcuts,
+    XML::Atom::Feed, XML::Atom::Entry.
+
+    <http://bblfish.net/work/atom-owl/2006-06-06/>.
+
+    <http://www.perlrdf.org/>.
+
+AUTHOR
+    Toby Inkster <tobyink@cpan.org>.
+
+COPYRIGHT
+    Copyright 2011 Toby Inkster
+
+    This library is free software; you can redistribute it and/or modify it
+    under the same terms as Perl itself.
+

XML-Atom-FromOWL-0.001.tar.gz

Binary file added.
+#!/usr/bin/perl
+
+use lib "lib";
+use RDF::TrineShortcuts;
+use XML::Atom::OWL;
+use XML::Atom::FromOWL;
+
+my $atom = <<ATOM;
+<feed xmlns="http://www.w3.org/2005/Atom">
+  <title type="text">dive into mark</title>
+  <subtitle type="html">
+	 A &lt;em&gt;lot&lt;/em&gt; of effort
+	 went into making this effortless
+  </subtitle>
+  <updated>2005-07-31T12:29:29Z</updated>
+  <id>tag:example.org,2003:3</id>
+  <logo>http://example.net/logo.jpeg</logo>
+  <link rel="alternate" type="text/html"
+	hreflang="en" href="http://example.org/"/>
+  <link rel="self" type="application/atom+xml"
+	href="http://example.org/feed.atom"/>
+  <rights>Copyright (c) 2003, Mark Pilgrim</rights>
+  <generator uri="http://www.example.com/" version="1.0">
+	 Example Toolkit
+  </generator>
+  <entry xml:lang="en-US">
+	 <title property="dc:title" xmlns:dc="http://example.com/dc#">Atom draft-07 snapshot</title>
+	 <link rel="alternate" type="text/html"
+	  href="http://example.org/2005/04/02/atom"/>
+	 <link rel="enclosure" type="audio/mpeg" length="1337"
+	  href="http://example.org/audio/ph34r_my_podcast.mp3"/>
+    <category term="technology" label="Technology" scheme="http://example.com/categories/" />
+	 <id>tag:example.org,2003:3.2397</id>
+	 <updated>2005-07-31T12:29:29Z</updated>
+	 <published>2003-12-13T08:29:29-04:00</published>
+	 <author>
+		<name>Mark Pilgrim</name>
+		<uri>http://example.org/</uri>
+		<email>f8dy\@example.com</email>
+	 </author>
+	 <contributor>
+		<name>Sam Ruby</name>
+	 </contributor>
+	 <contributor>
+		<name>Joe Gregorio</name>
+	 </contributor>
+	 <content type="xhtml" xml:lang="en"
+	  xml:base="http://diveintomark.org/">
+		<div xmlns="http://www.w3.org/1999/xhtml">
+		  <p><i>[Update: The Atom draft is finished.]</i></p>
+		</div>
+	 </content>
+  </entry>
+  <entry>
+	<id>http://example.net/id/2</id>
+	<content type="image/jpeg" src="data:text/html;base64,PCFET0NUWVBFIEhUTUwgUFVCTElDICItLy9XM0MvL0RURCBIVE1MIDQuMC8vRU4iPg0KPGh0bWwgbGFuZz0iZW4iPg0KIDxoZWFkPg0KICA8dGl0bGU%2BVGVzdDwvdGl0bGU%2BDQogIDxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI%2BDQogIDwvc3R5bGU%2BDQogPC9oZWFkPg0KIDxib2R5Pg0KICA8cD48L3A%2BDQogPC9ib2R5Pg0KPC9odG1sPg0K" />
+  </entry>
+</feed>
+ATOM
+
+my $awol  = XML::Atom::OWL->new($atom, 'http://example.net/')->consume;
+my $model = $awol->graph;
+
+my $exporter = XML::Atom::FromOWL->new();
+print $_->as_xml foreach $exporter->export_feeds($model);
+#print rdf_string($model => 'rdfxml');
+

lib/XML/Atom/FromOWL.pm

+package XML::Atom::FromOWL;
+
+use 5.008;
+use common::sense;
+
+use RDF::TrineShortcuts qw[:all];
+use Scalar::Util qw[blessed];
+use XML::Atom::Content;
+use XML::Atom::Entry;
+use XML::Atom::Feed;
+use XML::Atom::Link;
+use XML::Atom::Person;
+
+use constant ATOM => 'http://www.w3.org/2005/Atom';
+sub AWOL { return 'http://bblfish.net/work/atom-owl/2006-06-06/#' . shift; }
+sub AX   { return 'http://buzzword.org.uk/rdf/atomix#' . shift; }
+sub FOAF { return 'http://xmlns.com/foaf/0.1/' . shift; }
+sub LINK { return 'http://www.iana.org/assignments/relation/' . shift; }
+sub RDF  { return 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' . shift; }
+sub XSD  { return 'http://www.w3.org/2001/XMLSchema#' . shift; }
+
+use namespace::clean;
+
+our $VERSION;
+our (%feed_dispatch, %entry_dispatch);
+
+BEGIN
+{
+	$VERSION = '0.001';
+
+	%feed_dispatch = (
+		AWOL('entry')        => \&_export_feed_entry,
+		AWOL('id')           => \&_export_thing_id,
+		AWOL('title')        => \&_export_thing_TextConstruct,
+		AWOL('subtitle')     => \&_export_thing_TextConstruct,
+		AWOL('rights')       => \&_export_thing_TextConstruct,
+		AWOL('updated')      => \&_export_thing_DateConstruct,
+		AWOL('icon')         => \&_export_thing_ImageConstruct,
+		AWOL('logo')         => \&_export_thing_ImageConstruct,
+		AWOL('link')         => \&_export_thing_link,
+		AWOL('author')       => \&_export_thing_PersonConstruct,
+		AWOL('contributor')  => \&_export_thing_PersonConstruct,
+		AWOL('category')     => \&_export_thing_category,
+		);
+	%entry_dispatch = (
+		AWOL('id')           => \&_export_thing_id,
+		AWOL('title')        => \&_export_thing_TextConstruct,
+		AWOL('summary')      => \&_export_thing_TextConstruct,
+		AWOL('rights')       => \&_export_thing_TextConstruct,
+		AWOL('published')    => \&_export_thing_DateConstruct,
+		AWOL('updated')      => \&_export_thing_DateConstruct,
+		AWOL('link')         => \&_export_thing_link,
+		AWOL('author')       => \&_export_thing_PersonConstruct,
+		AWOL('contributor')  => \&_export_thing_PersonConstruct,
+		AWOL('category')     => \&_export_thing_category,
+		AWOL('content')      => \&_export_entry_content,
+		# source
+		);
+}
+
+sub new
+{
+	my ($class, %options) = @_;
+	bless { %options }, $class;
+}
+
+sub export_feeds
+{
+	my ($self, $model, %options) = @_;
+	$model = rdf_parse($model)
+		unless blessed($model) && $model->isa('RDF::Trine::Model');
+	
+	my @subjects =  $model->subjects(rdf_resource(RDF('type')), rdf_resource(AWOL('Feed')));
+
+	my @feeds;
+	foreach my $s (@subjects)
+	{
+		push @feeds, $self->export_feed($model, $s, %options);
+	}
+	
+	if ($options{sort} eq 'id')
+	{
+		return sort { $a->id cmp $b->id } @feeds;
+	}
+	return @feeds;
+}
+
+sub export_entries
+{
+	my ($self, $model, %options) = @_;
+	$model = rdf_parse($model)
+		unless blessed($model) && $model->isa('RDF::Trine::Model');
+	
+	my @subjects =  $model->subjects(rdf_resource(RDF('type')), rdf_resource(AWOL('Entry')));
+
+	my @entries;
+	foreach my $s (@subjects)
+	{
+		push @entries, $self->export_feed($model, $s, %options);
+	}
+	
+	if ($options{sort} eq 'id')
+	{
+		return sort { $a->id cmp $b->id } @entries;
+	}
+	return @entries;
+}
+
+sub export_feed
+{
+	my ($self, $model, $subject, %options) = @_;
+	$model = rdf_parse($model)
+		unless blessed($model) && $model->isa('RDF::Trine::Model');
+	
+	my $feed = XML::Atom::Feed->new(Version => 1.0);
+
+	my $attr = {
+		version => $VERSION,
+		uri     => 'http://search.cpan.org/dist/'.__PACKAGE__.'/',
+		};
+	$attr->{uri} =~ s/::/-/g;
+	$feed->set(ATOM(), 'generator', __PACKAGE__, $attr, 1);
+
+	my $triples = $model->get_statements($subject, undef, undef);
+	while (my $triple = $triples->next)
+	{
+		next
+			unless (substr($triple->predicate->uri, 0, length(&AWOL)) eq &AWOL or
+					  substr($triple->predicate->uri, 0, length(&AX)) eq &AX);
+
+		if (defined $feed_dispatch{$triple->predicate->uri}
+		and ref($feed_dispatch{$triple->predicate->uri}) eq 'CODE')
+		{
+			my $code = $feed_dispatch{$triple->predicate->uri};
+			$code->($self, $feed, $model, $triple, %options);
+		}
+		else
+		{
+			#warn("FEED: " . $triple->predicate->uri . " not implemented yet!");
+		}
+	}
+
+	return $feed;
+}
+
+sub export_entry
+{
+	my ($self, $model, $subject, %options) = @_;
+	$model = rdf_parse($model)
+		unless blessed($model) && $model->isa('RDF::Trine::Model');
+	
+	my $entry = XML::Atom::Entry->new(Version => 1.0);
+
+	my $triples = $model->get_statements($subject, undef, undef);
+	while (my $triple = $triples->next)
+	{
+#		next
+#			unless (substr($triple->predicate->uri, 0, length(&AWOL)) eq &AWOL or
+#					  substr($triple->predicate->uri, 0, length(&AX)) eq &AX);
+
+		if (defined $entry_dispatch{$triple->predicate->uri}
+		and ref($entry_dispatch{$triple->predicate->uri}) eq 'CODE')
+		{
+			my $code = $entry_dispatch{$triple->predicate->uri};
+			$code->($self, $entry, $model, $triple, %options);
+		}
+		else
+		{
+			#warn("ENTRY: " . $triple->predicate->uri . " not implemented yet!");
+		}
+	}
+	
+	return $entry;
+}
+
+sub _export_feed_entry
+{
+	my ($self, $feed, $model, $triple, %options) = @_;
+	my $entry = $self->export_entry($model, $triple->object, %options);
+	$feed->add_entry($entry);
+}
+
+sub _export_thing_id
+{
+	my ($self, $thing, $model, $triple, %options) = @_;
+	unless ($triple->object->is_blank)
+	{
+		return $thing->id( flatten_node($triple->object) );
+	}
+}
+
+sub _export_thing_link
+{
+	my ($self, $thing, $model, $triple, %options) = @_;
+	
+	my $link = XML::Atom::Link->new(Version => 1.0);
+	
+	my $iter = $model->get_statements($triple->object);
+	while (my $st = $iter->next)
+	{
+		if ($st->predicate->uri eq AWOL('rel')
+		and $st->object->is_resource)
+		{
+			(my $rel = $st->object->uri)
+				=~ s'^http://www\.iana\.org/assignments/relation/'';
+			$link->rel($rel);
+		}
+		elsif ($st->predicate->uri eq AWOL('to'))
+		{
+			my $iter2 = $model->get_statements($st->object);
+			while (my $st2 = $iter2->next)
+			{
+				if ($st2->predicate->uri eq AWOL('type')
+				and $st2->object->is_literal)
+				{
+					$link->type(flatten_node($st2->object));
+				}
+				elsif ($st2->predicate->uri eq AWOL('src')
+				and !$st2->object->is_blank)
+				{
+					$link->href(flatten_node($st2->object));
+				}
+				elsif ($st2->predicate->uri eq AWOL('lang')
+				and $st2->object->is_literal)
+				{
+					$link->hreflang(flatten_node($st2->object));
+				}
+			}
+		}
+	}
+	
+	return $thing->add_link($link);
+}
+
+sub _export_thing_PersonConstruct
+{
+	my ($self, $thing, $model, $triple, %options) = @_;
+	
+	my $person = XML::Atom::Person->new(Version => 1.0);
+	
+	my $iter = $model->get_statements($triple->object);
+	while (my $st = $iter->next)
+	{
+		if ($st->predicate->uri eq AWOL('email')
+		and !$st->object->is_blank)
+		{
+			(my $e = flatten_node($st->object)) =~ s'^mailto:'';
+			$person->email($e);
+		}
+		elsif ($st->predicate->uri eq AWOL('uri')
+		and !$st->object->is_blank)
+		{
+			$person->url(flatten_node($st->object));
+		}
+		elsif ($st->predicate->uri eq AWOL('name')
+		and $st->object->is_literal)
+		{
+			$person->name(flatten_node($st->object));
+		}
+	}
+	
+	if ($triple->predicate->uri eq AWOL('contributor'))
+	{
+		return $thing->add_contributor($person);
+	}
+	if ($triple->predicate->uri eq AWOL('author'))
+	{
+		return $thing->add_author($person);
+	}
+}
+
+sub _export_entry_content
+{
+	my ($self, $entry, $model, $triple, %options) = @_;
+	
+	my $content = XML::Atom::Content->new(Version => 1.0);
+	if ($triple->object->is_literal)
+	{
+		$content->body(flatten_node($triple->object));
+		$content->lang($triple->object->literal_value_language)
+			if $triple->object->has_language;
+	}
+	else
+	{
+		my $iter = $model->get_statements($triple->object);
+		while (my $st = $iter->next)
+		{
+			if ($st->predicate->uri eq AWOL('base')
+			and !$st->object->is_blank)
+			{
+				$content->base(flatten_node($st->object));
+			}
+			elsif ($st->predicate->uri eq AWOL('type')
+			and $st->object->is_literal)
+			{
+				$content->type(flatten_node($st->object));
+			}
+			elsif ($st->predicate->uri eq AWOL('lang')
+			and $st->object->is_literal)
+			{
+				$content->lang(flatten_node($st->object));
+			}
+			elsif ($st->predicate->uri eq AWOL('body')
+			and $st->object->is_literal)
+			{
+				$content->body(flatten_node($st->object));
+			}
+			elsif ($st->predicate->uri eq AWOL('src')
+			and !$st->object->is_blank)
+			{
+				$content->set_attr(src => flatten_node($st->object));
+			}
+		}
+	}
+	
+	return $entry->content($content);
+}
+
+sub _export_thing_category
+{
+	my ($self, $thing, $model, $triple, %options) = @_;
+	
+	my $category = XML::Atom::Category->new(Version => 1.0);
+
+	if ($triple->object->is_literal)
+	{
+		$category->term(flatten_node($triple->object));
+	}
+	else
+	{
+		my $iter = $model->get_statements($triple->object);
+		while (my $st = $iter->next)
+		{
+			if ($st->predicate->uri eq AWOL('term')
+			and $st->object->is_literal)
+			{
+				$category->term(flatten_node($st->object));
+			}
+			elsif ($st->predicate->uri eq AWOL('scheme')
+			and !$st->object->is_blank)
+			{
+				$category->scheme(flatten_node($st->object));
+			}
+			elsif ($st->predicate->uri eq AWOL('label')
+			and $st->object->is_literal)
+			{
+				$category->label(flatten_node($st->object));
+				$category->set_attr('xml:lang', $st->object->literal_value_language)
+					if $st->object->has_language;
+			}
+		}
+	}
+	
+	return $thing->add_category($category);
+}
+
+sub _export_thing_TextConstruct
+{
+	my ($self, $thing, $model, $triple, %options) = @_;
+
+	my $tag = {
+		AWOL('title')     => 'title',
+		AWOL('subtitle')  => 'subtitle',
+		AWOL('summary')   => 'summary',
+		AWOL('rights')    => 'rights',
+		}->{$triple->predicate->uri};
+	
+	if ($triple->object->is_literal)
+	{
+		my $attr = { type=>'text' };
+		$attr->{'xml:lang'} = $triple->object->literal_value_language
+			if $triple->object->has_language;
+		return $thing->set(ATOM(), $tag, flatten_node($triple->object), $attr, 1);
+	}
+	else
+	{
+		foreach my $fmt (qw(text html xhtml)) # TODO: does 'xhtml' need special handling??
+		{
+			my $iter = $model->get_statements(
+				$triple->object,
+				rdf_resource(AWOL($fmt)),
+				undef,
+				);
+			while (my $st = $iter->next)
+			{
+				if ($st->object->is_literal)
+				{
+					my $attr = { type=>$fmt };
+					$attr->{'xml:lang'} = $st->object->literal_value_language
+						if $st->object->has_language;
+					return $thing->set(ATOM(), $tag, flatten_node($st->object), $attr, 1);
+				}
+			}
+		}
+	}
+}
+
+sub _export_thing_DateConstruct
+{
+	my ($self, $thing, $model, $triple, %options) = @_;
+
+	my $tag = {
+		AWOL('published') => 'published',
+		AWOL('updated')   => 'updated',
+		}->{$triple->predicate->uri};
+	
+	if ($triple->object->is_literal)
+	{
+		my $attr = {};
+		return $thing->set(ATOM(), $tag, flatten_node($triple->object), $attr, 1);
+	}
+}
+
+sub _export_thing_ImageConstruct
+{
+	my ($self, $thing, $model, $triple, %options) = @_;
+
+	my $tag = {
+		AWOL('logo') => 'logo',
+		AWOL('icon') => 'icon',
+		}->{$triple->predicate->uri};
+	
+	if ($triple->object->is_resource)
+	{
+		my $attr = {};
+		return $thing->set(ATOM(), $tag, flatten_node($triple->object), $attr, 1);
+	}
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+XML::Atom::FromOWL - export RDF data to Atom
+
+=head1 SYNOPSIS
+
+ use LWP::UserAgent;
+ use XML::Atom::OWL;
+ use XML::Atom::FromOWL;
+ 
+ my $ua       = LWP::UserAgent->new;
+ my $r        = $ua->get('http://intertwingly.net/blog/index.atom');
+ my $atomowl  = XML::Atom::OWL->new($r->decoded_content, $r->base);
+ my $model    = $atomowl->consume->graph;  ## an RDF::Trine::Model
+ 
+ my $exporter = XML::Atom::FromOWL->new;
+ print $_->as_xml
+	foreach $exporter->export_feeds($model);
+
+=head1 DESCRIPTION
+
+This module reads RDF and writes Atom feeds. It does the reverse
+of L<XML::Atom::OWL>.
+
+=head2 Constructor
+
+=over
+
+=item * C<< new(%options) >>
+
+Returns a new XML::Atom::FromOWL object.
+
+There are no valid options at the moment - the hash is reserved
+for future use.
+
+=back
+
+=head2 Methods
+
+=over
+
+=item * C<< export_feeds($input, %options) >>
+
+Returns a list of feeds found in the input, in no particular order.
+
+The input may be a URI, file name, L<RDF::Trine::Model> or anything else
+that can be handled by the C<rdf_parse> method of L<RDF::TrineShortcuts>.
+
+Each item in the list returned is an L<XML::Atom::Feed>.
+
+=item * C<< export_feed($input, $subject, %options) >>
+
+As per C<export_feeds> but exports just a single feed.
+
+The subject provided must be an RDF::Trine::Node::Blank or
+RDF::Trine::Node::Resource of type awol:Feed.
+
+=item * C<< export_entries($input, %options) >>
+
+Returns a list of entries found in the input, in no particular order.
+
+The input may be a URI, file name, L<RDF::Trine::Model> or anything else
+that can be handled by the C<rdf_parse> method of L<RDF::TrineShortcuts>.
+
+Each item in the list returned is an L<XML::Atom::Entry>.
+
+=item * C<< export_entry($input, $subject, %options) >>
+
+As per C<export_entry> but exports just a single entry.
+
+The subject provided must be an RDF::Trine::Node::Blank or
+RDF::Trine::Node::Resource of type awol:Entry.
+
+=back
+
+=head2 RDF Input
+
+Input is expected to use AtomOwl
+L<http://bblfish.net/work/atom-owl/2006-06-06/#>.
+
+=head2 Feed Output
+
+This module doesn't attempt to enforce many of OWL's semantic
+constraints (e.g. it doesn't enforce that an entry has only one
+title). It relies on L<XML::Atom::Feed> and L<XML::Atom::Entry>
+for that sort of thing, but if your input is sensible that
+shouldn't be a problem.
+
+=head1 SEE ALSO
+
+L<XML::Atom::OWL>, L<HTML::Microformats>, L<RDF::TrineShortcuts>,
+L<XML::Atom::Feed>, L<XML::Atom::Entry>.
+
+L<http://bblfish.net/work/atom-owl/2006-06-06/>.
+
+L<http://www.perlrdf.org/>.
+
+=head1 AUTHOR
+
+Toby Inkster E<lt>tobyink@cpan.orgE<gt>.
+
+=head1 COPYRIGHT
+
+Copyright 2011 Toby Inkster
+
+This library is free software; you can redistribute it and/or modify it
+under the same terms as Perl itself.
+
+use lib 'inc';
+use Test::More tests => 1;
+use Test::Signature;
+signature_ok();
+use Test::More tests => 1;
+BEGIN { use_ok('XML::Atom::FromOWL') };
+