Commits

Toby Inkster committed fadb797

Beginnings of RDF-vCard revamp.

Comments (0)

Files changed (26)

+############################################################################
+## Changes #################################################################
+############################################################################
+
+RDF-vCard
+=========
+
+Created:      2011-01-05
+Home page:    <http://search.cpan.org/dist/RDF-vCard/>
+Bug tracker:  <http://rt.cpan.org/Dist/Display.html?Queue=RDF-vCard>
+Maintainer:   Toby Inkster <mailto:tobyink@cpan.org>
+
+0.008 [2011-07-13]
+ - (Addition) Use Content-Language HTTP header of downloaded vCards.
+ - (Addition) Add support for 'lang' option when converting vCards to RDF.
+ - (Addition) vcard2rdf script.
+ - (Addition) Include language tags on plain literals, if provided.
+
+0.007 [2011-02-16]
+ - (Addition) jCard output.
+ - (Bugfix) Setting vcard_version to 3 outputted version 4 vcards.
+
+0.006 [2011-02-04]
+ - (Bugfix) Fix WithXmlSupport's string overloading.
+
+0.005 [2011-01-30]
+ - (Addition) Partial vCard 4.0 support.
+ - (Addition) nvalue method on RDF::vCard::Line.
+ - (Addition) Experimental vCard XML support.
+
+0.004 [2011-01-23]
+ - More flexibility for other classes wanting to subclass this -
+   particularly around different datatypes.
+
+0.003 [2011-01-16] # Two-way conversions.
+ - (Addition) RDF::vCard::Entity now has add_to_model() and node() methods.
+ - (Addition) Add RDF::vCard::Importer module.
+ - (Addition) RDF::vCard::Line now has an add_to_model() method.
+
+0.002 [2011-01-12]
+ - Improved support for 'data:' URIs in binary vCard properties.
+ - More consistent support for comma-escaping.
+ - Proper support for vCard CATEGORIES property.
+ - Support for RDF vCard 2006 v:homeTel property and its ilk.
+ - Tidied up export_card method.
+ - (Addition) Include vCard VERSION property in output.
+
+0.001 [2011-01-06] # Initial release. RDF to vCard only.
+
+#############################################################
+
+@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/RDF-vCard/> .
+@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 RDF::vCard."@en ;
+	dc:subject       my:project ;
+	dc:creator       toby:i .
+
+#############################################################
+
+my:v_0-001
+
+	a               :Version ;
+	dc:issued       "2011-01-06"^^xsd:date ;
+	:revision       "0.001"^^xsd:string ;
+	:file-release   <http://backpan.cpan.org/authors/id/T/TO/TOBYINK/RDF-vCard-0.001.tar.gz> ;
+	rdfs:label      "Initial release. RDF to vCard only."@en .
+
+my:v_0-002
+
+	a               :Version ;
+	dc:issued       "2011-01-12"^^xsd:date ;
+	:revision       "0.002"^^xsd:string ;
+	:file-release   <http://backpan.cpan.org/authors/id/T/TO/TOBYINK/RDF-vCard-0.002.tar.gz> ;
+	dcs:changeset [
+		dcs:versus my:v_0-001 ;
+		dcs:item   [ rdfs:label "Proper support for vCard CATEGORIES property."@en ] ;
+		dcs:item   [ rdfs:label "Support for RDF vCard 2006 v:homeTel property and its ilk."@en ] ;
+		dcs:item   [ rdfs:label "Improved support for 'data:' URIs in binary vCard properties."@en ] ;
+		dcs:item   [ rdfs:label "Include vCard VERSION property in output."@en ; a dcs:Addition ] ;
+		dcs:item   [ rdfs:label "Tidied up export_card method."@en ] ;
+		dcs:item   [ rdfs:label "More consistent support for comma-escaping."@en ]
+		] .
+
+my:v_0-003
+
+	a               :Version ;
+	dc:issued       "2011-01-16"^^xsd:date ;
+	:revision       "0.003"^^xsd:string ;
+	:file-release   <http://backpan.cpan.org/authors/id/T/TO/TOBYINK/RDF-vCard-0.003.tar.gz> ;
+	rdfs:label      "Two-way conversions."@en ;
+	dcs:changeset [
+		dcs:versus my:v_0-002 ;
+		dcs:item   [ rdfs:label "Add RDF::vCard::Importer module."@en ; a dcs:Addition ] ;
+		dcs:item   [ rdfs:label "RDF::vCard::Entity now has add_to_model() and node() methods."@en ; a dcs:Addition ] ;
+		dcs:item   [ rdfs:label "RDF::vCard::Line now has an add_to_model() method."@en ; a dcs:Addition ]
+		] .
+
+my:v_0-004
+
+	a               :Version ;
+	dc:issued       "2011-01-23"^^xsd:date ;
+	:revision       "0.004"^^xsd:string ;
+	:file-release   <http://backpan.cpan.org/authors/id/T/TO/TOBYINK/RDF-vCard-0.004.tar.gz> ;
+	dcs:changeset [
+		dcs:versus my:v_0-003 ;
+		dcs:item   [ rdfs:label "More flexibility for other classes wanting to subclass this - particularly around different datatypes."@en ]
+		] .
+
+my:v_0-005
+
+	a               :Version ;
+	dc:issued       "2011-01-30"^^xsd:date ;
+	:revision       "0.005"^^xsd:string ;
+	:file-release   <http://backpan.cpan.org/authors/id/T/TO/TOBYINK/RDF-vCard-0.005.tar.gz> ;
+	dcs:changeset [
+		dcs:versus my:v_0-004 ;
+		dcs:item   [ rdfs:label "nvalue method on RDF::vCard::Line."@en ; a dcs:Addition ] ;
+		dcs:item   [ rdfs:label "Experimental vCard XML support."@en ; a dcs:Addition ] ;
+		dcs:item   [ rdfs:label "Partial vCard 4.0 support."@en ; a dcs:Addition ]
+		] .
+
+my:v_0-006
+
+	a               :Version ;
+	dc:issued       "2011-02-04"^^xsd:date ;
+	:revision       "0.006"^^xsd:string ;
+	:file-release   <http://backpan.cpan.org/authors/id/T/TO/TOBYINK/RDF-vCard-0.006.tar.gz> ;
+	dcs:changeset [
+		dcs:versus my:v_0-005 ;
+		dcs:item   [ rdfs:label "Fix WithXmlSupport's string overloading."@en ; a dcs:Bugfix ]
+		] .
+
+my:v_0-007
+
+	a               :Version ;
+	dc:issued       "2011-02-16"^^xsd:date ;
+	:revision       "0.007"^^xsd:string ;
+	:file-release   <http://backpan.cpan.org/authors/id/T/TO/TOBYINK/RDF-vCard-0.007.tar.gz> ;
+	dcs:changeset [
+		dcs:versus my:v_0-006 ;
+		dcs:item   [ rdfs:label "Setting vcard_version to 3 outputted version 4 vcards."@en ; a dcs:Bugfix ] ;
+		dcs:item   [ rdfs:label "jCard output."@en ; a dcs:Addition ]
+		] .
+
+my:v_0-008
+
+	a               :Version ;
+	dc:issued       "2011-07-13"^^xsd:date ;
+	:revision       "0.008"^^xsd:string ;
+	:file-release   <http://backpan.cpan.org/authors/id/T/TO/TOBYINK/RDF-vCard-0.008.tar.gz> ;
+	dcs:changeset [
+		dcs:versus my:v_0-007 ;
+		dcs:item   [ rdfs:label "vcard2rdf script."@en ; a dcs:Addition ] ;
+		dcs:item   [ rdfs:label "Add support for 'lang' option when converting vCards to RDF."@en ; a dcs:Addition ] ;
+		dcs:item   [ rdfs:label "Include language tags on plain literals, if provided."@en ; a dcs:Addition ] ;
+		dcs:item   [ rdfs:label "Use Content-Language HTTP header of downloaded vCards."@en ; a dcs:Addition ]
+		] .
+
+#############################################################
+
+my:project
+
+	a               :Project ;
+	:name           "RDF-vCard" ;
+	:shortdesc      "convert between RDF and vCard"@en ;
+	:programming-language  "Perl" ;
+	:homepage       <https://metacpan.org/release/RDF-vCard> ;
+	:download-page  <https://metacpan.org/release/RDF-vCard> ;
+	:bug-database   <http://rt.cpan.org/Dist/Display.html?Queue=RDF-vCard> ;
+	:repository     [ a :SVNRepository ; :browse <http://goddamn.co.uk/svn-web/perlmods/browse/RDF-vCard/> ] ;
+	:maintainer     toby:i ;
+	:developer      toby:i ;
+	:documenter     toby:i ;
+	:tester         toby:i ;
+	:created        "2011-01-05"^^xsd:date ;
+	:license        <http://dev.perl.org/licenses/> ;
+	:release        my:v_0-001 , my:v_0-002 , my:v_0-003 , my:v_0-004 , my:v_0-005 , my:v_0-006 , my:v_0-007 , my:v_0-008 .
+
+#############################################################
+
+toby:i
+
+	a               foaf:Person ;
+	foaf:name       "Toby Inkster" ;
+	foaf:homepage   <http://tobyinkster.co.uk/> ;
+	foaf:page       <https://metacpan.org/author/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="r1C655D92AD3111E0B6AB90F6C72C0CC0r0">
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r1"/>
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r2"/>
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r3"/>
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r4"/>
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r5"/>
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r6"/>
+	<dcs:versus rdf:resource="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-001"/>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r1">
+	<rdfs:label xml:lang="en">Proper support for vCard CATEGORIES property.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r10">
+	<rdf:type rdf:resource="http://ontologi.es/doap-changeset#Addition"/>
+	<rdfs:label xml:lang="en">RDF::vCard::Line now has an add_to_model() method.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r11">
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r12"/>
+	<dcs:versus rdf:resource="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-003"/>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r12">
+	<rdfs:label xml:lang="en">More flexibility for other classes wanting to subclass this - particularly around different datatypes.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r13">
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r14"/>
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r15"/>
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r16"/>
+	<dcs:versus rdf:resource="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-004"/>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r14">
+	<rdf:type rdf:resource="http://ontologi.es/doap-changeset#Addition"/>
+	<rdfs:label xml:lang="en">nvalue method on RDF::vCard::Line.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r15">
+	<rdf:type rdf:resource="http://ontologi.es/doap-changeset#Addition"/>
+	<rdfs:label xml:lang="en">Experimental vCard XML support.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r16">
+	<rdf:type rdf:resource="http://ontologi.es/doap-changeset#Addition"/>
+	<rdfs:label xml:lang="en">Partial vCard 4.0 support.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r17">
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r18"/>
+	<dcs:versus rdf:resource="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-005"/>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r18">
+	<rdf:type rdf:resource="http://ontologi.es/doap-changeset#Bugfix"/>
+	<rdfs:label xml:lang="en">Fix WithXmlSupport's string overloading.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r19">
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r20"/>
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r21"/>
+	<dcs:versus rdf:resource="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-006"/>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r2">
+	<rdfs:label xml:lang="en">Support for RDF vCard 2006 v:homeTel property and its ilk.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r20">
+	<rdf:type rdf:resource="http://ontologi.es/doap-changeset#Bugfix"/>
+	<rdfs:label xml:lang="en">Setting vcard_version to 3 outputted version 4 vcards.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r21">
+	<rdf:type rdf:resource="http://ontologi.es/doap-changeset#Addition"/>
+	<rdfs:label xml:lang="en">jCard output.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r22">
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r23"/>
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r24"/>
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r25"/>
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r26"/>
+	<dcs:versus rdf:resource="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-007"/>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r23">
+	<rdf:type rdf:resource="http://ontologi.es/doap-changeset#Addition"/>
+	<rdfs:label xml:lang="en">vcard2rdf script.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r24">
+	<rdf:type rdf:resource="http://ontologi.es/doap-changeset#Addition"/>
+	<rdfs:label xml:lang="en">Add support for 'lang' option when converting vCards to RDF.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r25">
+	<rdf:type rdf:resource="http://ontologi.es/doap-changeset#Addition"/>
+	<rdfs:label xml:lang="en">Include language tags on plain literals, if provided.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r26">
+	<rdf:type rdf:resource="http://ontologi.es/doap-changeset#Addition"/>
+	<rdfs:label xml:lang="en">Use Content-Language HTTP header of downloaded vCards.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r27">
+	<doap:browse rdf:resource="http://goddamn.co.uk/viewvc/perlmods/RDF-vCard/"/>
+	<rdf:type rdf:resource="http://usefulinc.com/ns/doap#SVNRepository"/>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r3">
+	<rdfs:label xml:lang="en">Improved support for 'data:' URIs in binary vCard properties.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r4">
+	<rdf:type rdf:resource="http://ontologi.es/doap-changeset#Addition"/>
+	<rdfs:label xml:lang="en">Include vCard VERSION property in output.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r5">
+	<rdfs:label xml:lang="en">Tidied up export_card method.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r6">
+	<rdfs:label xml:lang="en">More consistent support for comma-escaping.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r7">
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r10"/>
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r8"/>
+	<dcs:item rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r9"/>
+	<dcs:versus rdf:resource="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-002"/>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r8">
+	<rdf:type rdf:resource="http://ontologi.es/doap-changeset#Addition"/>
+	<rdfs:label xml:lang="en">Add RDF::vCard::Importer module.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r9">
+	<rdf:type rdf:resource="http://ontologi.es/doap-changeset#Addition"/>
+	<rdfs:label xml:lang="en">RDF::vCard::Entity now has add_to_model() and node() methods.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:about="file:///home/tai/src/perlmods/RDF-vCard/Changes.ttl">
+	<dc:creator rdf:resource="http://tobyinkster.co.uk/#i"/>
+	<dc:description xml:lang="en">Revision history for Perl extension RDF::vCard.</dc:description>
+	<dc:subject rdf:resource="http://purl.org/NET/cpan-uri/dist/RDF-vCard/project"/>
+	<dc:title>Changes</dc:title>
+</rdf:Description>
+<rdf:Description rdf:about="http://purl.org/NET/cpan-uri/dist/RDF-vCard/project">
+	<doap:bug-database rdf:resource="http://rt.cpan.org/Dist/Display.html?Queue=RDF-vCard"/>
+	<doap:created rdf:datatype="http://www.w3.org/2001/XMLSchema#date">2011-01-05</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/RDF-vCard/"/>
+	<doap:homepage rdf:resource="http://search.cpan.org/dist/RDF-vCard/"/>
+	<doap:license rdf:resource="http://dev.perl.org/licenses/"/>
+	<doap:maintainer rdf:resource="http://tobyinkster.co.uk/#i"/>
+	<doap:name>RDF-vCard</doap:name>
+	<doap:programming-language>Perl</doap:programming-language>
+	<doap:release rdf:resource="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-001"/>
+	<doap:release rdf:resource="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-002"/>
+	<doap:release rdf:resource="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-003"/>
+	<doap:release rdf:resource="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-004"/>
+	<doap:release rdf:resource="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-005"/>
+	<doap:release rdf:resource="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-006"/>
+	<doap:release rdf:resource="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-007"/>
+	<doap:release rdf:resource="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-008"/>
+	<doap:repository rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r27"/>
+	<doap:shortdesc xml:lang="en">convert between RDF and vCard</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/RDF-vCard/v_0-001">
+	<dc:issued rdf:datatype="http://www.w3.org/2001/XMLSchema#date">2011-01-06</dc:issued>
+	<doap:file-release rdf:resource="http://backpan.cpan.org/authors/id/T/TO/TOBYINK/RDF-vCard-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. RDF to vCard only.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:about="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-002">
+	<dcs:changeset rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r0"/>
+	<dc:issued rdf:datatype="http://www.w3.org/2001/XMLSchema#date">2011-01-12</dc:issued>
+	<doap:file-release rdf:resource="http://backpan.cpan.org/authors/id/T/TO/TOBYINK/RDF-vCard-0.002.tar.gz"/>
+	<doap:revision rdf:datatype="http://www.w3.org/2001/XMLSchema#string">0.002</doap:revision>
+	<rdf:type rdf:resource="http://usefulinc.com/ns/doap#Version"/>
+</rdf:Description>
+<rdf:Description rdf:about="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-003">
+	<dcs:changeset rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r7"/>
+	<dc:issued rdf:datatype="http://www.w3.org/2001/XMLSchema#date">2011-01-16</dc:issued>
+	<doap:file-release rdf:resource="http://backpan.cpan.org/authors/id/T/TO/TOBYINK/RDF-vCard-0.003.tar.gz"/>
+	<doap:revision rdf:datatype="http://www.w3.org/2001/XMLSchema#string">0.003</doap:revision>
+	<rdf:type rdf:resource="http://usefulinc.com/ns/doap#Version"/>
+	<rdfs:label xml:lang="en">Two-way conversions.</rdfs:label>
+</rdf:Description>
+<rdf:Description rdf:about="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-004">
+	<dcs:changeset rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r11"/>
+	<dc:issued rdf:datatype="http://www.w3.org/2001/XMLSchema#date">2011-01-23</dc:issued>
+	<doap:file-release rdf:resource="http://backpan.cpan.org/authors/id/T/TO/TOBYINK/RDF-vCard-0.004.tar.gz"/>
+	<doap:revision rdf:datatype="http://www.w3.org/2001/XMLSchema#string">0.004</doap:revision>
+	<rdf:type rdf:resource="http://usefulinc.com/ns/doap#Version"/>
+</rdf:Description>
+<rdf:Description rdf:about="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-005">
+	<dcs:changeset rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r13"/>
+	<dc:issued rdf:datatype="http://www.w3.org/2001/XMLSchema#date">2011-01-30</dc:issued>
+	<doap:file-release rdf:resource="http://backpan.cpan.org/authors/id/T/TO/TOBYINK/RDF-vCard-0.005.tar.gz"/>
+	<doap:revision rdf:datatype="http://www.w3.org/2001/XMLSchema#string">0.005</doap:revision>
+	<rdf:type rdf:resource="http://usefulinc.com/ns/doap#Version"/>
+</rdf:Description>
+<rdf:Description rdf:about="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-006">
+	<dcs:changeset rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r17"/>
+	<dc:issued rdf:datatype="http://www.w3.org/2001/XMLSchema#date">2011-02-04</dc:issued>
+	<doap:file-release rdf:resource="http://backpan.cpan.org/authors/id/T/TO/TOBYINK/RDF-vCard-0.006.tar.gz"/>
+	<doap:revision rdf:datatype="http://www.w3.org/2001/XMLSchema#string">0.006</doap:revision>
+	<rdf:type rdf:resource="http://usefulinc.com/ns/doap#Version"/>
+</rdf:Description>
+<rdf:Description rdf:about="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-007">
+	<dcs:changeset rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r19"/>
+	<dc:issued rdf:datatype="http://www.w3.org/2001/XMLSchema#date">2011-02-16</dc:issued>
+	<doap:file-release rdf:resource="http://backpan.cpan.org/authors/id/T/TO/TOBYINK/RDF-vCard-0.007.tar.gz"/>
+	<doap:revision rdf:datatype="http://www.w3.org/2001/XMLSchema#string">0.007</doap:revision>
+	<rdf:type rdf:resource="http://usefulinc.com/ns/doap#Version"/>
+</rdf:Description>
+<rdf:Description rdf:about="http://purl.org/NET/cpan-uri/dist/RDF-vCard/v_0-008">
+	<dcs:changeset rdf:nodeID="r1C655D92AD3111E0B6AB90F6C72C0CC0r22"/>
+	<dc:issued rdf:datatype="http://www.w3.org/2001/XMLSchema#date">2011-07-13</dc:issued>
+	<doap:file-release rdf:resource="http://backpan.cpan.org/authors/id/T/TO/TOBYINK/RDF-vCard-0.008.tar.gz"/>
+	<doap:revision rdf:datatype="http://www.w3.org/2001/XMLSchema#string">0.008</doap:revision>
+	<rdf:type rdf:resource="http://usefulinc.com/ns/doap#Version"/>
+</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>
+^MANIFEST.bak$
+^Makefile.old$
+^Makefile$
+^blib/
+^pm_to_blib
+^blibdirs
+\.svn
+^example.*\.pl$
+^[^/]+\.(tar\.gz|tar\.bz2|tgz|tbz2|tbz|zip|tar)$
+^MYMETA.yml
+^example\.vcard
+---
+abstract: 'convert between RDF and vCard'
+author:
+  - 'Toby Inkster <tobyink@cpan.org>'
+build_requires:
+  ExtUtils::MakeMaker: 6.42
+  Test::More: 0.61
+configure_requires:
+  ExtUtils::MakeMaker: 6.42
+distribution_type: module
+generated_by: 'Module::Install version 1.00'
+keywords:
+  - RDF
+  - Semantic
+  - Web
+  - SemWeb
+  - vCard
+  - Directory
+  - Contacts
+  - hCard
+  - XML
+license: perl
+meta-spec:
+  url: http://module-build.sourceforge.net/META-spec-v1.4.html
+  version: 1.4
+name: RDF-vCard
+no_index:
+  directory:
+    - inc
+    - t
+recommends:
+  XML::LibXML: 1.60
+requires:
+  Encode: 0
+  MIME::Base64: 0
+  RDF::Trine: 0.130
+  RDF::TrineShortcuts: 0.100
+  Scalar::Util: 0
+  Text::vFile::asData: 0.07
+  URI: 0
+  common::sense: 0
+  namespace::clean: 0
+  perl: 5.8.0
+resources:
+  bugtracker: http://rt.cpan.org/Dist/Display.html?Queue=RDF-vCard
+  homepage: http://search.cpan.org/dist/RDF-vCard/
+  license: http://dev.perl.org/licenses/
+  repository: http://goddamn.co.uk/viewvc/perlmods/RDF-vCard/
+version: 0.008
+use strict;
+use warnings;
+
+use inc::Module::Install;
+
+my $dist = 'RDF-vCard';
+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;
+requires            'Encode'              => 0;
+recommends          'XML::LibXML'         => '1.60';
+requires            'MIME::Base64'        => 0;
+requires            'namespace::clean'    => 0;
+requires            'RDF::Trine'          => '0.130';
+requires            'RDF::TrineShortcuts' => '0.100';
+requires            'Scalar::Util'        => 0;
+test_requires       'Test::More'          => '0.61';
+requires            'Text::vFile::asData' => '0.07';
+requires            'URI'                 => 0;
+
+resources(
+	'homepage'   => "https://metacpan.org/release/$dist",
+	'repository' => "http://goddamn.co.uk/svn-web/perlmods/browse/$dist/",
+	'bugtracker' => "http://rt.cpan.org/Dist/Display.html?Queue=$dist",
+	);
+
+keywords(qw(RDF Semantic Web SemWeb vCard Directory Contacts hCard XML));
+
+install_script 'vcard2rdf';
+install_script 'vcard2xml';
+
+write_doap_changes;
+write_doap_changes_xml;
+auto_license;
+auto_manifest;
+
+auto_install;
+WriteAll(
+	'meta' => 1,
+	'sign' => 1,
+	);
+NAME
+    RDF::vCard - convert between RDF and vCard
+
+SYNOPSIS
+     use RDF::vCard;
+     use RDF::TrineShortcuts qw(rdf_string);
+ 
+     my $input    = "http://example.com/contact-data.rdf";
+     my $exporter = RDF::vCard::Exporter->new;
+ 
+     my $data     = join '', $exporter->export_cards($input);
+     print $data; # vCard 3.0 data
+ 
+     my $importer = RDF::vCard::Importer->new;
+     $importer->import_string($data);
+     print rdf_string($importer->model => 'RDFXML');
+
+DESCRIPTION
+    This module doesn't do anything itself; it just loads
+    RDF::vCard::Exporter and RDF::vCard::Importer for you.
+
+  RDF::vCard::Exporter
+    RDF::vCard::Exporter takes some RDF using the W3C's vCard vocabulary,
+    and outputs RDF::vCard::Entity objects.
+
+  RDF::vCard::Importer
+    RDF::vCard::Importer does the reverse.
+
+  RDF::vCard::Entity
+    An RDF::vCard::Entity objects is an individual vCard. It overloads
+    stringification, so just treat it like a string.
+
+  RDF::vCard::Entity::WithXmlSupport
+    RDF::vCard::Entity::WithXmlSupport is a subclass of RDF::vCard::Entity,
+    with a "to_xml" method. It requires XML::LibXML to be installed and
+    working. The importer and exporter will try to create these if possible.
+
+  RDF::vCard::Line
+    RDF::vCard::Line is internal fu that you probably don't want to touch.
+
+BUGS
+    If your RDF asserts that Alice is Bob's AGENT and Bob is Alice's AGENT,
+    then RDF::vCard::Export will eat your face. Don't do it.
+
+    Please report any other bugs to
+    <https://rt.cpan.org/Public/Dist/Display.html?Name=RDF-vCard>.
+
+SEE ALSO
+    <http://www.w3.org/Submission/vcard-rdf/>.
+
+    <http://perlrdf.org/>.
+
+    RDF::vCard::Babelfish.
+
+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.
+
+TODO:
+* Add some more items to this list.
+* Add keywords to Makefile.PL
+BEGIN:VCARD
+fn:Toby Inkster
+note;lang=en:This is a test.
+END:VCARD
+BEGIN:VCARD
+fn:Test 2
+END:VCARD
+use lib "lib";
+use lib "../HTML-Microformats/lib";
+use RDF::TrineShortcuts;
+use HTML::Microformats;
+use RDF::vCard;
+use JSON -convert_blessed_universally;
+
+my $html = <<'HTML';
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<head>
+		<title>Example</title>
+	</head>
+	<body>
+		<div class="vcard">
+			<h1><a href="/" class="fn url">Alice Jones</a></h1>
+			<p class="adr">
+				<span class="locality">Lewes</span>,
+				<span class="region">East Sussex</span>
+				(<span class="geo">50.87363;0.01133</span>)
+			</p>
+			<div class="agent vcard">
+				<span class="role">Secretary</span>
+				<a class="fn email" href="mailto:bob@example.com">Bob Smith</a>
+			</div>
+			<div>Updated: <span class="rev">2011-01-06T11:00:00Z</span></div>
+		</div>
+	</body>
+</html>
+HTML
+
+my $doc = HTML::Microformats->new_document($html, "http://example.com/", type=>'application/xhtml+xml')->assume_all_profiles;
+
+my $model = rdf_parse(<<'MORE', type=>'turtle', model => $doc->model);
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix v: <http://www.w3.org/2006/vcard/ns#> .
+@prefix vx: <http://buzzword.org.uk/rdf/vcardx#> .
+
+<http://example.net/taxo/Quux> rdf:value "Quux" .
+
+  <http://example.com/> a v:VCard ;
+     v:fn "Example.Com LLC"@en ;
+     v:org
+         [   v:organisation-name "Example.Com LLC" ;
+             v:organisation-unit "Corporate Division"
+         ] ;
+	  vx:category <http://example.net/taxo/Quux> , <http://example.net/taxo/Xyzzy> ;
+	  v:category "Corporate", "Foobar";
+     v:logo <data:image/gif;base64,R0lGODdhIAAgAIAAAAAAAPj8+CwAAAAAIAAgAAAClYyPqcu9AJyCjtIKc5w5xP14xgeO2tlY3nWcajmZZdeJcGKxrmimms1KMTa1Wg8UROx4MNUq1HrycMjHT9b6xKxaFLM6VRKzI+pKS9XtXpcbdun6uWVxJXA8pNPkdkkxhxc21LZHFOgD2KMoQXa2KMWIJtnE2KizVUkYJVZZ1nczBxXlFopZBtoJ2diXGdNUymmJdFMAADs=> ;
+     v:homeAdr
+         [ a v:Work ;
+             v:country-name "Australia" ;
+             v:locality "WonderLand", "WonderCity" ;
+             v:postal-code "5555" ;
+             v:street-address "33 Enterprise Drive"
+         ] ;
+     v:geo
+         [ v:latitude "43.33" ;
+             v:longitude "55.45"
+         ] ;
+     v:tel
+         [ a v:Fax, v:Work , v:Pref ;
+             rdf:value "+61 7 5555 0000"
+         ] ; 
+     v:email <mailto:info@example.com> ;
+     v:logo <http://example.com/logo.png> .
+MORE
+
+#print rdf_string($model => 'rdfxml');
+#print "#######\n";
+
+my $exporter = RDF::vCard::Exporter->new(vcard_version=>4);
+my @cards = $exporter->export_cards($model);
+foreach my $c (@cards)
+{
+	print $c;
+	print "\n";
+	print $c->to_xml;
+	print "\n";
+	print "\n";
+}
+use lib "lib";
+use Data::Dumper;
+use RDF::TrineShortcuts;
+use HTML::Microformats;
+use RDF::vCard;
+use RDF::vCard::Importer;
+use JSON -convert_blessed_universally;
+
+my $html = <<'HTML';
+<html xmlns="http://www.w3.org/1999/xhtml">
+	<head>
+		<title>Example</title>
+	</head>
+	<body>
+		<div class="vcard">
+			<h1><a href="/" class="fn url">Alice Jones</a></h1>
+			<p class="adr"><span class="locality">Lewes</span>, <span class="region">East Sussex</span></p>
+			<div class="agent vcard">
+				<span class="role">Secretary</span>
+				<a class="fn email" href="mailto:bob@example.com">Bob Smith</a>
+			</div>
+			<div>Updated: <span class="rev">2011-01-06T11:00:00Z</span></div>
+		</div>
+	</body>
+</html>
+HTML
+
+my $doc = HTML::Microformats->new_document($html, "http://example.com/", type=>'application/xhtml+xml')->assume_all_profiles;
+
+my $model = rdf_parse(<<'MORE', type=>'turtle', model => $doc->model);
+@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
+@prefix v: <http://www.w3.org/2006/vcard/ns#> .
+@prefix vx: <http://buzzword.org.uk/rdf/vcardx#> .
+
+<http://example.net/taxo/Quux> rdf:value "Quux" .
+
+  <http://example.com/> a v:VCard ;
+     v:fn "Example.Com LLC" ;
+     v:org
+         [   v:organisation-name "Dewey, Cheatem and Howe" ;
+             v:organisation-unit "Corporate Division"
+         ] ;
+	  vx:category <http://example.net/taxo/Quux> , <http://example.net/taxo/Xyzzy> ;
+	  v:category "Corporate", "Foobar";
+     v:logo <data:image/gif;base64,R0lGODdhIAAgAIAAAAAAAPj8+CwAAAAAIAAgAAAClYyPqcu9AJyCjtIKc5w5xP14xgeO2tlY3nWcajmZZdeJcGKxrmimms1KMTa1Wg8UROx4MNUq1HrycMjHT9b6xKxaFLM6VRKzI+pKS9XtXpcbdun6uWVxJXA8pNPkdkkxhxc21LZHFOgD2KMoQXa2KMWIJtnE2KizVUkYJVZZ1nczBxXlFopZBtoJ2diXGdNUymmJdFMAADs=> ;
+     v:homeAdr
+         [ a v:Work ;
+             v:country-name "Australia" ;
+             v:locality "WonderLand", "WonderCity" ;
+             v:postal-code "5555" ;
+             v:street-address "33 Enterprise Drive"
+         ] ;
+     v:geo
+         [ v:latitude "43.33" ;
+             v:longitude "55.45"
+         ] ;
+     v:tel
+         [ a v:Fax, v:Work ;
+             rdf:value "+61 7 5555 0000"
+         ] ; 
+     v:email <mailto:info@example.com> ;
+     v:logo <http://example.com/logo.png> .
+MORE
+
+my $exporter = RDF::vCard::Exporter->new;
+my @cards = $exporter->export_cards($model);
+my $cards = join "", @cards;
+
+my $importer = RDF::vCard::Importer->new;
+my @rv = $importer->import_string($cards);
+print $cards;
+print Dumper([ @rv ]);
+print rdf_string($importer => 'RDFXML');
+#!/usr/bin/perl
+
+use strict;
+use lib "lib";
+use RDF::vCard;
+use Data::Dumper;
+
+my $vc = <<VCARD;
+begin:vcard
+fn:Test vCard
+note;lang=en-gb:This is just a test.
+end:vcard
+VCARD
+
+my $i = RDF::vCard::Importer->new;
+my @cards = $i->import_string($vc, lang=>'en');
+
+print Dumper(@cards);
+package AB;
+
+use 5.010;
+use common::sense;
+use utf8;
+
+BEGIN {
+	our $AB::AUTHORITY = q<cpan:TOBYINK>;
+	our $AB::VERSION   = q<0.010>;
+}
+
+use Carp qw//;
+
+use Module::Pluggable
+	search_path => [qw/AB::Reader ABx::Reader/],
+	require     => 1,
+	sub_name    => q(readers);
+
+use Module::Pluggable
+	search_path => [qw/AB::Writer ABx::Writer/],
+	require     => 1,
+	sub_name    => q(writers);
+
+use Module::Pluggable
+	search_path => [qw/AB::Util ABx::Util/],
+	require     => 1,
+	sub_name    => q(utilities);
+
+sub new
+{
+	my ($class, $role, $format, @args) = @_;
+	
+	my @plugins = do {
+		if ($format =~ /read/i)    { $class->readers }
+		elsif ($format =~ /writ/i) { $class->writers }
+		elsif ($format =~ /util/i) { $class->utilities }
+		else { Carp::croak q/What sort of thing did you want to instantiate?/ }
+		};
+	
+	my ($requested) = my @candidates = grep { /(^|::)${format}$/i } sort @plugins;
+	
+	Carp::croak q/No appropriate plugin found/
+		unless @candidates;
+	Carp::carp sprintf(q/Multiple candidates: %s - chosen %s/, (join q{, }, @candidates), $requested)
+		if scalar @candidates > 1;
+	
+	$requested->new(@args);
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+AB - manage contacts
+
+=head1 SYNOPSIS
+
+ use AB;
+ use RDF::TrineShortcuts qw(rdf_string);
+ 
+ my $input  = "http://example.com/contact-data.rdf";
+ my $reader = AB->new(reader => 'RDF');
+ 
+ my $address_book = $reader->read($input);
+ print $address_book; # vCard 3.0 data
+ 
+ my $writer = AB->new(writer => 'jCard');
+ print $writer->write($address_book); # jCard data
+
+=head1 DESCRIPTION
+
+This is a refactored version of the distribution formerly known as RDF-vCard.
+RDF-vCard will continue to exist 
+
+=head1 BUGS
+
+If your RDF asserts that Alice is Bob's AGENT and Bob is Alice's AGENT, then
+L<AB::Reader::RDF> will eat your face. Don't do it.
+
+Please report any other bugs to
+L<https://rt.cpan.org/Public/Dist/Display.html?Name=AB>.
+
+=head1 SEE ALSO
+
+L<http://www.w3.org/Submission/vcard-rdf/>.
+
+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 AB::Book;
+
+use 5.010;
+use common::sense;
+use utf8;
+
+use overload '@{}' => \&cards;
+use overload '""'  => \&to_string;
+
+sub new
+{
+	my ($class, @cards) = @_;
+	my %opts = %{ shift @cards }
+		if ref $cards[0] eq 'HASH';
+	$opts{cards} = \@cards;
+	bless \%opts, $class;
+}
+
+sub cards
+{
+	return @{ $_[0]->{cards} // [] };
+}
+
+sub to_string
+{
+	return join '', $_[0]->cards;
+}
+
+1;
+
+package AB::Entity;
+
+use 5.008;
+use common::sense;
+
+use JSON qw[];
+use RDF::TrineShortcuts ':all';
+
+sub V    { return 'http://www.w3.org/2006/vcard/ns#' . shift; }
+sub VX   { return 'http://buzzword.org.uk/rdf/vcardx#' . 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;
+
+use overload '""' => \&to_string;
+our $VERSION = '0.008';
+
+sub new
+{
+	my ($class, %options) = @_;
+	$options{profile}    ||= 'VCARD';
+	$options{lines}      ||= [];
+	$options{components} ||= [];
+	$options{node}       ||= $class->_node;
+	bless { %options }, $class;
+}
+
+sub _node
+{
+	my ($class) = @_;
+	return RDF::Trine::Node::Blank->new;
+}
+
+sub profile
+{
+	my ($self) = @_;
+	return $self->{profile};
+}
+
+sub lines
+{
+	my ($self) = @_;
+	return $self->{lines};
+}
+
+sub components
+{
+	my ($self) = @_;
+	return $self->{components};
+}
+
+sub add
+{
+	my ($self, $line) = @_;
+	push @{ $self->lines }, $line;
+	$self->_entity_order_fu($line);	
+	return $self;
+}
+
+sub add_component
+{
+	my ($self, $c) = @_;
+	push @{ $self->components }, $c;
+	return $self;
+}
+
+sub get
+{
+	my ($self, $property) = @_;
+	return grep {
+			lc $_->property eq lc $property
+		} @{ $self->lines };
+}
+
+sub matches
+{
+	my ($self, $property, $regexp) = @_;
+	return grep {
+		$_->value_to_string =~ $regexp;
+		} $self->get($property);
+}
+
+sub entity_order
+{
+	my ($self) = @_;
+	
+	return $self->{property}{'sort-string'}
+		||  $self->{property}{'n'}
+		||  $self->{property}{'n-faked'}
+		||  $self->{property}{'fn'}
+		||  $self->{property}{'nickname'};
+}
+
+sub _entity_order_fu
+{
+	my ($self, $line) = @_;
+	
+	if ($line->property =~ /^(sort.string|n|fn|nickname)$/i)
+	{
+		my $x = $line->value_to_string;
+		$self->{property}{ lc $line->property } = $x if length $x;
+		
+		if (lc $line->property eq 'fn')
+		{
+			my @parts = split /\s+/, $x;
+			my $last  = pop @parts;
+			unshift @parts, $last;
+			$self->{property}{'n-faked'} = join ';', @parts;
+		}
+	}
+	return $self;
+}
+
+sub to_string
+{
+	my ($self) = @_;
+	
+	my @lines = sort {
+		$a->property_order cmp $b->property_order;
+		} @{$self->lines};
+
+	my @components = sort {
+		$a->entity_order cmp $b->entity_order;
+		} @{$self->components};
+
+	my $str = sprintf("BEGIN:%s\r\n", $self->profile);
+	foreach my $line (@lines)
+	{
+		$str .= $line . "\r\n";
+	}
+	foreach my $component (@components)
+	{
+		$str .= $component;
+	}
+	$str .= sprintf("END:%s\r\n", $self->profile);
+	
+	return $str;
+}
+
+sub node
+{
+	my ($self) = @_;
+	return $self->{node};
+}
+
+sub add_to_model
+{
+	my ($self, $model) = @_;
+	
+	$model->add_statement(rdf_statement(
+		$self->node,
+		rdf_resource( RDF('type') ),
+		rdf_resource( V('VCard') ),
+		));
+
+	foreach my $line (@{ $self->lines })
+	{
+		$line->add_to_model($model, $self->node);
+	}
+	
+	return $self;
+}
+
+sub to_jcard
+{
+	my ($self, $hashref) = @_;
+	return ($hashref ? $self->TO_JSON : JSON::to_json($self));
+}
+
+{
+	my @singular = qw(fn n bday tz geo sort-string uid class rev
+		anniversary birth dday death gender kind prodid sex version);
+	my @typed = qw(email tel adr label impp);
+	
+	sub TO_JSON
+	{
+		my ($self) = @_;
+		my $object = {};
+		
+		foreach my $line (@{ $self->lines })
+		{
+			my $p = lc $line->property;
+			
+			if ($p eq 'n')
+			{
+				my $o;
+				my @sp = qw(family-name given-name additional-name
+					honorific-prefix honorific-suffix);
+				for my $i (0..4)
+				{
+					if ($line->nvalue->[$i] and @{$line->nvalue->[$i]})
+					{
+						$o->{ $sp[$i] } = [ @{$line->nvalue->[$i]} ];
+					}
+				}
+				push @{$object->{n}}, $o;
+			}
+			elsif ($p eq 'org')
+			{
+				my @components = map { $_->[0] } @{$line->nvalue};
+				my $o = { 'organization-name' => shift @components };
+				$o->{'organization-unit'} = \@components;
+				push @{$object->{n}}, $o;
+			}
+			elsif ($p eq 'adr')
+			{
+				my $o;
+				while (my ($k, $v) = each %{$line->type_parameters})
+				{
+					push @{$o->{$k}}, (ref $v eq 'ARRAY' ? @$v : $v);
+				}
+				if ($o->{type})
+				{
+					$o->{type} = [ sort map {lc $_} @{ $o->{type} } ]
+				}
+				my @sp = qw(post-office-box extended-address street-address
+					locality region country-name postal-code);
+				for my $i (0..6)
+				{
+					if ($line->nvalue->[$i] and @{$line->nvalue->[$i]})
+					{
+						$o->{ $sp[$i] } = [ @{$line->nvalue->[$i]} ];
+					}
+				}
+				push @{$object->{adr}}, $o;
+			}
+			elsif ($p eq 'categories')
+			{
+				push @{$object->{categories}}, '@@TODO';
+			}
+			elsif ($p eq 'geo')
+			{
+				$object->{geo} = {
+					latitude   => $line->nvalue->[0][0],
+					longitude  => $line->nvalue->[1][0],
+					};
+			}
+			elsif (grep { $_ eq $p } @typed)
+			{
+				my $o = {};
+				while (my ($k, $v) = each %{$line->type_parameters})
+				{
+					push @{$o->{$k}}, (ref $v eq 'ARRAY' ? @$v : $v);
+				}
+				$o->{value} = $line->nvalue->[0][0];
+				if ($o->{type})
+				{
+					$o->{type} = [ sort map {lc $_} @{ $o->{type} } ]
+				}
+				
+				push @{ $object->{$p} }, $o;
+			}
+			elsif (grep { $_ eq $p } @singular)
+			{
+				$object->{$p} ||= $line->nvalue->[0][0];
+			}
+			else
+			{
+				push @{ $object->{$p} }, $line->nvalue->[0][0];
+			}
+		}
+		
+		return $object;
+	}
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+AB::Entity - represents a single vCard
+
+=head1 DESCRIPTION
+
+Instances of this class correspond to individual vCard objects, though
+it could potentially be used as basis for other RFC 2425-based formats
+such as iCalendar.
+
+=head2 Constructor
+
+=over
+
+=item * C<< new(%options) >>
+
+Returns a new AB::Entity object.
+
+The only option worth worrying about is B<profile> which sets the
+profile for the entity. This defaults to "VCARD".
+
+AB::Entity overloads stringification, so you can do the following:
+
+  my $vcard = AB::Entity->new;
+  print $vcard if $vcard =~ /VCARD/i;
+
+=back
+
+=head2 Methods
+
+=over
+
+=item * C<< to_string() >>
+
+Formats the object according to RFC 2425 and RFC 2426.
+
+=item * C<< to_jcard() >>
+
+Formats the object according to L<http://microformats.org/wiki/jcard>.
+
+C<< to_jcard(1) >> will return the same data but without the JSON stringification.
+
+=item * C<< add_to_model($model) >>
+
+Given an RDF::Trine::Model, adds triples to the model for this entity.
+
+=item * C<< node() >>
+
+Returns an RDF::Trine::Node::Blank identifying this entity.
+
+=item * C<< entity_order() >>
+
+Returns a string along the lines of "Surname;Forename" useful for
+sorting a list of entities.
+
+=item * C<< profile() >>
+
+Returns the entity type - e.g. "VCARD".
+
+=item * C<< lines() >>
+
+Returns an arrayref of L<AB::Line> objects in the order they
+were originally added.
+
+This excludes the "BEGIN:VCARD" and "END:VCARD" lines.
+
+=item * C<< add($line) >>
+
+Add a L<AB::Line>.
+
+=item * C<< get($property) >>
+
+Returns a list of L<AB::Line> objects for the given property.
+
+e.g.
+
+  print "It has an address!\n" if ($vcard->get('ADR'));
+
+=item * C<< matches($property, $regexp) >>
+
+Checks to see if a property's value matches a regular expression.
+
+  print "In London\n" if $vcard->matches(ADR => /London/);
+
+=back
+
+=head1 SEE ALSO
+
+L<RDF::vCard>.
+
+=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.
+

lib/AB/Interfaces/Reader.pm

+package AB::Interfaces::Reader;
+
+use 5.010;
+use autodie;
+use common::sense;
+use MooseX::Declare;
+
+BEGIN {
+	$AB::Interfaces::Reader::AUTHORITY = 'cpan:TOBYINK';
+	$AB::Interfaces::Reader::VERSION   = '0.100';
+}
+
+use HTTP::Headers;
+use LWP::UserAgent;
+
+role AB::Interfaces::Reader
+{
+	requires 'import_from_string';
+	requires 'http_accept_header';
+	
+	has user_agent => (
+		is       => 'ro',
+		isa      => 'LWP::UserAgent',
+		lazy     => 1,
+		builder  => '_build_user_agent',
+		);
+		
+	has user_agent_class => (
+		is       => 'ro',
+		isa      => 'ClassName',
+		default  => 'LWP::UserAgent',
+		);
+	
+	has http_user_agent_header => (
+		is       => 'ro',
+		isa      => 'Str',
+		lazy     => 1,
+		default  => sub { sprintf 'AB/%s (%s)', AB->VERSION, AB->AUTHORITY },
+		);
+	
+	method _build_user_agent
+	{
+		my $headers = HTTP::Headers->new(
+			Accept      => $self->http_accept_header,
+			User_Agent  => $self->http_user_agent_header,
+			);
+		
+		$self
+			->user_agent_class
+			->new(default_headers => $headers);
+	}
+	
+	method import_from_file ($fh)
+	{
+		unless (ref $fh)
+		{
+			my $fn = $fh;
+			undef $fh;
+			open $fh, '>', $fn;
+		}
+		
+		my $str = do { local $/ = <$fh> };
+		$self->import_from_string($fh);
+	}
+}
+
+1;
+

lib/AB/Interfaces/Writer.pm

+package AB::Interfaces::Writer;
+
+use 5.010;
+use autodie;
+use common::sense;
+use MooseX::Declare;
+
+BEGIN {
+	$AB::Interfaces::Writer::AUTHORITY = 'cpan:TOBYINK';
+	$AB::Interfaces::Writer::VERSION   = '0.100';
+}
+
+role AB::Interfaces::Writer
+{
+	requires 'export_to_string';
+	
+	method export_to_file ($ab, $fh)
+	{
+		unless (ref $fh)
+		{
+			my $fn = $fh;
+			undef $fh;
+			open $fh, '<', $fn;
+		}
+		
+		print {$fh} $self->export_string($ab);
+	}
+}
+
+1;
+
+package AB::Line;
+
+use 5.008;
+use common::sense;
+
+use Encode;
+use MIME::Base64;
+use RDF::TrineShortcuts ':all';
+use URI::data;
+
+sub V    { return 'http://www.w3.org/2006/vcard/ns#' . shift; }
+sub VX   { return 'http://buzzword.org.uk/rdf/vcardx#' . 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;
+
+use overload '""' => \&to_string;
+our $VERSION = '0.008';
+
+sub new
+{
+	my ($class, %options) = @_;
+	die "Need to provide a property name\n"
+		unless defined $options{property};
+	$options{value} = [$options{value}]
+		unless ref $options{value} eq 'ARRAY';
+	$options{type_parameters} ||= [];
+	bless { %options }, $class;
+}
+
+sub property
+{
+	my ($self) = @_;
+	return $self->{property};
+}
+
+sub value
+{
+	my ($self) = @_;
+	return $self->{value};
+}
+
+sub nvalue
+{
+	my ($self) = @_;
+	my $value = $self->value;
+	my @nvalue;
+	foreach my $v (@$value)
+	{
+		push @nvalue, (ref($v) eq 'ARRAY' ? $v : [$v]);
+	}
+	return \@nvalue;
+}
+
+sub type_parameters
+{
+	my ($self) = @_;
+	$self->{type_parameters} = {} unless ref $self->{type_parameters} eq 'HASH';
+	return $self->{type_parameters};
+}
+
+sub property_order
+{
+	my ($self) = @_;
+	my $p = lc $self->property;
+	return 0 if $p eq 'version';
+	return 1 if $p eq 'prodid';
+	return 2 if $p eq 'source';
+	return 3 if $p eq 'kind';
+	return 4 if $p eq 'fn';
+	return 5 if $p eq 'n';
+	return 6 if $p eq 'org';
+	return $p;
+}
+
+sub to_string
+{
+	my ($self) = @_;
+	
+	my $str = uc $self->property;
+	if (keys %{ $self->type_parameters })
+	{
+		foreach my $parameter (sort keys %{ $self->type_parameters })
+		{
+			my $values = $self->type_parameters->{$parameter};
+			$values = [$values]
+				unless ref $values eq 'ARRAY';
+			my $values_string = join ",", map { $self->_escape_value($_, is_tp=>1) } @$values;
+			$str .= sprintf(";%s=%s", uc $parameter, $values_string);
+		}
+	}
+	$str .= ":";
+	$str .= $self->value_to_string;
+	
+	if (length $str > 75)
+	{
+		my $new = '';
+		while (length $str > 64)
+		{
+			$new .= substr($str, 0, 64) . "\r\n ";
+			$str  = substr($str, 64);
+		}
+		$new .= $str;
+		$str  = $new;
+	}
+	
+	return $str;
+}
+
+sub value_to_string
+{
+	my ($self) = @_;	
+	my $str = join ";",
+		map
+		{ join ",", map { $self->_escape_value($_) } @{$_}; }
+		@{ $self->nvalue };
+	$str =~ s/;+$//;
+	return $str;
+}
+
+sub _escape_value
+{
+	my ($self, $value, %options) = @_;
+	
+	if ($options{is_tp} and $value =~ /[;:,"]/)
+	{
+		$value =~ s/\\/\\\\/g;
+		$value =~ s/\"/\\\"/g;
+		return sprintf('"%s"', $value);
+	}
+	
+	$value =~ s/\\/\\\\/g;
+	
+	$value =~ s/\r//g;
+	$value =~ s/\n/\\n/g;
+	$value =~ s/;/\\;/g;
+	$value =~ s/,/\\,/g;
+	
+	return $value;
+}
+
+sub _unescape_value
+{
+	my ($self, $value, %options) = @_;
+	
+	$value =~ s/\\r//gi;
+	$value =~ s/\\n/\n/gi;
+	$value =~ s/\\;/;/g;
+	$value =~ s/\\,/,/g;
+	
+	$value =~ s/\\\\/\\/g;
+	
+	return $value;
+}
+
+# RDF Export Stuff...
+
+sub add_to_model
+{
+	my ($self, $model, $card_node) = @_;
+	
+	my $special_func = sprintf('_add_to_model_%s', uc $self->property);
+	if ($self->can($special_func))
+	{
+		$self->$special_func($model, $card_node);
+	}
+	elsif ($self->property_node)
+	{
+		$model->add_statement(rdf_statement(
+			$card_node,
+			$self->property_node,
+			$self->value_node,
+			));
+	}
+	return $self;
+}
+
+sub value_node
+{
+	my ($self) = @_;
+
+	return rdf_literal($self->value_to_string, datatype=>'date')
+		if (defined $self->type_parameters and uc $self->type_parameters->{VALUE} eq 'DATE');
+
+	return rdf_literal($self->value_to_string, datatype=>'dateTime')
+		if (defined $self->type_parameters and uc $self->type_parameters->{VALUE} eq 'DATE-TIME');
+
+	return rdf_resource($self->value_to_string)
+		if (defined $self->type_parameters and uc $self->type_parameters->{VALUE} eq 'URI');
+
+	if (defined $self->type_parameters
+	and uc $self->type_parameters->{VALUE} eq 'BINARY'
+	and uc $self->type_parameters->{ENCODING} eq 'B')
+	{
+		my $uri = URI->new('data:');
+		if (ref $self->type_parameters->{TYPE} eq 'ARRAY')
+		{
+			$uri->media_type(sprintf('image/%s', lc $self->type_parameters->{TYPE}->[0]));
+		}
+		elsif (ref $self->type_parameters->{TYPE})
+		{
+			$uri->media_type(sprintf('image/%s', lc $self->type_parameters->{TYPE}));
+		}
+		else
+		{
+			$uri->media_type('application/octet-stream');
+		}
+		$uri->data( decode_base64($self->value->[0]) );
+		return rdf_resource("$uri");
+	}
+
+	if (defined $self->type_parameters->{LANG})
+	{
+		return rdf_literal($self->value_to_string,
+			lang => $self->type_parameters->{LANG});
+	}
+
+	return rdf_literal($self->value_to_string);
+}
+
+sub property_node
+{
+	my ($self) = @_;
+	
+	return rdf_resource(V(lc $self->property))
+		if lc $self->property =~ /^(adr|agent|email|geo|key|logo|
+			n|org|photo|sound|tel|url|bday|category|class|fn|
+			label|mailer|nickname|note|prodid|rev|role|sort\-string|
+			title|tz|uid)$/xi;
+
+	return rdf_resource(VX(lc $self->property))
+		if lc $self->property =~ /^(kind|gender|sex|dday|
+			anniversary|lang|member|caladruri|caluri|fburl|
+			impp|source)$/xi;
+
+	return rdf_resource(VX(lc $self->property))
+		if lc $self->property =~ /^x-/;
+	
+	return;
+}
+
+{
+	my %usage_type = (
+		bbs      => V('BBS'),
+		car      => V('Car'),
+		cell     => V('Cell'),
+		dom      => V('Dom'),
+		fax      => V('Fax'),
+		home     => V('Home'),
+		internet => V('Internet'),
+		intl     => V('Intl'),
+		isdn     => V('ISDN'),
+		modem    => V('Modem'),
+		msg      => V('Msg'),
+		pager    => V('Pager'),
+		parcel   => V('Parcel'),
+		pcs      => V('PCS'),
+		postal   => V('Postal'),
+		pref     => V('Pref'),
+		video    => V('Video'),
+		voice    => V('Voice'),
+		work     => V('Work'),
+		x400     => V('X400'),
+		);
+
+	my %intrinsic_type = (
+		adr      => V('Address'),
+		email    => V('Email'),
+		impp     => VX('Impp'),
+		label    => V('Label'),
+		tel      => V('Tel'),
+		);
+
+	sub _add_to_model_typed_thing
+	{
+		my ($self, $model, $card_node) = @_;
+		my $intermediate_node = RDF::Trine::Node::Blank->new;
+		
+		$model->add_statement(rdf_statement(
+			$card_node,
+			$self->property_node,
+			$intermediate_node,
+			));
+		
+		$model->add_statement(rdf_statement(
+			$intermediate_node,
+			rdf_resource(RDF('type')),
+			rdf_resource($intrinsic_type{ lc $self->property }),
+			))
+			if $intrinsic_type{ lc $self->property };
+		
+		$model->add_statement(rdf_statement(
+			$intermediate_node,
+			rdf_resource(RDF('value')),
+			$self->value_node,
+			));
+		
+		if ($self->type_parameters)
+		{
+			foreach my $type (@{ $self->type_parameters->{TYPE} })
+			{
+				if ($usage_type{lc $type})
+				{
+					$model->add_statement(rdf_statement(
+						$intermediate_node,
+						rdf_resource(RDF('type')),
+						rdf_resource($usage_type{lc $type}),
+						));
+				}
+				$model->add_statement(rdf_statement(
+					$intermediate_node,
+					rdf_resource(VX('usage')),
+					rdf_literal($type),
+					));
+			}
+		}
+		
+		return $intermediate_node;  # useful for _add_to_model_ADR
+	}
+
+}
+
+*_add_to_model_TEL   = \&_add_to_model_typed_thing;
+*_add_to_model_EMAIL = \&_add_to_model_typed_thing;
+*_add_to_model_LABEL = \&_add_to_model_typed_thing;
+*_add_to_model_IMPP  = \&_add_to_model_typed_thing;
+
+sub _add_to_model_AGENT
+{
+	warn "Outputting AGENT property to RDF not yet supported.";
+}
+
+sub _add_to_model_ADR
+{
+	my ($self, $model, $card_node) = @_;
+	my $intermediate_node = $self->_add_to_model_typed_thing($model, $card_node);
+	
+	my @properties = (
+		V('post-office-box'),
+		V('extended-address'),
+		V('street-address'),
+		V('locality'),
+		V('region'),
+		V('postal-code'),
+		V('country-name'),
+		);
+	my @components = @{ $self->nvalue };
+	
+	for (my $i=0; defined $properties[$i]; $i++)
+	{
+		next unless $components[$i] && @{ $components[$i] };
+		
+		foreach my $v (@{ $components[$i] })
+		{
+			$model->add_statement(rdf_statement(
+				$intermediate_node,
+				rdf_resource($properties[$i]),
+				rdf_literal($v),
+				));
+		}
+	}
+	
+	return $intermediate_node;
+}
+
+sub _add_to_model_GEO
+{
+	my ($self, $model, $card_node) = @_;
+	my $intermediate_node = RDF::Trine::Node::Blank->new;
+	
+	my @properties = (
+		V('latitude'),
+		V('longitude'),
+		);
+
+	$model->add_statement(rdf_statement(
+		$card_node,
+		$self->property_node,
+		$intermediate_node,
+		));
+	
+	$model->add_statement(rdf_statement(
+		$intermediate_node,
+		rdf_resource(RDF('type')),
+		rdf_resource(V('Location')),
+		));
+
+	my @components = @{ $self->nvalue };
+	
+	for (my $i=0; defined $properties[$i]; $i++)
+	{
+		next unless $components[$i] && @{ $components[$i] };
+		
+		foreach my $v (@{ $components[$i] })
+		{
+			$model->add_statement(rdf_statement(
+				$intermediate_node,
+				rdf_resource($properties[$i]),
+				rdf_literal($v),
+				));
+		}
+	}
+		
+	return $intermediate_node;
+}
+
+sub _add_to_model_N
+{
+	my ($self, $model, $card_node) = @_;
+	my $intermediate_node = RDF::Trine::Node::Blank->new;
+	
+	my @properties = (
+		V('family-name'),
+		V('given-name'),
+		V('additional-name'),
+		V('honorific-suffix'),
+		V('honorific-prefix'),
+		);
+
+	$model->add_statement(rdf_statement(
+		$card_node,
+		$self->property_node,
+		$intermediate_node,
+		));
+	
+	$model->add_statement(rdf_statement(
+		$intermediate_node,
+		rdf_resource(RDF('type')),
+		rdf_resource(V('Name')),
+		));
+
+	my @components = @{ $self->nvalue };
+	
+	for (my $i=0; defined $properties[$i]; $i++)
+	{
+		next unless $components[$i] && @{ $components[$i] };
+		
+		foreach my $v (@{ $components[$i] })
+		{
+			$model->add_statement(rdf_statement(
+				$intermediate_node,
+				rdf_resource($properties[$i]),
+				rdf_literal($v),
+				));
+		}
+	}
+	
+	return $intermediate_node;
+}
+
+sub _add_to_model_ORG
+{
+	my ($self, $model, $card_node) = @_;
+
+	my @units;
+	foreach my $v1 (@{ $self->nvalue })
+	{
+		push @units, @$v1;
+	}
+
+	my $intermediate_node = RDF::Trine::Node::Blank->new;
+	
+	$model->add_statement(rdf_statement(
+		$card_node,
+		$self->property_node,
+		$intermediate_node,
+		));
+	
+	$model->add_statement(rdf_statement(
+		$intermediate_node,
+		rdf_resource(RDF('type')),
+		rdf_resource(V('Organization')),
+		));
+
+	my $org = shift @units;
+
+	if ($org)
+	{
+		$model->add_statement(rdf_statement(
+			$intermediate_node,
+			rdf_resource(V('organization-name')),
+			rdf_literal($org),
+			));
+	}
+
+	foreach my $u (@units)
+	{
+		$model->add_statement(rdf_statement(
+			$intermediate_node,
+			rdf_resource(V('organization-unit')),
+			rdf_literal($u),
+			));
+	}
+}
+
+1;
+
+__END__
+
+=head1 NAME
+
+AB::Line - analogous to a line within a vCard
+
+=head1 DESCRIPTION
+
+Instances of this class correspond to lines within vCards, though
+they could potentially be used as basis for other RFC 2425-based formats
+such as iCalendar.
+
+=head2 Constructor
+
+=over
+
+=item * C<< new(%options) >>
+
+Returns a new AB::Line object.
+
+The only options worth worrying about are: B<property> (case-insensitive
+property name), B<value> (arrayref or single string value), B<type_parameters>
+(hashref of property-related parameters).
+
+AB::Entity overloads stringification, so you can do the following:
+
+  my $line = AB::Line->new(
+    property        => 'email',
+    value           => 'joe@example.net',
+    type_parameters => { type=>['PREF','INTERNET'] },
+    );
+  print "$line\n" if $line =~ /internet/i;
+
+=back
+
+=head2 Methods
+
+=over
+
+=item * C<< to_string() >>
+
+Formats the line according to RFC 2425 and RFC 2426.
+
+=item * C<< add_to_model($model, $node) >>
+
+Given an RDF::Trine::Model and an RDF::Trine::Node representing the
+entity (i.e. vcard) that this line belongs to, adds triples to the
+model for this line.
+
+=item * C<< property() >>
+
+Returns the line's property - e.g. "EMAIL".
+
+=item * C<< property_node() >>
+
+Returns the line's property as an RDF::Trine::Node that can be used as an
+RDF predicate. Returns undef if a sensible URI cannot be found.
+
+=item * C<< property_order() >>
+
+Returns a string which can be used to sort a list of lines into a sensible
+order.
+
+=item * C<< value() >>
+
+Returns an arrayref for the value. Each item in the arrayref could be a plain scalar,
+or an arrayref of scalars. For example the arrayref representing this name:
+
+  N:Smith;John;Edward,James
+
+which is the vCard representation of somebody with surname Smith, given name
+John and additional names (middle names) Edward and James, might be represented
+with the following "value" arrayref:
+
+  [
+    'Smith',
+    'John',
+    ['Edward', 'James'],
+  ]
+
+or maybe:
+
+  [
+    ['Smith'],
+    'John',
+    ['Edward', 'James'],
+  ]
+
+That's why it's sometimes useful to have a normalised version of it...
+
+=item * C<< nvalue() >>
+
+Returns a normalised version of the arrayref for the value. It will always
+be an arrayref of arrayrefs. For example:
+
+  [
+    ['Smith'],
+    ['John'],
+    ['Edward', 'James'],
+  ]
+
+=item * C<< value_node() >>
+
+Returns the line's value as an RDF::Trine::Node that can be used as an
+RDF object. For some complex properties (e.g. ADR, GEO, ORG, N, etc) the
+result is not especially useful.
+
+=item * C<< value_to_string() >>
+
+Formats the line value according to RFC 2425 and RFC 2426.
+
+=item * C<< type_parameters() >>
+
+Returns the type_parameters hashref. Here be monsters (kinda).
+
+=back
+
+=head1 SEE ALSO
+
+L<RDF::vCard>.
+
+=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.
+
+package AB::Reader;
+
+use 5.010;
+use autodie;
+use common::sense;
+use MooseX::Declare;
+
+BEGIN {
+	$AB::Reader::AUTHORITY = 'cpan:TOBYINK';
+	$AB::Reader::VERSION   = '0.100';
+}
+
+class AB::Reader
+	with AB::Interfaces::Reader
+	with MooseX::Traits
+{
+	has '_trait_namespace' => (is => 'ro', default => 'AB::Trait::Read');
+}
+
+1;
+

lib/AB/Trait/Read/RDF.pm

+package RDF::vCard::Exporter;
+
+use 5.008;
+use common::sense;
+
+use MIME::Base64 qw[];
+use RDF::vCard::Entity;
+use RDF::vCard::Line;
+use RDF::TrineShortcuts qw[:all];
+use Scalar::Util qw[blessed];
+use URI;
+
+# kinda constants
+sub V    { return 'http://www.w3.org/2006/vcard/ns#' . shift; }
+sub VX   { return 'http://buzzword.org.uk/rdf/vcardx#' . 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 = '0.008';
+our $PRODID  = sprintf("+//IDN cpan.org//NONSGML %s v %s//EN", __PACKAGE__, $VERSION);
+
+our %dispatch = (
+	V('adr')             => \&_prop_export_adr,
+	V('n')               => \&_prop_export_n,
+	V('geo')             => \&_prop_export_geo,
+	V('org')             => \&_prop_export_org,
+	V('agent')           => \&_prop_export_agent,
+	V('tel')             => \&_prop_export_typed,
+	V('email')           => \&_prop_export_typed,
+	V('label')           => \&_prop_export_typed,
+	VX('impp')           => \&_prop_export_typed,
+	V('fax')             => \&_prop_export_shortcut,
+	V('homeAdr')         => \&_prop_export_shortcut,
+	V('homeTel')         => \&_prop_export_shortcut,
+	V('mobileEmail')     => \&_prop_export_shortcut,
+	V('mobileTel')       => \&_prop_export_shortcut,
+	V('personalEmail')   => \&_prop_export_shortcut,
+	V('unlabeledAdr')    => \&_prop_export_shortcut,
+	V('unlabeledEmail')  => \&_prop_export_shortcut,
+	V('unlabeledTel')    => \&_prop_export_shortcut,
+	V('workAdr')         => \&_prop_export_shortcut,
+	V('workEmail')       => \&_prop_export_shortcut,
+	V('workTel')         => \&_prop_export_shortcut,
+	V('photo')           => \&_prop_export_binary,
+	V('sound')           => \&_prop_export_binary,
+	V('logo')            => \&_prop_export_binary,
+	V('key')             => \&_prop_export_binary,
+	);
+
+sub new
+{
+	my ($class, %options) = @_;
+	bless { %options }, $class;
+}
+
+sub is_v4
+{
+	my ($self) = @_;
+	return ($self->{vcard_version} =~ /4/) ? 4 : 0;
+}
+
+sub is_v3
+{
+	my ($self) = @_;
+	return $self->is_v4 ? 0 : 3;
+}
+
+sub export_cards
+{
+	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(V('VCard')));
+	push @subjects, $model->subjects(rdf_resource(V('fn')), undef);	
+	my %subjects = map { flatten_node($_) => $_ } @subjects;
+	
+	my @cards;
+	foreach my $s (values %subjects)
+	{
+		push @cards, $self->export_card($model, $s, %options);
+	}
+	
+	if ($options{sort})
+	{
+		return sort { $a->entity_order cmp $b->entity_order } @cards;
+	}
+	
+	return @cards;
+}
+
+sub export_card
+{
+	my ($self, $model, $subject, %options) = @_;
+	$model = RDF::TrineShortcuts::rdf_parse($model)
+		unless blessed($model) && $model->isa('RDF::Trine::Model');
+	
+	my $card = RDF::vCard->new_entity( profile=>'VCARD' );
+	
+	my %categories;
+	my $triples = $model->get_statements($subject, undef, undef);
+	while (my $triple = $triples->next)
+	{
+		next
+			unless (substr($triple->predicate->uri, 0, length(&V)) eq &V or
+					  substr($triple->predicate->uri, 0, length(&VX)) eq &VX);
+
+		if (defined $dispatch{$triple->predicate->uri}
+		and ref($dispatch{$triple->predicate->uri}) eq 'CODE')
+		{
+			my $code = $dispatch{$triple->predicate->uri};
+			$card->add($code->($self, $model, $triple));
+		}
+		elsif ($triple->predicate->uri eq V('category')
+		or     $triple->predicate->uri eq VX('category'))
+		{
+			my $c = $self->_prop_extract_category($model, $triple);
+			$categories{$c}++;
+		}
+		elsif (! $triple->object->is_blank)
+		{
+			$card->add($self->_prop_export_simple($model, $triple));
+		}
+	}
+	
+	if (keys %categories)
+	{
+		$card->add(
+			RDF::vCard::Line->new(
+				property        => 'categories',
+				value           => [[ sort keys %categories ]],
+				)
+			);
+	}
+	
+	$card->add(
+		RDF::vCard::Line->new(
+			property        => 'version',
+			value           => $self->is_v4 ? '4.0' : '3.0',
+			)
+		);
+		
+	$card->add(
+		RDF::vCard::Line->new(
+			property        => 'prodid',
+			value           => (defined $options{prodid} ? $options{prodid} : $PRODID),
+			)
+		) unless exists $options{prodid} && !defined $options{prodid};
+
+	$card->add(
+		RDF::vCard::Line->new(
+			property        => 'source',
+			value           => $options{source},
+			type_parameters => {value=>'URI'},
+			)
+		) if defined $options{source};
+
+	return $card;
+}
+
+{
+	my %dtmap = (
+		XSD('anyURI')        => 'URI',
+		XSD('string')        => 'TEXT',
+		XSD('integer')       => 'INTEGER',
+		XSD('date')          => 'DATE',
+		XSD('dateTime')      => 'DATE-TIME',
+		XSD('duration')      => 'DURATION',
+		'urn:iso:std:iso:8601#timeInterval' => 'PERIOD',
+		XSD('decimal')       => 'FLOAT',
+		# BOOLEAN ?
+		);
+	
+	sub _prop_export_simple
+	{
+		my ($self, $model, $triple) = @_;
+		
+		my $prop = 'x-data';
+		if ($triple->predicate->uri =~ m/([^\#\/]+)$/)
+		{
+			$prop = $1;
+		}
+		
+		my $val    = flatten_node($triple->object);
+		my $params = undef;
+		
+		if ($triple->object->is_literal
+		and $triple->object->has_datatype
+		and defined $dtmap{ $triple->object->literal_datatype })
+		{
+			$params = { value => $dtmap{ $triple->object->literal_datatype } };
+		}