Introduction

See also the project repository on Github.

graffl is a simple RDF (Resource Description Framework) file format. It allows you to quickly and easily enter graph data manually. It is intended as data source and not as a target serialization format. Think of it as Markdown for RDF.

The two main design goals are

  1. It should be possible to write down data immediately, just as on a notepad

    and therefore

  2. unnecessary syntax elements like dots or commas should be avoided.

RDF is based on URI (Uniform Resource Identifier). However, URI are a very technical concept, and in most cases the URIs of the things you want to say something about are not defined until a later stage. This makes manually creating RDF files impractical. Therefore, graffl initially allows you to define the graph structure and specify the URIs later.

Nevertheless, graffl is a rigorous RDF file format. The parser produces statements consisting of subject, predicate and objects. Contexts aka. named graphs are also supported.

Another important feature is support for nested graphs. This is made possible by the concept of group graphs

Basic Structure

The basic structure:

  1. Directives starting with @

  2. Blocks

  3. Optional group graphs

A block starts with a subject followed by predicate-object pairs. The block is terminated with empty line:

@prefix <http://example.org/ns#>

  Alice email alice@example.org
    likes -> Bob

  Bob email bob@example.org
    // my comment: Bob really likes Alice
    likes -> Alice

An object produces a literal unless:

  • the object is a URI like <mailto:alice@example.org>

  • the object has been defined to be an alias (see below)

  • the predicate has been specified to be a URI property (see below)

  • the predicate is followed by an arrow -> making the predicate a relation

Nodes

Syntax Meaning

Alice

A node with label Alice

"Mr. Bean"

A node with label Mr. Bean

"""Mr. Bean"""

A node with label Mr. Bean

(my node)

A node reference, no label

()

A anonyoums node, no label

<https://example.org>

A node with specified URI, no label

The syntax with three quotation marks (""" …​ """) is for multiline string and may contain line breaks. Quotation marks ", slashes / and backslashes \ can be escaped with backslashes`.

If the label is a word without whitespace like "Alice", then Alice, (Alice), "Alice" or """Alice""" all refer to the same node.

If a node is defined with label (e.g. Alice, "Mr. Bean", """Mr. Bean""") graffl will produce a rdfs:label when it encounters the node for the first time as statement subject. If the node is a node reference_ (e.g. (Alice)) or URI (e.g. <mailto:alice@example.org>) then no rdfs:label is produced.

For example the graph:

Alice likes -> Bob

produces RDF as Turtle:

@prefix ns1: <https://www.hedenus.de/graffl/43f4a396-37e2-11f1-bdfb-e08f4ccbe174/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

ns1:Alice rdfs:label "Alice" ;
    ns1:likes ns1:Bob .

If a node is defined without label as node reference (e.g. (my node)) or URI <https://example.org> or it only appears as statement object then no additional statement is produced.

Incoming and Outgoing Relations

graffl supports outgoing relations (->) and incoming relations (<-). Incoming relations are very convenient if you are focusing on a certain subject. If you want to say that Alice`likes `Bob and Bob is in love with Alice you can write:

Alice likes -> Bob
      <- loves Bob

Comments

graffl supports line comments. A line with double slash // as the first non-whitespace characters is ignored.

Note: If you are within a multiline string and want to start line with // you can use escaping \//.

Implementation Note: Because the introduction of comments would make the grammar very complicated the line comments are removed in a pre-process.

Base URI Prefix

The directive @prefix defines the base URI prefix. It is called "prefix" because the resulting URI is a simple concatenation of the prefix with the encoded node label:

@ prefix <http://example.org/ns#>

"Mr. Bean"

produces the URI <http://example.org/ns#Mr.%20Bean>.

The default URI prefix is https://www.hedenus.de/graffl/ plus a runtime UUID, i.e. every parser run will create new unique URIs for the nodes. Define a custom prefix for reproducible URIs.

Aliases

Every word can be an alias for a URI. For defining an alias in the script use an assignment = followed by a URI in a directive:

@ x = <http://www.example.org/x>
@ y = <http://fowf#y>
@ z = <urn:z>

x y z

The set of aliases is called dictionary. Here is a list of predefined aliases or default dictionary (see also the code of src/graffl/config.py):

Alias URI

:, type

rdf:type

= , value

rdf:value

Alt

rdf:Alt

Bag

rdf:Bag

Seq

rdf:Seq

*

rdf:li

label

rdfs:label

;, comment

rdfs:comment

seeAlso

rdfs:seeAlso

Namespaces and QNames

graffl supports namespaces and qnames (qualified names). to make your script more concise and readable. A qname consists of a prefix and a local name, separated by a colon (e.g. foaf:Person). When parsing, graffl automatically expands this into a full URI.

You can define a namespace prefix using the assignment directive with an = sign followed by a URI at the beginning of your file:

@ ex = <http://example.org/persons#>

Once a namespace is defined, you can use it for classes, properties, or individuals:

@ ex = <http://example.org/persons#>

Alice : foaf:Person
    schema:name "Alice Müller"
    foaf:knows -> ex:Bob

The following prefixes are predefined (see also the code of src/graffl/config.py):

Prefix Namespace URI

dc

<http://purl.org/dc/elements/1.1/>

dcterms

<http://purl.org/dc/terms/>

foaf

<http://xmlns.com/foaf/0.1/>

owl

<http://www.w3.org/2002/07/owl#>

rdf

<http://www.w3.org/1999/02/22-rdf-syntax-ns#>

rdf2graphml

https://www.hedenus.de/rdf2graphml/

rdfs

<http://www.w3.org/2000/01/rdf-schema#>

schema

<http://schema.org/>

sh

<http://www.w3.org/ns/shacl#>

skos

<http://www.w3.org/2004/02/skos/core#>

xsd

<http://www.w3.org/2001/XMLSchema#>

Identity vs. Names

graffl's default behavior of generating URIs from labels is perfect for quick sketches ("ABox") and ontologies ("TBox"). Very often, especially in the ABox case, the final URI of an entity is not known from the beginning of the modeling process. You can either use namespaces or you can use aliases for specifying URIs ex post.

For example, you start with the initial graph:

Alice likes -> Bob

Later, when you have identified the URIs you define them as aliases:

@ Alice = <urn:example.org:persons:12345>
@ Bob   = <urn:example.org:persons:67890>
@ likes = <http://purl.org/spar/cito/likes>

Alice likes -> Bob

If you need to distinguish between two entities with the same name, use the node reference syntax (e.g. (person001)):

(person001)
    label "Alice"

(person002)
    label "Alice"

(person001) knows -> (person002)

URI Properties

Sometimes a predicate is considered a property but its value shall be an individual. Then a predicate can be specified to be a URI property by assigning it a URI type:

@ state : URI

Alice state ACTIVE

The produced RDF as Turtle:

@prefix ns1: <https://www.hedenus.de/graffl/5e7a8688-3705-11f1-b85e-e08f4ccbe174/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

ns1:Alice rdfs:label "Alice" ;
    ns1:state ns1:ACTIVE .

Predefined URI properties are (see also the code of src/graffl/config.py):

  • rdf:type

Datatypes

graffl supports datatypes for literals. To specify a datatype, use the @ symbol followed by the datatype name directly before the object value. The common XML Schema 1.1 datatypes are predefined (see also the code of src/graffl/config.py).

Example:

Event
  startDate @dateTime "2023-11-01T12:00:00"
  participantCount @integer 42
  isPublic @boolean true

The produced RDF as Turtle:

@prefix ns1: <https://www.hedenus.de/graffl/eec682c0-3747-11f1-9010-e08f4ccbe174/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

ns1:Event rdfs:label "Event" ;
    ns1:isPublic true ;
    ns1:participantCount 42 ;
    ns1:startDate "2023-11-01T12:00:00"^^xsd:dateTime .

Every literal without a datatype (or language) is a plain literal. The syntax makes it easy for defining the datatype ex post.

Note: If the datatype is not known it will be interpreted as language tag (see below).

Languages

An object may be tagged with an ISO 639 language code. This will produce tagged RDF literals:

(a) label @en Alice
    label @tlh QelIS

Blank Nodes

Objects may be blank nodes. They are surrounded by square brackets and look very much like Turtle:

Alice fullName [ firstName "Alice"
                 secondName "Jane" ]

Containers

By default * is a predefined alias for rdf:li.

A numbering nnn., where nnn is any positive integer number, produces rdf:_nnn:

(Tasks)
  1. Beginn
  2. "do something"
  * Break
  99. Done

The produced RDF as Turtle:

@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .

<https://www.hedenus.de/graffl/d406e486-37cd-11f1-a4c8-e08f4ccbe174/Tasks> rdf:_1 "Beginn" ;
    rdf:_2 "do something" ;
    rdf:_99 "Done" ;
    rdf:li "Break" .

Lists

A list (i.e. a closed collection or rdf:List) starts with *( and ends with ). The predicate type is applied to the list members. For example:

Rainbow colors *( Red Green Blue )

will regard the colors as literals whereas

Rainbow colors -> *( Red Green Blue )

will regard the colors as nodes.

Contexts (Named Graphs)

You can specify the target context (named graph) with the directive context followed by a URI

@ context <http://example.org/mygraph>

Profiles

Dictionary and URI Properties

graffl can be customized with profiles. A profile is a YAML configuration file. You can use more than one profile, they are loaded in the specified order.

For example, consider the following graffl code:

@ use test1
Alice likes Bob

and the profile test1.yaml:

dictionary:
  Alice: "mailto:alice@example.org"
  Bob: "http://example.org/~bob"
  likes: "http://purl.org/spar/cito/likes"

uri_properties:
  - "http://purl.org/spar/cito/likes"

then the produced RDF as Turtle is:

@prefix ns1: <http://purl.org/spar/cito/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

<mailto:alice@example.org> rdfs:label "Alice" ;
    ns1:likes <http://example.org/~bob> .

Vocabularies

While the dictionary is useful for mapping individual aliases, the vocabularies key allows you to map entire lists of terms to a base URI in a very concise way. A vocabulary is defined as a mapping where the key is the base URI and the value is a list of terms:

vocabularies:
  "http://example.org/vocab#":
    - DataProduct
    - domain

Note: If the Base URI does not already end with a slash / or a hash #, then # is automatically appended as separator.

Modeling with RDFSchema, OWL and SHACL

There are predfined profiles for RDFSchema, OWL and SHACL. This makes writing an ontology nice and simple:

@ use RDFSchema
@ use OWL
@ prefix <http://example.org/pizza#>

SalamiPizza : Class
    subClassOf [ : Class
        label "Pizza with Salami + Mozzarella"
        intersectionOf *(
            Pizza
            [ : Restriction
                onProperty hasTopping
                someValuesFrom Salami
            ]
            [ : Restriction
                onProperty hasTopping
                someValuesFrom Mozzarella
            ]
        )
    ]

If you use SHACL as well, there are naming conflicts, especially for hasValue. In that case you can disambiguate with namespaces:

@ prefix <http://example.org/ns#>
@ use RDFSchema
@ use OWL
@ use SHACL

MyClass : Class
    subClassOf [ : Restriction
        onProperty status
        owl:hasValue "ACTIVE"
        ]

(MyClass.status = ACTIVE) : NodeShape
    targetClass MyClass
    property [ : PropertyShape
        path status
        sh:hasValue "ACTIVE"
    ]

Group Graphs

Group graphs provide a simple and visual way to cluster statements into logical groups or contexts. A group graph begins with a header containing the group’s name enclosed by ---- (at least four minus characters) and ends with a closing line of ---- (at least four minus characters). Any node that appears as a subject within these boundaries is automatically linked as being contained by the group.

Note: The graffl syntax does not allow you to physically nest one group graph block inside another. Trying to open a new group graph before closing the current one will result in a syntax error.

Instead, to model hierarchical structures or logical nesting, you must define the group blocks side by side. You can then establish the hierarchy by simply using a child group’s name as a subject within the parent group’s block, or by linking them with explicit relations.

Simple Groups

For example, consider two teams Team-1 and Team-2. Alice and Bob are in the first, Chris and David the second team. The teams are working together and both are part of the Agile Release Train. You can write this down as follows:

---- Team-1 ----
    Alice

    Bob
----------------

Team-1 worksWith -> Team-2

---- Team-2 ----
    Chris

    David
----------------

---- The Agile Release Train ----

    Team-1

    Team-2

--------------------------------

The produced RDF as Turtle:

@prefix ns1: <https://www.hedenus.de/graffl/> .
@prefix ns2: <https://www.hedenus.de/graffl/9edf4a14-366c-11f1-8458-e08f4ccbe174/> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .

ns2:Team-1 a ns1:Group ;
    rdfs:label "Team-1" ;
    ns2:worksWith ns2:Team-2 ;
    ns1:contains ns2:Alice,
        ns2:Bob .

ns2:Team-2 a ns1:Group ;
    rdfs:label "Team-2" ;
    ns1:contains ns2:Chris,
        ns2:David .

ns2:The%20Agile%20Release%20Train a ns1:Group ;
    rdfs:label "The Agile Release Train" ;
    ns1:contains ns2:Team-1,
        ns2:Team-2 .


ns2:Alice rdfs:label "Alice" .

ns2:Bob rdfs:label "Bob" .

ns2:Chris rdfs:label "Chris" .

ns2:David rdfs:label "David" .

Referencing the Group from Within

Inside a group you can use . as self-reference:

---- Team-1 ----

. ; "The main delivery team"

(Alice) isLeadOf -> .

----------------

Hierarchical Modeling

One important application of this feature is for breaking down things into different levels of details. For example, imagine a battery holder which contains three batteries. These batteries are all instances of the same class and have the same inner structure. The inner structure of a battery is modeled as two connection points and electrochemistry:

System : Class

BatteryHolder subClassOf System

Battery subClassOf System

ConnectionPoint subClassOf System

Electrochemistry subClassOf System


---- BatteryHolder ----

b1 : Battery

b2 : Battery

b3 : Battery

b1 connectedTo -> b2

b2 connectedTo -> b3

-----------------------


---- Battery ----------------------------

plus : ConnectionPoint

minus : ConnectionPoint

electrochemistry : Electrochemistry

minus electricFlow -> electrochemistry

electrochemistry electricFlow -> plus

-----------------------------------------