rdf-validate-shacl
Advanced tools
Comparing version 0.5.4 to 0.5.5
{ | ||
"name": "rdf-validate-shacl", | ||
"version": "0.5.4", | ||
"version": "0.5.5", | ||
"description": "RDF SHACL validator", | ||
@@ -39,2 +39,3 @@ "main": "index.js", | ||
"@tpluscode/eslint-config": "^0.4.4", | ||
"@zazuko/env-node": "^2", | ||
"c8": "^8.0.1", | ||
@@ -44,3 +45,2 @@ "get-stream": "^6.0.0", | ||
"nanoid": "^4.0.2", | ||
"@zazuko/env-node": "^2", | ||
"rdf-ext": "^2.5.1", | ||
@@ -47,0 +47,0 @@ "rdf-utils-dataset": "^2.0.0" |
@@ -10,17 +10,48 @@ import TermSet from '@rdfjs/term-set' | ||
* @param {Term} startNode | ||
* @returns Array of quads | ||
* @yields {Quad} | ||
*/ | ||
export function extractStructure(dataset, startNode, visited = new TermSet()) { | ||
export function * extractStructure(dataset, startNode, visited = new TermSet()) { | ||
if (startNode.termType !== 'BlankNode' || visited.has(startNode)) { | ||
return [] | ||
return | ||
} | ||
visited.add(startNode) | ||
const quads = [...dataset.match(startNode, null, null)] | ||
for (const quad of dataset.match(startNode, null, null)) { | ||
yield quad | ||
yield * extractStructure(dataset, quad.object, visited) | ||
} | ||
} | ||
const children = quads.map((quad) => { | ||
return extractStructure(dataset, quad.object, visited) | ||
}) | ||
/** | ||
* Extracts all the quads forming the structure under a blank shape node. Stops at | ||
* non-blank nodes. Replaces sh:in with a comment if the list is too long. | ||
* | ||
* @param {Shape} shape | ||
* @param {DatasetCore} dataset | ||
* @param {Term} startNode | ||
* @yields {Quad} | ||
*/ | ||
export function * extractSourceShapeStructure(shape, dataset, startNode, visited = new TermSet()) { | ||
if (startNode.termType !== 'BlankNode' || visited.has(startNode)) { | ||
return | ||
} | ||
return quads.concat(...children) | ||
const { factory } = shape.context | ||
const { sh, rdfs } = shape.context.ns | ||
const inListSize = term => { | ||
const inConstraint = shape.constraints.find(x => x.paramValue.equals(term)) | ||
return inConstraint?.nodeSet.size | ||
} | ||
visited.add(startNode) | ||
for (const quad of dataset.match(startNode, null, null)) { | ||
if (quad.predicate.equals(sh.in) && inListSize(quad.object) > 3) { | ||
const msg = `sh:in has ${inListSize(quad.object)} elements and has been removed from the report for brevity. Please refer the original shape` | ||
yield factory.quad(quad.subject, rdfs.comment, factory.literal(msg)) | ||
} else { | ||
yield quad | ||
yield * extractSourceShapeStructure(shape, dataset, quad.object, visited) | ||
} | ||
} | ||
} | ||
@@ -27,0 +58,0 @@ |
@@ -23,3 +23,3 @@ // Design: | ||
import { extractPropertyPath, getPathObjects } from './property-path.js' | ||
import { getInstancesOf, isInstanceOf } from './dataset-utils.js' | ||
import { getInstancesOf, isInstanceOf, rdfListToArray } from './dataset-utils.js' | ||
@@ -128,2 +128,10 @@ class ShapesGraph { | ||
} | ||
get nodeSet() { | ||
const { sh } = this.shape.context.ns | ||
if (!this.inNodeSet) { | ||
this.inNodeSet = new NodeSet(rdfListToArray(this.shapeNodePointer.out(sh.in))) | ||
} | ||
return this.inNodeSet | ||
} | ||
} | ||
@@ -130,0 +138,0 @@ |
import clownface from 'clownface' | ||
import debug from 'debug' | ||
import ValidationReport from './validation-report.js' | ||
import { extractStructure } from './dataset-utils.js' | ||
import { extractStructure, extractSourceShapeStructure } from './dataset-utils.js' | ||
@@ -20,6 +20,6 @@ const error = debug('validation-enging::error') | ||
clone () { | ||
clone() { | ||
return new ValidationEngine(this.context, { maxErrors: this.maxErrors }) | ||
} | ||
initReport() { | ||
@@ -245,3 +245,3 @@ const { rdf, sh } = this.context.ns | ||
this.copyNestedStructure(sourceShape, result) | ||
this.copySourceShapeStructure(constraint.shape, result) | ||
this.copyNestedStructure(focusNode, result) | ||
@@ -271,2 +271,9 @@ | ||
copySourceShapeStructure(shape, result) { | ||
const structureQuads = extractSourceShapeStructure(shape, this.context.$shapes.dataset, shape.shapeNode) | ||
for (const quad of structureQuads) { | ||
result.dataset.add(quad) | ||
} | ||
} | ||
/** | ||
@@ -326,3 +333,12 @@ * Creates a result message from the validation result and the message pattern in the constraint | ||
function nodeLabel(node) { | ||
function * take(n, iterable) { | ||
let i = 0 | ||
for (const item of iterable) { | ||
if (i++ === n) break | ||
yield item | ||
} | ||
} | ||
function nodeLabel(constraint, param) { | ||
const node = constraint.getParameterValue(param) | ||
if (!node) { | ||
@@ -338,2 +354,12 @@ return 'NULL' | ||
if (node.termType === 'BlankNode') { | ||
if (constraint.nodeSet) { | ||
const limit = 3 | ||
if (constraint.nodeSet.size > limit) { | ||
const prefix = Array.from(take(limit, constraint.nodeSet)).map(x => x.value) | ||
return prefix.join(', ') + ` ... (and ${constraint.nodeSet.size - limit} more)` | ||
} else { | ||
return Array.from(constraint.nodeSet).map(x => x.value).join(', ') | ||
} | ||
} | ||
return 'Blank node ' + node.value | ||
@@ -348,3 +374,3 @@ } | ||
const paramName = localName(param.value) | ||
const paramValue = nodeLabel(constraint.getParameterValue(param)) | ||
const paramValue = nodeLabel(constraint, param) | ||
return message | ||
@@ -351,0 +377,0 @@ .replace(`{$${paramName}}`, paramValue) |
@@ -57,3 +57,3 @@ import validators from './validators.js' | ||
func: validators.validateIn, | ||
message: 'Value is not in {$in}', | ||
message: 'Value is not one of the allowed values: {$in}', | ||
}, | ||
@@ -60,0 +60,0 @@ }, |
@@ -134,6 +134,3 @@ import { validateTerm } from 'rdf-validate-datatype' | ||
function validateIn(context, focusNode, valueNode, constraint) { | ||
const { sh } = context.ns | ||
const inNode = constraint.getParameterValue(sh.in) | ||
return new NodeSet(rdfListToArray(context.$shapes.node(inNode))).has(valueNode) | ||
return constraint.nodeSet.has(valueNode) | ||
} | ||
@@ -140,0 +137,0 @@ |
63104
1588