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

  • §

    N3Writer writes N3 documents.

    import namespaces from './IRIs';
    import { default as N3DataFactory, Term } from './N3DataFactory';
    import { isDefaultGraph } from './N3Util';
    
    const DEFAULTGRAPH = N3DataFactory.defaultGraph();
    
    const { rdf, xsd } = namespaces;
  • §

    Characters in literals that require escaping

    const escape    = /["\\\t\n\r\b\f\u0000-\u0019\ud800-\udbff]/,
        escapeAll = /["\\\t\n\r\b\f\u0000-\u0019]|[\ud800-\udbff][\udc00-\udfff]/g,
        escapedCharacters = {
          '\\': '\\\\', '"': '\\"', '\t': '\\t',
          '\n': '\\n', '\r': '\\r', '\b': '\\b', '\f': '\\f',
        };
  • §

    Placeholder class to represent already pretty-printed terms

    class SerializedTerm extends Term {
  • §

    Pretty-printed nodes are not equal to any other node (e.g., [] does not equal [])

      equals(other) {
        return other === this;
      }
    }
  • §

    Constructor

    export default class N3Writer {
      constructor(outputStream, options) {
  • §

    _prefixRegex matches a prefixed name or IRI that begins with one of the added prefixes

        this._prefixRegex = /$0^/;
  • §

    Shift arguments if the first argument is not a stream

        if (outputStream && typeof outputStream.write !== 'function')
          options = outputStream, outputStream = null;
        options = options || {};
        this._lists = options.lists;
  • §

    If no output stream given, send the output as string through the end callback

        if (!outputStream) {
          let output = '';
          this._outputStream = {
            write(chunk, encoding, done) { output += chunk; done && done(); },
            end: done => { done && done(null, output); },
          };
          this._endStream = true;
        }
        else {
          this._outputStream = outputStream;
          this._endStream = options.end === undefined ? true : !!options.end;
        }
  • §

    Initialize writer, depending on the format

        this._subject = null;
        if (!(/triple|quad/i).test(options.format)) {
          this._lineMode = false;
          this._graph = DEFAULTGRAPH;
          this._prefixIRIs = Object.create(null);
          options.prefixes && this.addPrefixes(options.prefixes);
          if (options.baseIRI) {
            this._baseMatcher = new RegExp(`^${escapeRegex(options.baseIRI)
                }${options.baseIRI.endsWith('/') ? '' : '[#?]'}`);
            this._baseLength = options.baseIRI.length;
          }
        }
        else {
          this._lineMode = true;
          this._writeQuad = this._writeQuadLine;
        }
      }
  • §

    Private methods

  • §

    Whether the current graph is the default graph

      get _inDefaultGraph() {
        return DEFAULTGRAPH.equals(this._graph);
      }
  • §

    _write writes the argument to the output stream

      _write(string, callback) {
        this._outputStream.write(string, 'utf8', callback);
      }
  • §

    _writeQuad writes the quad to the output stream

      _writeQuad(subject, predicate, object, graph, done) {
        try {
  • §

    Write the graph’s label if it has changed

          if (!graph.equals(this._graph)) {
  • §

    Close the previous graph and start the new one

            this._write((this._subject === null ? '' : (this._inDefaultGraph ? '.\n' : '\n}\n')) +
                        (DEFAULTGRAPH.equals(graph) ? '' : `${this._encodeIriOrBlank(graph)} {\n`));
            this._graph = graph;
            this._subject = null;
          }
  • §

    Don’t repeat the subject if it’s the same

          if (subject.equals(this._subject)) {
  • §

    Don’t repeat the predicate if it’s the same

            if (predicate.equals(this._predicate))
              this._write(`, ${this._encodeObject(object)}`, done);
  • §

    Same subject, different predicate

            else
              this._write(`;\n    ${
                          this._encodePredicate(this._predicate = predicate)} ${
                          this._encodeObject(object)}`, done);
          }
  • §

    Different subject; write the whole quad

          else
            this._write(`${(this._subject === null ? '' : '.\n') +
                        this._encodeSubject(this._subject = subject)} ${
                        this._encodePredicate(this._predicate = predicate)} ${
                        this._encodeObject(object)}`, done);
        }
        catch (error) { done && done(error); }
      }
  • §

    _writeQuadLine writes the quad to the output stream as a single line

      _writeQuadLine(subject, predicate, object, graph, done) {
  • §

    Write the quad without prefixes

        delete this._prefixMatch;
        this._write(this.quadToString(subject, predicate, object, graph), done);
      }
  • §

    quadToString serializes a quad as a string

      quadToString(subject, predicate, object, graph) {
        return  `${this._encodeSubject(subject)} ${
                this._encodeIriOrBlank(predicate)} ${
                this._encodeObject(object)
                }${graph && graph.value ? ` ${this._encodeIriOrBlank(graph)} .\n` : ' .\n'}`;
      }
  • §

    quadsToString serializes an array of quads as a string

      quadsToString(quads) {
        return quads.map(t => {
          return this.quadToString(t.subject, t.predicate, t.object, t.graph);
        }).join('');
      }
  • §

    _encodeSubject represents a subject

      _encodeSubject(entity) {
        return entity.termType === 'Quad' ?
          this._encodeQuad(entity) : this._encodeIriOrBlank(entity);
      }
  • §

    _encodeIriOrBlank represents an IRI or blank node

      _encodeIriOrBlank(entity) {
  • §

    A blank node or list is represented as-is

        if (entity.termType !== 'NamedNode') {
  • §

    If it is a list head, pretty-print it

          if (this._lists && (entity.value in this._lists))
            entity = this.list(this._lists[entity.value]);
          return 'id' in entity ? entity.id : `_:${entity.value}`;
        }
        let iri = entity.value;
  • §

    Use relative IRIs if requested and possible

        if (this._baseMatcher && this._baseMatcher.test(iri))
          iri = iri.substr(this._baseLength);
  • §

    Escape special characters

        if (escape.test(iri))
          iri = iri.replace(escapeAll, characterReplacer);
  • §

    Try to represent the IRI as prefixed name

        const prefixMatch = this._prefixRegex.exec(iri);
        return !prefixMatch ? `<${iri}>` :
               (!prefixMatch[1] ? iri : this._prefixIRIs[prefixMatch[1]] + prefixMatch[2]);
      }
  • §

    _encodeLiteral represents a literal

      _encodeLiteral(literal) {
  • §

    Escape special characters

        let value = literal.value;
        if (escape.test(value))
          value = value.replace(escapeAll, characterReplacer);
  • §

    Write a language-tagged literal

        if (literal.language)
          return `"${value}"@${literal.language}`;
  • §

    Write dedicated literals per data type

        if (this._lineMode) {
  • §

    Only abbreviate strings in N-Triples or N-Quads

          if (literal.datatype.value === xsd.string)
            return `"${value}"`;
        }
        else {
  • §

    Use common datatype abbreviations in Turtle or TriG

          switch (literal.datatype.value) {
          case xsd.string:
            return `"${value}"`;
          case xsd.boolean:
            if (value === 'true' || value === 'false')
              return value;
            break;
          case xsd.integer:
            if (/^[+-]?\d+$/.test(value))
              return value;
            break;
          case xsd.decimal:
            if (/^[+-]?\d*\.\d+$/.test(value))
              return value;
            break;
          case xsd.double:
            if (/^[+-]?(?:\d+\.\d*|\.?\d+)[eE][+-]?\d+$/.test(value))
              return value;
            break;
          }
        }
  • §

    Write a regular datatyped literal

        return `"${value}"^^${this._encodeIriOrBlank(literal.datatype)}`;
      }
  • §

    _encodePredicate represents a predicate

      _encodePredicate(predicate) {
        return predicate.value === rdf.type ? 'a' : this._encodeIriOrBlank(predicate);
      }
  • §

    _encodeObject represents an object

      _encodeObject(object) {
        switch (object.termType) {
        case 'Quad':
          return this._encodeQuad(object);
        case 'Literal':
          return this._encodeLiteral(object);
        default:
          return this._encodeIriOrBlank(object);
        }
      }
  • §

    _encodeQuad encodes an RDF* quad

      _encodeQuad({ subject, predicate, object, graph }) {
        return `<<${
          this._encodeSubject(subject)} ${
          this._encodePredicate(predicate)} ${
          this._encodeObject(object)}${
          isDefaultGraph(graph) ? '' : ` ${this._encodeIriOrBlank(graph)}`}>>`;
      }
  • §

    _blockedWrite replaces _write after the writer has been closed

      _blockedWrite() {
        throw new Error('Cannot write because the writer has been closed.');
      }
  • §

    addQuad adds the quad to the output stream

      addQuad(subject, predicate, object, graph, done) {
  • §

    The quad was given as an object, so shift parameters

        if (object === undefined)
          this._writeQuad(subject.subject, subject.predicate, subject.object, subject.graph, predicate);
  • §

    The optional graph parameter was not provided

        else if (typeof graph === 'function')
          this._writeQuad(subject, predicate, object, DEFAULTGRAPH, graph);
  • §

    The graph parameter was provided

        else
          this._writeQuad(subject, predicate, object, graph || DEFAULTGRAPH, done);
      }
  • §

    addQuads adds the quads to the output stream

      addQuads(quads) {
        for (let i = 0; i < quads.length; i++)
          this.addQuad(quads[i]);
      }
  • §

    addPrefix adds the prefix to the output stream

      addPrefix(prefix, iri, done) {
        const prefixes = {};
        prefixes[prefix] = iri;
        this.addPrefixes(prefixes, done);
      }
  • §

    addPrefixes adds the prefixes to the output stream

      addPrefixes(prefixes, done) {
  • §

    Ignore prefixes if not supported by the serialization

        if (!this._prefixIRIs)
          return done && done();
  • §

    Write all new prefixes

        let hasPrefixes = false;
        for (let prefix in prefixes) {
          let iri = prefixes[prefix];
          if (typeof iri !== 'string')
            iri = iri.value;
          hasPrefixes = true;
  • §

    Finish a possible pending quad

          if (this._subject !== null) {
            this._write(this._inDefaultGraph ? '.\n' : '\n}\n');
            this._subject = null, this._graph = '';
          }
  • §

    Store and write the prefix

          this._prefixIRIs[iri] = (prefix += ':');
          this._write(`@prefix ${prefix} <${iri}>.\n`);
        }
  • §

    Recreate the prefix matcher

        if (hasPrefixes) {
          let IRIlist = '', prefixList = '';
          for (const prefixIRI in this._prefixIRIs) {
            IRIlist += IRIlist ? `|${prefixIRI}` : prefixIRI;
            prefixList += (prefixList ? '|' : '') + this._prefixIRIs[prefixIRI];
          }
          IRIlist = escapeRegex(IRIlist, /[\]\/\(\)\*\+\?\.\\\$]/g, '\\$&');
          this._prefixRegex = new RegExp(`^(?:${prefixList})[^\/]*$|` +
                                         `^(${IRIlist})([_a-zA-Z][\\-_a-zA-Z0-9]*)$`);
        }
  • §

    End a prefix block with a newline

        this._write(hasPrefixes ? '\n' : '', done);
      }
  • §

    blank creates a blank node with the given content

      blank(predicate, object) {
        let children = predicate, child, length;
  • §

    Empty blank node

        if (predicate === undefined)
          children = [];
  • §

    Blank node passed as blank(Term(“predicate”), Term(“object”))

        else if (predicate.termType)
          children = [{ predicate: predicate, object: object }];
  • §

    Blank node passed as blank({ predicate: predicate, object: object })

        else if (!('length' in predicate))
          children = [predicate];
    
        switch (length = children.length) {
  • §

    Generate an empty blank node

        case 0:
          return new SerializedTerm('[]');
  • §

    Generate a non-nested one-triple blank node

        case 1:
          child = children[0];
          if (!(child.object instanceof SerializedTerm))
            return new SerializedTerm(`[ ${this._encodePredicate(child.predicate)} ${
                                      this._encodeObject(child.object)} ]`);
  • §

    Generate a multi-triple or nested blank node

        default:
          let contents = '[';
  • §

    Write all triples in order

          for (let i = 0; i < length; i++) {
            child = children[i];
  • §

    Write only the object is the predicate is the same as the previous

            if (child.predicate.equals(predicate))
              contents += `, ${this._encodeObject(child.object)}`;
  • §

    Otherwise, write the predicate and the object

            else {
              contents += `${(i ? ';\n  ' : '\n  ') +
                          this._encodePredicate(child.predicate)} ${
                          this._encodeObject(child.object)}`;
              predicate = child.predicate;
            }
          }
          return new SerializedTerm(`${contents}\n]`);
        }
      }
  • §

    list creates a list node with the given content

      list(elements) {
        const length = elements && elements.length || 0, contents = new Array(length);
        for (let i = 0; i < length; i++)
          contents[i] = this._encodeObject(elements[i]);
        return new SerializedTerm(`(${contents.join(' ')})`);
      }
  • §

    end signals the end of the output stream

      end(done) {
  • §

    Finish a possible pending quad

        if (this._subject !== null) {
          this._write(this._inDefaultGraph ? '.\n' : '\n}\n');
          this._subject = null;
        }
  • §

    Disallow further writing

        this._write = this._blockedWrite;
  • §

    Try to end the underlying stream, ensuring done is called exactly one time

        let singleDone = done && ((error, result) => { singleDone = null, done(error, result); });
        if (this._endStream) {
          try { return this._outputStream.end(singleDone); }
          catch (error) { /* error closing stream */ }
        }
        singleDone && singleDone();
      }
    }
  • §

    Replaces a character by its escaped version

    function characterReplacer(character) {
  • §

    Replace a single character by its escaped version

      let result = escapedCharacters[character];
      if (result === undefined) {
  • §

    Replace a single character with its 4-bit unicode escape sequence

        if (character.length === 1) {
          result = character.charCodeAt(0).toString(16);
          result = '\\u0000'.substr(0, 6 - result.length) + result;
        }
  • §

    Replace a surrogate pair with its 8-bit unicode escape sequence

        else {
          result = ((character.charCodeAt(0) - 0xD800) * 0x400 +
                     character.charCodeAt(1) + 0x2400).toString(16);
          result = '\\U00000000'.substr(0, 10 - result.length) + result;
        }
      }
      return result;
    }
    
    function escapeRegex(regex) {
      return regex.replace(/[\]\/\(\)\*\+\?\.\\\$]/g, '\\$&');
    }