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
-
It should be possible to write down data immediately, just as on a notepad
and therefore
-
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:
-
Directives starting with
@ -
Blocks
-
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 |
|---|---|
|
A node with label |
|
A node with label |
|
A node with label |
|
A node reference, no label |
|
A anonyoums node, no label |
|
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 |
|---|---|
|
rdf:type |
|
rdf:value |
|
rdf:Alt |
|
rdf:Bag |
|
rdf:Seq |
|
rdf:li |
|
rdfs:label |
|
rdfs:comment |
|
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 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 -----------------------------------------