UNPKG

rdf-validate-shacl

Version:
121 lines (120 loc) 4.56 kB
import TermSet from '@rdfjs/term-set'; import factory from './src/defaultEnv.js'; import { prepareNamespaces } from './src/namespaces.js'; import ShapesGraph from './src/shapes-graph.js'; import ValidationEngine from './src/validation-engine.js'; import defaultValidators from './src/validators-registry.js'; /** * Validates RDF data based on a set of RDF shapes. */ class SHACLValidator { importsLoaded = false; /** * @param shapes - Dataset containing the SHACL shapes for validation * @param {object} [options] - Validator options */ constructor(shapes, options) { options = options || {}; this.factory = options.factory || factory; this.ns = prepareNamespaces(this.factory); this.allowNamedNodeInList = options.allowNamedNodeInList === undefined ? false : options.allowNamedNodeInList; const dataset = this.factory.dataset([...shapes]); this.$shapes = this.factory.clownface({ dataset }); this.$data = this.factory.clownface(); this.validators = this.factory.termMap(defaultValidators); this.shapesGraph = new ShapesGraph(this); this.validationEngine = new ValidationEngine(this, options); if (options.importGraph) { this.importGraph = options.importGraph; } this.depth = 0; } /** * Validates the provided data graph against the provided shapes graph */ async validate(dataGraph) { await this.loadOwlImports(); this.setDataGraph(dataGraph); this.validationEngine.validateAll(this.$data); return this.validationEngine.getReport(); } /** * Validates the provided focus node against the provided shape */ async validateNode(dataGraph, focusNode, shapeNode) { await this.loadOwlImports(); this.setDataGraph(dataGraph); this.nodeConformsToShape(focusNode, shapeNode, this.validationEngine); return this.validationEngine.getReport(); } setDataGraph(dataGraph) { if ('dataset' in dataGraph) { this.$data = dataGraph; } else { this.$data = this.factory.clownface({ dataset: dataGraph }); } } /** * Exposed to be available from validation functions as `SHACL.nodeConformsToShape` */ nodeConformsToShape(focusNode, shapeNode, propertyPathOrEngine) { let engine; let shape = this.shapesGraph?.getShape(shapeNode); if (propertyPathOrEngine && 'termType' in propertyPathOrEngine) { engine = this.validationEngine.clone({ recordErrorsLevel: this.validationEngine.recordErrorsLevel, }); shape = shape.overridePath(propertyPathOrEngine); } else if (propertyPathOrEngine && 'clone' in propertyPathOrEngine) { engine = propertyPathOrEngine; } else { engine = this.validationEngine.clone(); } try { this.depth++; const foundViolations = engine.validateNodeAgainstShape(focusNode, shape, this.$data); return !foundViolations; } finally { this.depth--; } } validateNodeAgainstShape(focusNode, shapeNode) { return this.nodeConformsToShape(focusNode, shapeNode, this.validationEngine); } async loadOwlImports() { if (this.importsLoaded) { return; } this.importsLoaded = true; const { owl } = this.ns; const loaded = new TermSet(); const doLoad = async (url) => { if (!this.importGraph) { throw new Error('importGraph parameter is required to load owl:imports'); } const imported = await this.importGraph(url); for (const quad of imported) { this.$shapes.dataset.add(quad); } return imported; }; const loadFromDataset = (dataset) => { const toImport = new TermSet(); for (const { object } of dataset.match(null, owl.imports)) { if (object.termType === 'NamedNode' && !loaded.has(object) && !toImport.has(object)) { loaded.add(object); toImport.add(object); } } return Promise.all([...toImport].map(async (url) => { await loadFromDataset(await doLoad(url)); })); }; await loadFromDataset(this.$shapes.dataset); } } export default SHACLValidator;