Skip to content

Commit

Permalink
fix: fix relative IRI and context issues (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
jeswr authored May 7, 2023
1 parent fce35eb commit f3f19fd
Show file tree
Hide file tree
Showing 12 changed files with 115 additions and 87 deletions.
3 changes: 3 additions & 0 deletions __tests__/valid/relative.shaclc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
BASE <http://example.org/basic-shape-with-targets>

shape <TestShape> -> <TestClass> {}
8 changes: 8 additions & 0 deletions __tests__/valid/relative.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@base <http://example.org/basic-shape-with-targets> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .

<> a owl:Ontology .

<TestShape> a sh:NodeShape ;
sh:targetClass <TestClass> .
3 changes: 3 additions & 0 deletions __tests__/valid/relativeHash.shaclc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
BASE <http://example.org/basic-shape-with-targets#thing>

shape <TestShape> -> <TestClass> {}
8 changes: 8 additions & 0 deletions __tests__/valid/relativeHash.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
@base <http://example.org/basic-shape-with-targets#thing> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .

<> a owl:Ontology .

<TestShape> a sh:NodeShape ;
sh:targetClass <TestClass> .
4 changes: 4 additions & 0 deletions __tests__/valid/relativePrefix.shaclc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
BASE <http://example.org/basic-shape-with-targets>
PREFIX ex: <>

shape ex:TestShape -> ex:TestClass {}
9 changes: 9 additions & 0 deletions __tests__/valid/relativePrefix.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@base <http://example.org/basic-shape-with-targets> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <> .

<> a owl:Ontology .

ex:TestShape a sh:NodeShape ;
sh:targetClass ex:TestClass .
4 changes: 4 additions & 0 deletions __tests__/valid/relativePrefixFragment.shaclc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
BASE <http://example.org/basic-shape-with-targets>
PREFIX ex: <#>

shape ex:TestShape -> ex:TestClass {}
9 changes: 9 additions & 0 deletions __tests__/valid/relativePrefixFragment.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@base <http://example.org/basic-shape-with-targets> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <#> .

<> a owl:Ontology .

ex:TestShape a sh:NodeShape ;
sh:targetClass ex:TestClass .
4 changes: 4 additions & 0 deletions __tests__/valid/relativePrefixFragmentThing.shaclc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
BASE <http://example.org/basic-shape-with-targets#thing>
PREFIX ex: <#>

shape ex:TestShape -> ex:TestClass {}
9 changes: 9 additions & 0 deletions __tests__/valid/relativePrefixFragmentThing.ttl
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@base <http://example.org/basic-shape-with-targets#thing> .
@prefix owl: <http://www.w3.org/2002/07/owl#> .
@prefix sh: <http://www.w3.org/ns/shacl#> .
@prefix ex: <#> .

<> a owl:Ontology .

ex:TestShape a sh:NodeShape ;
sh:targetClass ex:TestClass .
21 changes: 21 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,28 @@
const ShaclcParser = require('./ShaclcParser').Parser;
const N3 = require('n3');

// const arr = [];

// this._parser.Parser = {
// factory: N3.DataFactory,
// base: N3.DataFactory.namedNode('urn:x-base:default'),
// prefixes: {
// rdf: 'http://www.w3.org/1999/02/22-rdf-syntax-ns#',
// rdfs: 'http://www.w3.org/2000/01/rdf-schema#',
// sh: 'http://www.w3.org/ns/shacl#',
// xsd: 'http://www.w3.org/2001/XMLSchema#'
// },
// onQuad: (quad) => { arr.push(quad) },
// extended: extendedSyntax === true
// }

class Parser {
constructor() {
}

parse(str, { extendedSyntax } = {}) {
this._parser = new ShaclcParser();

this._parser.Parser.factory = N3.DataFactory;
this._parser.Parser.base = N3.DataFactory.namedNode('urn:x-base:default');
this._parser.Parser.extended = extendedSyntax === true;
Expand All @@ -16,6 +32,11 @@ class Parser {
sh: 'http://www.w3.org/ns/shacl#',
xsd: 'http://www.w3.org/2001/XMLSchema#'
}
this._parser.Parser.currentNodeShape = undefined;
this._parser.Parser.currentPropertyNode = undefined;
this._parser.Parser.nodeShapeStack = [];
this._parser.Parser.tempCurrentNodeShape = undefined;
this._parser.Parser.n3Parser = new N3.Parser({ baseIRI: 'urn:x-base:default' });

const arr = []
this._parser.Parser.onQuad = (quad) => { arr.push(quad) };
Expand Down
120 changes: 33 additions & 87 deletions lib/shaclc.jison
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// TODO: Work out why the alternativePath


%{
/*
Grammar specification for a SHACL compact
Expand All @@ -22,14 +19,6 @@
SH = 'http://www.w3.org/ns/shacl#',
OWL = 'http://www.w3.org/2002/07/owl#',
RDFS = 'http://www.w3.org/2000/01/rdf-schema#';
var base = Parser.base = '', basePath = '', baseRoot = '', currentNodeShape, currentPropertyNode, nodeShapeStack = [], tempCurrentNodeShape;
Parser.prefixes = {
rdf: RDF,
rdfs: RDFS,
sh: SH,
xsd: XSD
}
// TODO: Make sure all SPARQL supported datatypes are here
const datatypes = {
Expand Down Expand Up @@ -83,32 +72,8 @@
// TODO: Port over any updates to this from SPARLQL.js
// Resolves an IRI against a base path
function resolveIRI(iri) {
// Strip off possible angular brackets
if (iri[0] === '<')
iri = iri.substring(1, iri.length - 1);
// Return absolute IRIs unmodified
if (/^[a-z]+:/.test(iri))
return iri;
if (!Parser.base)
throw new Error('Cannot resolve relative IRI ' + iri + ' because no base IRI was set.');
switch (iri[0]) {
// An empty relative IRI indicates the base IRI
case undefined:
return base.value;
// Resolve relative fragment IRIs against the base IRI
case '#':
return base.value + iri;
// Resolve relative query string IRIs by replacing the query string
case '?':
return base.value.replace(/(?:\?.*)?$/, iri);
// Resolve root relative IRIs at the root of the base IRI
case '/':
return base.value.replace(/[^\/:]*$/, '') + iri;
// Resolve all other IRIs at the base IRI's path
default:
return base.value.match(/^(?:[a-z]+:\/*)?[^\/]*/)[0] + iri;
}
// Strip off possible angular brackets and resolve the IRI
return Parser.n3Parser._resolveIRI(iri[0] === '<' ? iri.substring(1, iri.length - 1) : iri)
}
function expandPrefix(iri) {
Expand Down Expand Up @@ -153,30 +118,7 @@
fromCharCode = String.fromCharCode;
// Translates escape codes in the string into their textual equivalent
function unescapeString(string, trimLength) {
string = string.substring(trimLength, string.length - trimLength);
try {
string = string.replace(escapeSequence, function (sequence, unicode4, unicode8, escapedChar) {
var charCode;
if (unicode4) {
charCode = parseInt(unicode4, 16);
if (isNaN(charCode)) throw new Error(); // can never happen (regex), but helps performance
return fromCharCode(charCode);
}
else if (unicode8) {
charCode = parseInt(unicode8, 16);
if (isNaN(charCode)) throw new Error(); // can never happen (regex), but helps performance
if (charCode < 0xFFFF) return fromCharCode(charCode);
return fromCharCode(0xD800 + ((charCode -= 0x10000) >> 10), 0xDC00 + (charCode & 0x3FF));
}
else {
var replacement = escapeReplacements[escapedChar];
if (!replacement) throw new Error();
return replacement;
}
});
}
catch (error) { return ''; }
return string;
return Parser.n3Parser._lexer._unescape(string.substring(trimLength, string.length - trimLength));
}
function emit(s, p, o) {
Expand All @@ -187,7 +129,7 @@
}
function emitProperty(p, o) {
emit(currentPropertyNode, Parser.factory.namedNode(SH + p), o)
emit(Parser.currentPropertyNode, Parser.factory.namedNode(SH + p), o)
}
function chainProperty(name, p, o) {
Expand Down Expand Up @@ -318,12 +260,16 @@ PARAM 'deactivated' | 'severity' | 'message' | 'class' | 'data
%%

// TODO: Work out why this occurs multiple times when the empty file is called with other things (the base from the previous file is somehow getting leaked thorugh)
shaclDoc : directive* (nodeShape|shapeClass)* ttlSection EOF -> emit(Parser.base, Parser.factory.namedNode(RDF_TYPE), Parser.factory.namedNode(OWL + 'Ontology'))
shaclDoc : directive* (nodeShape|shapeClass)* ttlSection EOF -> emit(Parser.factory.namedNode(resolveIRI('')), Parser.factory.namedNode(RDF_TYPE), Parser.factory.namedNode(OWL + 'Ontology'))
;

directive : baseDecl | importsDecl | prefixDecl ;
// TODO: Remove the duplicate declaration of base
baseDecl : KW_BASE IRIREF -> base = Parser.base = Parser.factory.namedNode($2.slice(1, -1))
baseDecl : KW_BASE IRIREF
{
Parser.base = Parser.factory.namedNode($2.slice(1, -1));
Parser.n3Parser._setBase(Parser.base.value);
}
;

// TODO: See if this should be resolveIRI($2)
Expand All @@ -335,15 +281,15 @@ prefixDecl : KW_PREFIX PNAME_NS IRIREF -> Parser.prefixes[$2.substr(0,

nodeShapeIri : iri
{
nodeShapeStack = false
emit(currentNodeShape = $1, Parser.factory.namedNode(RDF_TYPE), Parser.factory.namedNode(SH + 'NodeShape'))
Parser.nodeShapeStack = false
emit(Parser.currentNodeShape = $1, Parser.factory.namedNode(RDF_TYPE), Parser.factory.namedNode(SH + 'NodeShape'))
}
;

nodeShape : KW_SHAPE nodeShapeIri targetClass? turtleAnnotation? nodeShapeBody
;

shapeClass : KW_SHAPE_CLASS nodeShapeIri turtleAnnotation? nodeShapeBody -> emit(currentNodeShape, Parser.factory.namedNode(RDF_TYPE), Parser.factory.namedNode(RDFS + 'Class'))
shapeClass : KW_SHAPE_CLASS nodeShapeIri turtleAnnotation? nodeShapeBody -> emit(Parser.currentNodeShape, Parser.factory.namedNode(RDF_TYPE), Parser.factory.namedNode(RDFS + 'Class'))
;

turtleAnnotation : ';' turtleAnnotation2 -> ensureExtended()
Expand All @@ -352,7 +298,7 @@ turtleAnnotation : ';' turtleAnnotation2 -> ensureExtended()
turtleAnnotation2 : predicate turtleAnnotation?
;

predicate : iri objectList -> $2.forEach(e => emit(currentNodeShape, $1, e))
predicate : iri objectList -> $2.forEach(e => emit(Parser.currentNodeShape, $1, e))
;

objectList : object objectTail* -> [$1, ...$2]
Expand All @@ -371,14 +317,14 @@ objectTail : ',' object -> $2

LB : '['
{
tempCurrentNodeShape = currentNodeShape;
$$ = currentNodeShape = blank();
Parser.tempCurrentNodeShape = Parser.currentNodeShape;
$$ = Parser.currentNodeShape = blank();
}
;

RB : ']'
{
currentNodeShape = tempCurrentNodeShape;
Parser.currentNodeShape = Parser.tempCurrentNodeShape;
}
;

Expand All @@ -387,14 +333,14 @@ blankNodeSection : LB turtleAnnotation2 RB -> $1

LP : "%"
{
tempCurrentNodeShape = currentNodeShape;
currentNodeShape = currentPropertyNode;
Parser.tempCurrentNodeShape = Parser.currentNodeShape;
Parser.currentNodeShape = Parser.currentPropertyNode;
}
;

RP : "%"
{
currentNodeShape = tempCurrentNodeShape
Parser.currentNodeShape = Parser.tempCurrentNodeShape
}
;

Expand All @@ -403,7 +349,7 @@ pcSection : LP turtleAnnotation2 RP

iriHead : iri
{
currentNodeShape = $1
Parser.currentNodeShape = $1
}
;

Expand All @@ -415,34 +361,34 @@ ttlSection : ttlStatement*

startNodeShape : '{'
{
if (!nodeShapeStack) {
nodeShapeStack = [];
if (!Parser.nodeShapeStack) {
Parser.nodeShapeStack = [];
} else {
nodeShapeStack.push(currentNodeShape);
Parser.nodeShapeStack.push(Parser.currentNodeShape);
emit(
// In the grammar a path signals the start of a new property declaration
currentPropertyNode,
Parser.currentPropertyNode,
Parser.factory.namedNode(SH + 'node'),
currentNodeShape = blank(),
Parser.currentNodeShape = blank(),
)
}
$$ = currentNodeShape;
$$ = Parser.currentNodeShape;
}
;

endNodeShape : '}'
{
if (nodeShapeStack.length > 0) {
currentNodeShape = nodeShapeStack.pop();
if (Parser.nodeShapeStack.length > 0) {
Parser.currentNodeShape = Parser.nodeShapeStack.pop();
}
}
;

nodeShapeBody : startNodeShape constraint* endNodeShape -> $1
;

targetClass : '->' iri+ -> $2.forEach(node => { emit(currentNodeShape, Parser.factory.namedNode(SH + 'targetClass'), node) })
targetClass : '->' iri+ -> $2.forEach(node => { emit(Parser.currentNodeShape, Parser.factory.namedNode(SH + 'targetClass'), node) })
;

constraint : ( nodeOrEmit+ | propertyShape ) pcSection? '.'
Expand All @@ -451,7 +397,7 @@ constraint : ( nodeOrEmit+ | propertyShape ) pcSection? '.'
orNotComponent : '|' nodeNot -> $2
;

nodeOrEmit : nodeOr -> emit(currentNodeShape, Parser.factory.namedNode(SH + $1[0]), $1[1])
nodeOrEmit : nodeOr -> emit(Parser.currentNodeShape, Parser.factory.namedNode(SH + $1[0]), $1[1])
;

nodeOr : nodeNot
Expand Down Expand Up @@ -529,9 +475,9 @@ path : pathAlternative
{
emit(
// In the grammar a path signals the start of a new property declaration
currentNodeShape,
Parser.currentNodeShape,
Parser.factory.namedNode(SH + 'property'),
currentPropertyNode = blank(),
Parser.currentPropertyNode = blank(),
)
emitProperty('path', $1)
Expand Down

0 comments on commit f3f19fd

Please sign in to comment.