var ent = require('ent');
var htmlparser = require('htmlparser2');
var CSSselect = require('css-select');
var domutils = require('domutils');

module.exports = hyperfast;
function hyperfast (html, params) {
  // wrap elements so that there's always a root element
  // available, e.g. node.parent is never null
  var wrapped = htmlparser.parseDOM('<wrap>' + html + '</wrap>');
  var elems = wrapped[0].children;

  Object.keys(params).forEach(function (key) {
    var val = params[key];
    if (!val) return;
    if (typeof val === 'string') val = { _html: val };
    else if (Buffer.isBuffer(val)) val = { _html: val.toString('utf8') };
    else if (typeof val === 'function') val = { _iterator: val };
    else if (typeof val !== 'object') val = { _html: String(val) };

    if (Buffer.isBuffer(val._text)) val._text = val._text.toString('utf8');
    if (key === ':first') {
      each(elems[0], val);
    } else if (/:first$/.test(key)) {
      var k = key.replace(/:first$/, '');
      each(CSSselect.selectOne(CSSselect.compile(k), elems), val);
    } else {
      CSSselect(CSSselect.compile(key), elems).forEach(function (elem) {
        each(elem, val);
      });
    }
  });

  var body = elems.map(function (elm) {
    return domutils.getOuterHTML(elm);
  }).join('');

  return {
    outerHTML: body,
    innerHTML: body
  };

  function each (elem, val) {
    if (Array.isArray(val)) {
      htmlparser.parseDOM(val.map(function (x) {
        return hyperfast(domutils.getOuterHTML(elem), x).outerHTML;
      }).join('')).reverse().forEach(function (child) {
        domutils.append(elem, child);
      });
      domutils.removeElement(elem);
    } else {
      Object.keys(val).forEach(function (k) {
        if (
          k === '_text' ||
          k === '_html' ||
          k === '_append' ||
          k === '_appendText' ||
          k === '_appendHtml' ||
          k === '_prepend' ||
          k === '_prependText' ||
          k === '_prependHtml' ||
          k === '_iterator') return;
        if (val[k] === undefined) {
          delete elem.attribs[k];
        } else {
          if (val[k] && (val[k].append !== undefined || val[k].prepend !== undefined)) {
            if (val[k].prepend) {
              elem.attribs[k] = val[k].prepend + (elem.attribs[k] || '');
            }

            if (val[k].append) {
              elem.attribs[k] = (elem.attribs[k] || '') + val[k].append;
            }
          } else {
            elem.attribs[k] = val[k];
          }
        }
      });
      var children;
      if (val._text) {
        while (elem.children && elem.children.length > 0) {
          domutils.removeElement(elem.children[0]);
        }
        domutils.appendChild(elem, {
          data: ent.encode(val._text),
          type: 'text'
        });
      } else if (val._html) {
        while (elem.children && elem.children.length > 0) {
          domutils.removeElement(elem.children[0]);
        }
        children = htmlparser.parseDOM(val._html);
        children.forEach(function (child) {
          domutils.appendChild(elem, child);
        });
      } else if (val._appendHtml) {
        children = htmlparser.parseDOM(val._appendHtml);
        children.forEach(function (child) {
          domutils.appendChild(elem, child);
        });
      } else if (val._appendText || val._append) {
        children = htmlparser.parseDOM(ent.encode(val._appendText || val._append));
        children.forEach(function (child) {
          domutils.appendChild(elem, child);
        });
      } else if (val._prependHtml) {
        children = htmlparser.parseDOM(val._prependHtml);
        children.reverse().forEach(function (child) {
          if (!elem.children || elem.children.length === 0) {
            domutils.appendChild(elem, child);
          } else {
            domutils.prepend(elem.children[0], child);
          }
        });
      } else if (val._prependText || val._prepend) {
        children = htmlparser.parseDOM(ent.encode(val._prependText || val._prepend));
        children.reverse().forEach(function (child) {
          if (!elem.children || elem.children.length === 0) {
            domutils.appendChild(elem, child);
          } else {
            domutils.prepend(elem.children[0], child);
          }
        });
      } else if (val._iterator) {
        var newHtml = val._iterator(elem.children.map(function (child) {
          return domutils.getOuterHTML(child);
        }).join(''));
        var newChildren = htmlparser.parseDOM(newHtml);
        while (elem.children && elem.children.length > 0) {
          domutils.removeElement(elem.children[0]);
        }
        newChildren.forEach(function (child) {
          domutils.appendChild(elem, child);
        });
      }
    }
  }
}

@@ -168,32 +168,60 @@
 st;%0A
-function hyperfast (html
+module.exports.onDom = onDom;%0A%0Afunction onDom (elems
 , pa
@@ -344,17 +344,56 @@
 r wrappe
-d
+r;%0A  if (!elems%5B0%5D.parent) %7B%0A    wrapper
  = htmlp
@@ -418,65 +418,111 @@
 rap%3E
-' + html + '
 %3C/wrap
-%3E
 ')
+%5B0%5D
 ;%0A  
-var elems = wrapped%5B0%5D.children;
+  elems.forEach(function (elem) %7B%0A      domutils.appendChild(wrapper, elem);%0A    %7D);%0A  %7D
 %0A%0A  
@@ -1297,28 +1297,67 @@
 %0A%0A  
-var body = elems.map
+// unwrap dom, if needed%0A  if (wrapper) %7B%0A    elems.forEach
 (fun
@@ -1365,16 +1365,17 @@
 tion (el
+e
 m) %7B%0A   
@@ -1379,114 +1379,57 @@
     
-return domutils.getOuterHTML(elm);%0A  %7D).join('');%0A%0A  return %7B%0A    outerHTML: body,%0A    innerHTML: body%0A  %7D
+  elem.parent = null;%0A    %7D);%0A  %7D%0A%0A  return elems
 ;%0A%0A 
@@ -4852,8 +4852,260 @@
 %7D%0A  %7D%0A%7D%0A
+%0Afunction hyperfast (html, params) %7B%0A  var dom = htmlparser.parseDOM(html);%0A%0A  onDom(dom, params);%0A%0A  var body = dom.map(function (elm) %7B%0A    return domutils.getOuterHTML(elm);%0A  %7D).join('');%0A%0A  return %7B%0A    outerHTML: body,%0A    innerHTML: body%0A  %7D;%0A%7D%0A

Allow to run on a dom-element (from domutils.parseDOM)