diff --git a/index.js b/index.js index 5f18c2e..c70c4e3 100644 --- a/index.js +++ b/index.js @@ -1,9 +1,29 @@ -const find = require('./src/find.js'); -const count = require('./src/count.js'); -const setNamespace = require('./src/namespace.js'); +const libxmljs = require('libxmljs'); +const Document = require('./src/Document'); module.exports = { - find, - count, - setNamespace, + Document, + + /** + * Straight mapping from libxmljs exports + */ + + // constants + libxml_version: libxmljs.libxml_version, + libxml_parser_version: libxmljs.libxml_parser_version, + libxml_debug_enabled: libxmljs.libxml_debug_enabled, + + // lib exports + Comment: libxmljs.Comment, + Element: libxmljs.Element, + ProcessingInstruction: libxmljs.ProcessingInstruction, + Text: libxmljs.Text, + + // sax parser + SaxParser: libxmljs.SaxParser, + SaxPushParser: libxmljs.SaxPushParser, + + // others + memoryUsage: libxmljs.memoryUsage, + nodeCount: libxmljs.nodeCount, }; diff --git a/src/Document.js b/src/Document.js new file mode 100644 index 0000000..05cf466 --- /dev/null +++ b/src/Document.js @@ -0,0 +1,192 @@ +/* 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; + } + } 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(); + } + + 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 (!options) 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 (!options) 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 (!options) 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 === 0) return null; + + return found[0]; + } + + 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); + } + + 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;