好的,我自己解决了,所以我会把我的解决方案放在这里。我决定使用 DOM 而不是 html 作为字符串,然后我可以获取 innerHTML 作为最后一步。代码略显笨重,但思路是:
遍历元素的 DOM 树,将每个节点的数据保存到一个数组中(即线性,而不是树)。对于元素节点,在数组中同时存储一个“startelem”和“endelem”,相当于开始标签和结束标签。还要记下每个元素的计算“显示”属性(例如内联、块等),并将其放入数组中的两个项目中。 (对于所有节点,我还将深度存储到树中,但似乎不需要使用它)。
对于文本节点,请注意它是常规文本节点、全空格还是空字符串。
遍历数组,对于“空白”文本节点,查看数组中的上一项和下一项。如果其中任何一个是 display:inline,则将节点保留为单个空格。如果不是,请将文本节点更改为空字符串。
在此之后,对元素执行 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;
}
};