/** * @file domhandler * @author https://github.com/fb55/domhandler * @modify tanghao03 hiby * @date 2018/5/17 */ var ElementType = require('domelementtype'); var re_whitespace = /\s+/g; var NodePrototype = require('./lib/node'); var ElementPrototype = require('./lib/element'); function DomHandler(callback, options, elementCB) { if (typeof callback === 'object') { elementCB = options; options = callback; callback = null; } else if (typeof options === 'function') { elementCB = options; options = defaultOpts; } this._callback = callback; this._options = options || defaultOpts; this._elementCB = elementCB; this.dom = []; this._done = false; this._tagStack = []; this._parser = this._parser || null; } //default options var defaultOpts = { normalizeWhitespace: false, //Replace all whitespace with single spaces withStartIndices: false, //Add startIndex properties to nodes withEndIndices: false, //Add endIndex properties to nodes }; DomHandler.prototype.onparserinit = function (parser) { this._parser = parser; }; //Resets the handler back to starting state DomHandler.prototype.onreset = function () { DomHandler.call(this, this._callback, this._options, this._elementCB); }; //Signals the handler that parsing is done DomHandler.prototype.onend = function () { if (this._done) return; this._done = true; this._parser = null; this._handleCallback(null); }; DomHandler.prototype._handleCallback = DomHandler.prototype.onerror = function (error) { if (typeof this._callback === 'function') { this._callback(error, this.dom); } else { if (error) throw error; } }; DomHandler.prototype.onclosetag = function (name, isSelfClose) { //if(this._tagStack.pop().name !== name) this._handleCallback(Error('Tagname didn't match!')); var elem = this._tagStack.pop(); elem.selfclose = !!isSelfClose; if (this._options.withEndIndices && elem) { elem.endIndex = this._parser.endIndex; } if (this._elementCB) this._elementCB(elem); }; DomHandler.prototype._createDomElement = function (properties) { if (!this._options.withDomLvl1) return properties; var element; if (properties.type === 'tag') { element = Object.create(ElementPrototype); } else { element = Object.create(NodePrototype); } for (var key in properties) { if (properties.hasOwnProperty(key)) { element[key] = properties[key]; } } return element; }; DomHandler.prototype._addDomElement = function (element) { var parent = this._tagStack[this._tagStack.length - 1]; var siblings = parent ? parent.children : this.dom; var previousSibling = siblings[siblings.length - 1]; // element.next = null; if (this._options.withStartIndices) { element.startIndex = this._parser.startIndex; } if (this._options.withEndIndices) { element.endIndex = this._parser.endIndex; } // if(previousSibling){ // element.prev = previousSibling; // previousSibling.next = element; // } else { // element.prev = null; // } siblings.push(element); // element.parent = parent || null; }; DomHandler.prototype.onopentag = function (name, attribs, singleQuoteAttribs) { var properties = { type: name === 'script' ? ElementType.Script : name === 'style' ? ElementType.Style : ElementType.Tag, name: name, attribs: attribs, children: [] }; if (singleQuoteAttribs) { properties.singleQuoteAttribs = singleQuoteAttribs; } var element = this._createDomElement(properties); this._addDomElement(element); this._tagStack.push(element); }; DomHandler.prototype.ontext = function (data) { //the ignoreWhitespace is officially dropped, but for now, //it's an alias for normalizeWhitespace var normalize = this._options.normalizeWhitespace || this._options.ignoreWhitespace; var lastTag; if (!this._tagStack.length && this.dom.length && (lastTag = this.dom[this.dom.length - 1]).type === ElementType.Text) { if (normalize) { lastTag.data = (lastTag.data + data).replace(re_whitespace, ' '); } else { lastTag.data += data; } } else { if ( this._tagStack.length && (lastTag = this._tagStack[this._tagStack.length - 1]) && (lastTag = lastTag.children[lastTag.children.length - 1]) && lastTag.type === ElementType.Text ) { if (normalize) { lastTag.data = (lastTag.data + data).replace(re_whitespace, ' '); } else { lastTag.data += data; } } else { if (normalize) { data = data.replace(re_whitespace, ' '); } var element = this._createDomElement({ data: data, type: ElementType.Text }); this._addDomElement(element); } } }; DomHandler.prototype.oncomment = function (data) { var lastTag = this._tagStack[this._tagStack.length - 1]; if (lastTag && lastTag.type === ElementType.Comment) { lastTag.data += data; return; } var properties = { data: data, type: ElementType.Comment }; var element = this._createDomElement(properties); this._addDomElement(element); this._tagStack.push(element); }; DomHandler.prototype.oncdatastart = function () { var properties = { children: [{ data: '', type: ElementType.Text }], type: ElementType.CDATA }; var element = this._createDomElement(properties); this._addDomElement(element); this._tagStack.push(element); }; DomHandler.prototype.oncommentend = DomHandler.prototype.oncdataend = function () { this._tagStack.pop(); }; DomHandler.prototype.onprocessinginstruction = function (name, data) { var element = this._createDomElement({ name: name, data: data, type: ElementType.Directive }); this._addDomElement(element); }; module.exports = DomHandler;