rdf-validate-shacl
Version:
RDF SHACL validator
121 lines (120 loc) • 4.56 kB
JavaScript
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;