174 lines
4.2 KiB
JavaScript
174 lines
4.2 KiB
JavaScript
|
var ElementType = require("domelementtype");
|
||
|
|
||
|
var re_whitespace = /\s+/g;
|
||
|
|
||
|
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 = [];
|
||
|
}
|
||
|
|
||
|
//default options
|
||
|
var defaultOpts = {
|
||
|
normalizeWhitespace: false //Replace all whitespace with single spaces
|
||
|
};
|
||
|
|
||
|
//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._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){
|
||
|
//if(this._tagStack.pop().name !== name) this._handleCallback(Error("Tagname didn't match!"));
|
||
|
var elem = this._tagStack.pop();
|
||
|
if(this._elementCB) this._elementCB(elem);
|
||
|
};
|
||
|
|
||
|
DomHandler.prototype._addDomElement = function(element){
|
||
|
var lastTag = this._tagStack[this._tagStack.length - 1];
|
||
|
|
||
|
if(lastTag){
|
||
|
lastTag.children.push(element);
|
||
|
} else { //There aren't parent elements
|
||
|
this.dom.push(element);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
DomHandler.prototype.onopentag = function(name, attribs){
|
||
|
var lastTag = this._tagStack[this._tagStack.length - 1];
|
||
|
|
||
|
var element = {
|
||
|
type: name === "script" ? ElementType.Script : name === "style" ? ElementType.Style : ElementType.Tag,
|
||
|
name: name,
|
||
|
attribs: attribs,
|
||
|
children: [],
|
||
|
prev: null,
|
||
|
next: null,
|
||
|
parent: lastTag || null
|
||
|
};
|
||
|
|
||
|
if(lastTag){
|
||
|
var idx = lastTag.children.length;
|
||
|
while(idx > 0){
|
||
|
if(ElementType.isTag(lastTag.children[--idx])){
|
||
|
element.prev = lastTag.children[idx];
|
||
|
lastTag.children[idx].next = element;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
lastTag.children.push(element);
|
||
|
} else {
|
||
|
this.dom.push(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, " ");
|
||
|
}
|
||
|
|
||
|
this._addDomElement({
|
||
|
data: data,
|
||
|
type: ElementType.Text
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
DomHandler.prototype.oncomment = function(data){
|
||
|
var lastTag = this._tagStack[this._tagStack.length - 1];
|
||
|
|
||
|
if(lastTag && lastTag.type === ElementType.Comment){
|
||
|
lastTag.data += data;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var element = {
|
||
|
data: data,
|
||
|
type: ElementType.Comment
|
||
|
};
|
||
|
|
||
|
this._addDomElement(element);
|
||
|
this._tagStack.push(element);
|
||
|
};
|
||
|
|
||
|
DomHandler.prototype.oncdatastart = function(){
|
||
|
var element = {
|
||
|
children: [{
|
||
|
data: "",
|
||
|
type: ElementType.Text
|
||
|
}],
|
||
|
type: ElementType.CDATA
|
||
|
};
|
||
|
|
||
|
this._addDomElement(element);
|
||
|
this._tagStack.push(element);
|
||
|
};
|
||
|
|
||
|
DomHandler.prototype.oncommentend = DomHandler.prototype.oncdataend = function(){
|
||
|
this._tagStack.pop();
|
||
|
};
|
||
|
|
||
|
DomHandler.prototype.onprocessinginstruction = function(name, data){
|
||
|
this._addDomElement({
|
||
|
name: name,
|
||
|
data: data,
|
||
|
type: ElementType.Directive
|
||
|
});
|
||
|
};
|
||
|
|
||
|
module.exports = DomHandler;
|