import N3Lexer from './N3Lexer';
import N3DataFactory from './N3DataFactory';
import namespaces from './IRIs';
let blankNodePrefix = 0;
N3Parser parses N3 documents.
import N3Lexer from './N3Lexer';
import N3DataFactory from './N3DataFactory';
import namespaces from './IRIs';
let blankNodePrefix = 0;
export default class N3Parser {
constructor(options) {
this._contextStack = [];
this._graph = null;
Set the document IRI
options = options || {};
this._setBase(options.baseIRI);
options.factory && initDataFactory(this, options.factory);
Set supported features depending on the format
const format = (typeof options.format === 'string') ?
options.format.match(/\w*$/)[0].toLowerCase() : '',
isTurtle = /turtle/.test(format), isTriG = /trig/.test(format),
isNTriples = /triple/.test(format), isNQuads = /quad/.test(format),
isN3 = this._n3Mode = /n3/.test(format),
isLineMode = isNTriples || isNQuads;
if (!(this._supportsNamedGraphs = !(isTurtle || isN3)))
this._readPredicateOrNamedGraph = this._readPredicate;
Support triples in other graphs
this._supportsQuads = !(isTurtle || isTriG || isNTriples || isN3);
Support nesting of triples
this._supportsRDFStar = format === '' || /star|\*$/.test(format);
Disable relative IRIs in N-Triples or N-Quads mode
if (isLineMode)
this._resolveRelativeIRI = iri => { return null; };
this._blankNodePrefix = typeof options.blankNodePrefix !== 'string' ? '' :
options.blankNodePrefix.replace(/^(?!_:)/, '_:');
this._lexer = options.lexer || new N3Lexer({ lineMode: isLineMode, n3: isN3 });
Disable explicit quantifiers by default
this._explicitQuantifiers = !!options.explicitQuantifiers;
}
_resetBlankNodePrefix
restarts blank node prefix identification static _resetBlankNodePrefix() {
blankNodePrefix = 0;
}
_setBase
sets the base IRI to resolve relative IRIs _setBase(baseIRI) {
if (!baseIRI) {
this._base = '';
this._basePath = '';
}
else {
Remove fragment if present
const fragmentPos = baseIRI.indexOf('#');
if (fragmentPos >= 0)
baseIRI = baseIRI.substr(0, fragmentPos);
Set base IRI and its components
this._base = baseIRI;
this._basePath = baseIRI.indexOf('/') < 0 ? baseIRI :
baseIRI.replace(/[^\/?]*(?:\?.*)?$/, '');
baseIRI = baseIRI.match(/^(?:([a-z][a-z0-9+.-]*:))?(?:\/\/[^\/]*)?/i);
this._baseRoot = baseIRI[0];
this._baseScheme = baseIRI[1];
}
}
_saveContext
stores the current parsing contextwhen entering a new scope (list, blank node, formula)
_saveContext(type, graph, subject, predicate, object) {
const n3Mode = this._n3Mode;
this._contextStack.push({
type,
subject, predicate, object, graph,
inverse: n3Mode ? this._inversePredicate : false,
blankPrefix: n3Mode ? this._prefixes._ : '',
quantified: n3Mode ? this._quantified : null,
});
The settings below only apply to N3 streams
if (n3Mode) {
Every new scope resets the predicate direction
this._inversePredicate = false;
In N3, blank nodes are scoped to a formula (using a dot as separator, as a blank node label cannot start with it)
this._prefixes._ = (this._graph ? `${this._graph.value}.` : '.');
Quantifiers are scoped to a formula
this._quantified = Object.create(this._quantified);
}
}
_restoreContext(type, token) {
Obtain the previous context
const context = this._contextStack.pop();
if (!context || context.type !== type)
return this._error(`Unexpected ${token.type}`, token);
Restore the quad of the previous context
this._subject = context.subject;
this._predicate = context.predicate;
this._object = context.object;
this._graph = context.graph;
Restore N3 context settings
if (this._n3Mode) {
this._inversePredicate = context.inverse;
this._prefixes._ = context.blankPrefix;
this._quantified = context.quantified;
}
}
_readInTopContext
reads a token when in the top context _readInTopContext(token) {
switch (token.type) {
If an EOF token arrives in the top context, signal that we’re done
case 'eof':
if (this._graph !== null)
return this._error('Unclosed graph', token);
delete this._prefixes._;
return this._callback(null, null, this._prefixes);
It could be a prefix declaration
case 'PREFIX':
this._sparqlStyle = true;
case '@prefix':
return this._readPrefix;
It could be a base declaration
case 'BASE':
this._sparqlStyle = true;
case '@base':
return this._readBaseIRI;
It could be a graph
case '{':
if (this._supportsNamedGraphs) {
this._graph = '';
this._subject = null;
return this._readSubject;
}
case 'GRAPH':
if (this._supportsNamedGraphs)
return this._readNamedGraphLabel;
Otherwise, the next token must be a subject
default:
return this._readSubject(token);
}
}
_readEntity
reads an IRI, prefixed name, blank node, or variable _readEntity(token, quantifier) {
let value;
switch (token.type) {
Read a relative or absolute IRI
case 'IRI':
case 'typeIRI':
const iri = this._resolveIRI(token.value);
if (iri === null)
return this._error('Invalid IRI', token);
value = this._namedNode(iri);
break;
Read a prefixed name
case 'type':
case 'prefixed':
const prefix = this._prefixes[token.prefix];
if (prefix === undefined)
return this._error(`Undefined prefix "${token.prefix}:"`, token);
value = this._namedNode(prefix + token.value);
break;
Read a blank node
case 'blank':
value = this._blankNode(this._prefixes[token.prefix] + token.value);
break;
Read a variable
case 'var':
value = this._variable(token.value.substr(1));
break;
Everything else is not an entity
default:
return this._error(`Expected entity but got ${token.type}`, token);
}
In N3 mode, replace the entity if it is quantified
if (!quantifier && this._n3Mode && (value.id in this._quantified))
value = this._quantified[value.id];
return value;
}
_readSubject
reads a quad’s subject _readSubject(token) {
this._predicate = null;
switch (token.type) {
case '[':
Start a new quad with a new blank node as subject
this._saveContext('blank', this._graph,
this._subject = this._blankNode(), null, null);
return this._readBlankNodeHead;
case '(':
Start a new list
this._saveContext('list', this._graph, this.RDF_NIL, null, null);
this._subject = null;
return this._readListItem;
case '{':
Start a new formula
if (!this._n3Mode)
return this._error('Unexpected graph', token);
this._saveContext('formula', this._graph,
this._graph = this._blankNode(), null, null);
return this._readSubject;
case '}':
No subject; the graph in which we are reading is closed instead
return this._readPunctuation(token);
case '@forSome':
if (!this._n3Mode)
return this._error('Unexpected "@forSome"', token);
this._subject = null;
this._predicate = this.N3_FORSOME;
this._quantifier = this._blankNode;
return this._readQuantifierList;
case '@forAll':
if (!this._n3Mode)
return this._error('Unexpected "@forAll"', token);
this._subject = null;
this._predicate = this.N3_FORALL;
this._quantifier = this._variable;
return this._readQuantifierList;
case 'literal':
if (!this._n3Mode)
return this._error('Unexpected literal', token);
if (token.prefix.length === 0) {
this._literalValue = token.value;
return this._completeSubjectLiteral;
}
else
this._subject = this._literal(token.value, this._namedNode(token.prefix));
break;
case '<<':
if (!this._supportsRDFStar)
return this._error('Unexpected RDF* syntax', token);
this._saveContext('<<', this._graph, null, null, null);
this._graph = null;
return this._readSubject;
default:
Read the subject entity
if ((this._subject = this._readEntity(token)) === undefined)
return;
In N3 mode, the subject might be a path
if (this._n3Mode)
return this._getPathReader(this._readPredicateOrNamedGraph);
}
The next token must be a predicate, or, if the subject was actually a graph IRI, a named graph
return this._readPredicateOrNamedGraph;
}
_readPredicate
reads a quad’s predicate _readPredicate(token) {
const type = token.type;
switch (type) {
case 'inverse':
this._inversePredicate = true;
case 'abbreviation':
this._predicate = this.ABBREVIATIONS[token.value];
break;
case '.':
case ']':
case '}':
Expected predicate didn’t come, must have been trailing semicolon
if (this._predicate === null)
return this._error(`Unexpected ${type}`, token);
this._subject = null;
return type === ']' ? this._readBlankNodeTail(token) : this._readPunctuation(token);
case ';':
Additional semicolons can be safely ignored
return this._predicate !== null ? this._readPredicate :
this._error('Expected predicate but got ;', token);
case '[':
if (this._n3Mode) {
Start a new quad with a new blank node as subject
this._saveContext('blank', this._graph, this._subject,
this._subject = this._blankNode(), null);
return this._readBlankNodeHead;
}
case 'blank':
if (!this._n3Mode)
return this._error('Disallowed blank node as predicate', token);
default:
if ((this._predicate = this._readEntity(token)) === undefined)
return;
}
The next token must be an object
return this._readObject;
}
_readObject
reads a quad’s object _readObject(token) {
switch (token.type) {
case 'literal':
Regular literal, can still get a datatype or language
if (token.prefix.length === 0) {
this._literalValue = token.value;
return this._readDataTypeOrLang;
}
Pre-datatyped string literal (prefix stores the datatype)
else
this._object = this._literal(token.value, this._namedNode(token.prefix));
break;
case '[':
Start a new quad with a new blank node as subject
this._saveContext('blank', this._graph, this._subject, this._predicate,
this._subject = this._blankNode());
return this._readBlankNodeHead;
case '(':
Start a new list
this._saveContext('list', this._graph, this._subject, this._predicate, this.RDF_NIL);
this._subject = null;
return this._readListItem;
case '{':
Start a new formula
if (!this._n3Mode)
return this._error('Unexpected graph', token);
this._saveContext('formula', this._graph, this._subject, this._predicate,
this._graph = this._blankNode());
return this._readSubject;
case '<<':
if (!this._supportsRDFStar)
return this._error('Unexpected RDF* syntax', token);
this._saveContext('<<', this._graph, this._subject, this._predicate, null);
this._graph = null;
return this._readSubject;
default:
Read the object entity
if ((this._object = this._readEntity(token)) === undefined)
return;
In N3 mode, the object might be a path
if (this._n3Mode)
return this._getPathReader(this._getContextEndReader());
}
return this._getContextEndReader();
}
_readPredicateOrNamedGraph
reads a quad’s predicate, or a named graph _readPredicateOrNamedGraph(token) {
return token.type === '{' ? this._readGraph(token) : this._readPredicate(token);
}
_readGraph
reads a graph _readGraph(token) {
if (token.type !== '{')
return this._error(`Expected graph but got ${token.type}`, token);
The “subject” we read is actually the GRAPH’s label
this._graph = this._subject, this._subject = null;
return this._readSubject;
}
_readBlankNodeHead
reads the head of a blank node _readBlankNodeHead(token) {
if (token.type === ']') {
this._subject = null;
return this._readBlankNodeTail(token);
}
else {
this._predicate = null;
return this._readPredicate(token);
}
}
_readBlankNodeTail
reads the end of a blank node _readBlankNodeTail(token) {
if (token.type !== ']')
return this._readBlankNodePunctuation(token);
Store blank node quad
if (this._subject !== null)
this._emit(this._subject, this._predicate, this._object, this._graph);
Restore the parent context containing this blank node
const empty = this._predicate === null;
this._restoreContext('blank', token);
If the blank node was the object, restore previous context and read punctuation
if (this._object !== null)
return this._getContextEndReader();
If the blank node was the predicate, continue reading the object
else if (this._predicate !== null)
return this._readObject;
If the blank node was the subject, continue reading the predicate
else
If the blank node was empty, it could be a named graph label
return empty ? this._readPredicateOrNamedGraph : this._readPredicateAfterBlank;
}
_readPredicateAfterBlank
reads a predicate after an anonymous blank node _readPredicateAfterBlank(token) {
switch (token.type) {
case '.':
case '}':
No predicate is coming if the triple is terminated here
this._subject = null;
return this._readPunctuation(token);
default:
return this._readPredicate(token);
}
}
_readListItem
reads items from a list _readListItem(token) {
let item = null, // The item of the list
list = null, // The list itself
next = this._readListItem; // The next function to execute
const previousList = this._subject, // The previous list that contains this list
stack = this._contextStack, // The stack of parent contexts
parent = stack[stack.length - 1]; // The parent containing the current list
switch (token.type) {
case '[':
Stack the current list quad and start a new quad with a blank node as subject
this._saveContext('blank', this._graph,
list = this._blankNode(), this.RDF_FIRST,
this._subject = item = this._blankNode());
next = this._readBlankNodeHead;
break;
case '(':
Stack the current list quad and start a new list
this._saveContext('list', this._graph,
list = this._blankNode(), this.RDF_FIRST, this.RDF_NIL);
this._subject = null;
break;
case ')':
Closing the list; restore the parent context
this._restoreContext('list', token);
If this list is contained within a parent list, return the membership quad here.
This will be <parent list element> rdf:first <this list>.
.
if (stack.length !== 0 && stack[stack.length - 1].type === 'list')
this._emit(this._subject, this._predicate, this._object, this._graph);
Was this list the parent’s subject?
if (this._predicate === null) {
The next token is the predicate
next = this._readPredicate;
No list tail if this was an empty list
if (this._subject === this.RDF_NIL)
return next;
}
The list was in the parent context’s object
else {
next = this._getContextEndReader();
No list tail if this was an empty list
if (this._object === this.RDF_NIL)
return next;
}
Close the list by making the head nil
list = this.RDF_NIL;
break;
case 'literal':
Regular literal, can still get a datatype or language
if (token.prefix.length === 0) {
this._literalValue = token.value;
next = this._readListItemDataTypeOrLang;
}
Pre-datatyped string literal (prefix stores the datatype)
else {
item = this._literal(token.value, this._namedNode(token.prefix));
next = this._getContextEndReader();
}
break;
case '{':
Start a new formula
if (!this._n3Mode)
return this._error('Unexpected graph', token);
this._saveContext('formula', this._graph, this._subject, this._predicate,
this._graph = this._blankNode());
return this._readSubject;
default:
if ((item = this._readEntity(token)) === undefined)
return;
}
Create a new blank node if no item head was assigned yet
if (list === null)
this._subject = list = this._blankNode();
Is this the first element of the list?
if (previousList === null) {
This list is either the subject or the object of its parent
if (parent.predicate === null)
parent.subject = list;
else
parent.object = list;
}
else {
Continue the previous list with the current list
this._emit(previousList, this.RDF_REST, list, this._graph);
}
If an item was read, add it to the list
if (item !== null) {
In N3 mode, the item might be a path
if (this._n3Mode && (token.type === 'IRI' || token.type === 'prefixed')) {
Create a new context to add the item’s path
this._saveContext('item', this._graph, list, this.RDF_FIRST, item);
this._subject = item, this._predicate = null;
_readPath will restore the context and output the item
return this._getPathReader(this._readListItem);
}
Output the item
this._emit(list, this.RDF_FIRST, item, this._graph);
}
return next;
}
_readDataTypeOrLang
reads an optional datatype or language _readDataTypeOrLang(token) {
return this._completeObjectLiteral(token, false);
}
_readListItemDataTypeOrLang
reads an optional datatype or language in a list _readListItemDataTypeOrLang(token) {
return this._completeObjectLiteral(token, true);
}
_completeLiteral
completes a literal with an optional datatype or language _completeLiteral(token) {
Create a simple string literal by default
let literal = this._literal(this._literalValue);
switch (token.type) {
Create a datatyped literal
case 'type':
case 'typeIRI':
const datatype = this._readEntity(token);
if (datatype === undefined) return; // No datatype means an error occurred
literal = this._literal(this._literalValue, datatype);
token = null;
break;
Create a language-tagged string
case 'langcode':
literal = this._literal(this._literalValue, token.value);
token = null;
break;
}
return { token, literal };
}
Completes a literal in subject position
_completeSubjectLiteral(token) {
this._subject = this._completeLiteral(token).literal;
return this._readPredicateOrNamedGraph;
}
Completes a literal in object position
_completeObjectLiteral(token, listItem) {
const completed = this._completeLiteral(token);
if (!completed)
return;
this._object = completed.literal;
If this literal was part of a list, write the item (we could also check the context stack, but passing in a flag is faster)
if (listItem)
this._emit(this._subject, this.RDF_FIRST, this._object, this._graph);
If the token was consumed, continue with the rest of the input
if (completed.token === null)
return this._getContextEndReader();
Otherwise, consume the token now
else {
this._readCallback = this._getContextEndReader();
return this._readCallback(completed.token);
}
}
_readFormulaTail
reads the end of a formula _readFormulaTail(token) {
if (token.type !== '}')
return this._readPunctuation(token);
Store the last quad of the formula
if (this._subject !== null)
this._emit(this._subject, this._predicate, this._object, this._graph);
Restore the parent context containing this formula
this._restoreContext('formula', token);
If the formula was the subject, continue reading the predicate. If the formula was the object, read punctuation.
return this._object === null ? this._readPredicate : this._getContextEndReader();
}
_readPunctuation
reads punctuation between quads or quad parts _readPunctuation(token) {
let next, graph = this._graph;
const subject = this._subject, inversePredicate = this._inversePredicate;
switch (token.type) {
A closing brace ends a graph
case '}':
if (this._graph === null)
return this._error('Unexpected graph closing', token);
if (this._n3Mode)
return this._readFormulaTail(token);
this._graph = null;
A dot just ends the statement, without sharing anything with the next
case '.':
this._subject = null;
next = this._contextStack.length ? this._readSubject : this._readInTopContext;
if (inversePredicate) this._inversePredicate = false;
break;
Semicolon means the subject is shared; predicate and object are different
case ';':
next = this._readPredicate;
break;
Comma means both the subject and predicate are shared; the object is different
case ',':
next = this._readObject;
break;
{| means that the current triple is annotated with predicate-object pairs.
case '{|':
if (!this._supportsRDFStar)
return this._error('Unexpected RDF* syntax', token);
Continue using the last triple as quoted triple subject for the predicate-object pairs.
const predicate = this._predicate, object = this._object;
this._subject = this._quad(subject, predicate, object, this.DEFAULTGRAPH);
next = this._readPredicate;
break;
|} means that the current quoted triple in annotation syntax is finalized.
case '|}':
if (this._subject.termType !== 'Quad')
return this._error('Unexpected asserted triple closing', token);
this._subject = null;
next = this._readPunctuation;
break;
default:
An entity means this is a quad (only allowed if not already inside a graph)
if (this._supportsQuads && this._graph === null && (graph = this._readEntity(token)) !== undefined) {
next = this._readQuadPunctuation;
break;
}
return this._error(`Expected punctuation to follow "${this._object.id}"`, token);
}
A quad has been completed now, so return it
if (subject !== null) {
const predicate = this._predicate, object = this._object;
if (!inversePredicate)
this._emit(subject, predicate, object, graph);
else
this._emit(object, predicate, subject, graph);
}
return next;
}
_readBlankNodePunctuation
reads punctuation in a blank node _readBlankNodePunctuation(token) {
let next;
switch (token.type) {
Semicolon means the subject is shared; predicate and object are different
case ';':
next = this._readPredicate;
break;
Comma means both the subject and predicate are shared; the object is different
case ',':
next = this._readObject;
break;
default:
return this._error(`Expected punctuation to follow "${this._object.id}"`, token);
}
A quad has been completed now, so return it
this._emit(this._subject, this._predicate, this._object, this._graph);
return next;
}
_readQuadPunctuation
reads punctuation after a quad _readQuadPunctuation(token) {
if (token.type !== '.')
return this._error('Expected dot to follow quad', token);
return this._readInTopContext;
}
_readPrefix
reads the prefix of a prefix declaration _readPrefix(token) {
if (token.type !== 'prefix')
return this._error('Expected prefix to follow @prefix', token);
this._prefix = token.value;
return this._readPrefixIRI;
}
_readPrefixIRI
reads the IRI of a prefix declaration _readPrefixIRI(token) {
if (token.type !== 'IRI')
return this._error(`Expected IRI to follow prefix "${this._prefix}:"`, token);
const prefixNode = this._readEntity(token);
this._prefixes[this._prefix] = prefixNode.value;
this._prefixCallback(this._prefix, prefixNode);
return this._readDeclarationPunctuation;
}
_readBaseIRI
reads the IRI of a base declaration _readBaseIRI(token) {
const iri = token.type === 'IRI' && this._resolveIRI(token.value);
if (!iri)
return this._error('Expected valid IRI to follow base declaration', token);
this._setBase(iri);
return this._readDeclarationPunctuation;
}
_readNamedGraphLabel
reads the label of a named graph _readNamedGraphLabel(token) {
switch (token.type) {
case 'IRI':
case 'blank':
case 'prefixed':
return this._readSubject(token), this._readGraph;
case '[':
return this._readNamedGraphBlankLabel;
default:
return this._error('Invalid graph label', token);
}
}
_readNamedGraphLabel
reads a blank node label of a named graph _readNamedGraphBlankLabel(token) {
if (token.type !== ']')
return this._error('Invalid graph label', token);
this._subject = this._blankNode();
return this._readGraph;
}
_readDeclarationPunctuation
reads the punctuation of a declaration _readDeclarationPunctuation(token) {
SPARQL-style declarations don’t have punctuation
if (this._sparqlStyle) {
this._sparqlStyle = false;
return this._readInTopContext(token);
}
if (token.type !== '.')
return this._error('Expected declaration to end with a dot', token);
return this._readInTopContext;
}
Reads a list of quantified symbols from a @forSome or @forAll statement
_readQuantifierList(token) {
let entity;
switch (token.type) {
case 'IRI':
case 'prefixed':
if ((entity = this._readEntity(token, true)) !== undefined)
break;
default:
return this._error(`Unexpected ${token.type}`, token);
}
Without explicit quantifiers, map entities to a quantified entity
if (!this._explicitQuantifiers)
this._quantified[entity.id] = this._quantifier(this._blankNode().value);
With explicit quantifiers, output the reified quantifier
else {
If this is the first item, start a new quantifier list
if (this._subject === null)
this._emit(this._graph || this.DEFAULTGRAPH, this._predicate,
this._subject = this._blankNode(), this.QUANTIFIERS_GRAPH);
Otherwise, continue the previous list
else
this._emit(this._subject, this.RDF_REST,
this._subject = this._blankNode(), this.QUANTIFIERS_GRAPH);
Output the list item
this._emit(this._subject, this.RDF_FIRST, entity, this.QUANTIFIERS_GRAPH);
}
return this._readQuantifierPunctuation;
}
Reads punctuation from a @forSome or @forAll statement
_readQuantifierPunctuation(token) {
Read more quantifiers
if (token.type === ',')
return this._readQuantifierList;
End of the quantifier list
else {
With explicit quantifiers, close the quantifier list
if (this._explicitQuantifiers) {
this._emit(this._subject, this.RDF_REST, this.RDF_NIL, this.QUANTIFIERS_GRAPH);
this._subject = null;
}
Read a dot
this._readCallback = this._getContextEndReader();
return this._readCallback(token);
}
}
_getPathReader
reads a potential path and then resumes with the given function _getPathReader(afterPath) {
this._afterPath = afterPath;
return this._readPath;
}
_readPath
reads a potential path _readPath(token) {
switch (token.type) {
Forward path
case '!': return this._readForwardPath;
Backward path
case '^': return this._readBackwardPath;
Not a path; resume reading where we left off
default:
const stack = this._contextStack, parent = stack.length && stack[stack.length - 1];
If we were reading a list item, we still need to output it
if (parent && parent.type === 'item') {
The list item is the remaining subejct after reading the path
const item = this._subject;
Switch back to the context of the list
this._restoreContext('item', token);
Output the list item
this._emit(this._subject, this.RDF_FIRST, item, this._graph);
}
return this._afterPath(token);
}
}
_readForwardPath
reads a ‘!’ path _readForwardPath(token) {
let subject, predicate;
const object = this._blankNode();
The next token is the predicate
if ((predicate = this._readEntity(token)) === undefined)
return;
If we were reading a subject, replace the subject by the path’s object
if (this._predicate === null)
subject = this._subject, this._subject = object;
If we were reading an object, replace the subject by the path’s object
else
subject = this._object, this._object = object;
Emit the path’s current quad and read its next section
this._emit(subject, predicate, object, this._graph);
return this._readPath;
}
_readBackwardPath
reads a ‘^’ path _readBackwardPath(token) {
const subject = this._blankNode();
let predicate, object;
The next token is the predicate
if ((predicate = this._readEntity(token)) === undefined)
return;
If we were reading a subject, replace the subject by the path’s subject
if (this._predicate === null)
object = this._subject, this._subject = subject;
If we were reading an object, replace the subject by the path’s subject
else
object = this._object, this._object = subject;
Emit the path’s current quad and read its next section
this._emit(subject, predicate, object, this._graph);
return this._readPath;
}
_readRDFStarTailOrGraph
reads the graph of a nested RDF* quad or the end of a nested RDF* triple _readRDFStarTailOrGraph(token) {
if (token.type !== '>>') {
An entity means this is a quad (only allowed if not already inside a graph)
if (this._supportsQuads && this._graph === null && (this._graph = this._readEntity(token)) !== undefined)
return this._readRDFStarTail;
return this._error(`Expected >> to follow "${this._object.id}"`, token);
}
return this._readRDFStarTail(token);
}
_readRDFStarTail
reads the end of a nested RDF* triple _readRDFStarTail(token) {
if (token.type !== '>>')
return this._error(`Expected >> but got ${token.type}`, token);
Read the quad and restore the previous context
const quad = this._quad(this._subject, this._predicate, this._object,
this._graph || this.DEFAULTGRAPH);
this._restoreContext('<<', token);
If the triple was the subject, continue by reading the predicate.
if (this._subject === null) {
this._subject = quad;
return this._readPredicate;
}
If the triple was the object, read context end.
else {
this._object = quad;
return this._getContextEndReader();
}
}
_getContextEndReader
gets the next reader function at the end of a context _getContextEndReader() {
const contextStack = this._contextStack;
if (!contextStack.length)
return this._readPunctuation;
switch (contextStack[contextStack.length - 1].type) {
case 'blank':
return this._readBlankNodeTail;
case 'list':
return this._readListItem;
case 'formula':
return this._readFormulaTail;
case '<<':
return this._readRDFStarTailOrGraph;
}
}
_emit
sends a quad through the callback _emit(subject, predicate, object, graph) {
this._callback(null, this._quad(subject, predicate, object, graph || this.DEFAULTGRAPH));
}
_error
emits an error message through the callback _error(message, token) {
const err = new Error(`${message} on line ${token.line}.`);
err.context = {
token: token,
line: token.line,
previousToken: this._lexer.previousToken,
};
this._callback(err);
this._callback = noop;
}
_resolveIRI
resolves an IRI against the base path _resolveIRI(iri) {
return /^[a-z][a-z0-9+.-]*:/i.test(iri) ? iri : this._resolveRelativeIRI(iri);
}
_resolveRelativeIRI
resolves an IRI against the base path,assuming that a base path has been set and that the IRI is indeed relative
_resolveRelativeIRI(iri) {
An empty relative IRI indicates the base IRI
if (!iri.length)
return this._base;
Decide resolving strategy based in the first character
switch (iri[0]) {
Resolve relative fragment IRIs against the base IRI
case '#': return this._base + iri;
Resolve relative query string IRIs by replacing the query string
case '?': return this._base.replace(/(?:\?.*)?$/, iri);
Resolve root-relative IRIs at the root of the base IRI
case '/':
Resolve scheme-relative IRIs to the scheme
return (iri[1] === '/' ? this._baseScheme : this._baseRoot) + this._removeDotSegments(iri);
Resolve all other IRIs at the base IRI’s path
default:
Relative IRIs cannot contain a colon in the first path segment
return (/^[^/:]*:/.test(iri)) ? null : this._removeDotSegments(this._basePath + iri);
}
}
_removeDotSegments
resolves ‘./‘ and ‘../‘ path segments in an IRI as per RFC3986 _removeDotSegments(iri) {
Don’t modify the IRI if it does not contain any dot segments
if (!/(^|\/)\.\.?($|[/#?])/.test(iri))
return iri;
Start with an imaginary slash before the IRI in order to resolve trailing ‘./‘ and ‘../‘
const length = iri.length;
let result = '', i = -1, pathStart = -1, segmentStart = 0, next = '/';
while (i < length) {
switch (next) {
The path starts with the first slash after the authority
case ':':
if (pathStart < 0) {
Skip two slashes before the authority
if (iri[++i] === '/' && iri[++i] === '/')
Skip to slash after the authority
while ((pathStart = i + 1) < length && iri[pathStart] !== '/')
i = pathStart;
}
break;
Don’t modify a query string or fragment
case '?':
case '#':
i = length;
break;
Handle ‘/.’ or ‘/..’ path segments
case '/':
if (iri[i + 1] === '.') {
next = iri[++i + 1];
switch (next) {
Remove a ‘/.’ segment
case '/':
result += iri.substring(segmentStart, i - 1);
segmentStart = i + 1;
break;
Remove a trailing ‘/.’ segment
case undefined:
case '?':
case '#':
return result + iri.substring(segmentStart, i) + iri.substr(i + 1);
Remove a ‘/..’ segment
case '.':
next = iri[++i + 1];
if (next === undefined || next === '/' || next === '?' || next === '#') {
result += iri.substring(segmentStart, i - 2);
Try to remove the parent path from result
if ((segmentStart = result.lastIndexOf('/')) >= pathStart)
result = result.substr(0, segmentStart);
Remove a trailing ‘/..’ segment
if (next !== '/')
return `${result}/${iri.substr(i + 1)}`;
segmentStart = i + 1;
}
}
}
}
next = iri[++i];
}
return result + iri.substring(segmentStart);
}
parse
parses the N3 input and emits each parsed quad through the callback parse(input, quadCallback, prefixCallback) {
The read callback is the next function to be executed when a token arrives. We start reading in the top context.
this._readCallback = this._readInTopContext;
this._sparqlStyle = false;
this._prefixes = Object.create(null);
this._prefixes._ = this._blankNodePrefix ? this._blankNodePrefix.substr(2)
: `b${blankNodePrefix++}_`;
this._prefixCallback = prefixCallback || noop;
this._inversePredicate = false;
this._quantified = Object.create(null);
Parse synchronously if no quad callback is given
if (!quadCallback) {
const quads = [];
let error;
this._callback = (e, t) => { e ? (error = e) : t && quads.push(t); };
this._lexer.tokenize(input).every(token => {
return this._readCallback = this._readCallback(token);
});
if (error) throw error;
return quads;
}
Parse asynchronously otherwise, executing the read callback when a token arrives
this._callback = quadCallback;
this._lexer.tokenize(input, (error, token) => {
if (error !== null)
this._callback(error), this._callback = noop;
else if (this._readCallback)
this._readCallback = this._readCallback(token);
});
}
}
The empty function
function noop() {}
Initializes the parser with the given data factory
function initDataFactory(parser, factory) {
Set factory methods
const namedNode = factory.namedNode;
parser._namedNode = namedNode;
parser._blankNode = factory.blankNode;
parser._literal = factory.literal;
parser._variable = factory.variable;
parser._quad = factory.quad;
parser.DEFAULTGRAPH = factory.defaultGraph();
Set common named nodes
parser.RDF_FIRST = namedNode(namespaces.rdf.first);
parser.RDF_REST = namedNode(namespaces.rdf.rest);
parser.RDF_NIL = namedNode(namespaces.rdf.nil);
parser.N3_FORALL = namedNode(namespaces.r.forAll);
parser.N3_FORSOME = namedNode(namespaces.r.forSome);
parser.ABBREVIATIONS = {
'a': namedNode(namespaces.rdf.type),
'=': namedNode(namespaces.owl.sameAs),
'>': namedNode(namespaces.log.implies),
};
parser.QUANTIFIERS_GRAPH = namedNode('urn:n3:quantifiers');
}
initDataFactory(N3Parser.prototype, N3DataFactory);