From 812740ee367e98fceeab0b1690268653fb4b9e27 Mon Sep 17 00:00:00 2001 From: bergi Date: Mon, 30 Oct 2023 22:47:49 +0100 Subject: [PATCH] feat: Added .base method --- Grapoi.js | 20 ++++++++++++++ lib/base.js | 50 ++++++++++++++++++++++++++++++++++ test/Grapoi.test.js | 45 ++++++++++++++++++++++++++++++ test/support/datasets.multi.js | 27 ++++++++++++++++++ 4 files changed, 142 insertions(+) create mode 100644 lib/base.js diff --git a/Grapoi.js b/Grapoi.js index f986477..c0aeda3 100644 --- a/Grapoi.js +++ b/Grapoi.js @@ -1,3 +1,4 @@ +import { baseDataset, baseTerm } from './lib/base.js' import { rebaseDataset } from './lib/rebase.js' import { replaceDataset } from './lib/replace.js' import sortByScore from './lib/sortByScore.js' @@ -96,6 +97,25 @@ class Grapoi extends PathList { return super.addOut(this._toTermArray(predicates), this._toTermArray(objects), callback) } + /** + * Base all terms with a relative IRI with the given base. + * @param {Grapoi|Grapoi[]|Term|Term[]} base Base of the terms + * @returns {Constructor} Instance with a single pointer with the term based + */ + base (base) { + if (!base) { + throw new Error('base parameter is required') + } + + base = this._toTerm(base) + + for (const ptr of this.ptrs) { + baseDataset(base, { factory: this.factory })(ptr.dataset) + } + + return this.node(baseTerm(base, { factory: this.factory })(this.term)) + } + /** * Use the given score function on all pointers and return the pointer with the best score. * @param {function} score Score function diff --git a/lib/base.js b/lib/base.js new file mode 100644 index 0000000..3827bd0 --- /dev/null +++ b/lib/base.js @@ -0,0 +1,50 @@ +function baseDataset (newBaseTerm, { factory }) { + const base = baseQuad(newBaseTerm, { factory }) + + return dataset => { + for (const quad of [...dataset]) { + const newQuad = base(quad) + + if (newQuad !== quad) { + dataset.delete(quad) + dataset.add(newQuad) + } + } + } +} + +function baseQuad (newBaseTerm, { factory }) { + const base = baseTerm(newBaseTerm, { factory }) + + return quad => { + const subject = base(quad.subject) + const predicate = base(quad.predicate) + const object = base(quad.object) + + if (subject === quad.subject && predicate === quad.predicate && object === quad.object) { + return quad + } + + return factory.quad(subject, predicate, object, quad.graph) + } +} + +function baseTerm (newBaseTerm, { factory }) { + return term => { + if (term.termType !== 'NamedNode') { + return term + } + + if (/[a-z]+:\/\//.test(term.value)) { + return term + } + + return factory.namedNode(new URL(term.value, newBaseTerm.value).toString()) + } +} + +export { + baseDataset, + baseQuad, + baseTerm +} diff --git a/test/Grapoi.test.js b/test/Grapoi.test.js index c4f91c8..83330c2 100644 --- a/test/Grapoi.test.js +++ b/test/Grapoi.test.js @@ -277,6 +277,51 @@ describe('Grapoi', () => { }) }) + describe('.base', () => { + it('should be a method', () => { + const { grapoi } = datasets.default() + + strictEqual(typeof grapoi.base, 'function') + }) + + it('should throw an error if no base is given', () => { + const { grapoi } = datasets.default() + + throws(() => { + grapoi.base() + }, { + message: 'base parameter is required' + }) + }) + + it('should return a new Grapoi object', () => { + const { expectedTerm, grapoi } = datasets.base() + + const result = grapoi.base(expectedTerm) + + strictEqual(result instanceof Grapoi, true) + notStrictEqual(result, grapoi) + }) + + it('should base the dateset', () => { + const { expectedGrapoi, expectedTerm, grapoi } = datasets.base() + + const result = grapoi.base(expectedTerm) + + grapoiEqual(result, expectedGrapoi) + datasetEqual(result.dataset, expectedGrapoi.dataset) + }) + + it('should support base argument given as ptr', () => { + const { expectedGrapoi, grapoi } = datasets.base() + + const result = grapoi.base(expectedGrapoi) + + grapoiEqual(result, expectedGrapoi) + datasetEqual(result.dataset, expectedGrapoi.dataset) + }) + }) + describe('.best', () => { it('should be a method', () => { const { grapoi } = datasets.default() diff --git a/test/support/datasets.multi.js b/test/support/datasets.multi.js index 2566c9f..104e608 100644 --- a/test/support/datasets.multi.js +++ b/test/support/datasets.multi.js @@ -26,6 +26,20 @@ terms.graphs = [ns.ex.graph1, ns.ex.graph2] const triples = {} +triples.base = [ + [factory.namedNode(''), factory.namedNode('/propertyA'), links[0]], + [links[0], factory.namedNode('/propertyB'), factory.namedNode('/end')], + [ns.other(''), ns.other.propertyA, links[1]], + [links[1], ns.other.propertyB, ns.other.end] +] + +triples.based = [ + [ns.ex(''), ns.ex.propertyA, links[0]], + [links[0], ns.ex.propertyB, ns.ex.end], + [ns.other(''), ns.other.propertyA, links[1]], + [links[1], ns.other.propertyB, ns.other.end] +] + triples.in = [ [ns.ex.end1, ns.ex.propertyA, ns.ex.start1], [ns.ex.end1, ns.ex.propertyA, ns.ex.start2], @@ -371,6 +385,19 @@ multi.any = () => { return createPathListDataset([], { terms: [null] }) } +multi.base = () => { + const { ...others } = createPathListDataset(triples.base, { terms: [ns.ex('')] }) + + const expectedTerm = ns.ex('') + + const expectedGrapoi = new Grapoi({ + dataset: factory.dataset(triples.based.map(parts => factory.quad(...parts))), + term: expectedTerm + }) + + return { expectedGrapoi, expectedTerm, ...others } +} + multi.best = () => { const { dataset, ...others } = createPathListDataset(triples.inBlankNode, { subjects: [ns.ex.start1, ns.ex.start1]