• Jump To … +
    IRIs.js N3DataFactory.js N3Lexer.js N3Parser.js N3Store.js N3StreamParser.js N3StreamWriter.js N3Util.js N3Writer.js index.js
  • N3Parser.js

  • §

    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 <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);
      }
  • §

    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);