lincd
Version:
LINCD is a JavaScript library for building user interfaces with linked data (also known as 'structured data', or RDF)
1,130 lines (1,129 loc) • 115 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Quad = exports.defaultGraph = exports.Graph = exports.Literal = exports.BlankNode = exports.NamedNode = exports.Node = void 0;
const types_js_1 = require("rdflib/lib/types.js");
const default_graph_uri_js_1 = require("rdflib/lib/utils/default-graph-uri.js");
const QuadSet_js_1 = require("./collections/QuadSet.js");
const CoreMap_js_1 = require("./collections/CoreMap.js");
const QuadMap_js_1 = require("./collections/QuadMap.js");
const QuadArray_js_1 = require("./collections/QuadArray.js");
const NodeSet_js_1 = require("./collections/NodeSet.js");
const NodeValuesSet_js_1 = require("./collections/NodeValuesSet.js");
const EventBatcher_js_1 = require("./events/EventBatcher.js");
const EventEmitter_js_1 = require("./events/EventEmitter.js");
const NodeMap_js_1 = require("./collections/NodeMap.js");
const NodeURIMappings_js_1 = require("./collections/NodeURIMappings.js");
const CoreSet_js_1 = require("./collections/CoreSet.js");
const Prefix_js_1 = require("./utils/Prefix.js");
class Node extends EventEmitter_js_1.EventEmitter {
constructor(_value) {
super();
this._value = _value;
}
get value() {
return this._value;
}
set value(val) {
this._value = val;
}
/**
* Create an instance of the given class (or one of its subclasses) as a presentation of this node.
* NOTE: this node MUST have the static.type of the given class as its rdf:type property
* @param type - a class that extends Shape and thus who's instances represent a node as an instance of one specific type.
*/
getAs(type) {
return type.getOf(this);
}
/**
* Create an instance of the given class as a presentation of this node.
* Other than getAs this 'strict' message will ONLY return an exact instance of the given class, not one of its subclasses
* rdf.type properties of the node are IGNORED. This method can therefore also come in handy in circumstances when you don't have the node it's rdf.type properties at hand.
* Do not misuse this method though, the main use case is if you don't want to allow any subclass instances. If that's not neccecarily the case and it would make also sense to have the properties loaded, make sure to load them and use getAs.
* OR use getAsAsync automatically ensures the data of the node is fully loaded before creating an instance.
* @param type - a class that extends Shape and thus who's instances represent a node as an instance of one specific type.
*/
getStrictlyAs(type) {
return type.getStrictlyOf(this);
}
/**
* Compares whether the two nodes are equal
* @param other The other node
*/
equals(other) {
if (!other) {
return false;
}
return this.termType === other.termType && this.value === other.value;
}
set(property, value) {
return false;
}
setValue(property, value) {
return false;
}
has(property, value) {
return false;
}
hasValue(property, value) {
return false;
}
hasExplicit(property, value) {
return false;
}
hasExact(property, value) {
return false;
}
hasProperty(property) {
return false;
}
hasInverseProperty(property) {
return false;
}
hasInverse(property, value) {
return false;
}
mset(property, values) {
return false;
}
getProperties(includeFromIncomingArcs = false) {
return new NodeSet_js_1.NodeSet();
}
getInverseProperties() {
return new NodeSet_js_1.NodeSet();
}
getOne(property) {
return undefined;
}
getAll(property) {
return new NodeValuesSet_js_1.NodeValuesSet(this, property);
}
getValue(property) {
return undefined;
}
getDeep(property, maxDepth = -1, partialResult = new NodeSet_js_1.NodeSet()) {
return partialResult;
}
getOneInverse(property) {
return undefined;
}
getOneWhere(property, filterProperty, filterValue) {
return undefined;
}
getOneWhereEquivalent(property, filterProperty, filterValue, caseSensitive) {
return undefined;
}
getAllExplicit(property) {
return undefined;
}
getAllInverse(property) {
return undefined;
}
getMultiple(properties) {
return new NodeSet_js_1.NodeSet();
}
hasPath(properties) {
return false;
}
hasPathTo(properties, value) {
return false;
}
hasPathToSomeInSet(properties, endPoints) {
return false;
}
getOneFromPath(...properties) {
return undefined;
}
getAllFromPath(...properties) {
return new NodeSet_js_1.NodeSet();
}
getQuads(property, value) {
return new QuadSet_js_1.QuadSet();
}
getInverseQuad(property, subject) {
return undefined;
}
getInverseQuads(property) {
return new QuadSet_js_1.QuadSet();
}
getAllInverseQuads(includeImplicit) {
return new QuadArray_js_1.QuadArray();
}
getAllQuads(includeAsObject = false, includeImplicit = false) {
return null;
}
overwrite(property, value) {
return false;
}
moverwrite(property, value) {
return false;
}
unset(property, value) {
return false;
}
unsetAll(property) {
return false;
}
isLoaded(includingIncomingProperties) {
return false;
}
promiseLoaded(loadInverseProperties) {
return null;
}
getMultipleInverse(properties) {
return new NodeSet_js_1.NodeSet();
}
/**
* @internal
* @param quad
*/
unregisterInverseProperty(quad, alteration, emitEvents) { }
/**
* registers the use of a quad. Since a quad can only be used in 1 quad
* this method makes a clone of the Literal if it's used a second time,
* and returns that new Literal so it will be used by the quad
* @internal
* @param quad
*/
registerInverseProperty(quad, alteration, emitEvents) {
return null;
}
clone() {
return null;
}
print() {
return '';
}
}
exports.Node = Node;
/**
* A Named Node in the graph is a node that has outgoing edges to other nodes.
*
* In RDF specifications, a Named Node is a URI Node.
* You can manage this by setting and getting 'properties' of this node, which will reflect in which nodes this node is connected with.
* A Named Node is one of the two types of nodes in a graph in the semantic web / RDF.
* The other one being Literal
* @see https://www.w3.org/TR/rdf-concepts/#section-Graph-URIref
*
* @example
*
* Use NamedNode.getOrCreate() if you have a URI
* Use NamedNode.create() to create a new NamedNode without specifying a URI
* Do NOT use the constructor
*
* ```
* let node = NamedNode.create();
* let node = NamedNode.getOrCreate("http://url.of.some/node")
* ```
*/
class NamedNode extends Node {
/**
* WARNING: Do not directly create a Node, instead use NamedNode.getOrCreate(uri)
* This ensures the same node is used for the same uri system wide
* @param uri - the URI (more generic form of a URL) of the NamedNode
* @param _isTemporaryNode - set to true if this node is only temporarily available in the local environment
*/
constructor(uri = '', _isTemporaryNode = false) {
super(uri);
this._isTemporaryNode = _isTemporaryNode;
/**
* map of QuadMaps indexed by property (where this node occurs as subject)
* NOTE: 'properties' serves only to increase lookup speed but also costs memory
* since reverse lookup (where this node occurs as object) will be much less frequent
* the inverse of 'properties' is not kept, so all results for reverse lookup will be created from 'asObject'
* @internal
*/
this.properties = new CoreMap_js_1.CoreMap();
// private static termType: string = 'NamedNode';
this.termType = 'NamedNode';
/**
* map of QuadMaps indexed by property where this node occurs as subject
* NOTE: we use QuadMap here because in ES5 a quadMap is much faster than a quadSet, because we can check by key with uri directly if the quad exists instead of having to look in an array with indexOf (ES5 does not support objects as keys)
* @internal
*/
this.asSubject = new Map();
if (this._isTemporaryNode) {
//created locally, so we know everything about it there is to know
// this.allPropertiesLoaded = {promise: Promise.resolve(this), done: true};
}
}
get isStoring() {
return this._isStoring && true;
}
set isStoring(storing) {
//when storing
if (storing) {
//create a deferred promise and store it as _isStoring
this._isStoring = {};
this._isStoring.promise = new Promise((resolve, reject) => {
this._isStoring.resolve = resolve;
this._isStoring.reject = reject;
});
}
else {
//when done storing, resolve the promise
this._isStoring.resolve();
delete this._isStoring;
}
}
/**
* JSLib.js documentation states: "Alias for value, favored by Tim" ... LINCD author René agrees with Tim
* @see https://github.com/linkeddata/rdflib.js/blob/bbf456390afe7743020e0c8c4db20b10cfb808c7/src/named-node.ts#L88
*/
get uri() {
return this._value;
}
set uri(uri) {
this.value = uri;
}
/**
* Returns true if this node has a temporary URI and only exists in the local environment.
* e.g. this is usually true if you create a new NamedNode without having specified a URI yet
*/
get isTemporaryNode() {
return this._isTemporaryNode;
}
set isTemporaryNode(val) {
this._isTemporaryNode = val;
}
get value() {
return this._value;
}
set value(newUri) {
if (NamedNode.namedNodes.has(newUri)) {
throw new Error('Cannot update URI. A node with this URI already exists: ' +
newUri +
'. You tried to update the URI of ' +
this._value);
}
var oldUri = this._value;
NamedNode.namedNodes.delete(this._value);
this._value = newUri;
NamedNode.namedNodes.set(this._value, this);
// //if this node had a temporary URI
// if (this._isTemporaryNode) {
// //it now has an explicit URI, so it's no longer temporary
// this._isTemporaryNode = false;
// }
this.emit(NamedNode.URI_UPDATED, this, oldUri, newUri);
EventBatcher_js_1.eventBatcher.register(NamedNode);
NamedNode.nodesURIUpdated.set(this, [oldUri, newUri]);
}
/**
* Emits the batched (property) events of the NamedNode CLASS (meaning events that relate to all nodes)
* Used internally by the framework to batch and emit change events
* @internal
*/
static emitBatchedEvents(resolve, reject) {
if (this.nodesToRemove.size) {
this.emitter.emit(NamedNode.REMOVE_NODES, this.nodesToRemove);
this.nodesToRemove = new CoreSet_js_1.CoreSet();
}
if (this.nodesToSave.size) {
this.emitter.emit(NamedNode.STORE_NODES, this.nodesToSave);
this.nodesToSave = new NodeSet_js_1.NodeSet();
}
if (this.nodesToLoad.size || this.nodesToLoadFully.size) {
this.emitter.emit(NamedNode.LOAD_NODES, this.nodesToLoad, this.nodesToLoadFully);
this.nodesToLoad = new NodeSet_js_1.NodeSet();
this.nodesToLoadFully = new NodeSet_js_1.NodeSet();
}
if (this.nodesURIUpdated.size) {
this.emitter.emit(NamedNode.URI_UPDATED, this.nodesURIUpdated);
this.nodesURIUpdated = new CoreMap_js_1.CoreMap();
}
if (this.clearedProperties.size) {
this.emitter.emit(NamedNode.CLEARED_PROPERTIES, this.clearedProperties);
this.clearedProperties = new CoreMap_js_1.CoreMap();
}
}
/**
* Returns true if this node has any batched events waiting to be emitted
* Used internally by the framework to batch and emit change events
* @internal
*/
static hasBatchedEvents() {
return (this.nodesToRemove.size > 0 ||
this.nodesToSave.size > 0 ||
this.nodesToLoad.size ||
this.nodesToLoadFully.size > 0 ||
this.nodesURIUpdated.size > 0 ||
this.clearedProperties.size > 0);
}
/**
* Converts the string '<http://some.uri>' into a NamedNode
* @param uriString the string representation of a NamedNode, consisting of its URI surrounded by brackets: '<' URI '>'
*/
static fromString(uriString) {
var firstChar = uriString.substr(0, 1);
if (firstChar == '<') {
return this.getOrCreate(uriString.substr(1, uriString.length - 2));
}
else {
throw new Error('fromString expects a URI wrapped in brackets, like <http://www.example.com>');
}
}
/**
* Resets the map of nodes that is known in this local environment
* Mostly used for test functionality
*/
static reset() {
this.tempCounter = 0;
this.namedNodes = new NodeMap_js_1.NodeMap();
}
/**
* Create a new local NamedNode. A temporary URI will be generated for its URI.
* This node will not exist in the graph database (persistent storage) until you call `node.save()`
* Until saved, `node.isTemporaryNode()` will return true.
*/
static create() {
let tmpURI = this.createNewTempUri();
while (this.getNamedNode(tmpURI)) {
this.tempCounter++;
tmpURI = this.createNewTempUri();
}
return this._create(tmpURI, true);
}
/**
* Registers a NamedNode to the locally known list of nodes
* @internal
* @param node
*/
static register(node) {
if (this.namedNodes.has(node.uri)) {
throw new Error('A node with this URI already exists: "' +
node.uri +
'". You should probably use NamedNode.getOrCreate instead of NamedNode.create (' +
node.uri +
')');
}
this.namedNodes.set(node.uri, node);
}
/**
* Unregisters a NamedNode from the locally known list of nodes
* @internal
* @param node
*/
static unregister(node) {
if (!this.namedNodes.has(node.uri)) {
throw new Error('This node has already been removed from the registry: ' + node.uri);
}
this.namedNodes.delete(node.uri);
}
/**
* Returns a map of all locally known nodes.
* The map will have URI's as keys and NamedNodes as values
* @param node
*/
static getAllNamedNodes() {
return this.namedNodes;
}
/**
* Returns a map of all locally known nodes.
* The map will have URI's as keys and NamedNodes as values
* @param node
*/
static createNewTempUri() {
return this.TEMP_URI_BASE + this.tempCounter++; //+'/';+Date.now()+Math.random();
}
static getCounter() {
return this.tempCounter;
}
/**
* ##########################################################################
* ############# PUBLIC METHODS FOR REGULAR USE #############
* ##########################################################################
*/
/**
* The proper way to obtain a node from a URI.
* If requested before, this returns the existing NamedNode for the given URI.
* Or, if this is the first request for this URI, it creates a new NamedNode first, and returns that
* Using this method over `new NamedNode()` makes sure all nodes are registered, and no duplicates will exist.
* `new NamedNode()` should therefore never be used.
* @param uri
*/
static getOrCreate(uri, isTemporaryNode = false) {
return this.getNamedNode(uri) || this._create(uri, isTemporaryNode);
}
/**
* Returns the NamedNode with the given URI, IF it exists.
* DOES NOT create a new NamedNode if it didn't exist yet, instead it returns undefined.
* You can therefore use this method to see if a NamedNode already exists locally.
* Use `getOrCreate()` if you want to simply get a NamedNode for a certain URI
* @param uri
*/
static getNamedNode(uri) {
return this.namedNodes.get(uri);
}
static emitClearedProperty(node, property) {
//if not a local node we will emit events for storage controllers to be picked up
if (!node.isTemporaryNode) {
//regardless of how many values are known 'locally', we want to emit this event so that the source of data can eventually properly clear all values
if (!NamedNode.clearedProperties.has(node)) {
NamedNode.clearedProperties.set(node, []);
EventBatcher_js_1.eventBatcher.register(NamedNode);
}
//we save the property that was cleared AND the quads that were cleared
NamedNode.clearedProperties
.get(node)
.push([
property,
node.asSubject.has(property)
? new QuadArray_js_1.QuadArray(...node.asSubject.get(property).getQuadSet())
: null,
]);
}
}
static _create(uri, isLocalNode = false) {
var node = new NamedNode(uri, isLocalNode);
this.register(node);
return node;
}
/**
* Used by Quads to signal their subject about a new property
* @internal
* @param quad
* @param alteration
* @param emitEvents
*/
registerProperty(quad, alteration = false, emitEvents = true) {
var predicate = quad.predicate;
//first make sure we have a QuadMap value for key=predicate
if (!this.asSubject.has(predicate)) {
this.asSubject.set(predicate, new QuadMap_js_1.QuadMap());
this.properties.set(predicate, new NodeValuesSet_js_1.NodeValuesSet(this, predicate));
}
//Add the quad to the QuadMap (see implementation for more details)
let quadMap = this.asSubject.get(predicate);
//make sure we have a QuadSet ready for the object of this quad
quadMap.__set(quad.object, quad);
//Now for the property index (which gives direct access to the object values of a certain predicate)
//Because multiple graphs can hold the same subj-pred-obj triple, we want to avoid adding literal values
//that have the exact same literal value, so we need to test for equality here before adding it
if (!this.properties
.get(predicate)
.some((object) => object.equals(quad.object))) {
this.properties.get(predicate).__add(quad.object);
}
//add this quad to the map of events to send on the next tick
if (emitEvents) {
if (!this.changedProperties)
this.changedProperties = new CoreMap_js_1.CoreMap();
if (alteration && !this.alteredProperties)
this.alteredProperties = new CoreMap_js_1.CoreMap();
//register this change as alteration (user input) or as normal (automatic, data based) property change
this.registerPropertyChange(quad, alteration
? [this.changedProperties, this.alteredProperties]
: [this.changedProperties]);
}
}
/**
* Inverse property can be thought of as "this node is the value (object) of another nodes' property"
* This method is used by the class Quad to communicate its existence to the quads object
* @internal
* @param quad
* @param alteration
* @param emitEvents
*/
registerInverseProperty(quad, alteration = false, emitEvents = true) {
//asObject is not always initialised - to save some memory on nodes without incoming properties (only used as subject)
if (!this.asObject) {
this.asObject = new CoreMap_js_1.CoreMap();
}
var index = quad.predicate;
if (!this.asObject.has(index)) {
this.asObject.set(index, new QuadMap_js_1.QuadMap());
}
this.asObject.get(index).__set(quad.subject, quad);
//add this quad to the map of events to send on the next tick
if (emitEvents) {
if (!this.changedInverseProperties)
this.changedInverseProperties = new CoreMap_js_1.CoreMap();
if (alteration && !this.alteredInverseProperties)
this.alteredInverseProperties = new CoreMap_js_1.CoreMap();
this.registerPropertyChange(quad, alteration
? [this.changedInverseProperties, this.alteredInverseProperties]
: [this.changedInverseProperties]);
}
//need to return this, see Literal
return this;
}
/**
* Called when this node occurs as predicate in a quad
* @internal
*/
// registerAsPredicate(
// quad: Quad,
// alteration: boolean = false,
// emitEvents: boolean = true,
// ) {
// //asPredicate is not always initialised because only properties can occur as predicate
// if (!this.asPredicate) {
// this.asPredicate = new QuadArray();
// }
// this.asPredicate.push(quad);
//
// if (emitEvents) {
// this.registerPredicateChange(quad, alteration);
// }
// }
/**
* This method is used by the class Quad to communicate with its nodes
* @internal
* @param quad
* @param alteration
*/
registerValueChange(quad, alteration = false) {
if (!this.changedProperties)
this.changedProperties = new CoreMap_js_1.CoreMap();
if (alteration) {
if (!this.alteredProperties)
this.alteredProperties = new CoreMap_js_1.CoreMap();
}
this.registerPropertyChange(quad, alteration
? [this.changedProperties, this.alteredProperties]
: [this.changedProperties]);
}
/**
* This method is used by the class Quad to communicate with its nodes
* @internal
*/
unregisterProperty(quad, alteration = false, emitEvents = true) {
var predicate = quad.predicate;
//start by looking through the QuadMap (it is more complete than the quick & easy properties index, as it accounts for multiple quads per object value in different graphs)
var quadMap = this.asSubject.get(predicate);
if (quadMap) {
let valueQuads = quadMap.get(quad.object);
if (!valueQuads)
return;
valueQuads.delete(quad);
//if we no longer hold any quads for this object
if (valueQuads.size == 0) {
//remove the key
quadMap.__delete(quad.object);
//for this.properties we just keep ONE value for identical literals (in case multiple graphs hold the same subject-pred-obj triple)
//so here we check if any other object that is still registered equals the current object
if (![...quadMap.keys()].some((object) => quad.object.equals(object))) {
//if that's not the case, then also remove this object from the propertySet index (the index should exist)
//Note: in some cases, for example when a quad moved between graphs, the object registered here as property value might not be the same as the as the object of the quad that is still registered,
// therefor we also check & remove equivalent values in case regular removal didnt work
//TODO: we could improve this by making sure that this.properties stays up to date with the actual quads
this.properties.get(predicate).__delete(quad.object) ||
this.properties
.get(predicate)
.__delete(this.properties
.get(predicate)
.find((object) => object.equals(quad.object)));
}
//if we now also no longer hold any values for this predicate
//NOTE: this was turned off because NodeValuesSets are reused, recreating a new one when a new value
// is added then does not add the value to the old one.
// if (quadMap.size === 0) {
// //delete both indices for this predicate
// this.asSubject.delete(predicate);
// this.properties.delete(predicate);
// }
}
}
//NOTE: also when removing property values (therefore unregistering the property), we add the removed quad to the SAME list of changed properties
//event listeners will have to filter out which quad was added or removed
if (emitEvents) {
if (!this.changedProperties)
this.changedProperties = new CoreMap_js_1.CoreMap();
if (alteration && !this.alteredProperties)
this.alteredProperties = new CoreMap_js_1.CoreMap();
this.registerPropertyChange(quad, alteration
? [this.changedProperties, this.alteredProperties]
: [this.changedProperties]);
}
}
/**
* This method is used by the class Quad to communicate with its nodes
* @internal
*/
// unregisterAsPredicate(
// quad: Quad,
// alteration: boolean = false,
// emitEvents: boolean = true,
// ) {
// this.asPredicate.splice(this.asPredicate.indexOf(quad), 1);
//
// if (emitEvents) {
// this.registerPredicateChange(quad, alteration);
// }
// }
//
// /**
// * Returns a list of quads in which this node is now used as predicate
// * BEFORE these changes are sent as events in the normal event flow
// * Currently used by Reasoner to allow for immediate application of reasoning
// */
// getPendingPredicateChanges(): QuadArray {
// return this.changedAsPredicate;
// }
/**
* This method is used by the class Quad to communicate with its nodes
* @internal
*/
unregisterInverseProperty(quad, alteration = false, emitEvents = true) {
//start by looking through the QuadMap (it is more complete than the quick & easy properties index, as it accounts for identical sub-pred-obj triples that occur in different graphs)
//here we get a map of all the quads for the given predicate, grouped by subject (each map contains identical triples, but with different graphs)
var quadMap = this.asObject.get(quad.predicate);
if (quadMap) {
let quadSet = quadMap.get(quad.subject);
if (!quadSet)
return;
//remove this quad
quadSet.delete(quad);
//if we no longer hold any quads for this subject
if (quadSet.size === 0) {
quadMap.__delete(quad.subject);
}
if (quadMap.size == 0) {
this.asObject.delete(quad.predicate);
}
}
//also when removing the property do wee add the removed quad to the list of changed properties
//event listeners will have to filter out which quad was added or removed
if (emitEvents) {
if (!this.changedInverseProperties)
this.changedInverseProperties = new CoreMap_js_1.CoreMap();
if (alteration && !this.alteredInverseProperties)
this.alteredInverseProperties = new CoreMap_js_1.CoreMap();
this.registerPropertyChange(quad, alteration
? [this.changedInverseProperties, this.alteredInverseProperties]
: [this.changedInverseProperties]);
}
}
/**
* Returns a list of quads in which this node is now used as object
* BEFORE these changes are sent as events in the normal event flow
* Currently used by Reasoner to allow for immediate application of reasoning
*/
getPendingInverseChanges(property) {
return this.changedInverseProperties
? this.changedInverseProperties.get(property)
: new QuadSet_js_1.QuadSet();
}
/**
* Returns a list of quads in which this node is now used as subject
* BEFORE these changes are sent as events in the normal event flow
* Currently used by Reasoner to allow for immediate application of reasoning
*/
getPendingChanges(property) {
return this.changedProperties.get(property);
}
/**
* Set the a single property value
* Creates a single connection between two nodes in the graph: from this node, to the node given as value, with the property as the connecting 'edge' between them
* @param property - a NamedNode with rdf:type rdf:Property, the edge in the graph, the predicate of a quad
* @param value - the node that this new graph-edge points to. The object of the quad to be created.
*/
set(property, value) {
if (!value) {
throw Error('No value provided to set!');
}
//if there is already a quad with exactly this prop-value pair
if (this.has(property, value)) {
//make all quads with this pair explicit if they were not yet
this.getQuads(property, value).makeExplicit();
//yet return false because nothing was changes in the propreties
return false;
}
//if this pair didn't exist yet, create a new quad (the graph is undefined for now, Storage will pick this up and place it in the right graph)
//note that the sixth parameter is true, this indicates that this is an alteration (as in new data that triggers change events instead of a quad created for already existing data)
new Quad(this, property, value, undefined, false, true);
return true;
}
/**
* Same as set() except this method allows you to pass a string as value and converts it to a Literal for you
* @param property
* @param value
*/
setValue(property, value) {
return this.set(property, new Literal(value));
}
/**
* Set multiple values at once for a single property.
* You can use this for example to state that this node (a person) has a 'hasFriend' connection to multiple people (friends) in 1 statement
* @param property - a NamedNode with rdf:type rdf:Property, the edge in the graph, the predicate of a quad
* @param values - an array or set of nodes. Can be NamedNodes or Literals
*/
mset(property, values) {
//if(save) dacore.system.storageQueueStart(this);
var res = false;
for (var value of values) {
res = this.set(property, value) || res;
}
return res;
}
/**
* Returns true if this node has the given value as the value of the given property
* NOTE: returns true when a literal node is provided that is EQUIVALENT to any of the values that this node has for this property (whilst not neccecarilly being the exact same object in memory)
* See also: Literal.equals
* @param property - a NamedNode with rdf:type rdf:Property, the edge in the graph, the predicate of a quad
* @param value - a single node. Can be a NamedNode or Literal
*/
has(property, value) {
if (!value) {
throw new Error('No value provided to NamedNode.has(). Did you mean `hasProperty`?');
}
var properties = this.properties.get(property);
return (properties &&
(properties.has(value) ||
properties.some((object) => object.equals(value))));
}
/**
* Returns true if this node has the given value for the given property in an EXPLICIT quad.
* That is, this property-value has been explicitly set, and is NOT generated by the Reasoner.
* See the documentation for more information about implicit vs explicit
* @param property - a NamedNode with rdf:type rdf:Property, the edge in the graph, the predicate of a quad
* @param value - a single node. Can be a NamedNode or Literal
*/
hasExplicit(property, value) {
if (!this.asSubject.has(property))
return false;
return this.getQuadsByValue(property, value).some((quad) => !quad.implicit);
}
/**
* Returns true if this node has ANY explicit quad with the given property
* See the documentation for more information about implicit vs explicit
* @param property - a NamedNode with rdf:type rdf:Property, the edge in the graph, the predicate of a quad
*/
hasExplicitProperty(property) {
if (!this.asSubject.has(property))
return false;
return this.getQuads(property).some((quad) => !quad.implicit);
}
/**
* Returns true if this node has a Literal as value of the given property who's literal-value (a string) matches the given value
* So works the same as `has()` except you can provide a string as value, and will obviously not match any NamedNode values
* And unlike has() this method will NOT check for the Literal its datatype. Instead only checking the literal-value
* @param property - a NamedNode with rdf:type rdf:Property, the edge in the graph, the predicate of a quad
* @param value - the string value we want to check for
*/
hasValue(property, value) {
var properties = this.properties.get(property);
return (properties &&
properties.some((object) => 'value' in object && object['value'] === value));
}
/**
* Returns true if this node has the given value as the value of the given property with an EXACT match (meaning the same object in memory)
* So works the same as has() except for Literals this only returns true if the value of the property is exactly the same object as the given value
* UNLIKE `has()` which checks if the literal value, datatype and language tag of two literal nodes are equivalent
* @param property - a NamedNode with rdf:type rdf:Property, the edge in the graph, the predicate of a quad
* @param value - a single node. Can be a NamedNode or Literal
*/
hasExact(property, value) {
var properties = this.properties.get(property);
return properties && properties.has(value);
}
/**
* Returns true if this node has ANY value set for the given property.
* That is, if any quad exists that has this node as the subject and the given property as predicate
* @param property - a NamedNode with rdf:type rdf:Property, the edge in the graph, the predicate of a quad
*/
hasProperty(property) {
//properties can be empty sets, so we need to check if there are any values in the set
return (this.properties.has(property) && this.properties.get(property).size > 0);
}
/**
* Returns true if the given end point can be reached by following the given properties in order
* Example: hasPathTo([foaf.hasFriend,rdf.type],foaf.Person) will return true if any of the friends of this node (this person in this example) is of the type foaf:Person
* @param properties an array of NamedNodes
* @param endPoint the node to reach, a Literal or a NamedNode
*/
hasPathTo(properties, endPoint) {
//we just need to find one matching path, so we do a depth-first algorithm which will be more performant, so:
//take first property
var property = properties.shift();
//if more properties left
if (properties.length > 0) {
var res;
//check if any of the values of that property for this node
//has a path to the rest of the properties, and if so return the found value
for (var value of this.getAll(property)) {
if ((res = value.hasPathTo([...properties], endPoint))) {
return res;
}
}
return false;
}
else {
//if last property
//see if we can reach the value if a value was given
//else: see if any value (any path) exists
if (endPoint) {
return this.has(property, endPoint);
}
else {
return this.hasProperty(property);
}
}
}
getAs(type) {
return type.getOf(this);
}
/**
* returns true if ANY of the given end points can be reached by following the given properties in the given order
* Example: hasPathTo([foaf.hasFriend,foaf.hasFriend],[mike,jenny]) will return true if this node (person) has a friend that has mike or jenny as a friend
* @param properties an array of NamedNodes
* @param endPoint the node to reach, a Literal or a NamedNode
*/
hasPathToSomeInSet(properties, endPoints) {
//we just need to find one matching path, so we do a depth-first algorithm which will be more performant, so:
//take first property
var property = properties.shift();
//if more properties left
if (properties.length > 0) {
var res;
//check if any of the values of that property for this node
//has a path to the rest of the properties, and if so return the found value
for (var value of this.getAll(property)) {
if ((res = value.hasPathToSomeInSet([...properties], endPoints))) {
return res;
}
}
return false;
}
else {
//if last property
//see if we can reach the value if a value was given
//else: see if any value (any path) exists
return endPoints.some((endPoint) => this.has(property, endPoint));
}
}
/**
* returns true if ANY end point (node) can be reached by following the given properties in order
* @param properties an array of NamedNodes
*/
hasPath(properties) {
//we just need to find one matching path, so we do a depth-first algorithm which will be more performant, so:
//take first property
var property = properties.shift();
//if more properties left
if (properties.length > 0) {
var res;
//check if any of the values of that property for this node
//has a path to the rest of the properties, and if so return the found value
for (var value of this.getAll(property)) {
if ((res = value.hasPath([...properties]))) {
return res;
}
}
return false;
}
else {
//if last property
//see if we can reach the value if a value was given
//else: see if any value (any path) exists
return this.hasProperty(property);
}
}
/**
* Returns a set of all the properties this node has.
* That is, all unique predicates of quads where this node is the subject
* @param includeFromIncomingArcs if true, also includes predicates (properties) of quads where this node is the VALUE of another nodes' property. Default: false
*/
getProperties(includeFromIncomingArcs = false) {
if (includeFromIncomingArcs) {
return new NodeSet_js_1.NodeSet((this.asObject
? [...this.asSubject.keys(), ...this.asObject.keys()]
: this.asSubject.keys()));
}
else {
return new NodeSet_js_1.NodeSet(this.asSubject.keys());
}
}
/**
* Returns a set of all the properties used by this node in EXPLICIT facts (quads)
* See the documentation for more information about implicit vs explicit facts
* @param includeFromIncomingArcs if true, also includes predicates (properties) of quads where this node is the VALUE of another nodes' property. Default: false
*/
getExplicitProperties(includeFromIncomingArcs = false) {
return new NodeSet_js_1.NodeSet([
...this.getAllQuads(includeFromIncomingArcs)
.filter((t) => !t.implicit)
.map((t) => t.predicate),
]);
}
/**
* Returns a set of all the properties used by other nodes where this node is the VALUE of that property
* For example if this node is Jenny and the following is true: Mike foaf:hasFriend Jenny, calling this method on Jenny will return hasFriend
*/
getInverseProperties() {
return this.asObject
? new NodeSet_js_1.NodeSet(this.asObject.keys())
: new NodeSet_js_1.NodeSet();
}
/**
* If this node has values for the given property, the first value is returned
* NOTE: the order of multiple values CANNOT be guaranteed. Therefore use this value if it DOESN'T matter to you which of multiple possible values for this property you'll get OR if you're certain there will be only 1 value.
* @param property - a NamedNode with rdf:type rdf:Property, the edge in the graph, the predicate of a quad
*/
getOne(property) {
return this.properties.has(property)
? this.properties.get(property).first()
: undefined;
}
/**
* If this node has EXPLICIT values for the given property, the first value is returned
* Same as `getOne()` except only explicit quads / facts are considered
* @param property - a NamedNode with rdf:type rdf:Property, the edge in the graph, the predicate of a quad
*/
getOneExplicit(property) {
for (let quad of this.getQuads(property)) {
if (!quad.implicit) {
return quad.object;
}
}
}
/**
* Returns all values this node has for the given property
* @param property - a NamedNode with rdf:type rdf:Property, the edge in the graph, the predicate of a quad
*/
getAll(property) {
//we usually just index all the existing properties
//but to have consistent behaviour with PropertyValueSets, when you request a property that has no values
//we need to create an index for the empty result set
if (!this.properties.has(property)) {
this.asSubject.set(property, new QuadMap_js_1.QuadMap());
this.properties.set(property, new NodeValuesSet_js_1.NodeValuesSet(this, property));
}
return this.properties.get(property);
// return this.properties.has(property)
// ? this.properties.get(property)
// : new PropertyValueSet(this,property);
}
/**
* Returns all values this node EXPLICITLY has for the given property
* So, same as `getAll()` except only explicit facts are considered.
* See the documentation for more information about implicit vs explicit
* @param property - a NamedNode with rdf:type rdf:Property, the edge in the graph, the predicate of a quad
*/
getAllExplicit(property) {
return this.getExplicitQuads(property).getObjects();
}
/**
* Returns the literal value of the first Literal value for the given property
* Only returns a results in the disired language if specified.
* For example if `this rdfs:label "my name" then this.getValue(rdfs.label) will return "my name".
* So, works the same as getOne() except it will return the literal (string) value of the first found Literal
* NOTE: the order of multiple values CANNOT be guaranteed. Therefore use this value if it DOESN'T matter to you which of multiple possible values for this property you'll get OR if you're certain there will be only one value.
* @param property - a NamedNode with rdf:type rdf:Property, the edge in the graph, the predicate of a quad
*/
//NOTE: we have to overload getValue without parameters here to be compatible with Literal
getValue(property, language) {
//going over all property values
for (var valueObject of this.getAll(property)) {
//see if its a Literal
//we do this by checking if value exists in the valueObject.
//And we do that like this because we dont want to explicitly import Literal here.
//@TODO: Possibly create an interface to avoid this 'hacky' workaround
if (valueObject['value'] &&
(!language || valueObject['isOfLanguage'](language))) {
return valueObject.value;
}
}
}
/**
* Returns the literal values (strings) of all Literals this this node has a value for the given property
* For example if `this rdfs:label "my name" and `this rdfs:label "my other name" it will return ["my name","my other name"].
* So, works the same as getAll() except it will return an array of strings
* @param property - a NamedNode with rdf:type rdf:Property, the edge in the graph, the predicate of a quad
*/
getValues(property) {
var valueObjects = this.getAll(property);
var res = [];
valueObjects.forEach((valueObject) => {
if ('value' in valueObject) {
res.push(valueObject['value']);
}
});
return res;
}
/**
* Returns any value (node, node) that is connected to this node with one or more connections of the given property.
* For example getDeep(hasFriend) will return all the people that are my friends or friends of friends
* @param property - a NamedNode with rdf:type rdf:Property, the edge in the graph, the predicate of a quad
* @param maxDepth - the maximum number of connections that resulting nodes are removed from this node. In the example above maxDepth=2 would return only friends and friends of friends
*/
getDeep(property, maxDepth = Infinity) {
var result = new NodeSet_js_1.NodeSet();
var stack = new NodeSet_js_1.NodeSet([this]);
while (stack.size > 0 && maxDepth > 0) {
var nextLevelStack = new NodeSet_js_1.NodeSet();
for (let node of stack) {
for (var value of node.getAll(property)) {
if (!result.has(value)) {
result.add(value);
nextLevelStack.add(value);
}
}
}
stack = nextLevelStack;
maxDepth--;
}
return result;
}
/**
* Returns the first found value following the given properties in the given order.
* For example: getOneFromPath([hasFriend,hasFather]) would return the first found father out of the set 'fathers of my friends'
* @param properties - an array of NamedNodes. Which are nodes with rdf:type rdf:Property, the edges in the graph, the predicates of quads.
*/
getOneFromPath(...properties) {
//we just need one, so we do a depth-first algorithm which will be more performant, so:
//take first property
var property = properties.shift();
//if more properties left
if (properties.length > 0) {
var res;
//check if any of the values of that property for this node
//has a path to the rest of the properties, and if so return the found value
for (var value of this.getAll(property)) {
if ((res = value.getOneFromPath(...properties))) {
return res;
}
}
}
else {
//return the first value possible
return this.getOne(property);
}
}
/**
* Returns all values that can be reached by following the given properties in order.
* For example getAllFromPath([hasFriend,hasFather]) will return all fathers of all my (direct) friends
* @param properties - an array of NamedNodes. Which are nodes with rdf:type rdf:Property, the edges in the graph, the predicates of quads.
*/
getAllFromPath(...properties) {
//we just need all paths, so we can do a breadth first implementation
//take first property
var property = properties.shift();
if (properties.length > 0) {
//and ask the whole set of values to return all values of the rest of the path
return this.getAll(property).getAllFromPath(...properties);
}
else {
return this.getAll(property);
}
}
/**
* Same as getDeep() but for inverse properties.
* Best understood with an example: if this is a person. this.getInverseDeep(hasChild) would return all this persons ancestors (which had children that eventually had this person as their child)
* @param property - a NamedNode with rdf:type rdf:Property, the edge in the graph, the predicate of a quad
* @param maxDepth - the maximum number of connections that resulting nodes are removed from this node. In the example above maxDepth=2 would return only the parents and grand parents
*/
getInverseDeep(property, maxDepth = Infinity) {
var result = new NodeSet_js_1.NodeSet();
var stack = new NodeSet_js_1.NodeSet([this]);
while (stack.size > 0 && maxDepth > 0) {
var nextLevelStack = new NodeSet_js_1.NodeSet();
for (let node of stack) {
for (var value of node.getAllInverse(property)) {
if (!result.has(value)) {
result.add(value);
nextLevelStack.add(value);
}
}
}
stack = nextLevelStack;