Newer
Older
libxmljs-extra / src / Document.js
/* eslint-disable no-dupe-class-members */

const libxmljs = require('libxmljs');
const utils = require('./utils');

class Document {
  static TYPE_XML = 0;

  static TYPE_HTML = 1;

  static TYPE_HTML_FRAGMENT = 2;

  _original = null;

  _hasContent = false;

  namespace = null;

  constructor(source = null, type = Document.TYPE_XML, options = null) {
    if (source !== null) {
      switch (type) {
        case Document.TYPE_XML:
          this._original = libxmljs.parseXml(source, options);
          break;
        case Document.TYPE_HTML:
          this._original = libxmljs.parseHtml(source, options);
          break;
        case Document.TYPE_HTML_FRAGMENT:
          this._original = libxmljs.parseHtmlFragment(source, options);
          break;
        default:
          break;
      }

      this._hasContent = true;
    } else {
      this._original = new libxmljs.Document();
    }
  }

  get errors() {
    if (!this._original) return [];

    return this._original.errors;
  }

  get validationErrors() {
    if (!this._original) return undefined;

    return this._original.validationErrors;
  }

  child(idx) {
    this.assertNoContentError();

    if (!idx) idx = 0;

    return this._original.child(idx);
  }

  childNodes() {
    this.assertNoContentError();

    return this._original.childNodes();
  }

  count(xpath) {
    if (typeof xpath !== 'string') throw new Error('xpath must be a string.');

    return this.get(`count(${xpath})`);
  }

  encoding() {
    return this._original.encoding();
  }

  encoding(enc) {
    return this._original.encoding(enc);
  }

  find(xpath) {
    this.assertNoContentError();
    if (typeof xpath !== 'string') throw new Error('xpath must be a string.');

    if (!this.namespace) return this._original.find(xpath);

    const fullXPath = utils.addNamespacePrefixInPath(this.namespace.alias, xpath);

    return this._original.find(fullXPath, { [this.namespace.alias]: this.namespace.url });
  }

  fromHtml(html, options = {}) {
    if (typeof html !== 'string') throw new Error('html must be a string.');
    if (typeof options !== 'object') throw new Error('options must be an object.');

    this._original = libxmljs.Document.fromHtml(html, options);
    this._hasContent = true;

    return this;
  }

  fromHtmlFragment(htmlFragment, options = {}) {
    if (typeof htmlFragment !== 'string') throw new Error('htmlFragment must be a string.');
    if (typeof options !== 'object') throw new Error('options must be an object.');

    this._original = libxmljs.Document.fromHtmlFragment(htmlFragment, options);
    this._hasContent = true;

    return this;
  }

  fromXml(xml, options = {}) {
    if (typeof xml !== 'string') throw new Error('htmlFragment must be a string.');
    if (typeof options !== 'object') throw new Error('options must be an object.');

    this._original = libxmljs.Document.fromXml(xml, options);
    this._hasContent = true;

    return this;
  }

  get(xpath) {
    const found = this.find(xpath);

    if (found.length === undefined) return found; // found is not an array so we just return it.
    if (found.length === 0) return null; // found is an empty array so we return null.

    return found[0]; // found is a non-empty array so we return the first element.
  }

  getDtd() {
    return this._original.getDtd();
  }

  namespaces() {
    this.assertNoContentError();

    return this._original.namespaces();
  }

  node(name, content) {
    if (!this._hasContent) this._hasContent = true;

    return this._original.node(name, content);
  }

  rngValidate(rng) {
    return this._original.rngValidate(rng);
  }

  root() {
    this.assertNoContentError();

    return this._original.root();
  }

  root(node) {
    this.assertNoContentError();

    return this._original.root(node);
  }

  setDtd(name, ext, sys) {
    return this._original.setDtd(name, ext, sys);
  }

  setNamespace(alias, url) {
    if (typeof alias !== 'string') throw new Error('alias must be a string.');
    if (typeof url !== 'string') throw new Error('url must be a string.');
    if (!utils.isValidURL(url)) throw new Error('url is not a valid URL.');

    this.namespace = {
      alias,
      url,
    };
  }

  toString(formatting = true) {
    return this._original.toString(formatting);
  }

  type() {
    return this._original.type();
  }

  validate(xsdDoc) {
    this.assertNoContentError();
    if (!xsdDoc) throw new Error('No XSD document to validate against.');

    return this._original.validate(xsdDoc);
  }

  version() {
    return this._original.version();
  }

  assertNoContentError() {
    if (!this._hasContent) {
      const err = new Error('No parsed content found.');
      err.name = 'NoContentError';
      throw err;
    }
  }
}

module.exports = Document;