From 219b7382dddd9babac19be6401808fba40fbceda Mon Sep 17 00:00:00 2001 From: "@s.roertgen" Date: Tue, 30 Jan 2024 14:39:37 +0100 Subject: [PATCH 1/2] Validate after vocabulary enrichment #289 --- gatsby-node.js | 9 +++++---- package-lock.json | 27 +++++++++++++++------------ package.json | 3 ++- src/validate.js | 17 +++++++++++++++-- 4 files changed, 37 insertions(+), 19 deletions(-) diff --git a/gatsby-node.js b/gatsby-node.js index 1d99546..99decc5 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -106,18 +106,19 @@ exports.onPreBootstrap = async ({ createContentDigest, actions, getNode }) => { console.info(`Found these turtle files:`) ttlFiles.forEach((e) => console.info(e)) for (const f of ttlFiles) { + const ttlString = fs.readFileSync(f).toString() + const doc = await jsonld.fromRDF(ttlString, { format: "text/turtle" }) + const compacted = await jsonld.compact(doc, context.jsonld) + if (config.failOnValidation) { try { console.info("Validating: ", f) - await validate("shapes/skohub.shacl.ttl", f) + await validate("shapes/skohub.shacl.ttl", f, doc) } catch (e) { console.error(e) throw e } } - const ttlString = fs.readFileSync(f).toString() - const doc = await jsonld.fromRDF(ttlString, { format: "text/turtle" }) - const compacted = await jsonld.compact(doc, context.jsonld) const conceptSchemeIds = compacted["@graph"] .filter((node) => node.type === "ConceptScheme") diff --git a/package-lock.json b/package-lock.json index 4b0fd4f..08a58bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "dependencies": { "@emotion/react": "^11.10.5", "@gatsbyjs/reach-router": "^2.0.0", + "@rdfjs/dataset": "^2.0.1", "@rdfjs/parser-n3": "^2.0.1", "crypto-browserify": "^3.12.0", "dotenv": "^16.0.3", @@ -25,7 +26,7 @@ "gatsby-transformer-sharp": "^5.0.0", "graceful-fs": "^4.2.8", "js-yaml": "^4.1.0", - "jsonld": "^8.2.0", + "jsonld": "^8.3.2", "lodash.escaperegexp": "^4.1.2", "markdown-to-jsx": "^7.1.8", "n3": "^1.16.3", @@ -4726,13 +4727,13 @@ } }, "node_modules/@rdfjs/fetch-lite": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@rdfjs/fetch-lite/-/fetch-lite-3.2.1.tgz", - "integrity": "sha512-cnCuSkEpMGsSbkd3+bIKheCKTDE4iBSGG6l/Inp0qg4y5WMLtcffKtSUzWhq09cAajm0dWs+5W3EGPNBqF5A4w==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@rdfjs/fetch-lite/-/fetch-lite-3.2.2.tgz", + "integrity": "sha512-hcdg9gvMgaOLPGS1LAYPjyS3rjQg2x8G/do+ZTlHjIHrAtRzXZCa0ui+pzoT98258RQzxEGqajY4ug4IqSuHZw==", "dependencies": { "is-stream": "^3.0.0", "nodeify-fetch": "^3.1.0", - "readable-stream": "^4.2.0" + "readable-stream": "^4.4.2" } }, "node_modules/@rdfjs/fetch-lite/node_modules/is-stream": { @@ -7908,11 +7909,13 @@ } }, "node_modules/clownface": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/clownface/-/clownface-2.0.1.tgz", - "integrity": "sha512-8RVfn/LZEl7BTDhIEIamz13Bhm5YahA1qiJigMb0HYGaiKnsVV0PpLBz0kzqyAI0+IzOlYbCLMFOAc1dkQfwgQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/clownface/-/clownface-2.0.2.tgz", + "integrity": "sha512-HjTYqVXiCrw4FmoAWF46aQ3c2OmdVLoqZrAGkowdWWUoBBIcBht55pOxkyvoVe2BsPE/HqMzfnu51JpgqM4KEg==", "dependencies": { - "@rdfjs/environment": "^0.1.2" + "@rdfjs/data-model": "^2.0.1", + "@rdfjs/environment": "0 - 1", + "@rdfjs/namespace": "^2.0.0" } }, "node_modules/color": { @@ -14292,9 +14295,9 @@ } }, "node_modules/jsonld": { - "version": "8.3.1", - "resolved": "https://registry.npmjs.org/jsonld/-/jsonld-8.3.1.tgz", - "integrity": "sha512-tYfKpWL56meSJCHS91Ph0+EUThHZOZ8bKuboME4998SF+Kkukp2PhCPdRCvA7tsGUKr9FvSoyIRqJPuImBcBuA==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/jsonld/-/jsonld-8.3.2.tgz", + "integrity": "sha512-MwBbq95szLwt8eVQ1Bcfwmgju/Y5P2GdtlHE2ncyfuYjIdEhluUVyj1eudacf1mOkWIoS9GpDBTECqhmq7EOaA==", "dependencies": { "@digitalbazaar/http-client": "^3.4.1", "canonicalize": "^1.0.1", diff --git a/package.json b/package.json index a218f93..a22af00 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "dependencies": { "@emotion/react": "^11.10.5", "@gatsbyjs/reach-router": "^2.0.0", + "@rdfjs/dataset": "^2.0.1", "@rdfjs/parser-n3": "^2.0.1", "crypto-browserify": "^3.12.0", "dotenv": "^16.0.3", @@ -21,7 +22,7 @@ "gatsby-transformer-sharp": "^5.0.0", "graceful-fs": "^4.2.8", "js-yaml": "^4.1.0", - "jsonld": "^8.2.0", + "jsonld": "^8.3.2", "lodash.escaperegexp": "^4.1.2", "markdown-to-jsx": "^7.1.8", "n3": "^1.16.3", diff --git a/src/validate.js b/src/validate.js index 1f42bec..4434151 100644 --- a/src/validate.js +++ b/src/validate.js @@ -2,6 +2,9 @@ const fs = require("fs") const f = import("rdf-ext") const PN3 = import("@rdfjs/parser-n3") const shacl = import("rdf-validate-shacl") +const jsonld = require("jsonld") +const datasetFactory = import("@rdfjs/dataset") +const N3 = require("n3") async function loadDataset(filePath) { const ParserN3 = await PN3 @@ -11,19 +14,29 @@ async function loadDataset(filePath) { return factory.default.dataset().import(parser.import(stream)) } +async function loadDataFromJsonld(doc) { + const nquads = await jsonld.toRDF(doc, { format: "application/n-quads" }) + const parser = new N3.Parser({ format: "N-Triples" }) + const parsed = parser.parse(nquads) + const dFactory = await datasetFactory + const dataset = dFactory.default.dataset(parsed) + return dataset +} + /** * Validates a file against a given shape. * @param {string} shapePath - Path to shape file * @param {string} filePath - Path to file to validate + * @param {Object} doc - JSON-LD document * @returns {boolean} Validation result * @throws {Error} Throws an error if the validation fails */ -async function validate(shapePath, filePath) { +async function validate(shapePath, filePath, doc) { const factory = await f const SHACLValidator = await shacl const shapes = await loadDataset(shapePath) - const data = await loadDataset(filePath) + const data = await loadDataFromJsonld(doc) const validator = new SHACLValidator.default(shapes, { factory: factory.default, }) From 7087ad2295bf389de2b3aa1801055b13fb897145 Mon Sep 17 00:00:00 2001 From: "@s.roertgen" Date: Tue, 30 Jan 2024 15:14:35 +0100 Subject: [PATCH 2/2] Fix validation test #289 --- test/validate.test.js | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/test/validate.test.js b/test/validate.test.js index 2e865da..0df7739 100644 --- a/test/validate.test.js +++ b/test/validate.test.js @@ -1,5 +1,10 @@ import { describe, expect, it } from "vitest" import { validate } from "../src/validate" +import fs from "fs" +import jsonld from "jsonld" +import n3 from "n3" +const { DataFactory } = n3 +const { namedNode } = DataFactory describe("validate", () => { it("Returns true for a valid SkoHub Turtle File", async () => { @@ -10,10 +15,45 @@ describe("validate", () => { expect(result).toBeTruthy() }) it("Throws error for an invalid SkoHub Turtle File", async () => { + const inverses = { + "http://www.w3.org/2004/02/skos/core#narrower": + "http://www.w3.org/2004/02/skos/core#broader", + "http://www.w3.org/2004/02/skos/core#broader": + "http://www.w3.org/2004/02/skos/core#narrower", + "http://www.w3.org/2004/02/skos/core#related": + "http://www.w3.org/2004/02/skos/core#related", + "http://www.w3.org/2004/02/skos/core#hasTopConcept": + "http://www.w3.org/2004/02/skos/core#topConceptOf", + "http://www.w3.org/2004/02/skos/core#topConceptOf": + "http://www.w3.org/2004/02/skos/core#hasTopConcept", + } + + jsonld.registerRDFParser("text/turtle", (ttlString) => { + const quads = new n3.Parser().parse(ttlString) + const store = new n3.Store() + store.addQuads(quads) + quads.forEach((quad) => { + quad.object.language && + inverses[quad.predicate.id] && + store.addQuad( + quad.object, + namedNode(inverses[quad.predicate.id]), + quad.subject, + quad.graph + ) + }) + return store.getQuads() + }) + const f = "./test/data/ttl/invalid_hashURIConceptScheme.ttl" + + const ttlString = fs.readFileSync(f).toString() + const doc = await jsonld.fromRDF(ttlString, { format: "text/turtle" }) + await expect(() => validate( "./shapes/skohub.shacl.ttl", - "./test/data/ttl/invalid_hashURIConceptScheme.ttl" + "./test/data/ttl/invalid_hashURIConceptScheme.ttl", + doc ) ).rejects.toThrowError() })