如何在保留HTML的同时截断元素的文本内容?

How can I truncate the text contents of an Element while preserving HTML?

本文关键字:元素 文本 保留 HTML      更新时间:2023-09-26

我意识到这里有几个类似的问题,但没有一个答案能解决我的问题。

我需要能够将元素的innerHTML截断为给定的字符长度,并考虑到任何内部HTML元素的文本内容,并保留所有HTML标签。

我已经找到了几个很好的解决这部分问题的答案,以及几个插件,它们都能做到这一点。

然而,在所有情况下,解决方案将直接截断任何内部元素的中间,然后关闭标签。

在我的例子中,我需要所有内部标签的内容保持完整,基本上允许任何"将被"截断的内部标签超过给定的字符限制。

任何帮助都将是非常感激的。

编辑:

例如:

This is an example <a href="link">of a link</a> inside another element

包含空格的长度为51个字符。如果我想将其截断为23个字符,我们必须缩短</a>标记内的文本。这正是大多数解决方案所做的。

这将得到以下内容:

This is an example <a href="link">of a</a>

然而,对于我的用例,我需要保持任何剩余的可见标记完全完整,并且不以任何方式截断。

所以对于上面的例子,当尝试截断为23个字符时,我想要的最终输出如下:

This is an example <a href="link">of a link</a>

所以本质上我们是在检查截断发生在哪里。如果它位于元素的外部,我们可以将HTML字符串拆分为该长度。另一方面,如果元素中元素,则移动到该元素的结束标记,对所有父元素重复此操作,直到返回到根字符串并将其拆分。

听起来您希望能够截断HTML字符串的长度作为文本字符串,例如考虑以下HTML:

'<b>foo</b> bar'
在本例中,HTML长度为14个字符,文本长度为7个字符。您希望能够将其截断为X文本字符(例如2),以便新的HTML现在是:
'<b>fo</b>'

说明:我的答案使用了我开发的一个库

你可以使用HTMLString库- Docs: GitHub.

库使这个任务非常简单。要使用HTMLString截断我们上面概述的HTML(例如截断为2个文本字符),您需要使用以下代码:

var myString = new HTMLString.String('<b>foo</b> bar');
var truncatedString = myString.slice(0, 2);
console.log(truncatedString.html());

EDIT: After additional information from OP.

下面的truncate函数截断到最后一个完整的标记,并满足嵌套标记。

function truncate(str, len) {
    // Convert the string to a HTMLString
    var htmlStr = new HTMLString.String(str);
    // Check the string needs truncating
    if (htmlStr.length() <= len) {
        return str;
    }
    // Find the closing tag for the character we are truncating to
    var tags = htmlStr.characters[len - 1].tags();
    var closingTag = tags[tags.length - 1];
    // Find the last character to contain this tag
    for (var index = len; index < htmlStr.length(); index++) {
        if (!htmlStr.characters[index].hasTags(closingTag)) {
            break;
        }
    }
    return htmlStr.slice(0, index);
}
var myString = 'This is an <b>example ' +
    '<a href="link">of a link</a> ' +
    'inside</b> another element';
console.log(truncate(myString, 23).html());
console.log(truncate(myString, 18).html());

这将输出:

This is an <b>example <a href="link">of a link</a></b>
This is an <b>example <a href="link">of a link</a> inside</b>

你已经标记了你的问题regex,但是你不能可靠地用正则表达式做到这一点。的链接。所以innerHTML出局了

如果你真的在谈论字符,我认为除了循环遍历元素内的节点,递归到后代元素,加上你发现的文本节点的长度之外,没有其他方法可以做到这一点。当找到需要截断的点时,截断该文本节点,然后删除后面的所有节点 —或者更好的方法是,将文本节点分成两部分(使用splitText),并将后半部分移动到display: none span中(使用insertBefore),然后将所有后续文本节点移动到display: none span中。

虽然HTML是臭名昭著的可怕的格式和边缘情况,不受正则表达式的影响,这里有一个超级轻的方法,你可以轻松地处理HTML与嵌套标签在香草JS。

(function(s, approxNumChars) {
  var taggish = /<[^>]+>/g;
  var s = s.slice(0, approxNumChars); // ignores tag lengths for solution brevity
  s = s.replace(/<[^>]*$/, '');  // rm any trailing partial tags
  tags = s.match(taggish);
  // find out which tags are unmatched
  var openTagsSeen = [];
  for (tag_i in tags) {
    var tag = tags[tag_i];
    if (tag.match(/<[^>]+>/) !== null) {
      openTagsSeen.push(tag);
    }
    else {
      // quick version that assumes your HTML is correctly formatted (alas) -- else we would have to check the content inside for matches and loop through the opentags
      openTagsSeen.pop();
    }
  }
  // reverse and close unmatched tags
  openTagsSeen.reverse();
  for (tag_i in openTagsSeen) {
    s += ('<''' + openTagsSeen[tag_i].match(/'w+/)[0] + '>');
  }
  return s + '...';
})

简而言之:截断它(忽略一些不可见的字符),regex匹配标记,将打开标记推入堆栈,并在遇到关闭标记时弹出堆栈(再次假设格式良好);然后关闭所有仍然打开的标签。

(如果您想实际获得一定数量的可见字符,您可以保持一个运行计数器,显示到目前为止看到的非标记字符的数量,并在达到配额时停止截断。)

免责声明:你不应该把它作为一个生产解决方案,但如果你想要一个超轻的,个人的,黑客的解决方案,这将得到基本的格式良好的HTML。

因为它是盲的和词法的,这个解决方案错过了很多边缘情况,包括应该关闭的标签,如<img>,但是您可以硬编码这些边缘情况,或者,您知道,如果您想要,为真正的HTML解析器包含一个库。幸运的是,由于HTML格式很差,您不会看到它;)

感谢T.J.我很快意识到,要高效率地完成这一工作,唯一的方法就是使用本地DOM方法并遍历元素。

我已经编写了一个快速、相当优雅的函数来完成这个任务。

function truncate(rootNode, max){
    //Text method for cross browser compatibility
    var text = ('innerText' in rootNode)? 'innerText' : 'textContent';
    //If total length of characters is less that the limit, short circuit
    if(rootNode[text].length <= max){ return; }
    var cloneNode = rootNode.cloneNode(true),
        currentNode = cloneNode,
        //Create DOM iterator to loop only through text nodes
        ni = document.createNodeIterator(currentNode, NodeFilter.SHOW_TEXT),
        frag = document.createDocumentFragment(),
        len = 0;
    //loop through text nodes
    while (currentNode = ni.nextNode()) {
        //if nodes parent is the rootNode, then we are okay to truncate
        if (currentNode.parentNode === cloneNode) {
            //if we are in the root textNode and the character length exceeds the maximum, truncate the text, add to the fragment and break out of the loop
            if (len + currentNode[text].length > max){
                currentNode[text] = currentNode[text].substring(0, max - len);
                frag.appendChild(currentNode);
                break;
            }
            else{
                frag.appendChild(currentNode);
            }
        }
        //If not, simply add the node to the fragment
        else{
            frag.appendChild(currentNode.parentNode);
        }
        //Track current character length
        len += currentNode[text].length;
    }
    rootNode.innerHTML = '';
    rootNode.appendChild(frag);
}

这可能会得到改进,但从我最初的测试来看,它非常快,可能是由于使用了本机DOM方法,它似乎完美地为我完成了这项工作。我希望这对其他有类似需求的人有所帮助。

免责声明:以上代码将只处理一级深的 HTML标签,它不会处理标签内的标签。不过,通过跟踪节点父节点并将节点附加到片段中的正确位置,可以很容易地对其进行修改。就目前而言,这对我的需求很好,但对其他人可能没用。