Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
anaigmo committed May 9, 2023
2 parents 4f60a16 + 9253c25 commit 904fbc9
Show file tree
Hide file tree
Showing 18 changed files with 2,553 additions and 290 deletions.
29 changes: 29 additions & 0 deletions .github/workflows/shapes.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions

name: Test case validation with SHACL shapes

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
uses: actions/setup-python@v2
with:
python-version: 3.9
- name: Install dependencies
run: |
python -m pip install --upgrade pip
cd shapes
pip install -r requirements.txt
- name: Run tests
run: |
cd shapes
python3 tests.py
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.DS_Store
.DS_Store
__pycache__
397 changes: 397 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
# Collections and Containers in RML specification
# RML-CC

Collections and Containers in RML specification, SHACL shapes, ontology, and test-cases.

- Specification: http://w3id.org/rml/cc/spec
- Ontology: http://w3id.org/rml/cc/
- SHACL shapes: http://w3id.org/rml/cc/shapes
- Test-cases: [test-cases](./test-cases)

## Specification

Expand All @@ -19,3 +26,13 @@ It is accessible using URL https://w3id.org/rml/cc/spec.
- Run `node publish.js` to get the `index.html` + archived version in the [`docs`](docs) folder

URL https://w3id.org/rml/cc/spec must redirect to the `/spec/docs`(docs) folder.

## License

RML-CC (c) by W3C Community Group on Knowledge Graph Construction

RML-CC is licensed under a
Creative Commons Attribution-ShareAlike 4.0 International License.

You should have received a copy of the license along with this
work. If not, see <http://creativecommons.org/licenses/by-sa/4.0/>.
97 changes: 97 additions & 0 deletions shapes/cc.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
@prefix brick: <https://brickschema.org/schema/Brick#> .
@prefix csvw: <http://www.w3.org/ns/csvw#> .
@prefix dc: <http://purl.org/dc/elements/1.1/> .
@prefix dcam: <http://purl.org/dc/dcam/> .
@prefix dcat: <http://www.w3.org/ns/dcat#> .
@prefix dcmitype: <http://purl.org/dc/dcmitype/> .
@prefix dcterms: <http://purl.org/dc/terms/> .
@prefix doap: <http://usefulinc.com/ns/doap#> .
@prefix foaf: <http://xmlns.com/foaf/0.1/> .
@prefix geo: <http://www.opengis.net/ont/geosparql#> .
@prefix odrl: <http://www.w3.org/ns/odrl/2/> .
@prefix org: <http://www.w3.org/ns/org#> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix prof: <http://www.w3.org/ns/dx/prof/> .
@prefix prov: <http://www.w3.org/ns/prov#> .
@prefix qb: <http://purl.org/linked-data/cube#> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@prefix rml: <http://w3id.org/rml/> .
@prefix : <http://w3id.org/rml/shapes/> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix skos: <http://www.w3.org/2004/02/skos/core#> .
@prefix sosa: <http://www.w3.org/ns/sosa/> .
@prefix ssn: <http://www.w3.org/ns/ssn/> .
@prefix time: <http://www.w3.org/2006/time#> .
@prefix vann: <http://purl.org/vocab/vann/> .
@prefix void: <http://rdfs.org/ns/void#> .
@prefix wgs: <https://www.w3.org/2003/01/geo/wgs84_pos#> .
@prefix xml: <http://www.w3.org/XML/1998/namespace> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

<http://w3id.org/rml/shapes/RMLGatherMapShape> a sh:NodeShape ;
sh:and ( <http://w3id.org/rml/shapes/RMLTermMapShape> <http://w3id.org/rml/shapes/RMLLogicalTargetPropertiesShape> [ sh:description """
rml:strategy specifies the collection strategy to use: rml:append
or rml:catessianProduct. rml:append is the default strategy.
""" ;
sh:maxCount 1 ;
sh:message """
Zero or one rml:strategy is required.
""" ;
sh:minCount 0 ;
sh:name "strategy" ;
sh:node <http://w3id.org/rml/shapes/RMLStrategyAppendShape> ;
sh:nodeKind sh:IRI ;
sh:path rml:strategy ] [ sh:description """
rml:gatherAs specifies how to gather the collection e.g. a rdf:Alt,
rdf:List, rdf:Bag, or rdf:Seq.
""" ;
sh:in ( rdf:Alt rdf:List rdf:Bag rdf:Seq ) ;
sh:maxCount 1 ;
sh:message """
One rml:gatherAs is required.
""" ;
sh:minCount 1 ;
sh:name "gatherAs" ;
sh:nodeKind sh:IRI ;
sh:path rml:gatherAs ] [ sh:datatype xsd:boolean ;
sh:description """
Defines the behavior when the collection is empty. True will generate
a rdf:nil for a RDF collection or a resource with no members for an RDF
container. False will not generate any collection or container.
Default is false.
""" ;
sh:maxCount 1 ;
sh:message """
Zero or one rml:allowEmptyListAndContainer is required with datatype
xsd:boolean.
""" ;
sh:minCount 0 ;
sh:name "allowEmptyListAndContainer" ;
sh:nodeKind sh:Literal ;
sh:path rml:allowEmptyListAndContainer ] [ sh:description """
RML Term Maps to gather in the collection or container.
""" ;
sh:message """
one or more rml:gather properties are needed, each pointing to a
RML Term Map.
""" ;
sh:minCount 1 ;
sh:name "gather" ;
sh:nodeKind sh:BlankNodeOrIRI ;
sh:path rml:gather ] ) ;
sh:description """
Represents a Gather Map.
""" ;
sh:message """
Gather Map requires one rml:strategy, one rml:gatherAs, and a list of
Term Map with rml:gather.
""" ;
sh:name "GatherMap" .

<http://w3id.org/rml/shapes/RMLLogicalTargetPropertiesShape> a sh:NodeShape .

<http://w3id.org/rml/shapes/RMLStrategyAppendShape> a sh:NodeShape .

<http://w3id.org/rml/shapes/RMLTermMapShape> a sh:NodeShape .

24 changes: 24 additions & 0 deletions shapes/external_shapes.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
###############################################################################
# RML FNML external resources shapes #
# Copyright Dylan Van Assche, IDLab - UGent - imec (2023) #
###############################################################################
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix : <http://w3id.org/rml/shapes/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rml: <http://w3id.org/rml/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

# Shapes are defined in the corresponding repositories of each specification
# Provide their IRI as stub to allow validation in CC.

:RMLTermMapShape
a sh:NodeShape;
.

:RMLLogicalTargetPropertiesShape
a sh:NodeShape;
.

:RMLStrategyAppendShape
a sh:NodeShape;
.
89 changes: 89 additions & 0 deletions shapes/gather_map.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
###############################################################################
# RMLCC Gather Map shape #
# Copyright Dylan Van Assche, IDLab - UGent - imec (2023) #
###############################################################################
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix : <http://w3id.org/rml/shapes/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rml: <http://w3id.org/rml/> .
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .

:RMLGatherMapShape
a sh:NodeShape ;
sh:name "GatherMap" ;
sh:description """
Represents a Gather Map.
""" ;
sh:message """
Gather Map requires one rml:strategy, one rml:gatherAs, and a list of
Term Map with rml:gather.
""" ;

sh:and (
# Inherited shapes
:RMLTermMapShape
:RMLLogicalTargetPropertiesShape
# Gather Map specific shapes
[
sh:path rml:strategy ;
sh:name "strategy" ;
sh:description """
rml:strategy specifies the collection strategy to use: rml:append
or rml:catessianProduct. rml:append is the default strategy.
""" ;
sh:message """
Zero or one rml:strategy is required.
""" ;
sh:node :RMLStrategyAppendShape ;
sh:nodeKind sh:IRI ;
sh:minCount 0 ;
sh:maxCount 1 ;
]
[
sh:path rml:gatherAs ;
sh:name "gatherAs" ;
sh:description """
rml:gatherAs specifies how to gather the collection e.g. a rdf:Alt,
rdf:List, rdf:Bag, or rdf:Seq.
""" ;
sh:message """
One rml:gatherAs is required.
""" ;
sh:in (rdf:Alt rdf:List rdf:Bag rdf:Seq) ;
sh:nodeKind sh:IRI;
sh:minCount 1 ;
sh:maxCount 1 ;
]
[
sh:path rml:allowEmptyListAndContainer ;
sh:name "allowEmptyListAndContainer" ;
sh:description """
Defines the behavior when the collection is empty. True will generate
a rdf:nil for a RDF collection or a resource with no members for an RDF
container. False will not generate any collection or container.
Default is false.
""" ;
sh:message """
Zero or one rml:allowEmptyListAndContainer is required with datatype
xsd:boolean.
""" ;
sh:minCount 0 ;
sh:maxCount 1 ;
sh:nodeKind sh:Literal ;
sh:datatype xsd:boolean ;
]
[
sh:path rml:gather ;
sh:name "gather" ;
sh:description """
RML Term Maps to gather in the collection or container.
""" ;
sh:message """
one or more rml:gather properties are needed, each pointing to a
RML Term Map.
""" ;
sh:nodeKind sh:BlankNodeOrIRI ;
sh:minCount 1 ;
]
) ;
.
79 changes: 79 additions & 0 deletions shapes/generate_shape.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#!/usr/bin/env python

import argparse
import sys
from os import path
from glob import glob
from rdflib import Graph, Namespace
from rdflib.plugins.serializers.turtle import TurtleSerializer
from rdflib import plugin

SUBSHAPE_FORMAT = 'turtle'
SUBSHAPE_GLOB_PATTERN = '*.ttl'
SHACL = Namespace('http://www.w3.org/ns/shacl#')
RML = Namespace('http://w3id.org/rml/')


class TurtleWithPrefixes(TurtleSerializer):
"""
A turtle serializer that always emits prefixes
Workaround for https://github.com/RDFLib/rdflib/issues/1048
"""
roundtrip_prefixes = True # Undocumented, forces to write all prefixes


class ShapeGenerator:
"""
Generates a complete SHACL shape of all the SHACL subshapes.
Useful for using the shapes in the shacl.org Playground.
"""
def __init__(self, glob_pattern: str, rdf_format: str,
destination: str) -> None:
self._glob_pattern: str = glob_pattern
self._rdf_format: str = rdf_format
self._destination: str = destination
self._shape = Graph()
self._shape.bind('sh', SHACL)
self._shape.bind('rml', RML)
# Register TurtleWithPrefixes serializer as 'tortoise' format
plugin.register('tortoise',
plugin.Serializer,
'generate_shape',
'TurtleWithPrefixes')

def generate(self) -> None:
"""
Generate a full shape from the sub shapes.
"""
print('Reading subshapes:')
for sub_shape in glob(self._glob_pattern):
if 'cc' in sub_shape or 'shacl' in sub_shape:
continue
print(f"\t{sub_shape}")
g = Graph()
g.bind('sh', SHACL)
g.bind('rml', RML)
self._shape += g.parse(sub_shape, format=self._rdf_format)

print(f'Writing shape to {self._destination}')
self._shape.serialize(destination=self._destination, format='tortoise')


if __name__ == '__main__':
p = argparse.ArgumentParser(description='Generate a complete SHACL shape '
'from the subshapes.')
p.add_argument('destination', type=str, help='Location to write the '
'complete SHACL shape as Turtle.')
# Arguments parsing
args = p.parse_args()
if not path.exists(args.destination):
print(f'{args.destination} file path does not exist!')
sys.exit(1)

if path.isdir(args.destination):
args.destination = path.join(args.destination, 'cc.ttl')

# Generate shape
generator = ShapeGenerator(SUBSHAPE_GLOB_PATTERN, SUBSHAPE_FORMAT,
args.destination)
generator.generate()
40 changes: 40 additions & 0 deletions shapes/mapping_validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from logging import debug, info, critical
from pyshacl import validate
from rdflib import Graph
from shutil import get_terminal_size


class MappingValidator:
def __init__(self, shape: str) -> None:
g = Graph()
g.parse(shape, format='turtle')
self._shape = g

def validate(self, rules: Graph, print_report: bool = True) -> None:
valid: bool
report_graph: Graph
report_text: str
valid, report_graph, report_text = validate(rules,
shacl_graph=self._shape)
debug(f'RML rules valid: {valid}')
debug(f'SHACL validation report: {report_text}')

# If mapping rules are invalid, print SHACL report and raise exception
if not valid and print_report:
self._print_report(report_text)
msg = 'RML mapping rules are invalid, a detailed explanation' \
' is available in the report'
critical(msg)
raise ValueError(msg)

def _print_report(self, report_text: str) -> None:
tty_columns: int
tty_columns, _ = get_terminal_size()
info('-' * tty_columns)
title: str = 'RML rules validation report'
white_space: int = int((tty_columns - len(title)) / 2)
title = ' ' * white_space + title + ' ' * white_space
info(title)
info('-' * tty_columns)
info(report_text)
info('-' * tty_columns)
Loading

0 comments on commit 904fbc9

Please sign in to comment.