UNPKG

rdf

Version:

RDF datatype integration, RDF Interfaces API, and utility functions

84 lines (81 loc) 3.81 kB
"use strict"; // Verify that `ref` simply entails `tst` // According to RDF 1.1 Semantics, this is true iff a subgraph of `ref` is an instance of `tst` // Go through every triple in `tst` and map bnodes to nodes in `ref` module.exports = GraphSimplyEntails; function GraphSimplyEntails(ref, tst, depth){ if(typeof ref.length!='number') throw new Error('Expected an RDFGraph for argument `ref`'); if(typeof tst.length!='number') throw new Error('Expected an RDFGraph for argument `tst`'); if(tst.length > ref.length) return false; var tstl = tst.toArray(); //if(!a.every(function(s))) return false; var stack = [ {i:0, depth:0, bindings:{}} ]; // Iterate through each statement in `a` // test that named nodes/literals/bound bnodes have a match in the other document // For each bnode encountered in the statement that's unbound, determine every possible binding and recurse while(stack.length){ // If `depth` starts as 0 this will never be hit if(--depth===0) throw new Error('Hit search limit'); var state = stack.pop(); if(state.i===tstl.length) return state.bindings; var stmt = tstl[state.i]; // If it's a bnode, then map it. If it's not mapped, use `null` to search for any values. // in theory the predicate will never be a bnode, but the additional test shouldn't hurt anything var stmtsubject = stmt.subject.termType==='BlankNode' ? (state.bindings[stmt.subject] || null) : stmt.subject ; var stmtpredicate = stmt.predicate.termType==='BlankNode' ? (state.bindings[stmt.predicate] || null) : stmt.predicate ; var stmtobject = stmt.object.termType==='BlankNode' ? (state.bindings[stmt.object] || null) : stmt.object ; var matches = ref.match(stmtsubject, stmtpredicate, stmtobject).filter(function(m){ // wildcards must only match bnodes // The predicate should never be a bnode, so skip over that code path for now // if(stmtsubject===null && (m.subject.termType!=='BlankNode' && m.subject.termType!=='NamedNode')) return false; //if(stmtpredicate===null && (m.predicate.termType!=='BlankNode' && m.predicate.termType!=='NamedNode')) return false; // if(stmtobject===null && (m.object.termType!=='BlankNode' && m.object.termType!=='NamedNode' && m.object.termType!=='Literal')) return false; return true; }); if(stmtsubject && stmtpredicate && stmtobject){ if(matches.length===1){ // if there's a single match where all nodes match exactly, push the comparison for the next item stack.push({ i:state.i+1, depth:state.depth, bindings:state.bindings }); }else if(matches.length===0){ continue; }else{ throw new Error('Multiple matches, expected exactly one or zero match'); } }else{ // otherwise there's an unbound bnode, get the possible mappings and push those onto the stack matches.forEach(function(match){ var b2 = {}; var depth = state.depth; for(var n in state.bindings) b2[n] = state.bindings[n]; if(stmtsubject===null){ if(b2[stmt.subject]===undefined){ for(var n in b2) if(b2[n]===match.subject) return; b2[stmt.subject] = match.subject; depth++; }else{ throw new Error('bnode already mapped'); } } // if(stmtpredicate===null && b2[stmt.predicate]===undefined){ // if(b2[stmt.predicate]===undefined){ // for(var n in b2) if(b2[n]===match.predicate) return; // b2[stmt.predicate] = match.predicate; // depth++; // }else{ // throw new Error('bnode already mapped'); // } // } if(stmtobject===null){ if(b2[stmt.object]===undefined){ for(var n in b2) if(b2[n]===match.object) return; b2[stmt.object] = match.object; depth++; }else{ throw new Error('bnode already mapped'); } } stack.push({ i:state.i, depth:depth, bindings:b2 }); }); } } }