.. Copyright © 2015 Lukas Rosenthaler, Benjamin Geer, Ivan Subotic,
Tobias Schweizer, André Kilchenmann, and Sepideh Alassi.
This file is part of Knora.
Knora is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Knora is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public
License along with Knora. If not, see .
******************************
An Example Project: Incunabula
******************************
This section introduces some of the basic concepts involved in creating
ontologies for Knora projects, by means of a relatively simple example
project. Before reading this document, it will be helpful to have some
familiarity with the basic concepts explained in :ref:`knora-base`.
Knora comes with two example projects, called ``incunabula`` and
``images-demo``. Here we will consider the ``incunabula`` example, which is
a reduced version of a real research project on early printed books. It
is designed to store an image of each page of each book, as well as RDF data
about books, pages, their contents, and relationships between them. At the moment,
only the RDF data is provided in the example project, not the images.
The ``incunabula`` ontology is in the file ``incunabula-onto.ttl``, and its
data is in the file ``incunabula-demo-data.ttl``. Both these files are in a
standard RDF file format called Turtle_. The Knora distribution includes
sample scripts (in the ``webapi/scripts`` directory) for importing these files
directly into different triplestores. If you are starting a new project from
scratch, you can adapt these scripts to import your ontology (and any existing
RDF data) into your triplestore for use with Knora.
The syntax of Turtle is fairly simple: it is basically a sequence of triples.
We will consider some details of Turtle syntax as we go along.
The Incunabula Ontology
-----------------------
Here we will just focus on some of the main aspects of the ontology. An
ontology file typically begins by defining prefixes for the IRIs of other
ontologies that will be referred to. First there are some prefixes for
ontologies that are very commonly used in RDF:
::
@prefix rdf: .
@prefix rdfs: .
@prefix owl: .
@prefix xsd: .
@prefix foaf: .
The ``rdf``, ``rdfs``, and ``owl`` ontologies contain basic properties that
are used to define ontology entities. The ``xsd`` ontology contains
definitions of literal data types such as ``string`` and ``integer``. (For
more information about these ontologies, see the references in :ref:`knora-base`.)
The ``foaf`` ontology contains classes and properties for representing people.
Then we define prefixes for Knora ontologies:
::
@prefix knora-base: .
@prefix dc: .
@prefix salsah-gui: .
The ``knora-base`` ontology contains Knora's core abstractions, and is
described in :ref:`knora-base`. The ``dc`` ontology is Knora's version of
`Dublin Core`_. It is intended to make it possible to define properties in a Knora
project in terms of Dublin Core abstractions, to facilitate queries that
search for data across multiple projects. The ``salsah-gui`` ontology includes
properties that Knora projects must use to enable SALSAH, Knora's generic
virtual research environment.
For convenience, we can use the empty prefix to refer to the ``incunabula``
ontology itself:
::
@prefix : .
However, outside the ontology file, it would make more sense to define an
``incunabula`` prefix to refer to the ``incunabula`` ontology.
Properties
^^^^^^^^^^
All the content produced by a Knora project must be stored in Knora resources
(see :ref:`incunabula-resource-classes`). Resources have properties that point
to different parts of their contents; for example, the ``incunabula`` project
contains books, which have properties like ``title``. Every property that
poitns to a Knora value must be a subproperty of ``knora-base:hasValue``, and
every property that points to another Knora resource must be a subproperty of
``knora-base:hasLinkTo``.
Here is the definition of the ``incunabula:title`` property:
::
:title rdf:type owl:ObjectProperty ;
rdfs:subPropertyOf dc:title ;
rdfs:label "Titel"@de ,
"Titre"@fr ,
"Titolo"@it ,
"Title"@en ;
knora-base:subjectClassConstraint :book ;
knora-base:objectClassConstraint knora-base:TextValue ;
salsah-gui:guiOrder "1"^^xsd:integer ;
salsah-gui:guiElement salsah-gui:SimpleText ;
salsah-gui:guiAttribute "size=80" ,
"maxlength=255" .
The definition of ``incunabula:title`` consists of a list of triples, all of
which have ``:title`` as their subject. To avoid repeating ``:title`` for each
triple, Turtle syntax allows us to use a semicolon (``;``) to separate triples
that have the same subject. Moreover, some triples also have the same
predicate; a comma (``,``) is used to avoid repeating the predicate. The
definition of ``:title`` says:
* ``rdf:type owl:ObjectProperty``: It is an ``owl:ObjectProperty``. There are
two kinds of OWL properties: object properties and datatype properties.
Object properties point to objects, which have IRIs and can have their own
properties. Datatype properties point to literal values, such as strings and
integers.
* ``rdfs:subPropertyOf dc:title``: It is a subproperty of ``dc:title``, which
is a subproperty of ``knora-base:hasValue``. It would have been possible to
define ``incunabula:title`` as a direct subproperty of ``knora-base:hasValue``,
and indeed many properties in Knora projects are defined in that way. The
advantage of using ``dc:title`` is that if you do a search for resources that
have a certain ``dc:title``, and there is a resource with a matching
``incunabula:title``, the search results could include that resource. (This
feature is planned but not yet implemented in the Knora API server.)
* ``rdfs:label "Titel"@de``, etc.: It has the specified labels in various
languages. These are needed, for example, by user interfaces, to prompt the
user to enter a value.
* ``knora-base:subjectClassConstraint :book``: The subject of the property
must be an ``incunabula:book``.
* ``knora-base:objectClassConstraint knora-base:TextValue``: The object of
this property must be a ``knora-base:TextValue`` (which is a subclass of
``knora-base:Value``).
* ``salsah-gui:guiOrder "1"^^xsd:integer``: When a resource with this and
other properties is displayed in SALSAH, this property will be displayed
first. The notation ``"1"^^xsd:integer`` means that the literal ``"1"`` is
of type ``xsd:integer``.
* ``salsah-gui:guiElement salsah-gui:SimpleText``: When SALSAH asks a user to
enter a value for this property, it should use a simple text field.
* ``salsah-gui:guiAttribute "size=80" , "maxlength=255"``: The SALSAH text
field for entering a value for this property should be 80 characters wide,
and should accept at most 255 characters.
The ``incunabula`` ontology contains several other property definitions that
are basically similar. Note that different subclasses of ``Value`` are used.
For example, ``incunabula:pubdate``, which represents the publication date of
a book, points to a ``knora-base:DateValue``. The ``DateValue`` class stores a
date range, with a specified degree of precision and a preferred calendar
system for display.
A property can point to a Knora resource instead of to a Knora value. For
example, in the ``incunabula`` ontology, there are resources representing
pages and books, and each page is part of some book. This relationship is
expressed using the property ``incunabula:partOf``:
::
:partOf rdf:type owl:ObjectProperty ;
rdfs:subPropertyOf knora-base:isPartOf ;
rdfs:label "ist ein Teil von"@de ,
"est un part de"@fr ,
"e una parte di"@it ,
"is a part of"@en ;
rdfs:comment """Diese Property bezeichnet eine Verbindung zu einer anderen Resource, in dem ausgesagt wird, dass die vorliegende Resource ein integraler Teil der anderen Resource ist. Zum Beispiel ist eine Buchseite ein integraler Bestandteil genau eines Buches."""@de ;
knora-base:subjectClassConstraint :page ;
knora-base:objectClassConstraint :book ;
salsah-gui:guiOrder "2"^^xsd:integer ;
salsah-gui:guiElement salsah-gui:Searchbox .
The key things to notice here are:
* ``rdfs:subPropertyOf knora-base:isPartOf``: The Knora base ontology provides
a generic ``isPartOf`` property to express part-whole relationships. Like
many properties defined in ``knora-base``, a project cannot use
``knora-base:isPartOf`` directly, but must make a subproperty such as
``incunabula:partOf``. It is important to note that ``knora-base:isPartOf``
is a subproperty of ``knora-base:hasLinkTo``. Any property that points to a
``knora-base:Resource`` must be a subproperty of ``knora-base:hasLinkTo``.
In Knora terminology, such a property is called a *link property*.
* ``knora-base:objectClassConstraint :book``: The object of this property must
be a member of the class ``incunabula:book``, which, as we will see below,
is a subclass of ``knora-base:Resource``.
* ``salsah-gui:guiElement salsah-gui:Searchbox``: When SALSAH prompts a user
to select the book that a page is part of, it should provide a search box
enabling the user to find the desired book.
Because ``incunabula:partOf`` is a link property, it must always accompanied
by a *link value property*, which enables Knora to store metadata about each
link that is created with the link property. This metadata includes the date
and time when the link was created, its owner, the permissions it grants, and
whether it has been deleted. Storing this metadata allows Knora to authorise
users to see or modify the link, as well as to query a previous state of a
repository in which a deleted link had not yet been deleted. (The ability to
query previous states of a repository is planned for Knora API version 2.)
The name of a link property and its link value property must be related by the
following naming convention: to determine the name of the link value property,
add the word ``Value`` to the name of the link property. Hence, the
``incunabula`` ontology defines the property ``partOfValue``:
::
:partOfValue rdf:type owl:ObjectProperty ;
rdfs:subPropertyOf knora-base:isPartOfValue ;
knora-base:subjectClassConstraint :page ;
knora-base:objectClassConstraint knora-base:LinkValue .
As a link value property, ``incunabula:partOfValue`` must point to a
``knora-base:LinkValue``. The ``LinkValue`` class is an RDF *reification* of a triple
(in this case, the triple that links a page to a book). For more details about
this, see :ref:`knora-base-linkvalue`.
Note that the property ``incunabula:hasAuthor`` points to a
``knora-base:TextValue``, because the ``incunabula`` project repåresents
authors simply by their names. A more complex project could represent each
author as a resource, in which case ``incunabula:hasAuthor`` would need to be
a subproperty of ``knora-base:hasLinkTo``.
.. _incunabula-resource-classes:
Resource Classes
^^^^^^^^^^^^^^^^
The two main resource classes in the ``incunabula`` ontology are ``book`` and ``page``.
Here is ``incunabula:book``:
::
:book rdf:type owl:Class ;
rdfs:subClassOf knora-base:Resource ,
[
rdf:type owl:Restriction ;
owl:onProperty :title ;
owl:minCardinality "1"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :hasAuthor ;
owl:minCardinality "0"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :publisher ;
owl:minCardinality "0"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :publoc ;
owl:maxCardinality "1"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :pubdate ;
owl:maxCardinality "1"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :location ;
owl:maxCardinality "1"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :url ;
owl:maxCardinality "1"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :description ;
owl:maxCardinality "1"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :physical_desc ;
owl:maxCardinality "1"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :note ;
owl:minCardinality "0"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :citation ;
owl:minCardinality "0"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :book_comment ;
owl:minCardinality "0"^^xsd:nonNegativeInteger ] ;
knora-base:resourceIcon "book.gif" ;
rdfs:label "Buch"@de ,
"Livre"@fr ,
"Libro"@it ,
"Book"@en ;
rdfs:comment """Diese Resource-Klasse beschreibt ein Buch"""@de .
Like every Knora resource class, ``incunabula:book`` is a subclass of
``knora-base:Resource``. It is also a subclass of a number of other classes of type
``owl:Restriction``, which are defined in square brackets, using Turtle's
syntax for anonymous blank nodes. Each ``owl:Restriction`` specifies a
cardinality for a property that is allowed in resources of type
``incunabula:book``. A cardinality is indeed a kind of restriction: it means
that a resource of this type may have, or must have, a certain number of
instances of the specified property. For example, ``incunabula:book`` has
cardinalities saying that a book must have at least one title and at most one
publication date. In the Knora API version 1, the word 'occurrence' is used
instead of 'cardinality'.
As explained in :ref:`knora-base-cardinalities`, these
are the cardinalities supported by Knora:
* ``owl:cardinality 1`` A resource of this class must have exactly one
instance of the specified property (occurrence ``1``).
* ``owl:minCardinality 1`` A resource of this class must have at least one
instance of the specified property (occurrence ``1-n``).
* ``owl:maxCardinality 1`` A resource of this class may have zero or one
instance of the specified property (occurrence ``0-1``).
* ``owl:minCardinality 0`` A resource of this class may have zero or more
instances of the specified property (occurrence ``0-n``).
Note that ``incunabula:book`` specifies a cardinality of ``owl:minCardinality
0`` on the property ``incunabula:hasAuthor``. At first glance, this might seem
as if it serves no purpose, since it says that the property is optional and
can have any number of instances. You may be wondering whether this
cardinality could simply be omitted from the definition of
``incunabula:book``. However, Knora requires every property of a resource to
have some cardinality in the resource's class. This is because Knora uses
the cardinalities to determine which properties are *possible* for instances
of the class, and the Knora API relies on this information. If there was no
cardinality for ``incunabula:hasAuthor``, Knora would not allow a book to have
an author.
Here is the definition of ``incunabula:page``:
::
:page rdf:type owl:Class ;
rdfs:subClassOf knora-base:StillImageRepresentation ,
[
rdf:type owl:Restriction ;
owl:onProperty :pagenum ;
owl:maxCardinality "1"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :partOfValue ;
owl:cardinality "1"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :partOf ;
owl:cardinality "1"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :seqnum ;
owl:maxCardinality "1"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :description ;
owl:maxCardinality "1"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :citation ;
owl:minCardinality "0"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :page_comment ;
owl:minCardinality "0"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :origname ;
owl:cardinality "1"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :hasLeftSidebandValue ;
owl:maxCardinality "1"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :hasLeftSideband ;
owl:maxCardinality "1"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :hasRightSidebandValue ;
owl:maxCardinality "1"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :hasRightSideband ;
owl:maxCardinality "1"^^xsd:nonNegativeInteger ] ,
[
rdf:type owl:Restriction ;
owl:onProperty :transcription ;
owl:minCardinality "0"^^xsd:nonNegativeInteger ] ;
knora-base:resourceIcon "page.gif" ;
rdfs:label "Seite"@de ,
"Page"@fr ,
"Page"@en ;
rdfs:comment """Eine Seite ist ein Teil eines Buchs"""@de ,
"""Une page est une partie d'un livre"""@fr ,
"""A page is a part of a book"""@en .
The ``incunabula:page`` class is a subclass of
``knora-base:StillImageRepresentation``, which is a subclass of
``knora-base:Representation``, which is a subclass of ``knora-base:Resource``.
The class ``knora-base:Representation`` is used for resources that contain
metadata about files stored by Knora. Each It has different subclasses that can
hold different types of files, including still images, audio, and video files.
A given ``Representation`` can store metadata about several different files,
as long as they are of the same type and are semantically equivalent, e.g.
are different versions of the same image with different colorspaces, so that
coordinates in one file will work in the other files.
In Knora, a subclass inherits the cardinalities defined in its superclasses.
Let's look at the class hierarchy of ``incunabula:page``, starting with
``knora-base:Representation``:
::
:Representation rdf:type owl:Class ;
rdfs:subClassOf :Resource ,
[ rdf:type owl:Restriction ;
owl:onProperty :hasFileValue ;
owl:minCardinality "1"^^xsd:nonNegativeInteger
] ;
rdfs:comment "A resource that can store one or more FileValues"@en .
This says that a ``Representation`` must have at least one instance of the
property ``hasFileValue``, which is defined like this:
::
:hasFileValue rdf:type owl:ObjectProperty ;
rdfs:subPropertyOf :hasValue ;
:subjectClassConstraint :Representation ;
:objectClassConstraint :FileValue .
The subject of ``hasFileValue`` must be a ``Representation``, and its object
must be a ``FileValue``. There are different subclasses of ``FileValue`` for
different kinds of files, but we'll skip the details here.
This is the definition of ``knora-base:StillImageRepresentation``:
::
:StillImageRepresentation rdf:type owl:Class ;
rdfs:subClassOf :Representation ,
[ rdf:type owl:Restriction ;
owl:onProperty :hasStillImageFileValue ;
owl:minCardinality "1"^^xsd:nonNegativeInteger
] ;
rdfs:comment "A resource that can contain two-dimensional still image files"@en .
It must have at least one instance of the property ``hasStillImageFileValue``, which
is defined as follows:
::
:hasStillImageFileValue rdf:type owl:ObjectProperty ;
rdfs:subPropertyOf :hasFileValue ;
:subjectClassConstraint :StillImageRepresentation ;
:objectClassConstraint :StillImageFileValue .
Because ``hasStillImageFileValue`` is a subproperty of ``hasFileValue``, the
cardinality on ``hasStillImageFileValue``, defined in the subclass
``StillImageRepresentation``, overrides the cardinality on ``hasFileValue``,
defined in the superclass ``Representation``. In other words, the more general
cardinality in the superclass is replaced by a more specific cardinality in
the base class. Since ``incunabula:page`` is a subclass of
``StillImageRepresentation``, it inherits the cardinality on
``hasStillImageFileValue``. As a result, a page must have at least one image
file attached to it.
Here's another example of cardinality inheritance. The class ``knora-base:Resource``
has a cardinality for ``knora-base:seqnum``. The idea is that resources of any
type could be arranged in some sort of sequence. As we saw above,
``incunabula:page`` is a subclass of ``knora-base:Resource``. But
``incunabula:page`` has its own cardinality for ``incunabula:seqnum``, which
is a subproperty of ``knora-base:seqnum``. Once again, the subclass's
cardinality on the subproperty replaces the superclass's cardinality on the
superproperty: a page is allowed to have an ``incunabula:seqnum``, but it is
not allowed to have a ``knora-base:seqnum``.
.. _Turtle: https://www.w3.org/TR/turtle/
.. _Dublin Core: http://dublincore.org/
..