137 lines
3.1 KiB
JavaScript
137 lines
3.1 KiB
JavaScript
'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;
|