Source: dom-handler.js

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});
}