Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

+ store.sequesterLists() #322

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 126 additions & 0 deletions src/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const RDFArrayRemove = require('./util').RDFArrayRemove
const Statement = require('./statement')
const Node = require('./node')
const Variable = require('./variable')
import { namedNode } from './data-factory'

// const indexedFormulaQuery = require('./query').indexedFormulaQuery

Expand Down Expand Up @@ -819,6 +820,131 @@ class IndexedFormula extends Formula { // IN future - allow pass array of statem
}
return res
}

/** Remove all triples first/rest triples in valid rdf Collections.
* It returns a map from blank node identifier at the list head tp an
* array of Collection members. For example calling with these quads:
* <n1> <p1> _:b1
* _:b1 rdf:first <element1> ; rdf:rest _:b2
* _:b2 rdf:first "element2" ; rdf:rest rdf:nill
* will change the database to:
* <n1> <p1> _:b1
* and return a map from "b1" to [<element1>, "element2"]. This can be passed
* to N3Writer like:
* new N3Writer({ prefixes: {...}, listHeads: myStore.sequesterLists() })
* @param {function} failParam is a function to handle malformed lists. sequesterLists
* will non-destructively test Collections if passed a fail function like:
* myStore.sequesterLists((node, msg) => {
* console.log(node, message);
* return false;
* })
*/
sequesterLists (failParam) {
const store = this
const NsRdf = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#';
const first = NsRdf + 'first', rest = NsRdf + 'rest', nil = NsRdf + 'nil';
const nonEmptyLists = new Map(); // has scalar keys so could be a simple Object
const fail = failParam || defaultListFailFunction;

// start from the tail of each list
const tails = store.statementsMatching(null, namedNode(rest), namedNode(nil), null);
tails.forEach(tailQuad => {
let skipList = false; // signals the current list is malformed
let listQuads = []; // which triples to remove of list is well-formed
let head = null; // the head of the list (_:b1 in above example)
let headPOS = null; // set to subject or object when head is set
let graph = tailQuad.graph; // make sure list is in exactly one graph
let members = []; // the members found as objects of rdf:first quads

let li = tailQuad.subject; // li walks from the tail to the head
while (li && !skipList) {
let ins = store.statementsMatching(null, null, li, null);
let outs = store.statementsMatching(li, null, null, null);
let f = null, r = null, parent = null;
var i, q;
for (i = 0; i < outs.length && !skipList; ++i) {
q = outs[i];
if (!q.graph.equals(graph)) {
skipList = fail(li, 'list not confined to single graph');
}
else if (head) {
skipList = fail(li, 'intermediate list element has non-list arcs out');
}

// one rdf:first
else if (q.predicate.value === first) {
if (f) {
skipList = fail(li, 'multiple rdf:first arcs');
}
else {
f = q;
listQuads.push(q);
}
}

// one rdf:rest
else if (q.predicate.value === rest) {
if (r) {
skipList = fail(li, 'multiple rdf:rest arcs');
}
else {
r = q;
listQuads.push(q);
}
}

// alien triple
else if (ins.length) {
skipList = fail(li, 'can\'t be subject and object');
}
else {
head = q; // e.g. { (1 2 3) :p :o }
headPOS = 'subject';
}
}
// { :s :p (1 2) } arrives here with no head
// { (1 2) :p :o } arrives here with head set to the list.

for (i = 0; i < ins.length && !skipList; ++i) {
q = ins[i];
if (head) {
skipList = fail(li, 'list item can\'t have coreferences');
}

// one rdf:rest
else if (q.predicate.value === rest) {
if (parent) {
skipList = fail(li, 'multiple incoming rdf:rest arcs');
}
else {
parent = q;
}
}
else {
head = q; // e.g. { :s :p (1 2) }
headPOS = 'object';
}
}

members.unshift(f.object);
li = parent ? parent.subject : null; // null means we're done
}

if (head && !skipList) {
if (!failParam)
store.removeStatements(listQuads);
nonEmptyLists.set(head[headPOS].value, members);
}
});

return nonEmptyLists;

// ### `defaultListFailFunction` return true to signal an error and
// to abort recognition of the current list.
function defaultListFailFunction(listNode, failureMessage) {
return true;
}
}
}
module.exports = IndexedFormula
IndexedFormula.handleRDFType = handleRDFType
Loading