// **N3Parser** parses N3 documents. import N3Lexer from './N3Lexer'; import N3DataFactory from './N3DataFactory'; import namespaces from './IRIs'; let blankNodePrefix = 0; // ## Constructor 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; } // ## Static class methods // ### `_resetBlankNodePrefix` restarts blank node prefix identification static _resetBlankNodePrefix() { blankNodePrefix = 0; } // ## Private methods // ### `_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 context // when 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` restores the parent context // when leaving a scope (list, blank node, formula) _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 ` rdf:first .`. 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); } // ## Public methods // ### `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);