|
|
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(){ //if(this._tagStack.pop().name !== name) this._handleCallback(Error("Tagname didn't match!"));
var elem = this._tagStack.pop();
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){ var properties = { type: name === "script" ? ElementType.Script : name === "style" ? ElementType.Style : ElementType.Tag, name: name, attribs: attribs, children: [] };
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;
|