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

时间:2015-09-03 17:16:44

标签: javascript regex

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

我需要能够获取元素的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字符串拆分为该长度。另一方面,如果元素中,我们移动到该元素的结束标记,重复任何父元素,直到我们回到根字符串并在那里将其拆分。

4 个答案:

答案 0 :(得分:1)

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

'<b>foo</b> bar'

在这种情况下,HTML的长度为14个字符,文本为7.您希望能够将其截断为 X 文本字符(例如2),以便新的HTML是现在:

'<b>fo</b>'

披露:我的回答使用了我开发的库。

您可以使用HTMLString库 - DocsGitHub

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

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

编辑:来自OP的其他信息。

以下截断函数会截断到最后一个完整标记,并且会满足嵌套标记。

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>

答案 1 :(得分:1)

虽然HTML因形成非常糟糕并且具有不受正则表达式影响的边缘情况而臭名昭着,但是这是一种超轻的方式,你可以用vanilla JS中的嵌套标签来处理HTML。

(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 + '...';
})

简而言之:截断它(忽略一些字符将不可见),正则表达式匹配标记,将打开的标记推入堆栈,并在遇到结束标记时弹出堆栈(同样,假设格式正确);然后在最后关闭所有仍然打开的标签。

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

免责声明:您不应该将其用作生产解决方案,但如果您想要一个超轻,个性化的hacky解决方案,这将获得基本格式良好的HTML。

由于它是盲目和词汇,这个解决方案错过了很多边缘情况,包括关闭的标签,如<img>,但你可以对这些边缘情况进行硬编码,或者你知道,如果你愿意,可以包含一个真正的HTML解析器的库。幸运的是,由于HTML形成不佳,你不会看到它;)

答案 2 :(得分:0)

您已标记了问题regex,但使用正则表达式无法可靠地执行此操作。 Obligatory link。所以innerHTML已经出局了。

如果你真的在谈论角色,除了循环遍历元素中的节点,递归到后代元素,总结你找到的文本节点的长度时,我没有办法做到这一点。 。当您找到需要截断的点时,将截断该文本节点,然后删除所有后续文本节点 - 或者可能更好,将该文本节点拆分为两部分(使用splitText)并将后半部分移动到display:none span(使用insertBefore),然后将所有后续文本节点移动到display:none span。 (这使得撤消它变得容易得多。)

答案 3 :(得分:0)

感谢 T.J. Crowder 我很快意识到,以任何效率执行此操作的唯一方法是使用本机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标记,不会处理标记内的标记。虽然可以通过跟踪父节点并将节点附加到片段中的正确位置来轻松修改它。就目前而言,这对我的要求很好,但可能对其他人没用。