<?xml version='1.0' encoding='utf-8'?>
<?xml-stylesheet href="/style/browser.xsl" type="text/xsl"?>
<essay xmlns="http://docbook.org/ns/docbook"
       xmlns:xlink="http://www.w3.org/1999/xlink"
       xmlns:rdf='http://www.w3.org/1999/02/22-rdf-syntax-ns#'
       xmlns:dc='http://purl.org/dc/elements/1.1/'
       xmlns:dcterms="http://purl.org/dc/terms/"
       xmlns:gal='http://norman.walsh.name/rdf/gallery#'
       xmlns:foaf="http://xmlns.com/foaf/0.1/"
       xml:lang="en"
       version='5.0'>
<info>
<title>Extracting vCards from hCard markup</title>
<volumenum>8</volumenum>
<issuenum>161</issuenum>
<pubdate>2005-12-12T18:48:35-05:00</pubdate>
<date>$Date: 2005-12-13 06:00:10 -0500 (Tue, 13 Dec 2005) $</date>
<author><personname>
<firstname>Norman</firstname><surname>Walsh</surname>
</personname></author>
<copyright><year>2005</year><holder>Norman Walsh</holder></copyright>
<abstract>
<para>Another attempt at refining the ontology that I've been
working on for modelling vCard data in RDF. This time with a GRDDL
transformation to extract RDF from hCard markup.</para>
</abstract>
<dcterms:replaces>http://norman.walsh.name/2005/12/05/vcard</dcterms:replaces>
<dcterms:replaces>http://norman.walsh.name/2005/11/25/contacts</dcterms:replaces>
<dcterms:replaces>http://norman.walsh.name/2005/11/30/contacts</dcterms:replaces>
</info>

<para xml:id='p1'>I've done another revision of the
<link xlink:href="http://norman.walsh.name/2005/12/05/vcard">vCard
ontology</link> that I've been working on. I think the only really significant
change is that I've reverted back to using property names (not labels)
to identify classes of phone numbers, email addresses, and postal addresses.
I had a chance to chat with <personname><firstname>Dan</firstname>
<surname role="suppress">Connolly</surname></personname> and
<personname><firstname>Tim</firstname>
<surname role="suppress">Berners-Lee</surname></personname> after
the TAG face-to-face meeting and they convinced me that these are really
relationships.
It's not sufficient to label a phone number “work” because, for example,
different people might have different relationships with the same phone
number. (To be fair, some other folks made that point in comments on
earlier essays.)</para>

<para xml:id='p2'>As a starting point,
I created “work”, “mobile”, “fax”, and “home” phone number properties;
“work”, “personal”, and “mobile” email addresses, and
"work” and “home” postal addresses. I also created an explicitly “unlabeled”
version of each property. If more subproperties are needed, they can always
be added (and in OWL terms, any property that declares itself appropriately
becomes a subproperty).</para>

<para xml:id='p3'>I removed the references to the
<uri>http://www.w3.org/2003/01/geo/wgs84_pos#</uri> vocabulary,
choosing instead to model the geographic information in the vCard
vocabulary. This way, the vCard ontology stands alone and provides a
mapping to and from vCards. I think it makes sense to augment your
model with appropriate rules to construct, for example, FOAF and Geo
properties from your vCards so that you can take advantage of other
vocabularies in your data, but the vCard ontology doesn't have to do
that for you.</para>

<para xml:id='p4'>Here's <link xlink:href="http://nwalsh.com/rdf/vCard">the
ontology</link> <foreignphrase>du jour</foreignphrase>:</para>

<programlisting><![CDATA[# -*- N3 -*-

@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix xs: <http://www.w3.org/2001/XMLSchema#> .
@prefix v: <http://nwalsh.com/rdf/vCard#> .
@prefix geo: <http://www.w3.org/2003/01/geo/wgs84_pos#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix dc: <http://purl.org/dc/elements/1.1/> .
@prefix cc: <http://web.resource.org/cc/> .

<http://nwalsh.com/rdf/vCard> a owl:Ontology;
    rdfs:comment "Norm's attempt at a vCard/hCard RDF ontology.";
    dc:title "An OWL Ontology for vCards";
    dc:description """This ontology attempts to model a subset of vCards
in RDF using modern (circa 2005) RDF best practices. The subset selected
is the same subset that the microformats community has adopted for use in
hCard.""";
    dc:creator <http://norman.walsh.name/knows/who#norman-walsh>;
    dc:date "2005-12-12";
    dc:rights "Copyright © 2005 Norman Walsh. This work is licensed under the Creative Commons Attribution-NonCommercial License.";
    cc:license <http://creativecommons.org/licenses/by-nc/2.0/> .

# ------------------------------------------------------------

v:VCard a owl:Class;
    rdfs:subClassOf
        [    a owl:Restriction;
             owl:maxCardinality "1"^^xs:nonNegativeInteger;
             owl:onProperty v:rev ];
    rdfs:label "vCard Class";
    rdfs:comment "Resources that are vCards." .

v:Name a owl:Class;
    rdfs:subClassOf
        [    a owl:Restriction;
             owl:maxCardinality "1"^^xs:nonNegativeInteger;
             owl:onProperty v:family-name ];
    rdfs:subClassOf
        [    a owl:Restriction;
             owl:maxCardinality "1"^^xs:nonNegativeInteger;
             owl:onProperty v:given-name ];
    rdfs:subClassOf
        [    a owl:Restriction;
             owl:maxCardinality "1"^^xs:nonNegativeInteger;
             owl:onProperty v:additional-name ];
    rdfs:subClassOf
        [    a owl:Restriction;
             owl:maxCardinality "1"^^xs:nonNegativeInteger;
             owl:onProperty v:honorific-prefix ];
    rdfs:subClassOf
        [    a owl:Restriction;
             owl:maxCardinality "1"^^xs:nonNegativeInteger;
             owl:onProperty v:honorific-suffix ];
    rdfs:label "vCard Name Class";
    rdfs:comment "Resources that are vCard personal names." .

v:Address a owl:Class;
    rdfs:subClassOf
        [    a owl:Restriction;
             owl:maxCardinality "1"^^xs:nonNegativeInteger;
             owl:onProperty v:post-office-box ];
    rdfs:subClassOf
        [    a owl:Restriction;
             owl:maxCardinality "1"^^xs:nonNegativeInteger;
             owl:onProperty v:extended-address ];
    rdfs:subClassOf
        [    a owl:Restriction;
             owl:maxCardinality "1"^^xs:nonNegativeInteger;
             owl:onProperty v:street-address ];
    rdfs:subClassOf
        [    a owl:Restriction;
             owl:maxCardinality "1"^^xs:nonNegativeInteger;
             owl:onProperty v:locality ];
    rdfs:subClassOf
        [    a owl:Restriction;
             owl:maxCardinality "1"^^xs:nonNegativeInteger;
             owl:onProperty v:region ];
    rdfs:subClassOf
        [    a owl:Restriction;
             owl:maxCardinality "1"^^xs:nonNegativeInteger;
             owl:onProperty v:postal-code ];
    rdfs:subClassOf
        [    a owl:Restriction;
             owl:maxCardinality "1"^^xs:nonNegativeInteger;
             owl:onProperty v:country-name ];
    rdfs:label "vCard Address Class";
    rdfs:comment "Resources that are vCard (postal) addresses." .

v:Organization a owl:Class;
    rdfs:subClassOf
        [    a owl:Restriction;
             owl:maxCardinality "1"^^xs:nonNegativeInteger;
             owl:onProperty v:latitude ];
    rdfs:subClassOf
        [    a owl:Restriction;
             owl:maxCardinality "1"^^xs:nonNegativeInteger;
             owl:onProperty v:longitude ];
    rdfs:label "vCard Organization Class";
    rdfs:comment "Resources that are vCard organizations." .

v:Geo a owl:Class;
    rdfs:subClassOf
        [    a owl:Restriction;
             owl:maxCardinality "1"^^xs:nonNegativeInteger;
             owl:onProperty v:organization-name ];
    rdfs:subClassOf
        [    a owl:Restriction;
             owl:maxCardinality "1"^^xs:nonNegativeInteger;
             owl:onProperty v:organization-unit ];
    rdfs:label "vCard Geographic Location Class";
    rdfs:comment "Resources that are vCard geographic locations." .

# ------------------------------------------------------------

v:fn a owl:DatatypeProperty;
    rdfs:domain v:VCard;
    rdfs:label "formatted name";
    rdfs:comment "A formatted name of a person." .

v:n a owl:ObjectProperty;
    rdfs:domain v:VCard;
    rdfs:range v:Name;
    rdfs:label "name";
    rdfs:comment "The components of the name of a person." .

v:family-name a owl:DatatypeProperty;
    rdfs:domain v:Name;
    rdfs:label "family name";
    rdfs:comment "A family name part of a person's name." .

v:given-name a owl:DatatypeProperty;
    rdfs:domain v:Name;
    rdfs:label "given name";
    rdfs:comment "A given name part of a person's name." .

v:additional-name a owl:DatatypeProperty;
    rdfs:domain v:Name;
    rdfs:label "additional name";
    rdfs:comment "An additional part of a person's name." .

v:honorific-prefix a owl:DatatypeProperty;
    rdfs:domain v:Name;
    rdfs:label "honorific prefix";
    rdfs:comment "An honorific prefix part of a person's name." .

v:honorific-suffix a owl:DatatypeProperty;
    rdfs:domain v:Name;
    rdfs:label "honorific suffix";
    rdfs:comment "An honorific suffix part of a person's name." .

v:photo a owl:ObjectProperty;
    rdfs:domain v:VCard;
    rdfs:label "photo";
    rdfs:comment "A photograph of a person." .

v:bday a owl:DatatypeProperty;
    rdfs:domain v:VCard;
    rdfs:range xs:date;
    rdfs:label "birthday";
    rdfs:comment "The birthday of a person." .

v:adr a owl:ObjectProperty;
    rdfs:range v:Address;
    rdfs:label "address";
    rdfs:comment "A postal or street address of a person." .

# A defined set of xxxAdr subproperties; more could be added

v:workAdr a owl:ObjectProperty;
    rdfs:subPropertyOf v:adr;
    rdfs:range v:Address;
    rdfs:label "Work address";
    rdfs:comment "A work address of a person." .

v:homeAdr a owl:ObjectProperty;
    rdfs:subPropertyOf v:adr;
    rdfs:range v:Address;
    rdfs:label "Home address";
    rdfs:comment "A home address of a person." .

v:unlabeledAdr a owl:ObjectProperty;
    rdfs:subPropertyOf v:adr;
    rdfs:range v:Address;
    rdfs:label "address";
    rdfs:comment "An (explicitly) unlabeled address of a person." .

v:post-office-box a owl:DatatypeProperty;
    rdfs:domain v:Address;
    rdfs:label "P.O. Box";
    rdfs:comment "The post office box of a postal address." .

v:extended-address a owl:DatatypeProperty;
    rdfs:domain v:Address;
    rdfs:label "extended";
    rdfs:comment "The extended address of a postal address." .

v:street-address a owl:DatatypeProperty;
    rdfs:domain v:Address;
    rdfs:label "street";
    rdfs:comment "The street address of a postal address." .

v:locality a owl:DatatypeProperty;
    rdfs:domain v:Address;
    rdfs:label "locality";
    rdfs:comment "The locality (e.g., city) of a postal address." .

v:region a owl:DatatypeProperty;
    rdfs:domain v:Address;
    rdfs:label "region";
    rdfs:comment "The region (e.g., state or province) of a postal address." .

v:postal-code a owl:DatatypeProperty;
    rdfs:domain v:Address;
    rdfs:label "postal code";
    rdfs:comment "The postal code (e.g., U.S. ZIP code) of a postal address." .

v:country-name a owl:DatatypeProperty;
    rdfs:domain v:Address;
    rdfs:label "country";
    rdfs:comment "The country of a postal address." .

v:label a owl:DatatypeProperty;
    rdfs:domain v:Address;
    rdfs:label "address label";
    rdfs:comment "The formatted version of a postal address (a string with embedded line breaks, punctuation, etc.)." .

v:tel a owl:ObjectProperty;
    rdfs:domain v:VCard;
    rdfs:label "phone";
    rdfs:comment "A telephone number of a person." .

# A defined set of xxxTel subproperties; more could be added

v:workTel a owl:ObjectProperty;
    rdfs:subPropertyOf v:tel;
    rdfs:label "Work phone";
    rdfs:comment "A work phone number of a person." .

v:mobileTel a owl:ObjectProperty;
    rdfs:subPropertyOf v:tel;
    rdfs:label "Mobile phone";
    rdfs:comment "A mobile phone number of a person." .

v:fax a owl:ObjectProperty;
    rdfs:subPropertyOf v:tel;
    rdfs:label "Fax";
    rdfs:comment "A fax number of a person." .

v:homeTel a owl:ObjectProperty;
    rdfs:subPropertyOf v:tel;
    rdfs:label "Home phone";
    rdfs:comment "A home phone number of a person." .

v:unlabeledTel a owl:ObjectProperty;
    rdfs:subPropertyOf v:tel;
    rdfs:label "phone";
    rdfs:comment "An (explicitly) unlabeled phone number of a person." .

v:email a owl:ObjectProperty;
    rdfs:domain v:VCard;
    rdfs:label "email";
    rdfs:comment "An email address." .

# A defined set of xxxEmail subproperties; more could be added

v:workEmail a owl:ObjectProperty;
    rdfs:subPropertyOf v:email;
    rdfs:label "Work email";
    rdfs:comment "A work email address of a person." .

v:personalEmail a owl:ObjectProperty;
    rdfs:subPropertyOf v:email;
    rdfs:label "Personal email";
    rdfs:comment "An email address unaffiliated with any particular organization or employer; a personal email address." .

v:mobileEmail a owl:ObjectProperty;
    rdfs:subPropertyOf v:email;
    rdfs:label "Mobile email";
    rdfs:comment "A mobile email address of a person." .

v:unlabeledEmail a owl:ObjectProperty;
    rdfs:subPropertyOf v:email;
    rdfs:label "email";
    rdfs:comment "An (explicitly) unlabeled email address of a person." .

v:mailer a owl:DatatypeProperty;
    rdfs:domain v:VCard;
    rdfs:label "mailer";
    rdfs:comment "A mailer associated with a vCard." .

v:tz a owl:DatatypeProperty;
    rdfs:domain v:VCard;
    rdfs:label "timezone";
    rdfs:comment "A timezone associated with a person." .

v:geo a owl:ObjectProperty;
    rdfs:domain v:VCard;
    rdfs:range v:Geo;
    rdfs:label "geographic location";
    rdfs:comment "A geographic location associated with a person." .

v:latitude a owl:DatatypeProperty;
    rdfs:domain v:Geo;
    rdfs:range xs:double;
    rdfs:label "latitude";
    rdfs:comment "The latitude of a geographic location." .

v:longitude a owl:DatatypeProperty;
    rdfs:domain v:Geo;
    rdfs:range xs:double;
    rdfs:label "longitude";
    rdfs:comment "The longitude of a geographic location" .

v:title a owl:DatatypeProperty;
    rdfs:domain v:VCard;
    rdfs:label "title";
    rdfs:comment "A person's title." .

v:role a owl:DatatypeProperty;
    rdfs:domain v:VCard;
    rdfs:label "role";
    rdfs:comment "A role a person plays within an organization." .

v:logo a owl:ObjectProperty;
    rdfs:domain v:VCard;
    rdfs:label "logo";
    rdfs:comment "A logo associated with a person or their organization." .

v:agent a owl:ObjectProperty;
    rdfs:domain v:VCard;
    rdfs:range v:VCard;
    rdfs:label "agent";
    rdfs:comment "A person that acts as one's agent." .

v:org a owl:ObjectProperty;
    rdfs:domain v:VCard;
    rdfs:range v:Organization;
    rdfs:label "organization";
    rdfs:comment "An organization associated with a person." .

v:organization-name a owl:DatatypeProperty;
    rdfs:domain v:Organization;
    rdfs:label "name";
    rdfs:comment "The name of an organization." .

v:organization-unit a owl:DatatypeProperty;
    rdfs:domain v:Organization;
    rdfs:label "unit";
    rdfs:comment "The name of a unit within an organization." .

v:category a owl:DatatypeProperty;
    rdfs:domain v:VCard;
    rdfs:label "category";
    rdfs:comment "A category of a vCard." .

v:note a owl:DatatypeProperty;
    rdfs:domain v:VCard;
    rdfs:label "notes";
    rdfs:comment "Notes about a person on a vCard." .

v:rev a owl:DatatypeProperty;
    rdfs:domain v:VCard;
    rdfs:range [ owl:unionOf (xs:date xs:dateTime) ];
    rdfs:label "revision";
    rdfs:comment "The timestamp of a revision of a vCard." .

v:sort-string a owl:DatatypeProperty;
    rdfs:domain v:VCard;
    rdfs:label "sort";
    rdfs:comment "A version of a person's name suitable for collation." .

v:sound a owl:ObjectProperty;
    rdfs:domain v:VCard;
    rdfs:label "sound";
    rdfs:comment "A sound (e.g., a greeting or pronounciation) of a person." .

v:uid a owl:DatatypeProperty, owl:InverseFunctionalProperty;
    rdfs:domain v:VCard;
    rdfs:label "uid";
    rdfs:comment "A UID of a person's vCard." .

v:url a owl:ObjectProperty;
    rdfs:domain v:VCard;
    rdfs:label "url";
    rdfs:comment "A URL associated with a person." .

v:class a owl:DatatypeProperty;
    rdfs:domain v:VCard;
    rdfs:label "class";
    rdfs:comment "A class (e.g., public, private, etc.) of a vCard." .

v:key a owl:ObjectProperty;
    rdfs:domain v:VCard;
    rdfs:label "key";
    rdfs:comment "A key (e.g, PKI key) of a person." .
]]></programlisting>

<para xml:id='p5'>Finally, I created
<link xlink:href="examples/hcard2rdf.xsl">a transformation</link> suitable
for use with
<link xlink:href="http://www.w3.org/2004/01/rdxh/spec">GRDDL</link> that
extracts vCard RDF from HTML marked up with
<link xlink:href="http://microformats.org/wiki/hcard">hCard</link>.
It really isn't tested as fully as it should be, but it appeared to do
the right thing with the test data I fed to it.</para>

<para xml:id='p6'>I think I've taken this about as far as I should all by myself.
If there's value in this ontology, I think it makes sense to find some
way of moving maintenance to a community process. Dan suggested
<link xlink:href="http://www.w3.org/2005/Incubator/procedures.html">an
incubator group</link>, which seems reasonable. That would probably
get it a namespace URI in the W3C domain which would certainly lend it
an air of respectability. Anyway, I think it's in pretty good shape and
I'm willing to work on maintaining it in public if we can find some reasonably
low cost way of doing so.</para>

<para xml:id='p7'>Until then, it'll live on in its current namespace, but I probably
won't write about it again unless something dramatic changes.</para>

</essay>
