Copyright © 2021-2026 the Contributors to the RML-FNML Specification, published by the Knowledge Graph Construction Community Group under the W3C Community Contributor License Agreement (CLA). A human-readable summary is available.
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
Referenced in:
RML+FnO is an approach to provide for data transformations when generating knowledge graphs from (semi-)structured data using the RDF Mapping Language (RML).
In RML+FnO, data transformations are defined declaratively, supporting the Function Ontology (FnO).
This approach is not case-specific: data transformations are independent of their implementation and thus interoperable, while the functions are decoupled and reusable. This allows developers to improve the generation framework independent from the contributors that focus on generating the knowledge graphs.
This specification was published by the Knowledge Graph Construction Community Group. It is not a W3C Standard nor is it on the W3C Standards Track. Please note that under the W3C Community Contributor License Agreement (CLA) there is a limited opt-out and other conditions apply. Learn more about W3C Community and Business Groups.
GitHub Issues are preferred for discussion of this specification.
As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.
The key words MAY and MUST in this document are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.
Mapping languages allow us to define how knowledge graphs are generated from (semi-)structured data, but only if the original data values can be used as-is. Complex data transformations in mapping languages typically are either implemented as custom solutions, or are done by systems separate from the mapping process. The former data transformations remain case-specific, often coupled with the mapping, whereas the latter is not reusable across systems.
For example, we have a data source where all names are lowercase, but we want the resulting knowledge to have uppercase values. The following RML Mapping contains the descriptions to generate a knowledge graph from a data source, but no data transformations.
[RML]-independent defintions:
function int sum(int a, int b) throws StackOverFlowException, namely,
the Function sum has two input parameters, int a and int b, and returns an int or throws a StackOverFlowException.sum(2, 4) = 6 is an execution,
where 2 is bound to the a Parameter, 4 is bound to the b Parameter, and 6 is bound as Return value.Definitions taken from [RML] or R2RML: RDB to RDF Mapping Language:
Within this specification, the Expression Map definition is extended by adding new possible properties and thus also a new type of Expression Map. The change is included below with changes highlighted in bold.
An Expression Map can have the following properties:
It is currently assumed that a Function-valued Expression Map always returns an RDF term [rdf-concepts]. How a list of RDF terms is handled, is out of scope of this spec, but discussed at Collections and Containers in RML.
Instead of integrating a specific set of functions in [RML], we combine [RML] with declarative function descriptions in [FnO].
Within [FnO], Functions and Executions are described. Within FNML, we refer to Executions that link to specific Functions.
Triples Maps generate output triples from input data. We use an intermediate Function-valued Expression Map to use a specific output (via a Return Map) of a Function Execution. That Function Execution specifies which [FnO] function to use (via a Function Map) and uses Inputs to link input data (via regular [RML] Term Maps) to Parameters (via Parameter Maps).
If an execution returns multiple returning outputs (eg, a result and a status code), by referring to the same execution, you can use both outputs in different locations of the same mapping. If you leave out the intermediate Function-valued Expression Map, you don't allow for reuse, which means that you cannot specify the difference between 'using 2 outputs from one execution' vs 'use a different output from 2 different executions'.
We use Example 1, where we want to perform an uppercase operation to a set of fields.
The FnO description of the function toUppercase is as follows:
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix fno: <https://w3id.org/function/ontology#> .
@prefix grel: <http://users.ugent.be/~bjdmeest/function/grel.ttl#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
grel:toUpperCase
a fno:Function ;
fno:name "to Uppercase" ;
rdfs:label "to Uppercase" ;
dcterms:description "Returns the input with all letters in upper case." ;
fno:expects ( grel:valueParam ) ;
fno:returns ( grel:stringOut ) .
grel:valueParam
a fno:Parameter ;
fno:name "input value" ;
rdfs:label "input value" ;
fno:predicate grel:valueParameter ;
fno:type xsd:string ;
fno:required "true"^^xsd:boolean .
grel:stringOut
a fno:Output ;
fno:name "output string" ;
rdfs:label "output string" ;
fno:predicate grel:stringOutput ;
fno:type xsd:string .
The execution of such a function converts a string to its uppercase sibling,
so test becomes TEST and This is an input STRING. becomes THIS IS AN INPUT STRING..
The latter would be described as follows using an FnO Execution description:
@prefix fno: <https://w3id.org/function/ontology#> .
@prefix grel: <http://users.ugent.be/~bjdmeest/function/grel.ttl#> .
@prefix : <http://example.com/> .
:exe a fno:Execution ;
fno:executes grel:toUppercase ;
grel:valueParameter "This is an input STRING." ;
grel:stringOutput "THIS IS AN INPUT STRING." .
To connect this function with the RML mapping document, we make use of FNML, see below for an example, which makes maximal use of shortcuts.
@prefix grel: <http://users.ugent.be/~bjdmeest/function/grel.ttl#> .
@prefix rml: <http://w3id.org/rml/> .
@prefix ex: <http://example.com/> .
<#Person_Mapping>
rml:logicalSource <#LogicalSource> ;
rml:subjectMap <#SubjectMap> ;
rml:predicateObjectMap <#NameMapping> .
<#NameMapping>
rml:predicate ex:title ;
rml:objectMap [ # A function-valued expression map
rml:functionExecution <#Execution> ; # Link to a Function Execution
rml:return grel:stringOut # Specify which return of the referenced function to use, if omitted, the first specified return is used
] .
<#Execution> a rml:FunctionExecution ; # A new class
rml:function grel:toUppercase ; # Specify which FnO function
rml:input [ # Specify the inputs
a rml:Input ; # A new class
rml:parameter grel:valueParam ; # Specify this specific parameter
rml:inputValueMap [ # Link to the term map that creates the input value
rml:reference "name" # Specify the reference within the data source
]
] .
The name-value is not referenced directly,
instead, its value is used as grel:valueParam-parameter
for the grel:toUppercase-function.
After execution, the grel:stringOut result of that function is returned to generate the object
within the <#NameMapping>.
We make use of an intermediate Function-valued Expression Map so that we can reuse the returning output of an execution in multiple TermMaps.
The same example, but written without shortcuts, is as follows:
@prefix grel: <http://users.ugent.be/~bjdmeest/function/grel.ttl#> .
@prefix rml: <http://w3id.org/rml/> .
@prefix ex: <http://example.com/> .
<#Person_Mapping>
rml:logicalSource <#LogicalSource> ; # Specify the data source
rml:subjectMap <#SubjectMap> ; # Specify the subject
rml:predicateObjectMap <#NameMapping> . # Specify the predicate-object-map
<#NameMapping>
rml:predicate ex:title ; # Specify the predicate
rml:objectMap [ # Specify the object-map: a function-valued expression map
rml:functionExecution <#Execution> ; # Link to a Function Execution
rml:returnMap [
a rml:ReturnMap ;
rml:constant grel:stringOut # Specify which return of the referenced function to use
]
] .
<#Execution> a rml:FunctionExecution ; # A new class
rml:functionMap [
a rml:FunctionMap ;
rml:constant grel:toUppercase # Specify which FnO function
] ;
rml:input # Specify the inputs
[
a rml:Input ; # A new class
rml:parameterMap [
a rml:ParameterMap ;
rml:constant grel:valueParam ; # Specify this specific parameter
] ;
rml:inputValueMap [ # Link to the term map that creates the input value
a rml:TermMap ;
rml:reference "name" # Specify the reference within the data source
]
] .
We use terms defined in the FNML ontology to link [RML] with [FNO].
The ontology namespace is http://w3id.org/rml/,
the preferred prefix is rml:.
See below for how FNML introduced terms align with RML Core.
A Function-valued Expression Map is an Expression Map that is represented by a resource that has exactly one rml:functionExecution.
The value of the rml:functionExecution property must be a valid Execution.
As a consequence, the default [RML] processing is extended,
specifically concerning the default term type depending on whether the Term Map is an Object Map or not,
namely, the Function-valued Expression Maps default term type is rml:Literal.
The change is included below with changes highlighted in bold.
If the Term Map does not have a rml:termType property, then its term type is:
rml:Literal, if it is an Object Map and at least one of the following conditions is true:rml:languageMap and/or rml:language property (and thus a Language Map and/or a specified language tag).rml:datatype property (and thus a specified datatype).rml:IRI, otherwise.A Function-valued Expression Map MUST have exactly one rml:functionExecution relation.
Further, it MAY have following relations specified:
rml:termType: for processing, see paragraph aboverml:language OR rml:languageMap OR rml:datatype: for processing, see RML Language Tags and RML Typed Literalsrml:return: this relationship MUST refer to exactly one of the Returns as specified by the Function. This signifies which result of the execution to use. The default value is the first Return value as specified by the Function.A proper term map definition in RML is pending.
For now, we refer to the R2RML spec, but it is assumed these references will be updated based on the evolution of RML.
This also means that all changes to existing definitions such as term type etc. are complementary to this specification.
See Return map.
See Function Execution.
See Function map.
See Input.
See Parameter map.
Links function-valued expression map with Function Execution.
Domain: rml:ExpressionMap
Range: rml:FunctionExecution
Links function-valued expression map with Return map.
Domain: rml:ExpressionMap
Range: rml:ReturnMap
constant expression shortcut property of rml:returnMap.
Links a Function Execution with Function.
Domain: rml:FunctionExecution
Range: rml:FunctionMap
constant expression shortcut property of rml:functionMap.
Links Input with Parameter map.
Domain: rml:Input
Range: rml:ParameterMap
constant expression shortcut property of rml:parameterMap
Domain: rml:Input
Range: rml:TermMap
constant expression shortcut property of rml:inputValueMap
When you specifically want to have join conditions, you should use functions within rml:joinCondition, see, e.g. test case RMLFNOTC0019.
Aligned with the other RML specifications,
multivalue expression evaluation results are processed in sequence.
So, if a multivalue expression evaluation contains the multivalue "a", "b", and "c",
the function is applied to each individual value in that order.
As the values of a function are represented using Expression Maps, it is possible to nest functions: you generate a term in a first function, and that term is used as an parameter value in a second function.
For an old example, see RMLFNOTC0018.
For now, it is unclear how to handle a nested function where that nested Triples Map contains a join condition.
@prefix grel: <http://users.ugent.be/~bjdmeest/function/grel.ttl#> .
@prefix rml: <http://w3id.org/rml/> .
@prefix ex: <http://example.com/> .
<#Person_Mapping>
rml:logicalSource <#LogicalSource> ;
rml:subjectMap <#SubjectMap> ;
rml:predicateObjectMap <#NameMapping> .
<#NameMapping>
rml:predicate ex:title ;
rml:objectMap [
rml:functionExecution <#Execution> ;
rml:return grel:stringOut
] ;
.
<#Execution> a rml:FunctionExecution ;
rml:function grel:toUppercase ;
rml:input
[
a rml:Input ;
rml:parameter grel:valueParam ;
rml:inputValueMap [
rml:functionExecution <#Execution2> ; # Link to another function-valued expression map to nest functions
rml:return grel:stringOut
]
] .
<#Execution2> a rml:FunctionExecution ; # First, replace spaces with dashes from the `name` reference
rml:function grel:string_replace ;
rml:input
[
a rml:Input ;
rml:parameter grel:valueParam ;
rml:inputValueMap [
rml:reference "name"
]
] ,
[
a rml:Input ;
rml:parameter grel:param_find ;
rml:inputValue " "
] ,
[
a rml:Input ;
rml:parameter grel:param_replace ;
rml:inputValue "-"
] .
Conditions are a shortcut to make RML mappings more intuitive, but rely on existing FNML functionality.
It is a shortcut that is applied using the rml:condition: an additional ExpressionMap predicate.
To be able to use this shortcut, conforming mapping engines MUST support following functions:
isNotNull and IF are defined below, rest is an excercise for the reader. The actual FnO definitions are TODO.
@prefix fns: <http://example.com/fns#> .
@prefix rml: <http://w3id.org/rml/> .
@prefix ex: <http://example.com/> .
<#Person_Mapping>
rml:logicalSource <#LogicalSource> ;
rml:subjectMap <#SubjectMap> ;
rml:predicateObjectMap <#NameMapping> .
<#NameMapping>
rml:predicate ex:title ;
# A condition can be defined in any expression map
rml:objectMap [
# new predicate that links to a function-valued expression map,
# that function MUST return a boolean
rml:condition [
rml:functionExecution [
# isNotNull(parameter: X) / definition: X != NULL ? TRUE : FALSE ;
rml:function fns:isNotNull ;
rml:input [
# The parameter that is checked for NULL
rml:parameter fns:parameter ;
rml:inputValueMap [
rml:reference "name"
]
]
] ;
rml:return fns:boolOut # if fno:boolOut is the first specified return, this triple can be ommitted.
] ;
# The actual expression used if the condition returns TRUE
rml:constant "[a filled in title]"
] .
This is actually a shortcut to the following
@prefix fns: <http://example.com/fns#> .
@prefix rml: <http://w3id.org/rml/> .
@prefix ex: <http://example.com/> .
<#Person_Mapping>
rml:logicalSource <#LogicalSource> ;
rml:subjectMap <#SubjectMap> ;
rml:predicateObjectMap <#NameMappingExtended> .
<#NameMappingExtended>
rml:predicate ex:title ;
rml:objectMap [
rml:functionExecution [
# IF(bool: X, expression: Y)
# Function definition: X === TRUE ? Y : NULL
rml:function fns:IF ;
rml:input [
# = original condition function
rml:parameter fns:boolParameter ;
rml:inputValueMap [
rml:functionExecution [
rml:function fns:isNotNull ;
rml:input [
rml:parameter fns:parameter ;
rml:inputValueMap [
rml:reference "name"
]
]
]
]
] , [
# = original expression
rml:parameter fns:expressionParameter ;
rml:inputValueMap [
rml:constant "[a filled in title]"
]
]
] ;
] .
# Any custom function can be used,
# or nested functions (eg AND/OR),
# depending on what the engines support
Let's take following example data
[
{
"conditionValue": [1, 0, 5],
"values": ["a", "b", "c"]
}
]
If we execute following RML mapping
@prefix fns: <http://example.com/fns#> .
@prefix rml: <http://w3id.org/rml/> .
@prefix ex: <http://example.com/> .
<#Person_Mapping>
rml:logicalSource <#LogicalSource> ;
rml:subjectMap <#SubjectMap> ;
rml:predicateObjectMap <#NameMapping> .
# Suggestion: add rml:condition predicate to expression map,
# and conforming mapping engines MUST support following functions:
# - isNull, isNotNull, equals, noEquals, IF
# (isNotNull and IF are defined below, rest is an excercise for the reader)
<#NameMapping>
rml:predicate ex:id ;
# A condition can be defined in any expression map
rml:objectMap [
# new predicate that links to a function-valued expression map,
# that function MUST return a boolean
rml:condition [
rml:functionExecution [
# notEquals(parameter: X, compared: Y) / definition: X != Y ? TRUE : FALSE ;
rml:function fns:notEquals ;
rml:input [
# The parameter that is checked
rml:parameter fns:parameter ;
rml:inputValueMap [
rml:reference "conditionValue"
]
] , [
# The parameter that is compared to
rml:parameter fns:compared ;
rml:input "0"
]
] ;
rml:return fns:boolOut # if fno:boolOut is the first specified return, this triple can be ommitted.
] ;
# The actual expression used if the condition returns TRUE,
# In this case a UUID generator function is used.
rml:objectMap <#ANestedFunctionThatReturnsAUUIDv4> ;
] .
This will result in two triples, because in conditionValue you have two valid condition executions
<subject> ex:id "df2c61cc-6fad-435d-aa95-10761840478b" .
<subject> ex:id "44d01ee9-448f-4804-acc8-3272939494b0" .