rdf-validate-shacl
Advanced tools
Comparing version 0.4.0 to 0.4.1
# Changelog | ||
## 0.4.1 (2022-01-19) | ||
* Fix comparison of non-string terms with `sh:lessThan`, `sh:lessThanOrEquals`, etc. | ||
[[#84]](https://github.com/zazuko/rdf-validate-shacl/pull/84) | ||
[[#83]](https://github.com/zazuko/rdf-validate-shacl/issues/83) | ||
## 0.4.0 (2021-11-29) | ||
@@ -5,0 +11,0 @@ |
{ | ||
"name": "rdf-validate-shacl", | ||
"version": "0.4.0", | ||
"version": "0.4.1", | ||
"description": "RDF SHACL validator", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -105,3 +105,3 @@ const { validateTerm } = require('rdf-validate-datatype') | ||
solutions++ | ||
if (compareNodes(focusNode, value, context.ns) !== 0) { | ||
if (!value.equals(focusNode)) { | ||
results.push({ value }) | ||
@@ -168,3 +168,3 @@ } | ||
for (const referenceValue of referenceValues) { | ||
const c = compareNodes(value, referenceValue, context.ns) | ||
const c = compareTerms(value, referenceValue, context.ns) | ||
if (c === null || c >= 0) { | ||
@@ -188,3 +188,3 @@ invalidValues.push({ value }) | ||
for (const referenceValue of referenceValues) { | ||
const c = compareNodes(value, referenceValue, context.ns) | ||
const c = compareTerms(value, referenceValue, context.ns) | ||
if (c === null || c > 0) { | ||
@@ -210,8 +210,5 @@ invalidValues.push({ value }) | ||
const maxExclusiveNode = constraint.getParameterValue(sh.maxExclusive) | ||
const comp = compareTerms(valueNode, maxExclusiveNode, context.ns) | ||
return ( | ||
valueNode.termType === 'Literal' && | ||
checkTimezone(valueNode, maxExclusiveNode, context.ns) && | ||
fromRdf(valueNode) < fromRdf(maxExclusiveNode) | ||
) | ||
return (comp !== null && comp < 0) | ||
} | ||
@@ -222,8 +219,5 @@ | ||
const maxInclusiveNode = constraint.getParameterValue(sh.maxInclusive) | ||
const comp = compareTerms(valueNode, maxInclusiveNode, context.ns) | ||
return ( | ||
valueNode.termType === 'Literal' && | ||
checkTimezone(valueNode, maxInclusiveNode, context.ns) && | ||
fromRdf(valueNode) <= fromRdf(maxInclusiveNode) | ||
) | ||
return (comp !== null && comp <= 0) | ||
} | ||
@@ -253,8 +247,5 @@ | ||
const minExclusiveNode = constraint.getParameterValue(sh.minExclusive) | ||
const comp = compareTerms(valueNode, minExclusiveNode, context.ns) | ||
return ( | ||
valueNode.termType === 'Literal' && | ||
checkTimezone(valueNode, minExclusiveNode, context.ns) && | ||
fromRdf(valueNode) > fromRdf(minExclusiveNode) | ||
) | ||
return (comp !== null && comp > 0) | ||
} | ||
@@ -265,22 +256,7 @@ | ||
const minInclusiveNode = constraint.getParameterValue(sh.minInclusive) | ||
const comp = compareTerms(valueNode, minInclusiveNode, context.ns) | ||
return ( | ||
valueNode.termType === 'Literal' && | ||
checkTimezone(valueNode, minInclusiveNode, context.ns) && | ||
fromRdf(valueNode) >= fromRdf(minInclusiveNode) | ||
) | ||
return (comp !== null && comp >= 0) | ||
} | ||
// Checks that, if one of the compared nodes is a datetime with a timezone, | ||
// the other one is too. A datetime with a specified timezone is not comparable | ||
// with a datetime without a timezone. | ||
function checkTimezone (valueNode, constraintNode, ns) { | ||
return hasTimezone(valueNode, ns) === hasTimezone(constraintNode, ns) | ||
} | ||
function hasTimezone (node, ns) { | ||
const pattern = /^.*(((\+|-)\d{2}:\d{2})|Z)$/ | ||
return ns.xsd.dateTime.equals(node.datatype) && pattern.test(node.value) | ||
} | ||
function validateMinLength (context, focusNode, valueNode, constraint) { | ||
@@ -453,46 +429,42 @@ if (valueNode.termType === 'BlankNode') { | ||
function compareNodes (node1, node2, ns) { | ||
// TODO: Does not handle the case where nodes cannot be compared | ||
if (node1 && node1.termType === 'Literal' && node2 && node2.termType === 'Literal') { | ||
if ((node1.datatype != null) !== (node2.datatype != null)) { | ||
return null | ||
} else if (node1.datatype && node2.datatype && node1.datatype.value !== node2.datatype.value) { | ||
return null | ||
} | ||
/** | ||
* Compare 2 terms. | ||
* | ||
* Returns: | ||
* - a negative number if term1 occurs before term2 | ||
* - a positive number if the term1 occurs after term2 | ||
* - 0 if they are equivalent | ||
* - null if they are not comparable | ||
*/ | ||
function compareTerms (term1, term2, ns) { | ||
if (!term1 || !term2 || term1.termType !== 'Literal' || term2.termType !== 'Literal') { | ||
return null | ||
} | ||
return compareTerms(node1, node2, ns) | ||
} | ||
function compareTerms (t1, t2, ns) { | ||
if (!t1) { | ||
return !t2 ? 0 : 1 | ||
} else if (!t2) { | ||
return -1 | ||
// Check that if one of the compared nodes is a datetime with a timezone, | ||
// the other one is too. A datetime with a specified timezone is not comparable | ||
// with a datetime without a timezone. | ||
if (hasTimezone(term1, ns) !== hasTimezone(term2, ns)) { | ||
return null | ||
} | ||
const bt = t1.termType.localeCompare(t2.termType) | ||
if (bt !== 0) { | ||
return bt | ||
const value1 = fromRdf(term1) | ||
const value2 = fromRdf(term2) | ||
if (typeof value1 !== typeof value2) { | ||
return null | ||
} | ||
if (typeof value1 === 'string') { | ||
return value1.localeCompare(value2) | ||
} else { | ||
// TODO: Does not handle numeric or date comparison | ||
const bv = t1.value.localeCompare(t2.value) | ||
if (bv !== 0) { | ||
return bv | ||
} else { | ||
if (t1.termType === 'Literal') { | ||
const bd = t1.datatype.value.localeCompare(t2.datatype.value) | ||
if (bd !== 0) { | ||
return bd | ||
} else if (ns.rdf.langString.equals(t1.datatype)) { | ||
return t1.language.localeCompare(t2.language) | ||
} else { | ||
return 0 | ||
} | ||
} else { | ||
return 0 | ||
} | ||
} | ||
return value1 - value2 | ||
} | ||
} | ||
function hasTimezone (node, ns) { | ||
const pattern = /^.*(((\+|-)\d{2}:\d{2})|Z)$/ | ||
return ns.xsd.dateTime.equals(node.datatype) && pattern.test(node.value) | ||
} | ||
module.exports = { | ||
@@ -499,0 +471,0 @@ validateAnd, |
400810
8301