Хорошо, я решил это сам, поэтому я поставлю свое решение здесь. Я решил поработать с DOM, а не с html как строкой, и тогда я смогу взять innerHTML в качестве последнего шага. Код немного громоздкий, но идея такова:
Пройдите по дереву DOM элемента, сохранив данные для каждого узла в массив (то есть, линейный, а не дерево). Для узлов элементов сохраните в массиве как "startelem", так и "endelem", эквивалентные начальным и конечным тегам. Также обратите внимание на вычисляемое свойство каждого элемента «display» (например, inline, block и т. Д.) И поместите его в оба элемента массива. (Для всех узлов я также сохраняю глубину в дереве, но не похоже, что мне нужно это использовать).
Для текстовых узлов обратите внимание на то, является ли это обычным текстовым узлом, всеми пробелами или пустой строкой.
Пройдите по массиву, и для текстовых узлов «пробел» посмотрите на предыдущий и следующий элемент в массиве. Если любой из них отображен: встроенный, оставьте узел как один пробел. Если нет, измените текстовый узел на пустую строку.
После этого выполнение innerHTML над элементом не будет иметь лишних пробелов, и, насколько я могу судить, внешний вид элемента в браузере останется неизменным.
Вот код:
var stripUnneededTextNodes= function (elem) {
var array = [];
addNodeAndChildrenToArray(elem, 1, array);
for (var i=1; i<array.length-1; i++) {
if (array[i].type == "whitespace") {
if (array[i-1].display == "inline" && array[i+1].display == "inline") {
array[i].node.nodeValue = ' ';
}
else {
array[i].node.nodeValue = '';
array[i].killed = true;
}
delete array[i].node;
}
else if (array[i].type == "text") {
var val = array[i].node.nodeValue;
if (val.charAt(0) == ' ' && array[i-1].display != "inline") {
array[i].node.nodeValue = val = val.substring(1);
}
if (val.charAt(val.length-1) == ' ' && array[i+1].display != "inline") {
array[i].node.nodeValue = val.substring(0, val.length-1);
}
delete array[i].node;
}
}
};
var addNodeAndChildrenToArray = function (node, depth, array) {
switch (node.nodeType) {
case 1: { // ELEMENT_NODE
var display = document.defaultView.getComputedStyle (node, null).display;
array.push ({type: "startelem", tag: node.tagName, display: display, depth: depth});
if (node.childNodes && node.childNodes.length != 0) {
for (var i=0; i<node.childNodes.length; i++)
addNodeAndChildrenToArray(node.childNodes.item(i), depth+1, array);
}
array.push ({type: "endelem", tag: node.tagName, display: display, depth: depth});
}
break;
case 3: { //TEXT_NODE
var newVal = node.nodeValue.replace(/\s+/g, ' ');
node.nodeValue = newVal;
if (newVal == ' ')
array.push ({type: "whitespace", node: node, depth: depth});
else if (newVal == '')
array.push ({type: "emptytext", depth: depth});
else
array.push ({type: "text", node: node, display: "inline", depth: depth});
}
break;
}
};