import namespaces from './IRIs';
import { isDefaultGraph } from './N3Util';
const { rdf, xsd } = namespaces;
N3.js implementations of the RDF/JS core data types See https://github.com/rdfjs/representation-task-force/blob/master/interface-spec.md
import namespaces from './IRIs';
import { isDefaultGraph } from './N3Util';
const { rdf, xsd } = namespaces;
eslint-disable-next-line prefer-const
let DEFAULTGRAPH;
let _blankNodeCounter = 0;
const escapedLiteral = /^"(.*".*)(?="[^"]*$)/;
const quadId = /^<<("(?:""|[^"])*"[^ ]*|[^ ]+) ("(?:""|[^"])*"[^ ]*|[^ ]+) ("(?:""|[^"])*"[^ ]*|[^ ]+) ?("(?:""|[^"])*"[^ ]*|[^ ]+)?>>$/;
const DataFactory = {
namedNode,
blankNode,
variable,
literal,
defaultGraph,
quad,
triple: quad,
};
export default DataFactory;
export class Term {
constructor(id) {
this.id = id;
}
get value() {
return this.id;
}
equals(other) {
If both terms were created by this library, equality can be computed through ids
if (other instanceof Term)
return this.id === other.id;
Otherwise, compare term type and value
return !!other && this.termType === other.termType &&
this.value === other.value;
}
equals
https://immutable-js.com/docs/v4.0.0/ValueObject/#hashCode()
hashCode() {
return 0;
}
toJSON() {
return {
termType: this.termType,
value: this.value,
};
}
}
export class NamedNode extends Term {
get termType() {
return 'NamedNode';
}
}
export class Literal extends Term {
get termType() {
return 'Literal';
}
get value() {
return this.id.substring(1, this.id.lastIndexOf('"'));
}
get language() {
Find the last quotation mark (e.g., ‘“abc”@en-us’)
const id = this.id;
let atPos = id.lastIndexOf('"') + 1;
If “@” it follows, return the remaining substring; empty otherwise
return atPos < id.length && id[atPos++] === '@' ? id.substr(atPos).toLowerCase() : '';
}
get datatype() {
return new NamedNode(this.datatypeString);
}
get datatypeString() {
Find the last quotation mark (e.g., ‘“abc”^^http://ex.org/types#t')
const id = this.id, dtPos = id.lastIndexOf('"') + 1;
const char = dtPos < id.length ? id[dtPos] : '';
If “^” it follows, return the remaining substring
return char === '^' ? id.substr(dtPos + 2) :
If “@” follows, return rdf:langString; xsd:string otherwise
(char !== '@' ? xsd.string : rdf.langString);
}
equals(other) {
If both literals were created by this library, equality can be computed through ids
if (other instanceof Literal)
return this.id === other.id;
Otherwise, compare term type, value, language, and datatype
return !!other && !!other.datatype &&
this.termType === other.termType &&
this.value === other.value &&
this.language === other.language &&
this.datatype.value === other.datatype.value;
}
toJSON() {
return {
termType: this.termType,
value: this.value,
language: this.language,
datatype: { termType: 'NamedNode', value: this.datatypeString },
};
}
}
export class BlankNode extends Term {
constructor(name) {
super(`_:${name}`);
}
get termType() {
return 'BlankNode';
}
get value() {
return this.id.substr(2);
}
}
export class Variable extends Term {
constructor(name) {
super(`?${name}`);
}
get termType() {
return 'Variable';
}
get value() {
return this.id.substr(1);
}
}
export class DefaultGraph extends Term {
constructor() {
super('');
return DEFAULTGRAPH || this;
}
get termType() {
return 'DefaultGraph';
}
equals(other) {
If both terms were created by this library, equality can be computed through strict equality; otherwise, compare term types.
return (this === other) || (!!other && (this.termType === other.termType));
}
}
DEFAULTGRAPH = new DefaultGraph();
export function termFromId(id, factory) {
factory = factory || DataFactory;
Falsy value or empty string indicate the default graph
if (!id)
return factory.defaultGraph();
Identify the term type based on the first character
switch (id[0]) {
case '?':
return factory.variable(id.substr(1));
case '_':
return factory.blankNode(id.substr(2));
case '"':
Shortcut for internal literals
if (factory === DataFactory)
return new Literal(id);
Literal without datatype or language
if (id[id.length - 1] === '"')
return factory.literal(id.substr(1, id.length - 2));
Literal with datatype or language
const endPos = id.lastIndexOf('"', id.length - 1);
return factory.literal(id.substr(1, endPos - 1),
id[endPos + 1] === '@' ? id.substr(endPos + 2)
: factory.namedNode(id.substr(endPos + 3)));
case '<':
const components = quadId.exec(id);
return factory.quad(
termFromId(unescapeQuotes(components[1]), factory),
termFromId(unescapeQuotes(components[2]), factory),
termFromId(unescapeQuotes(components[3]), factory),
components[4] && termFromId(unescapeQuotes(components[4]), factory)
);
default:
return factory.namedNode(id);
}
}
export function termToId(term) {
if (typeof term === 'string')
return term;
if (term instanceof Term && term.termType !== 'Quad')
return term.id;
if (!term)
return DEFAULTGRAPH.id;
Term instantiated with another library
switch (term.termType) {
case 'NamedNode': return term.value;
case 'BlankNode': return `_:${term.value}`;
case 'Variable': return `?${term.value}`;
case 'DefaultGraph': return '';
case 'Literal': return `"${term.value}"${
term.language ? `@${term.language}` :
(term.datatype && term.datatype.value !== xsd.string ? `^^${term.datatype.value}` : '')}`;
case 'Quad':
To identify RDF* quad components, we escape quotes by doubling them. This avoids the overhead of backslash parsing of Turtle-like syntaxes.
return `<<${
escapeQuotes(termToId(term.subject))
} ${
escapeQuotes(termToId(term.predicate))
} ${
escapeQuotes(termToId(term.object))
}${
(isDefaultGraph(term.graph)) ? '' : ` ${termToId(term.graph)}`
}>>`;
default: throw new Error(`Unexpected termType: ${term.termType}`);
}
}
export class Quad extends Term {
constructor(subject, predicate, object, graph) {
super('');
this._subject = subject;
this._predicate = predicate;
this._object = object;
this._graph = graph || DEFAULTGRAPH;
}
get termType() {
return 'Quad';
}
get subject() {
return this._subject;
}
get predicate() {
return this._predicate;
}
get object() {
return this._object;
}
get graph() {
return this._graph;
}
toJSON() {
return {
termType: this.termType,
subject: this._subject.toJSON(),
predicate: this._predicate.toJSON(),
object: this._object.toJSON(),
graph: this._graph.toJSON(),
};
}
equals(other) {
return !!other && this._subject.equals(other.subject) &&
this._predicate.equals(other.predicate) &&
this._object.equals(other.object) &&
this._graph.equals(other.graph);
}
}
export { Quad as Triple };
export function escapeQuotes(id) {
return id.replace(escapedLiteral, (_, quoted) => `"${quoted.replace(/"/g, '""')}`);
}
export function unescapeQuotes(id) {
return id.replace(escapedLiteral, (_, quoted) => `"${quoted.replace(/""/g, '"')}`);
}
function namedNode(iri) {
return new NamedNode(iri);
}
function blankNode(name) {
return new BlankNode(name || `n3-${_blankNodeCounter++}`);
}
function literal(value, languageOrDataType) {
Create a language-tagged string
if (typeof languageOrDataType === 'string')
return new Literal(`"${value}"@${languageOrDataType.toLowerCase()}`);
Automatically determine datatype for booleans and numbers
let datatype = languageOrDataType ? languageOrDataType.value : '';
if (datatype === '') {
Convert a boolean
if (typeof value === 'boolean')
datatype = xsd.boolean;
Convert an integer or double
else if (typeof value === 'number') {
if (Number.isFinite(value))
datatype = Number.isInteger(value) ? xsd.integer : xsd.double;
else {
datatype = xsd.double;
if (!Number.isNaN(value))
value = value > 0 ? 'INF' : '-INF';
}
}
}
Create a datatyped literal
return (datatype === '' || datatype === xsd.string) ?
new Literal(`"${value}"`) :
new Literal(`"${value}"^^${datatype}`);
}
function variable(name) {
return new Variable(name);
}
function defaultGraph() {
return DEFAULTGRAPH;
}
function quad(subject, predicate, object, graph) {
return new Quad(subject, predicate, object, graph);
}