UNPKG

@jahed/sparql-engine

Version:

SPARQL query engine for servers and web browsers.

316 lines 9.95 kB
import { isNull, isUndefined } from "lodash-es"; import { stringToTerm, termToString } from "rdf-string"; import { isVariable } from "../utils/rdf.js"; /** * A set of mappings from a variable to a RDF Term. * @abstract */ export class Bindings { _properties; constructor() { this._properties = new Map(); } /** * Get metadata attached to the set using a key * @param key - Metadata key * @return The metadata associated with the given key */ getProperty(key) { return this._properties.get(key); } /** * Check if a metadata with a given key is attached to the set * @param key - Metadata key * @return Tur if the metadata exists, False otherwise */ hasProperty(key) { return this._properties.has(key); } /** * Attach metadata to the set * @param key - Key associated to the value * @param value - Value to attach */ setProperty(key, value) { this._properties.set(key, value); } /** * Serialize the set of mappings as a plain JS Object * @return The set of mappings as a plain JS Object */ toObject() { return this.reduce((acc, variable, value) => { acc[variable] = value; return acc; }, {}); } /** * Serialize the set of mappings as a string * @return The set of mappings as a string */ toString() { const value = this.reduce((acc, variable, value) => { return `${acc} ${variable} -> ${termToString(value)},`; }, "{"); return value.substring(0, value.length - 1) + " }"; } /** * Creates a deep copy of the set of mappings * @return A deep copy of the set */ clone() { const cloned = this.empty(); // copy properties then values if (this._properties.size > 0) { this._properties.forEach((value, key) => { cloned.setProperty(key, value); }); } this.forEach((variable, value) => { cloned.set(variable, value); }); return cloned; } /** * Test the equality between two sets of mappings * @param other - A set of mappings * @return True if the two sets are equal, False otherwise */ equals(other) { if (this.size !== other.size) { return false; } for (let variable in other.variables()) { if (!this.has(variable) || this.get(variable) !== other.get(variable)) { return false; } } return true; } /** * Bound a triple pattern using the set of mappings, i.e., substitute variables in the triple pattern * @param triple - Triple pattern * @return An new, bounded triple pattern */ bound(triple) { const newTriple = Object.assign({}, triple); if (isVariable(triple.subject) && this.has(triple.subject.value)) { newTriple.subject = this.get(triple.subject.value); } if (isVariable(triple.predicate) && this.has(triple.predicate.value)) { newTriple.predicate = this.get(triple.predicate.value); } if (isVariable(triple.object) && this.has(triple.object.value)) { newTriple.object = this.get(triple.object.value); } return newTriple; } /** * Creates a new bindings with additionnal mappings * @param values - Pairs [variable, value] to add to the set * @return A new Bindings with the additionnal mappings */ extendMany(values) { const cloned = this.clone(); values.forEach((v) => { cloned.set(v[0], v[1]); }); return cloned; } /** * Perform the union of the set of mappings with another set * @param other - Set of mappings * @return The Union set of mappings */ union(other) { const cloned = this.clone(); other.forEach((variable, value) => { cloned.set(variable, value); }); return cloned; } /** * Perform the intersection of the set of mappings with another set * @param other - Set of mappings * @return The intersection set of mappings */ intersection(other) { const res = this.empty(); this.forEach((variable, value) => { if (other.has(variable) && other.get(variable) === value) { res.set(variable, value); } }); return res; } /** * Performs a set difference with another set of mappings, i.e., A.difference(B) returns all mappings that are in A and not in B. * @param other - Set of mappings * @return The results of the set difference */ difference(other) { return this.filter((variable, value) => { return !other.has(variable) || value !== other.get(variable); }); } /** * Test if the set of bindings is a subset of another set of mappings. * @param other - Superset of mappings * @return Ture if the set of bindings is a subset of another set of mappings, False otherwise */ isSubset(other) { return Array.from(this.variables()).every((v) => { return other.has(v) && other.get(v) === this.get(v); }); } /** * Creates a new set of mappings using a function to transform the current set * @param mapper - Transformation function (variable, value) => [string, string] * @return A new set of mappings */ map(mapper) { const result = this.empty(); this.forEach((variable, value) => { let [newVar, newValue] = mapper(variable, value); if (!(isNull(newVar) || isUndefined(newVar) || isNull(newValue) || isUndefined(newValue))) { result.set(newVar, newValue); } }); return result; } /** * Same as map, but only transform variables * @param mapper - Transformation function * @return A new set of mappings */ mapVariables(mapper) { return this.map((variable, value) => [mapper(variable, value), value]); } /** * Same as map, but only transform values * @param mapper - Transformation function * @return A new set of mappings */ mapValues(mapper) { return this.map((variable, value) => [variable, mapper(variable, value)]); } /** * Filter mappings from the set of mappings using a predicate function * @param predicate - Predicate function * @return A new set of mappings */ filter(predicate) { return this.map((variable, value) => { if (predicate(variable, value)) { return [variable, value]; } return [null, null]; }); } /** * Reduce the set of mappings to a value which is the accumulated result of running each element in collection thru a reducing function, where each successive invocation is supplied the return value of the previous. * @param reducer - Reducing function * @param start - Value used to start the accumulation * @return The accumulated value */ reduce(reducer, start) { let acc = start; this.forEach((variable, value) => { acc = reducer(acc, variable, value); }); return acc; } /** * Test if some mappings in the set pass a predicate function * @param predicate - Function to test for each mapping * @return True if some mappings in the set some the predicate function, False otheriwse */ some(predicate) { let res = false; this.forEach((variable, value) => { res = res || predicate(variable, value); }); return res; } /** * Test if every mappings in the set pass a predicate function * @param predicate - Function to test for each mapping * @return True if every mappings in the set some the predicate function, False otheriwse */ every(predicate) { let res = true; this.forEach((variable, value) => { res = res && predicate(variable, value); }); return res; } } /** * A set of mappings from a variable to a RDF Term, implements using a HashMap */ export class BindingBase extends Bindings { _content; constructor() { super(); this._content = new Map(); } get size() { return this._content.size; } get isEmpty() { return this.size === 0; } /** * Creates a set of mappings from a plain Javascript Object * @param obj - Source object to turn into a set of mappings * @return A set of mappings */ static fromObject(obj) { const res = new BindingBase(); for (let key in obj) { res.set(key, obj[key]); } return res; } static fromValuePatternRow(row) { const res = new BindingBase(); for (let variableString in row) { const v = row[variableString]; if (v) { res.set(stringToTerm(variableString).value, v); } } return res; } variables() { return this._content.keys(); } values() { return this._content.values(); } get(variable) { if (this._content.has(variable)) { return this._content.get(variable); } return null; } has(variable) { return this._content.has(variable); } set(variable, value) { this._content.set(variable, value); } clear() { this._content.clear(); } empty() { return new BindingBase(); } forEach(callback) { this._content.forEach((value, variable) => callback(variable, value)); } } //# sourceMappingURL=bindings.js.map