【问题标题】:Marking text in a html document在 html 文档中标记文本
【发布时间】:2016-05-07 02:39:44
【问题描述】:

假设我有以下标记:

<html>
    <head>
        <title>Page Title</title>
    </head>
    <body>
        <h1>Some title</h1>
        <p>First paragraph</p>
        <p>Second paragraph</p>
    </body>
<html>

我需要标记文本的某些部分,即“第一段秒” 它看起来像这样:

<html>
    <head>
        <title>Page Title</title>
    </head>
    <body>
        <h1>Some title</h1>
        <p>F
            <mark>
                irst paragraph</p><p>Secon
            </mark>
        d paragraph</p>
    </body>
<html>

但问题是 html 标记会被破坏。标记越复杂,这种方法的问题就越多。

问题:

寻找关于如何获取第一个 HTML 示例并应用一个函数来返回一个 html 结构的想法,其中以某种方式专门标记了“第一段第二”。

我目前拥有的是:

  • 字符串“第一段”的父容器
  • 文本“第一段第二段”
  • “第一段”中文本“第一个”的偏移量

【问题讨论】:

    标签: highlight highlighting


    【解决方案1】:

    原则上你必须:

    • 将文档拆分为单词
    • 通过父元素识别第一个单词
    • 跳过偏移量
    • 标记匹配的单词

    在单词级别进行更改将防止您破坏标记。 我在下面添加了一个工作示例。但是我不确定它是否适用于所有浏览器。

    示例中没有使用 mergeWords 等一些函数,但我将它们包括在内,因为它们可以证明是有用的。

    var splittedToWords = false;
    
    function ignore(el) {
      return (el.nodeType == 8) || 
        (el.tagName == "BLOCKQUOTE") ||
        (el.tagName == "SCRIPT") ||
        (el.tagName == "DIV") ||
        (!el.hasChildNodes() && el.textContent.match(/\S+/) == null);
    }
    
    function splitToWords(el) {
      if (el.hasChildNodes()){
        var count = el.childNodes.length;
        for (var i = count - 1; i >= 0; i--) {
          var node = el.childNodes[i];
          if (!ignore(node))
            splitToWords(node);
        }
      }
      else {	//text node
        var words = el.textContent.match(/(\S+\s*)/g) || [];
        var count = words.length;
        var parentNode = el.parentNode;
        for (var i = 0; i < count; i++) {
          var wordNode = document.createElement("span");
          wordNode.className = "word";
          wordNode.innerText = words[i];
    
          wordNode.setAttribute["word-index"] = i;
    
          parentNode.insertBefore(wordNode, el);
        }
        parentNode.removeChild(el);
      }
      splittedToWords = true;
    }
    
    function unwrap(element) {
      var next = element.nextSibling;
      var parent = element.parentNode;
      parent.removeChild(element);
      var current;
      var frag = document.createDocumentFragment();
      do {
        current = element.nextSibling;
        frag.insertBefore(element, null);
      } while ((element = current));
      parent.insertBefore(frag, next);
    }
    
    function mergeWords(el) {
      var words = document.getElementsByClassName("word");
      count = words.length;
      if (count > 0)
        for (var i = 0; i < count; i++)
          uwrap(words[i]);
    }
    
    function markWord(el, pos, len) {
      var text = el.innerText;
      var pre = text.substr(0, pos);
      var mark = '<mark>' + text.substr(pos, len) + '</mark>';
      var post = text.substring(pos + len, text.length);
      el.innerHTML = pre + mark + post;
    }
    
    function mark(element, offset, text) {
      if (!splittedToWords) {
        var body = document.body;
        splitToWords(body);
      }
    
      var words = document.getElementsByClassName("word");
      var wordsCount = words.length;
      var first = null;
      for (var i = 0; i < wordsCount; i++ ) {
        if (words[i].parentElement == element) {
          first = i;
          break;
        }
      }
    
      done = false;
      var i = first;
      var pos = 0;
    
      do {
        var word = words[i];
        var wordLength = word.innerText.length;
    
        if (offset > pos + wordLength) {
          i++;
          pos += wordLength;
          continue;
        }
        else {
          done = true;
        }
      } while (!done);
    
      var tWords = text.match(/(\S+\s*)/g) || [];
      var tWordsCount = tWords.length;
      if (tWordsCount == 0)
        return;
    
      for (var ti = 0; ti < tWordsCount; ti++) {
        var wordEl = words[i++];
        var word = wordEl.innerText;
        var tWord = tWords[ti].trim();
        var pos = word.indexOf(tWord);
    
        if (pos == -1)
          continue;	//or maybe return.
    
        markWord(wordEl, pos, tWord.length);
      }
    
    }
    var e = document.getElementById("e");
    
    //do the magic
    mark(e, 1, 'irst paragraph Second');
    <h1>Some title</h1>
    <p id="e">First paragraph</p>
    <p>Second paragraph</p>

    【讨论】:

    • 唯一的缺点是它不标记空格
    • 虽然可以更改。如果您标记两个相邻的单词(跨度),包括单词末尾的空格,它们将显示为一个连续的标记元素。
    • @B0Andrew +1,这实际上是个好主意,但是对于较大的网页,“将文档拆分为单词”不会花费很长时间吗?
    • 这太有问题了,它只能在这种小情况下工作。包裹“第一段”,例如使用&lt;span&gt; (jsfiddle.net/2c91f8rs) 或在末尾使用空白搜索 (jsfiddle.net/2c91f8rs/1) 将引发错误。我可以列出几十种引发错误的情况。使用@Anamika Shrivastava 推荐的插件可以让您有机会自定义元素名称、类名、还标记变音符号、使用同义词等。
    • @user3631654 假设之一是“要搜索的 string 开头的父元素是已知的”而不是“任何父元素”。当然,如果您将搜索字符串的开头与另一个元素包装起来,则该功能将不起作用。称它为错误,很好。但不要忘记这是如此。您可以获得特定问题的答案,而不是可能出现的所有问题。
    【解决方案2】:

    如果您想突出显示文档中的文本,那么此插件将对您有所帮助。

    https://github.com/julmot/jquery.mark

    小提琴示例:https://jsfiddle.net/julmot/vpav6tL1/

    用法很简单:

    $(".context").mark("keyword");
    

    【讨论】:

    • 很棒,但没有“单独的单词搜索”选项,它不会像预期的那样使用更复杂的标记(例如,当在不同的子元素中找到部分搜索词时)。
    • @B0Andrew 我无法重现错误。你到底什么意思?你能在回购页面上打开一个问题吗?
    猜你喜欢
    • 1970-01-01
    • 2013-07-19
    • 2016-09-11
    • 1970-01-01
    • 2021-10-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-04
    相关资源
    最近更新 更多