text: var ElementType = require("domelementtype"),
      DomUtils = module.exports;
  
  var isTag = DomUtils.isTag = ElementType.isTag;
  
  function getChildren(elem){
  	return elem.children;
  }
  function getParent(elem){
  	return elem.parent;
  }
  function getSiblings(elem){
  	var parent = getParent(elem);
  	return parent ? getChildren(parent) : [elem];
  }
  function getAttributeValue(elem, name){
  	return elem.attribs && elem.attribs[name];
  }
  function hasAttrib(elem, name){
  	return name in elem.attribs;
  }
  function getName(elem){
  	return elem.name;
  }
  
  DomUtils.getChildren = getChildren;
  DomUtils.getParent = getParent;
  DomUtils.getAttributeValue = getAttributeValue;
  DomUtils.hasAttrib = hasAttrib;
  DomUtils.getName = getName;
  DomUtils.getSiblings = getSiblings;
  
  function find(test, arr, recurse, limit){
  	var result = [], childs;
  
  	for(var i = 0, j = arr.length; i < j; i++){
  		if(test(arr[i])){
  			result.push(arr[i]);
  			if(--limit <= 0) break;
  		}
  
  		childs = getChildren(arr[i]);
  		if(recurse && childs && childs.length > 0){
  			childs = find(test, childs, recurse, limit);
  			result = result.concat(childs);
  			limit -= childs.length;
  			if(limit <= 0) break;
  		}
  	}
  
  	return result;
  }
  
  function findOneChild(test, arr){
  	for(var i = 0, l = arr.length; i < l; i++){
  		if(test(arr[i])) return arr[i];
  	}
  
  	return null;
  }
  
  function findOne(test, arr){
  	var elem = null;
  
  	for(var i = 0, l = arr.length; i < l && !elem; i++){
  		if(test(arr[i])){
  			elem = arr[i];
  		} else if(arr[i].children && arr[i].children.length > 0){
  			elem = findOne(test, arr[i].children);
  		}
  	}
  
  	return elem;
  }
  
  DomUtils.findOne = findOne;
  
  function findAll(test, elems){
  	var result = [];
  	for(var i = 0, j = elems.length; i < j; i++){
  		if(!isTag(elems[i])) continue;
  		if(test(elems[i])) result.push(elems[i]);
  
  		var childs = getChildren(elems[i]);
  		if(childs.length) result = result.concat(findAll(test, childs));
  	}
  	return result;
  }
  
  DomUtils.findAll = findAll;
  
  function filter(test, element, recurse, limit){
  	if(!Array.isArray(element)) element = [element];
  
  	if(typeof limit !== "number" || !isFinite(limit)){
  		if(recurse === false){
  			return element.filter(test);
  		} else {
  			return findAll(test, element);
  		}
  	} else if(limit === 1){
  		if(recurse === false){
  			element = findOneChild(test, element);
  		} else {
  			element = findOne(test, element);
  		}
  		return element ? [element] : [];
  	} else {
  		return find(test, element, recurse !== false, limit);
  	}
  }
  
  DomUtils.filter = filter;
  
  DomUtils.testElement = function(options, element){
  	for(var key in options){
  		if(!options.hasOwnProperty(key));
  		else if(key === "tag_name"){
  			if(!isTag(element) || !options.tag_name(element.name)){
  				return false;
  			}
  		} else if(key === "tag_type"){
  			if(!options.tag_type(element.type)) return false;
  		} else if(key === "tag_contains"){
  			if(isTag(element) || !options.tag_contains(element.data)){
  				return false;
  			}
  		} else if(!element.attribs || !options[key](element.attribs[key])){
  			return false;
  		}
  	}
  	return true;
  };
  
  var Checks = {
  	tag_name: function(name){
  		if(typeof name === "function"){ 
  			return function(elem){ return isTag(elem) && name(elem.name); };
  		} else if(name === "*"){
  			return isTag;
  		} else {
  			return function(elem){ return isTag(elem) && elem.name === name; };
  		}
  	},
  	tag_type: function(type){
  		if(typeof type === "function"){
  			return function(elem){ return type(elem.type); };
  		} else {
  			return function(elem){ return elem.type === type; };
  		}
  	},
  	tag_contains: function(data){
  		if(typeof type === "function"){
  			return function(elem){ return !isTag(elem) && data(elem.data); };
  		} else {
  			return function(elem){ return !isTag(elem) && elem.data === data; };
  		}
  	}
  };
  
  function getAttribCheck(attrib, value){
  	if(typeof value === "function"){
  		return function(elem){ return elem.attribs && value(elem.attribs[attrib]); };
  	} else {
  		return function(elem){ return elem.attribs && elem.attribs[attrib] === value; };
  	}
  }
  
  DomUtils.getElements = function(options, element, recurse, limit){
  	var funcs = [];
  	for(var key in options){
  		if(options.hasOwnProperty(key)){
  			if(key in Checks) funcs.push(Checks[key](options[key]));
  			else funcs.push(getAttribCheck(key, options[key]));
  		}
  	}
  
  	if(funcs.length === 0) return [];
  	if(funcs.length === 1) return filter(funcs[0], element, recurse, limit);
  	return filter(
  		function(elem){
  			return funcs.some(function(func){ return func(elem); });
  		},
  		element, recurse, limit
  	);
  };
  
  DomUtils.getElementById = function(id, element, recurse){
  	if(!Array.isArray(element)) element = [element];
  	return findOne(getAttribCheck("id", id), element, recurse !== false);
  };
  
  DomUtils.getElementsByTagName = function(name, element, recurse, limit){
  	return filter(Checks.tag_name(name), element, recurse, limit);
  };
  
  DomUtils.getElementsByTagType = function(type, element, recurse, limit){
  	return filter(Checks.tag_type(type), element, recurse, limit);
  };
  
  DomUtils.removeElement = function(elem){
  	if(elem.prev) elem.prev.next = elem.next;
  	if(elem.next) elem.next.prev = elem.prev;
  
  	if(elem.parent){
  		var childs = elem.parent.children;
  		childs.splice(childs.lastIndexOf(elem), 1);
  	}
  };
  
  DomUtils.replaceElement = function(elem, replacement){
  	if(elem.prev){
  		elem.prev.next = replacement;
  		replacement.prev = elem.prev;
  	}
  	if(elem.next){
  		elem.next.prev = replacement;
  		replacement.next = elem.next;
  	}
  	if(elem.parent){
  		var childs = elem.parent.children;
  		childs.splice(childs.lastIndexOf(elem), 1, replacement);
  		replacement.parent = elem.parent;
  	}
  };
  
  DomUtils.getInnerHTML = function(elem){
  	if(!elem.children) return "";
  
  	var childs = elem.children,
  		childNum = childs.length,
  		ret = "";
  
  	for(var i = 0; i < childNum; i++){
  		ret += DomUtils.getOuterHTML(childs[i]);
  	}
  
  	return ret;
  };
  
  //boolean attributes without a value (taken from MatthewMueller/cheerio)
  var booleanAttribs = {
  	__proto__: null,
  	async: true,
  	autofocus: true,
  	autoplay: true,
  	checked: true,
  	controls: true,
  	defer: true,
  	disabled: true,
  	hidden: true,
  	loop: true,
  	multiple: true,
  	open: true,
  	readonly: true,
  	required: true,
  	scoped: true,
  	selected: true,
  	"/": true //TODO when is this required?
  };
  
  var emptyTags = {
  	__proto__: null,
  	area: true,
  	base: true,
  	basefont: true,
  	br: true,
  	col: true,
  	frame: true,
  	hr: true,
  	img: true,
  	input: true,
  	isindex: true,
  	link: true,
  	meta: true,
  	param: true,
  	embed: true
  };
  
  DomUtils.getOuterHTML = function(elem){
  	var type = elem.type;
  
  	if(type === ElementType.Text) return elem.data;
  	if(type === ElementType.Comment) return "<!--" + elem.data + "-->";
  	if(type === ElementType.Directive) return "<" + elem.data + ">";
  	if(type === ElementType.CDATA) return "<!CDATA " + DomUtils.getInnerHTML(elem) + "]]>";
  
  	var ret = "<" + elem.name;
  	if("attribs" in elem){
  		for(var attr in elem.attribs){
  			if(elem.attribs.hasOwnProperty(attr)){
  				ret += " " + attr;
  				var value = elem.attribs[attr];
  				if(!value){
  					if( !(attr in booleanAttribs) ){
  						ret += '=""';
  					}
  				} else {
  					ret += '="' + value + '"';
  				}
  			}
  		}
  	}
  
  	if (elem.name in emptyTags && elem.children.length === 0) {
  		return ret + " />";
  	} else {
  		return ret + ">" + DomUtils.getInnerHTML(elem) + "</" + elem.name + ">";
  	}
  };
  
  DomUtils.getText = function getText(elem){
  	if(Array.isArray(elem)) return elem.map(getText).join("");
  	if(isTag(elem) || elem.type === ElementType.CDATA) return getText(elem.children);
  	if(elem.type === ElementType.Text) return elem.data;
  	return "";
  };
  