'use strict'; var voidElements = require('void-elements'); Object.keys(voidElements).forEach(function (name) { voidElements[name.toUpperCase()] = 1; }); var blockElements = {}; require('block-elements').forEach(function (name) { blockElements[name.toUpperCase()] = 1; }); /** * isBlockElem(node) determines if the given node is a block element. * * @param {Node} node * @return {Boolean} */ function isBlockElem(node) { return !!(node && blockElements[node.nodeName]); } /** * isVoid(node) determines if the given node is a void element. * * @param {Node} node * @return {Boolean} */ function isVoid(node) { return !!(node && voidElements[node.nodeName]); } /** * whitespace(elem [, isBlock]) removes extraneous whitespace from an * the given element. The function isBlock may optionally be passed in * to determine whether or not an element is a block element; if none * is provided, defaults to using the list of block elements provided * by the `block-elements` module. * * @param {Node} elem * @param {Function} blockTest */ function collapseWhitespace(elem, isBlock) { if (!elem.firstChild || elem.nodeName === 'PRE') return; if (typeof isBlock !== 'function') { isBlock = isBlockElem; } var prevText = null; var prevVoid = false; var prev = null; var node = next(prev, elem); while (node !== elem) { if (node.nodeType === 3) { // Node.TEXT_NODE var text = node.data.replace(/[ \r\n\t]+/g, ' '); if ((!prevText || / $/.test(prevText.data)) && !prevVoid && text[0] === ' ') { text = text.substr(1); } // `text` might be empty at this point. if (!text) { node = remove(node); continue; } node.data = text; prevText = node; } else if (node.nodeType === 1) { // Node.ELEMENT_NODE if (isBlock(node) || node.nodeName === 'BR') { if (prevText) { prevText.data = prevText.data.replace(/ $/, ''); } prevText = null; prevVoid = false; } else if (isVoid(node)) { // Avoid trimming space around non-block, non-BR void elements. prevText = null; prevVoid = true; } } else { node = remove(node); continue; } var nextNode = next(prev, node); prev = node; node = nextNode; } if (prevText) { prevText.data = prevText.data.replace(/ $/, ''); if (!prevText.data) { remove(prevText); } } } /** * remove(node) removes the given node from the DOM and returns the * next node in the sequence. * * @param {Node} node * @return {Node} node */ function remove(node) { var next = node.nextSibling || node.parentNode; node.parentNode.removeChild(node); return next; } /** * next(prev, current) returns the next node in the sequence, given the * current and previous nodes. * * @param {Node} prev * @param {Node} current * @return {Node} */ function next(prev, current) { if (prev && prev.parentNode === current || current.nodeName === 'PRE') { return current.nextSibling || current.parentNode; } return current.firstChild || current.nextSibling || current.parentNode; } module.exports = collapseWhitespace;