From cf8a8eac35fd8164e855329aaebf2d7d44bc8003 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:30:44 +0100 Subject: [PATCH 1/4] chore: normalize shacl12-core html document --- shacl12-core/index.html | 299 +++++++++++++++++++++++++--------------- 1 file changed, 186 insertions(+), 113 deletions(-) diff --git a/shacl12-core/index.html b/shacl12-core/index.html index 1fce7e21..04b99f6c 100644 --- a/shacl12-core/index.html +++ b/shacl12-core/index.html @@ -1,9 +1,7 @@ - - - + SHACL 1.2 Core - + - + + + From 397ca90409d1940bb09c33d0e32d7ebe5843f5dc Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:31:33 +0100 Subject: [PATCH 2/4] chore: add scripts to sync shaclc --- scripts/lib/utils.js | 12 +++- scripts/package-lock.json | 130 +++++++++++++++++++++++++++++++++- scripts/package.json | 4 +- scripts/sync-code-snippets.js | 122 +++++++++++++++++++++++++++++-- 4 files changed, 259 insertions(+), 9 deletions(-) diff --git a/scripts/lib/utils.js b/scripts/lib/utils.js index 0157a2e5..7cfaee71 100644 --- a/scripts/lib/utils.js +++ b/scripts/lib/utils.js @@ -1,4 +1,5 @@ import rdf from 'rdf-ext' +import { parse } from 'shaclc-parse' async function parseJsonld (jsonld, context = {}) { try { @@ -23,7 +24,16 @@ async function parseTurtle (turtle, prefixes = '') { } } +async function parseShaclc (shaclc, prefixes = '') { + try { + return rdf.dataset(parse(`${prefixes}\n${shaclc}`)) + } catch (err) { + return null + } +} + export { parseJsonld, - parseTurtle + parseTurtle, + parseShaclc } diff --git a/scripts/package-lock.json b/scripts/package-lock.json index 7267759e..2a2c4286 100644 --- a/scripts/package-lock.json +++ b/scripts/package-lock.json @@ -13,7 +13,9 @@ "commander": "^13.1.0", "jsdom": "^26.1.0", "mkdirp": "^3.0.1", - "rdf-ext": "^2.5.2" + "rdf-ext": "^2.5.2", + "shaclc-parse": "^1.4.3", + "shaclc-write": "^1.5.0" }, "devDependencies": { "stricter-standard": "^0.3.1" @@ -278,6 +280,18 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@jeswr/prefixcc": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jeswr/prefixcc/-/prefixcc-1.2.1.tgz", + "integrity": "sha512-kBBXbqsaeh3Irp416h/RbelqJgIOp6X/OJJlYmLyr/9qlBYKTKSCuEv5/xjZ0Yf8Yec+QFRYBaOQ2JkMBSH7KA==", + "license": "MIT", + "dependencies": { + "cross-fetch": "^3.1.5" + }, + "optionalDependencies": { + "fsevents": "^2.3.2" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1092,6 +1106,57 @@ "dev": true, "license": "MIT" }, + "node_modules/cross-fetch": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", + "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/cross-fetch/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/cross-fetch/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/cross-fetch/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/cross-fetch/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2116,6 +2181,20 @@ "dev": true, "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -3937,6 +4016,34 @@ "readable-stream": "^4.3.0" } }, + "node_modules/rdf-string-ttl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/rdf-string-ttl/-/rdf-string-ttl-1.3.2.tgz", + "integrity": "sha512-yqolaVoUvTaSC5aaQuMcB4BL54G/pCGsV4jQH87f0TvAx8zHZG0koh7XWrjva/IPGcVb1QTtaeEdfda5mcddJg==", + "license": "MIT", + "dependencies": { + "@rdfjs/types": "*", + "rdf-data-factory": "^1.1.0" + } + }, + "node_modules/rdf-string-ttl/node_modules/@rdfjs/types": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-1.1.2.tgz", + "integrity": "sha512-wqpOJK1QCbmsGNtyzYnojPU8gRDPid2JO0Q0kMtb4j65xhCK880cnKAfEOwC+dX85VJcCByQx5zOwyyfCjDJsg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/rdf-string-ttl/node_modules/rdf-data-factory": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/rdf-data-factory/-/rdf-data-factory-1.1.3.tgz", + "integrity": "sha512-ny6CI7m2bq4lfQQmDYvcb2l1F9KtGwz9chipX4oWu2aAtVoXjb7k3d8J1EsgAsEbMXnBipB/iuRen5H2fwRWWQ==", + "license": "MIT", + "dependencies": { + "@rdfjs/types": "^1.0.0" + } + }, "node_modules/rdfxml-streaming-parser": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/rdfxml-streaming-parser/-/rdfxml-streaming-parser-3.0.1.tgz", @@ -4279,6 +4386,27 @@ "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", "license": "MIT" }, + "node_modules/shaclc-parse": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/shaclc-parse/-/shaclc-parse-1.4.3.tgz", + "integrity": "sha512-MQJWVFjfzzMUvieFO0STWjIo49ywy63UkVSsr0e8+8xHUns6X+i3yWYxNKd+GtSEJjBNZxxrUubog+hnd7PvRA==", + "license": "MIT", + "dependencies": { + "@rdfjs/types": "^2.0.0", + "n3": "^1.16.3" + } + }, + "node_modules/shaclc-write": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/shaclc-write/-/shaclc-write-1.5.0.tgz", + "integrity": "sha512-5+uFmw28BUqCW5UC4FyFwB8VTsdzPyh4LB2pAI5DRjn6xWgl1bsfied9K9HbQYrONNtmhLpPFse79N3Tvj+kLA==", + "license": "MIT", + "dependencies": { + "@jeswr/prefixcc": "^1.2.1", + "n3": "^1.16.3", + "rdf-string-ttl": "^1.3.2" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/scripts/package.json b/scripts/package.json index 4bba365d..f7538718 100644 --- a/scripts/package.json +++ b/scripts/package.json @@ -14,7 +14,9 @@ "commander": "^13.1.0", "jsdom": "^26.1.0", "mkdirp": "^3.0.1", - "rdf-ext": "^2.5.2" + "rdf-ext": "^2.5.2", + "shaclc-parse": "^1.4.3", + "shaclc-write": "^1.5.0" }, "devDependencies": { "stricter-standard": "^0.3.1" diff --git a/scripts/sync-code-snippets.js b/scripts/sync-code-snippets.js index 6c6094a9..b8908836 100755 --- a/scripts/sync-code-snippets.js +++ b/scripts/sync-code-snippets.js @@ -8,8 +8,9 @@ import { Command } from 'commander' import jsdom from 'jsdom' import { mkdirp } from 'mkdirp' import rdf from 'rdf-ext' +import { write as shaclcWrite } from 'shaclc-write' import prettyJsonld from './lib/prettyJsonld.js' -import { parseJsonld, parseTurtle } from './lib/utils.js' +import { parseJsonld, parseTurtle, parseShaclc } from './lib/utils.js' rdf.formats.import(pretty) @@ -40,12 +41,31 @@ const turtlePrefixes = ` @prefix ex: . ` +const shaclcPrefixes = + 'BASE ' + + turtlePrefixes.replaceAll('@prefix', 'PREFIX').replaceAll('.', '') + function escape (str) { return str .replaceAll('<', '<') .replaceAll('>', '>') } +// Convert Turtle shapes to SHACL Compact Syntax using shaclc-write +async function convertTurtleToShaclc (dataset) { + // Convert to SHACL-C using shaclc-write + const result = await shaclcWrite(dataset, { prefixes: jsonldContext['@context'], requireBase: false }) + const shaclcContent = result.text + + // Clean up the output (remove base and unwanted prefixes) + let cleanedShaclc = shaclcContent + .replace(/^BASE\s+<[^>]+>\s*\n/i, '') + .replace(/^PREFIX\s+\w*:\s*<[^>]+>\s*\n/gmi, '') + .trim() + + return cleanedShaclc +} + class Snippet { constructor (root, { index, spec }) { this.root = root @@ -69,9 +89,11 @@ class Snippet { this.turtleContent = this.root.querySelector('.turtle')?.textContent this.jsonldContent = this.root.querySelector('.jsonld pre.jsonld')?.textContent + this.shaclcContent = this.root.querySelector('.shaclc pre.shaclc')?.textContent this.jsonldRdf = await parseJsonld(this.jsonldContent, jsonldContext) this.turtleRdf = await parseTurtle(this.turtleContent, turtlePrefixes) + this.shaclcRdf = this.shaclcContent ? await parseShaclc(this.shaclcContent, shaclcPrefixes) : null; if (this.turtleRdf) { const jsonldStr = await rdf.io.dataset.toText('application/ld+json', this.turtleRdf, { context: jsonldContext }) @@ -81,11 +103,28 @@ class Snippet { jsonldFull = prettyJsonld(jsonldFull) this.jsonldExpected = JSON.stringify(jsonldFull, null, '\t') - this.isExpected = this.jsonldExpected === this.jsonldContent + this.isJsonExpected = this.jsonldExpected === this.jsonldContent + + if (this.root.classList.contains('shapes-graph')) { + try { + this.shaclcExpected = await convertTurtleToShaclc(this.turtleRdf) + } catch (err) { + console.error(`Error converting Turtle to SHACL-C for ${this.id}:`) + this.shaclcExpected = null + } + if (this.shaclcExpected) { + this.isShaclcExpected = this.shaclcExpected === this.shaclcContent + } else { + this.isShaclcExpected = true + } + } } if (this.jsonldRdf && this.turtleRdf) { - this.isEqual = this.jsonldRdf.equals(this.turtleRdf) + this.isJsonldEqual = this.jsonldRdf.equals(this.turtleRdf) + } + if (this.shaclcRdf && this.turtleRdf) { + this.isShaclcEqual = this.shaclcRdf.equals(this.turtleRdf) } } catch (err) { this.error = err.message @@ -116,6 +155,15 @@ class Snippet { if (this.jsonldExpected) { await writeFile(join(base, `${this.indexStr}-jsonld-expected.json`), escape(this.jsonldExpected)) } + if (this.shaclcContent) { + await writeFile(join(base, `${this.indexStr}-shaclc-content.shce`), this.shaclcContent) + } + if (this.shaclcRdf) { + await writeFile(join(base, `${this.indexStr}-shaclc-rdf.ttl`), this.shaclcRdf.toCanonical()) + } + if (this.shaclcExpected) { + await writeFile(join(base, `${this.indexStr}-shaclc-expected.shce`), escape(this.shaclcExpected)) + } } static async from (root, options) { @@ -127,11 +175,12 @@ class Snippet { } } -async function main (path, { output, spec, writeEqual }) { +async function main (path, { output, spec, writeEqual, writeShaclc, writeJsonld }) { try { const content = await readFile(path, { encoding: 'utf8' }) const dom = new jsdom.JSDOM(content) const snippets = [...dom.window.document.querySelectorAll('.shapes-graph, .data-graph, .results-graph')] + let documentModified = false await mkdirp(output) @@ -151,12 +200,22 @@ async function main (path, { output, spec, writeEqual }) { if (!snippet.jsonldContent) { console.log(`${snippet.id}: JSON-LD content missing`) + // Write back to spec if enabled + if (writeJsonld && snippet.jsonldExpected) { + const jsonldElement = snippet.root.querySelector('.jsonld pre.jsonld') + if (jsonldElement) { + jsonldElement.textContent = snippet.jsonldExpected + documentModified = true + console.log(`${snippet.id}: Updated JSON-LD content in spec`) + } + } + await snippet.write(output) - } else if (!snippet.isEqual) { + } else if (!snippet.isJsonldEqual) { console.log(`${snippet.id}: JSON-LD triples are different from turtle`) await snippet.write(output) - } else if (!snippet.isExpected) { + } else if (!snippet.isJsonExpected) { console.log(`${snippet.id}: JSON-LD content doesn't look as expected`) await snippet.write(output) @@ -165,6 +224,55 @@ async function main (path, { output, spec, writeEqual }) { await snippet.write(output) } } + + if (snippet.root.classList.contains('shapes-graph')) { + if (!snippet.shaclcContent) { + console.log(`${snippet.id}: SHACL-C content missing`) + + // Write back to spec if enabled + if (writeShaclc && snippet.shaclcExpected) { + console.log(`${snippet.id}: Writing SHACL-C content to spec`) + let shaclcElement = snippet.root.querySelector('.shaclc pre.shaclc') + + // If the element does not exist, create it + if (!shaclcElement) { + const divElement = dom.window.document.createElement('div') + divElement.className = 'shaclc' + const preElement = dom.window.document.createElement('pre') + preElement.className = 'shaclc' + preElement.textContent = snippet.shaclcExpected + divElement.appendChild(preElement) + snippet.root.appendChild(divElement) + } else { + shaclcElement.textContent = snippet.shaclcExpected + } + + documentModified = true + console.log(`${snippet.id}: Updated SHACL-C content in spec`) + } + + await snippet.write(output) + } else if (!snippet.isShaclcEqual) { + console.log(`${snippet.id}: SHACL-C triples are different from turtle`) + + await snippet.write(output) + } else if (!snippet.isShaclcExpected) { + console.log(`${snippet.id}: SHACL-C content doesn't look as expected`) + + await snippet.write(output) + } else { + if (writeEqual) { + await snippet.write(output) + } + } + } + } + if (documentModified) { + const updatedContent = dom.serialize() + '\n' + await writeFile(path, updatedContent, { encoding: 'utf8' }) + console.log(`Updated specification file: ${path}`) + } else { + console.log('No changes made to the specification file.') } } catch (err) { console.error(err) @@ -178,6 +286,8 @@ program .option('-o, --output ', 'output folder') .option('-s, --spec ', 'id of the specification') .option('--write-equal', 'write snippets that are equal') + .option('--write-shaclc', 'write shaclc to the specification file') + .option('--write-jsonld', 'write jsonld to the specification file') .action(async (path, { ...options }) => { if (!options.spec) { options.spec = path.split(sep).slice(-2)[0] From bd3cbfdcdbfb56d8f7bc57600cf00821e8f8b36f Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:32:39 +0100 Subject: [PATCH 3/4] chore: add shaclc snippets to shacl12-core --- shacl12-core/index.html | 121 +++++++++++++++++++++++++++++++--------- 1 file changed, 96 insertions(+), 25 deletions(-) diff --git a/shacl12-core/index.html b/shacl12-core/index.html index 04b99f6c..0ceda90e 100644 --- a/shacl12-core/index.html +++ b/shacl12-core/index.html @@ -886,7 +886,11 @@

SHACL Example

} } - +
shape ex:PersonShape -> ex:Person {
+	closed=true ignoredProperties=[rdf:type] .
+	ex:ssn xsd:string [0..1] pattern="^\\d{3}-\\d{2}-\\d{4}$" .
+	ex:worksFor IRI ex:Company .
+}

We can use the shape declaration above to illustrate some of the key terminology used by SHACL. @@ -1304,7 +1308,10 @@

Constraints, Parameters and Constraint Components

] } - +
shape ex:MultiplePatternsShape {
+	ex:name pattern="^Start" flags="i" .
+	ex:name pattern="End$" .
+}

Constraint components are associated with validators, which provide instructions (for example expressed via SPARQL queries) @@ -1398,7 +1405,9 @@

Node targets (sh:targetNode)

} } - +
shape ex:PersonShape {
+	targetNode=ex:Alice .
+}
ex:Alice a ex:Person . @@ -1451,7 +1460,8 @@

Class-based Targets (sh:targetClass)

} }
-
+
shape ex:PersonShape -> ex:Person {
+}
@@ -1569,7 +1579,8 @@

Implicit Class Targets and sh:ShapeClass

] }
-
+
shapeClass ex:Person {
+}
ex:Alice a ex:Person . @@ -1641,7 +1652,9 @@

Subjects-of targets (sh:targetSubjectsOf)

} }
-
+
shape ex:TargetSubjectsOfExampleShape {
+	targetSubjectsOf=ex:knows .
+}
ex:Alice ex:knows ex:Bob . @@ -1703,7 +1716,9 @@

Objects-of targets (sh:targetObjectsOf)

} }
-
+
shape ex:TargetObjectsOfExampleShape {
+	targetObjectsOf=ex:knows .
+}
ex:Alice ex:knows ex:Bob . @@ -1854,7 +1869,11 @@

Declaring the Severity of a Shape or Constraint

} }
-
+
shape ex:MyShape {
+	targetNode=ex:MyInstance .
+	ex:myProperty xsd:string [1..*] severity=Warning .
+	ex:myProperty maxLength=10 message="Too many characters"@en message="Zu viele Zeichen"@de .
+}
ex:MyInstance @@ -3220,7 +3239,10 @@

sh:class

] }
-
+
shape ex:ClassExampleShape {
+	targetNode=ex:Bob targetNode=ex:Alice targetNode=ex:Carol .
+	ex:address ex:PostalAddress .
+}
ex:Alice a ex:Person . @@ -3372,7 +3394,10 @@

sh:datatype

] }
-
+
shape ex:DatatypeExampleShape {
+	targetNode=ex:Alice targetNode=ex:Bob targetNode=ex:Carol .
+	ex:age xsd:integer .
+}
ex:Alice ex:age "23"^^xsd:integer . @@ -3499,7 +3524,9 @@

sh:nodeKind

} }
-
+
shape ex:NodeKindExampleShape {
+	targetObjectsOf=ex:knows nodeKind=IRI .
+}
ex:Bob ex:knows ex:Alice . @@ -3690,7 +3717,10 @@

sh:maxCount

} }
-
+
shape ex:MaxCountExampleShape {
+	targetNode=ex:Bob .
+	ex:birthDate [0..1] .
+}
ex:Bob ex:birthDate "May 5th 1990" . @@ -3755,7 +3785,10 @@

Value Range Constraint Components

] }
-
+
shape ex:NumericRangeExampleShape {
+	targetNode=ex:Bob targetNode=ex:Alice targetNode=ex:Ted .
+	ex:age minInclusive=0 maxInclusive=150 .
+}
@@ -4057,7 +4090,10 @@

sh:maxLength

] }
-
+
shape ex:PasswordExampleShape {
+	targetNode=ex:Bob targetNode=ex:Alice .
+	ex:password minLength=8 maxLength=10 .
+}
ex:Bob ex:password "123456789" . @@ -4164,7 +4200,10 @@

sh:pattern

] }
-
+
shape ex:PatternExampleShape {
+	targetNode=ex:Bob targetNode=ex:Alice targetNode=ex:Carol .
+	ex:bCode pattern="^B" flags="i" .
+}
ex:Bob ex:bCode "b101" . @@ -4385,7 +4424,10 @@

sh:languageIn

] }
-
+
shape ex:NewZealandLanguagesShape {
+	targetNode=ex:Mountain targetNode=ex:Berg .
+	ex:prefLabel languageIn=["en" "mi"] .
+}

From the example instances, ex:Berg will lead to constraint violations for all of its labels. @@ -4513,7 +4555,10 @@

sh:uniqueLang

] } - +
shape ex:UniqueLangExampleShape {
+	targetNode=ex:Alice targetNode=ex:Bob .
+	ex:label uniqueLang=true .
+}
ex:Alice @@ -5104,7 +5149,10 @@

sh:equals

} }
-
+
shape ex:EqualExampleShape {
+	targetNode=ex:Bob .
+	ex:firstName equals=ex:givenName .
+}
ex:Bob @@ -5197,7 +5245,10 @@

sh:disjoint

] }
-
+
shape ex:DisjointExampleShape {
+	targetNode=ex:USA targetNode=ex:Germany .
+	ex:prefLabel disjoint=ex:altLabel .
+}
ex:USA @@ -5293,7 +5344,9 @@

sh:lessThan

} }
-
+
shape ex:LessThanExampleShape {
+	ex:startDate lessThan=ex:endDate .
+}
@@ -5742,7 +5795,9 @@

sh:or

} } - +
shape ex:PersonAddressShape -> ex:Person {
+	ex:address xsd:string|ex:Address .
+}
ex:Bob ex:address "123 Prinzengasse, Vaduz, Liechtenstein" . @@ -6074,7 +6129,12 @@

sh:node

] }
-
+
shape ex:AddressShape {
+	ex:postalCode xsd:string [0..1] .
+}
+shape ex:PersonShape -> ex:Person {
+	ex:address [1..*] @ex:AddressShape .
+}
ex:Bob a ex:Person ; @@ -6834,7 +6894,11 @@

sh:closed, sh:ignoredProperties

] }
-
+
shape ex:ClosedShapeExampleShape {
+	targetNode=ex:Alice targetNode=ex:Bob closed=true ignoredProperties=[rdf:type] .
+	ex:firstName .
+	ex:lastName .
+}
ex:Alice @@ -6933,7 +6997,10 @@

sh:hasValue

} }
-
+
shape ex:StanfordGraduate {
+	targetNode=ex:Alice .
+	ex:alumniOf hasValue=ex:Stanford .
+}
ex:Alice @@ -7031,7 +7098,10 @@

sh:in

} }
-
+
shape ex:InExampleShape {
+	targetNode=ex:RainbowPony .
+	ex:color in=[ex:Pink ex:Purple] .
+}
ex:RainbowPony ex:color ex:Pink . @@ -7614,4 +7684,5 @@

Changes between SHACL 1.0 Core and SHACL 1.2 Core

+ From d12c2e3e1db07e647bb7fdf4fdb84e094388d999 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Mon, 14 Jul 2025 12:34:45 +0100 Subject: [PATCH 4/4] chore: add shaclc selector button to shacl12-core.html --- shacl12-core/index.html | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/shacl12-core/index.html b/shacl12-core/index.html index 0ceda90e..39b66916 100644 --- a/shacl12-core/index.html +++ b/shacl12-core/index.html @@ -70,9 +70,14 @@ for (const tabs of document.querySelectorAll(".ds-selector-tabs")) { const selectors = document.createElement("div"); selectors.classList.add("selectors"); + + // Check if shaclc div exists in this tab group + const hasShaclc = tabs.querySelector(".shaclc"); + selectors.innerHTML = ` + ${hasShaclc ? '' : ''} ` tabs.prepend(selectors);