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 hasOwnProperty.call(elem.attribs, name); } 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(test(elems[i])) result.push(elems[i]); var childs = getChildren(elems[i]); if(childs && 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 ""; if(type === ElementType.Directive) return "<" + elem.data + ">"; if(type === ElementType.CDATA) return ""; 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) + ""; } }; 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 ""; };