var AST = require('src/ast')
, CharBuf = require('src/char-buf')
;
/**
* Dom handler for htmlparser2
* @class DomHandler
* @param {Stream} stream - The parser stream
*/
var DomHandler = module.exports = function DomHandler(stream)
{
if(!(this instanceof DomHandler))
return new DomHandler(stream);
/** @member {MCNode[]} DomHandler#ast */
this.ast = [];
/** @member {Stream} DomHandler#_stream */
this._stream = stream;
/** @member {Boolean} DomHandler#_done */
this._done = false;
/** @member {MCTag[]} DomHandler#_tagStack */
this._tagStack = [];
/** @member {HtmlParser} DomHandler#_parser */
this._parser = this._parser || null;
}
/**
* Dom handler initializer
* @function DomHandler#onparserinit
* @param {htmlparser2.Parser} parser - The current instance of the htmlparser
*/
DomHandler.prototype.onparserinit = function (parser) {
this._parser = parser;
};
/**
* Resets the Dom handler to it's initial state
* @function DomHandler#onreset
*/
DomHandler.prototype.onreset = function () {
DomHandler.call(this, this._stream);
};
/**
* Html parser is complete
* @function DomHandler#onend
*/
DomHandler.prototype.onend = function () {
if(this._done) return;
this._done = true;
this._parser = null;
this._stream.push(this.ast);
};
/**
* Throws an error
* @function DomHandler#onerror
* @param {Error} err - The error
*/
DomHandler.prototype.onerror = function (err) {
throw err;
};
/**
* Handles finding a close tag
* @function DomHandler#onclosetag
*/
DomHandler.prototype.onclosetag = function () {
this._tagStack.pop();
};
/**
* Adds a node to the AST
* @function DomHandler#_addNode
* @param {Node} node - The node to add
*/
DomHandler.prototype._addNode = function (node) {
var parent = this._tagStack[this._tagStack.length - 1]
, siblings = parent ? parent.children : this.ast
;
siblings.push(node);
};
/**
* Handles finding an open tag
* @function DomHandler#onopenttag
* @param {string} name - The tag name
* @param {Object.<string, string>} attribs - An object of attributes
*/
DomHandler.prototype.onopentag = function (name, attribs)
{
var TagType = /^mc:/.test(name) ? AST.TagDirective : AST.Tag
, mcAttribs = []
;
for( attrName in attribs )
{
var AttrType = /^mc:/.test(attrName) ? AST.AttribDirective : AST.Attrib
, attrib = new AttrType({
name: attrName.replace(/^mc:/, ''),
data: AttrType == AST.AttribDirective || TagType == AST.TagDirective ? attribs[attrName] : DomHandler.parseText(attribs[attrName])
})
;
mcAttribs.push(attrib);
};
var tag = new TagType({
name: name.replace(/^mc:/, ''),
attribs: mcAttribs
});
this._addNode(tag);
this._tagStack.push(tag);
}
DomHandler.prototype.ontext = function (data) {
if( /\S/.test(data) )
{
this._addNode(new AST.Text({
data: DomHandler.parseText(data)
}));
}
};
/**
* Parses a string of text into an array of strings and MCChunks
* @function DomHandle.parseText
* @param {string} str - The text to parse
* @return {Interpolator} The parsed interpolator
*/
DomHandler.parseText = function (str)
{
var charBuf = new CharBuf(str)
, isInMC = false
, chunkCur = ''
, chunks = []
;
while(charBuf.consume())
{
if(!isInMC)
{
if( charBuf.charCur == '{' && charBuf.charNxt[0] == '{' )
{
charBuf.consume();
isInMC = true;
if(chunkCur.length) chunks.push( AST.InterpolatorString({data:chunkCur}) );
chunkCur = '';
continue;
}
else
{
chunkCur += charBuf.charCur;
}
}
else
{
if( charBuf.charCur == '}' && charBuf.charNxt[0] == '}' )
{
charBuf.consume();
isInMC = false;
chunks.push( AST.InterpolatorExpression({data:chunkCur}) );
chunkCur = '';
}
else
{
chunkCur += charBuf.charCur;
}
}
}
if( chunkCur.length ) chunks.push( AST.InterpolatorString({data:chunkCur}) );
return new AST.Interpolator({data: chunks});
}