UNPKG

clownface

Version:

Simple but powerful graph traversing library

496 lines (412 loc) 13.7 kB
import ns from './namespace.js' import toArray from './toArray.js' import toTermArray from './toTermArray.js' import Context from './Context.js' /** * A graph pointer object, which points at 0..N nodes within a dataset */ export default class Clownface { constructor({ dataset, graph, term, value, factory, _context }) { this.factory = factory this.namespace = ns(factory) if (_context) { this._context = _context return } const terms = (term && toArray(term)) || (value && toArray(value)) || [null] /** * The underlying context which references actual node being pointed * * @type {Context[]} * @private */ this._context = terms.map(term => { return new Context({ dataset, graph, value: term, factory: this.factory, namespace: this.namespace }) }) } /** * Gets the current RDF/JS term or undefined if pointer has no context * * @returns {undefined|Term} */ get term() { const terms = this.terms if (terms.length !== 1) { return undefined } return terms[0] } /** * Gets the current terms or an empty array if the pointer has no context * * @returns {Term[]} */ get terms() { return this._context.map(node => node.term).filter(Boolean) } /** * Gets the string representation of term * * @returns {undefined|string} */ get value() { const term = this.term return term && term.value } /** * Gets the string representation of terms * * @returns {string[]} */ get values() { return this.terms.map(term => term.value) } /** * Gets the current context's dataset, or undefined if there are multiple * * @returns {undefined|DatasetCore} */ get dataset() { const datasets = this.datasets if (datasets.length !== 1) { return undefined } return datasets[0] } /** * Gets the current context's datasets * * @returns {DatasetCore[]} */ get datasets() { return this._context.map(node => node.dataset).filter(Boolean) } /** * Removes current pointers from the context and return an "any pointer". * The returned object can be used to find any nodes in the dataset * * @returns {Clownface} */ any() { return Clownface.fromContext(this._context.map(current => current.clone({ })), this) } /** * Returns true if the current term is a rdf:List * * @returns {boolean} */ isList() { if (!this.term) { return false } // empty list if (this.term.equals(this.namespace.nil)) { return true } // list element if (this.out(this.namespace.first).term) { return true } return false } /** * Creates an iterator which iterates and rdf:List of the current term * * @returns {Iterable | null} */ list() { if (this.terms.length > 1) { throw new Error('iterator over multiple terms is not supported') } if (this.term) { if (this.term.termType !== 'NamedNode' && this.term.termType !== 'BlankNode') { return null } if (!this.term.equals(this.namespace.nil) && !this.out(this.namespace.first).term) { return null } } let item = this return { [Symbol.iterator]: () => { return { next: () => { if (!item.term || item.term.equals(this.namespace.nil)) { return { done: true } } const value = item.out(this.namespace.first) if (value.terms.length > 1) { throw new Error(`Invalid list: multiple values for rdf:first on ${item.value}`) } const rest = item.out(this.namespace.rest) if (rest.terms.length > 1) { throw new Error(`Invalid list: multiple values for rdf:rest on ${item.value}`) } item = rest return { done: false, value } }, } }, } } /** * Returns an array of graph pointers where each one has a single _context * * @returns {Clownface[]} */ toArray() { return this._context .map(context => Clownface.fromContext(context, this)) .filter(context => context.terms.some(Boolean)) } /** * Returns graph pointers which meet the condition specified in a callback function * @param {FilterCallback} callback * @returns {Clownface} */ filter(callback) { const pointers = this._context.map(context => Clownface.fromContext(context, this)) return Clownface.fromContext(this._context.filter((context, index) => callback(Clownface.fromContext(context, this), index, pointers)), this) } /** * Performs the specified action on every graph pointer * @param {ForEachCallback} callback * @returns {Clownface} */ forEach(callback) { this.toArray().forEach(callback) return this } /** * Calls a defined callback function on each graph pointer, and returns an array that contains the results. * @template T * @param {MapCallback<T>} callback * @returns {T[]} */ map(callback) { return this.toArray().map(callback) } toString() { return this.values.join() } /** * Creates graph pointer to one or more node(s) * * Depending on the value creates pointers to: * * - blank node context for null `values` * - literal for string `values` and no `options` paramter * - matching RDF/JS term * - term created according to `options.type` parameter * * @param {null|string|string[]|Term|Term[]|Clownface|Clownface[]} values * @param {Object} [options] * @param {"NamedNode"|"BlankNode"|"Literal"} [options.type] explicit type for nodes * @param {string} [options.language] language tag of literals * @param {string} [options.datatype] datatype of literals * @returns {Clownface} */ node(values, { type, datatype, language } = {}) { values = this._toTermArray(values, type, datatype || language) || [null] const context = values.reduce((context, value) => { return context.concat(this._context.reduce((all, current) => { return all.concat([current.clone({ value })]) }, [])) }, []) return Clownface.fromContext(context, { factory: this.factory }) } /** * Creates graph pointer to one or more blank nodes * @param {null|string|string[]|BlankNode|BlankNode[]|Clownface|Clownface[]} [values] blank node identifiers (generates it when falsy) or existing RDF/JS blank node(s) * @returns {Clownface} */ blankNode(values) { return this.node(values, { type: 'BlankNode' }) } /** * Creates graph pointer to one or more literal nodes * @param {string|string[]|boolean|boolean[]|number|number[]|Literal|Literal[]|Clownface|Clownface[]} values literal values as JS objects or RDF/JS Literal(s) * @param {string|Term} [languageOrDatatype] a language tag string or datatype term * @returns {Clownface} */ literal(values, languageOrDatatype) { return this.node(values, { type: 'Literal', datatype: languageOrDatatype }) } /** * Creates graph pointer to one or more named nodes * @param {string|string[]|NamedNode|NamedNode[]|Clownface|Clownface[]} values URI(s) or RDF/JS NamedNode(s) * @returns {Clownface} */ namedNode(values) { return this.node(values, { type: 'NamedNode' }) } /** * Creates a graph pointer to nodes which are linked to the current pointer by `predicates` * @param {Term|Term[]|Clownface|Clownface[]} [predicates] one or more RDF/JS term identifying a property * @returns {Clownface} */ in(predicates) { predicates = this._toTermArray(predicates) const context = this._context.reduce((all, current) => all.concat(current.in(predicates)), []) return Clownface.fromContext(context, this) } /** * Creates a graph pointer to the result nodes after following a predicate, or after * following any predicates in an array, starting from the subject(s) (current graph pointer) to the objects. * @param {Term|Term[]|Clownface|Clownface[]} [predicates] any predicates to follow * @param {object} [options] * @param {string | string[] | undefined} [options.language] * @returns {Clownface} */ out(predicates, options = {}) { predicates = this._toTermArray(predicates) const context = this._context.reduce((all, current) => all.concat(current.out(predicates, options)), []) return Clownface.fromContext(context, this) } /** * Creates a graph pointer to nodes which are subjects of predicates, optionally also with specific objects * * If the current context is empty, will check all potential subjects * * @param {Term|Term[]|Clownface|Clownface[]} predicates RDF property identifiers * @param {*} [objects] object values to match * @returns {Clownface} */ has(predicates, objects) { predicates = this._toTermArray(predicates) objects = this._toTermArray(objects) const context = this._context.reduce((all, current) => all.concat(current.has(predicates, objects)), []) return Clownface.fromContext(context, this) } /** * Creates a new quad(s) in the dataset where the current context is the object * * @param {Term|Term[]|Clownface|Clownface[]} predicates * @param {NamedNode|NamedNode[]|Clownface|Clownface[]} subjects one or more nodes to use as subjects * @param {GraphPointerCallback} [callback] called for each object, with subject pointer as parameter * @returns {Clownface} current graph pointer */ addIn(predicates, subjects, callback) { if (!predicates) { throw new Error('predicate parameter is required') } if (typeof subjects === 'function') { callback = subjects subjects = null } predicates = this._toTermArray(predicates) subjects = this._toTermArray(subjects) || [this.factory.blankNode()] const context = this._context.map(context => context.addIn(predicates, subjects)) if (callback) { Clownface.fromContext(context, this).forEach(callback) } return this } /** * Creates a new quad(s) in the dataset where the current context is the subject * * @param {Term|Term[]|Clownface|Clownface[]} predicates * @param {*} objects one or more values to use for objects * @param {GraphPointerCallback} [callback] called for each subject, with object pointer as parameter * @returns {Clownface} current graph pointer */ addOut(predicates, objects, callback) { if (!predicates) { throw new Error('predicate parameter is required') } if (typeof objects === 'function') { callback = objects objects = null } predicates = this._toTermArray(predicates) objects = this._toTermArray(objects) || [this.factory.blankNode()] const context = this._context.map(context => context.addOut(predicates, objects)) if (callback) { Clownface.fromContext(context, this).forEach(callback) } return this } /** * Creates a new RDF list or lists containing the given items * * @param {Term|Term[]|Clownface|Clownface[]} predicates * @param {*} items one or more values to use for subjects * @returns {Clownface} current graph pointer */ addList(predicates, items) { if (!predicates || !items) { throw new Error('predicate and items parameter is required') } predicates = this._toTermArray(predicates) items = this._toTermArray(items) this._context.forEach(context => context.addList(predicates, items)) return this } /** * Deletes all quads where the current graph pointer contexts are the objects * * @param {Term|Term[]|Clownface|Clownface[]} [predicates] * @param {Term|Term[]|Clownface|Clownface[]} [subjects] * @returns {Clownface} current graph pointer */ deleteIn(predicates, subjects) { predicates = this._toTermArray(predicates) subjects = this._toTermArray(subjects) this._context.forEach(context => context.deleteIn(predicates, subjects)) return this } /** * Deletes all quads where the current graph pointer contexts are the subjects * * @param {Term|Term[]|Clownface|Clownface[]} [predicates] * @param {Term|Term[]|Clownface|Clownface[]} [objects] * @returns {Clownface} current graph pointer */ deleteOut(predicates, objects) { predicates = this._toTermArray(predicates) objects = this._toTermArray(objects) this._context.forEach(context => context.deleteOut(predicates, objects)) return this } /** * Deletes entire RDF lists where the current graph pointer is the subject * * @param {Term|Term[]|Clownface|Clownface[]} predicates * @returns {Clownface} current graph pointer */ deleteList(predicates) { if (!predicates) { throw new Error('predicate parameter is required') } predicates = this._toTermArray(predicates) this._context.forEach(context => context.deleteList(predicates)) return this } _toTermArray(predicates, type, languageOrDatatype) { return toTermArray(predicates, type, languageOrDatatype, this.factory) } static fromContext(context, { factory }) { return new Clownface({ _context: toArray(context), factory }) } } /** * @callback GraphPointerCallback * @param {Clownface} pointer graph pointer to the new or existing node */ /** * @callback FilterCallback * @param {Clownface} pointer * @param {number} index * @param {Clownface[]} pointers * @return {boolean} */ /** * @callback ForEachCallback * @param {Clownface} pointer */ /** * @callback MapCallback * @template T * @param {Clownface} pointer * @return {T} */