Skip to content

Instantly share code, notes, and snippets.

@BruJu
Created July 1, 2021 12:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BruJu/c7ef9c7705579d4ec51f75bb7ba21a4b to your computer and use it in GitHub Desktop.
Save BruJu/c7ef9c7705579d4ec51f75bb7ba21a4b to your computer and use it in GitHub Desktop.
'use strict';
/*
* This script enables to store multi nested quads in an N3.Store
* Usage:
* Add quads with addQuadsWithoutMultiNesting(store, quads)
* The store will contain the quads, but 2sd level nested quads will be replaced with a blank node
* When you have a quad for which you want the true version, use remakeMultiNesting(store, quads)
*
* Licence: CC0, Julian Bruyat / INSA Lyon
* Originally developed for PREC (https://bruy.at/PREC), not used anymore because PREC now uses
* a custom RDF/JS Dataset implementation
*/
const N3 = require('n3');
const namespace = require('@rdfjs/namespace');
const prec = namespace("http://bruy.at/prec#" , N3.DataFactory);
/**
* Returns a quad equals to
* ```
* Quad(
* unaryFunction(quad.subject),
* unaryFunction(quad.predicate),
* unaryFunction(quad.object),
* unaryFunction(quad.graph)
* )
* ```
*
* Compared to a naive approach, in some cases this quad returns the passed quad
* if it would be equal.
*
* @param {Quad} quad Quad to rebuild. Must be of type quad
* @param {function(Quad): Quad} unaryFunction Function to call to convert an
* inner term
*/
function eventuallyRebuildQuad(quad, unaryFunction) {
let elements = [quad.subject, quad.predicate, quad.object, quad.graph];
let conversion = elements.map(
e => {
if (e.termType === 'Quad') return eventuallyRebuildQuad(e, unaryFunction);
else return unaryFunction(e);
}
);
for (let i = 0 ; i != 4 ; ++i) {
if (elements[i] !== conversion[i]) {
return N3.DataFactory.quad(
conversion[0], conversion[1], conversion[2], conversion[3]
);
}
}
return quad;
}
// ==== N3.Store population with multi level nested quads
// Problem : << << :a :b :c >> :d :e >> :f :g can't be stored in a N3.Store
// Solution: A multi nested quad Q is replaced with `[ prec:_ Q ]`
// `prec:_` is similar to `owl:sameAs`
/** A function that generates blank nodes prefixes with PREC_ */
function blankNodeGenerator() {
++blankNodeGenerator.id;
return N3.DataFactory.blankNode("PREC_" + blankNodeGenerator.id);
}
blankNodeGenerator.id = 1;
/** Helper namespace for addQuadsWithoutMultiNesting */
let addQuadsWithoutMultiNesting_ = {
/**
* A conform quad is a quad that doesn't contain 2 level nested quad or more
* @param {*} quad
* @param {*} todoList
*/
zeroLevel: function(quad, todoList) {
let [cs, s] = this.firstLevel(quad.subject , todoList);
let [cp, p] = this.firstLevel(quad.predicate, todoList);
let [co, o] = this.firstLevel(quad.object , todoList);
let [cg, g] = this.firstLevel(quad.graph , todoList);
if (cs && cp && co && cg) return quad;
return N3.DataFactory.quad(s, p, o, g);
},
firstLevel: function(term, todoList) {
if (term.termType !== 'Quad') return [true, term];
// 1 level nested quad
let [cs, s] = this.secondLevel(term.subject , todoList);
let [cp, p] = this.secondLevel(term.predicate, todoList);
let [co, o] = this.secondLevel(term.object , todoList);
let [cg, g] = this.secondLevel(term.graph , todoList);
if (cs && cp && co && cg) return [true, term];
return [false, N3.DataFactory.quad(s, p, o, g)];
},
secondLevel: function(term, todoList) {
if (term.termType !== 'Quad') return [true, term];
// 2 level nested quad, we have to replace with a blank node
const bn = blankNodeGenerator();
// We have to add the request to add the blank node semantic
todoList.push(N3.DataFactory.quad(bn, prec._, term));
return [false, bn];
}
};
/**
* Add the given quads to the store. If a quad has multi level nested quads,
* the multi level will be removed.
*
* Currently, N3.Store does not support storing quads which contains nested
* quads with several labels.
*
* This function bypass this limitation with the blank nodes using a
* [ owl:sameAs << s p o >> ] pattern (but prec:_ takes the place of owl:sameAs)
* @param {N3.Store} store
* @param {String} quads
*/
function addQuadsWithoutMultiNesting(store, quads) {
if (quads === undefined) return;
// List of quads to add. This list can be extended during the loop
let todo = [...quads];
// todo.length is not const!
for (let i = 0 ; i != todo.length ; ++i) {
store.addQuad(addQuadsWithoutMultiNesting_.zeroLevel(todo[i], todo));
}
}
/**
* Transform a quad that has been un-multi-level-nested into a
* possibily-nested quad.
* @param {N3.Store} store
* @param {*} quad
*/
function remakeMultiNesting(store, quad) {
return eventuallyRebuildQuad(
quad,
term => {
if (term.termType === 'BlankNode') {
let quads = store.getQuads(term, prec._, null, N3.DataFactory.defaultGraph());
if (quads.length === 0) return term;
return remakeMultiNesting(store, quads[0].object);
} else {
return term;
}
}
)
}
module.exports = { addQuadsWithoutMultiNesting, remakeMultiNesting };
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment