UNPKG

lincd

Version:

LINCD is a JavaScript library for building user interfaces with linked data (also known as 'structured data', or RDF)

156 lines 5.73 kB
/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { NodeSet } from '../collections/NodeSet.js'; import { rdf } from '../ontologies/rdf.js'; import { BlankNode } from '../models.js'; import { Shape } from './Shape.js'; /** * Class to work with rdf Lists * A list is a way to store an ORDERED array in RDF (so in the graph with triples) * For example: * person.location = ["Portugal","Bali"] * If the order of these 2 items matters then it can be stored as a list in the graph like this: * _:somePerson _:location _:list1 * _:list1 rdf:first "Portugal" * _:list1 rdf:rest _:list2 <-- continuation of the list can be found here * _:list2 rdf:first "Bali" * _:list2 rdf:rest rdf:nil <-- end of the list * */ export class List extends Shape { constructor(blanknode = new BlankNode()) { super(blanknode); if (!blanknode.hasProperty(rdf.first) && !blanknode.has(rdf.rest, rdf.nil)) { //starting a NEW empty list is a bit difficult. Because officially the only empty list is rdf:nil. //But then how do we later add things to that list? We would need to switch out the node of this instance, which is not ideal in case the consumer of this class had saved/used that node already //So instead we're using a blanknode with rdf:type rdf:List blanknode.set(rdf.type, rdf.List); } } /** * Create a new list from a given set of nodes * Most performant way to create a new list if you already have the items in the list * @param items */ static createFrom(items, isTemporaryNode = true) { //NOTE: this method exists because new List(nodes) will not work because all shapes require a node as first parameter, hence a static method let firstItem = this.getFirstItem(items); //create the list and the first entry manually let list = BlankNode.create(isTemporaryNode); list.set(rdf.type, rdf.List); list.set(rdf.first, firstItem); //add all the other items List.appendItems(list, items); return List.getOf(list); } /** * Returns the contents of a rdf.List * Will return an empty set if the given node is not a list * @param list * @param result * @private */ static getContents(list, result = new NodeSet()) { if (list.hasProperty(rdf.first)) { result.add(list.getOne(rdf.first)); } if (list.hasProperty(rdf.rest)) { let rest = list.getOne(rdf.rest); if (rest !== rdf.nil) { return List.getContents(rest, result); } } return result; } static getFirstItem(items) { if (items instanceof NodeSet) { let firstItem = items.first(); items.delete(firstItem); return firstItem; } else { return items.shift(); } } static appendItems(currentEnd, items) { items.forEach((item) => { let rest = List._createListEntry(item, currentEnd.isTemporaryNode); currentEnd.set(rdf.rest, rest); currentEnd = rest; }); //close the list currentEnd.set(rdf.rest, rdf.nil); return currentEnd; } static getLastListItem(list) { let last; while (list && !list.has(rdf.rest, rdf.nil)) { last = list; list = list.getOne(rdf.rest); } return list || last; } static _createListEntry(item, isTemporaryNode = true) { let list = BlankNode.create(isTemporaryNode); list.set(rdf.first, item); return list; } //TODO: not working, needs to be fixed //see example here: http://www.snee.com/bobdc.blog/2014/04/rdf-lists-and-sparql.html // removeItem(item: Node): boolean { // return this._removeItem(this.namedNode, item); // } // private _removeItem(list: NamedNode, item: Node): boolean { // if (list.has(rdf.first, item)) { // let prev = list.getOneInverse(rdf.rest); // prev.overwrite(rdf.rest, list.getOne(rdf.rest)); // list.remove(); // return true; // } else if (!list.has(rdf.rest, rdf.nil)) { // return this._removeItem(list.getOne(rdf.rest) as NamedNode, item); // } // } static _append(item, last) { let next = this._createListEntry(item, last.isTemporaryNode); last.overwrite(rdf.rest, next); //this newly appended item is now the end of the list next.set(rdf.rest, rdf.nil); return next; } getContents() { return List.getContents(this.namedNode); } isEmpty() { return !this.hasProperty(rdf.first); } addItem(item) { //we need to check if the list is empty when adding items one by one //we keep this out of _append for performance reasons if (this.isEmpty()) { this.set(rdf.first, item); this.set(rdf.rest, rdf.nil); } else { List._append(item, List.getLastListItem(this.namedNode)); } } addItems(items) { let endPoint; if (this.isEmpty()) { endPoint = this.node; let firstItem = List.getFirstItem(items); this.set(rdf.first, firstItem); } else { endPoint = List.getLastListItem(this.namedNode); } //add all items to the end of the list List.appendItems(endPoint, items); } } List.targetClass = rdf.List; //# sourceMappingURL=List.js.map