【问题标题】:Wrap text in HTML tags if not already wrapped in a tag将文本包装在 HTML 标签中(如果尚未包装在标签中)
【发布时间】:2021-09-10 15:28:14
【问题描述】:

我需要用<span> 标记将特定术语包装在带有<span> 标记的文本字符串中,前提是该术语尚未包含在<span> 标签中。

例如我有一串文本:

Test string of text containing foo bar and baz.

还有一个带有键值对的对象在字符串中搜索:

toolTips = {
  foo: 'tooltip for foo',
  bar: 'problematic tooltip that also contains baz',
  baz: 'tooltip for baz'
}

我需要遍历对象键并使用<span> 标记包装匹配项以添加工具提示文本。

所以在循环的第一次迭代之后,字符串将是:

Test string of text containing
  <span class="tooltip">foo
    <span class="tooltip-text">tooltip for foo</span>
  </span>
bar and baz.

第二个之后是:

Test string of text containing
  <span class="tooltip">foo
    <span class="tooltip-text">tooltip for foo</span>
  </span>
  <span class="tooltip">bar
    <span class="tooltip-text">problematic tooltip that also contains baz</span>
  </span> 
and baz.

在第三个之后它将是:

Test string of text containing
  <span class="tooltip">foo
    <span class="tooltip-text">tooltip for foo</span>
  </span>
  <span class="tooltip">bar
    <span class="tooltip-text">problematic tooltip that also contains baz</span>
  </span> 
and
  <span class="tooltip">baz
    <span class="tooltip-text">tooltip for baz</span>
  </span>
.

我已经尝试使用string.replace() 和各种正则表达式模式来执行此操作,但我无法让它完全发挥作用。之前添加的 &lt;span&gt; 中的文本被匹配和替换,或者我对正则表达式中的结束 &lt;/span 标记进行否定预测,然后任何跨度之前的文本都不会匹配。

不胜感激有关如何处理此问题的想法。

【问题讨论】:

    标签: javascript html regex


    【解决方案1】:

    这不是最有效的想法,但您可以尝试使用占位符,以免短语相互重叠。

    let string = "Test string of text containing foo bar and baz.";
    
    const toolTips = {
      foo: 0,
      bar: 1,
      baz: 2
    }
    
    const toolTipsPlaceholders = {
      0: {key: 'foo', value: 'tooltip for foo'},
      1: {key: 'bar', value: 'problematic tooltip that also contains baz'},
      2: {key: 'baz', value: 'tooltip for baz'}
    }
    
    const keys = Object.keys(toolTips)
    
    keys.forEach(k => string = string.replaceAll(k, toolTips[k]))
    
    const keysPlaceholders = Object.keys(toolTipsPlaceholders)
    
    keysPlaceholders.forEach(k => string = string.replaceAll(k, `<span class="tooltip">${toolTipsPlaceholders[k].key}<span class="tooltip-text">${toolTipsPlaceholders[k].value}</span></span>`))
    
    document.getElementById("test").innerHTML = string;
    .tooltip {
      color: red;
    }
    
    .tooltip-text {
      color: blue;
    }
    &lt;div id="test"&gt;&lt;/div&gt;

    【讨论】:

      【解决方案2】:

      下面是一种应该让你照顾它的方法。

      您可以使用非常复杂的正则表达式来检查键是否已经存在于元素中,但这可能会让人难以编写、理解和维护。

      相反,诀窍是循环遍历元素中的每个节点。如果它是一个文本节点,您知道其中没有 HTML,因此任何替换都是安全的。如果是元素节点,则递归遍历,查找文本节点,并跳过工具提示元素。

      在我看来,这使得更容易理解和保持前进。

      在替换中,我只是将键替换为工具提示值,这不是您想要的,但可以根据自己的喜好轻松调整。

      document.querySelector('button').addEventListener('click', () => {
        const root = document.querySelector('div');
        
        applyTooltips(root);
      });
      
      const tooltips = {
        foo: 'hello bar and baz',
        bar: 'goodbye',
        baz: 'cake and foo'
      };
      
      // Regex that can match all of the keys at the same time
      //  so we don't risk getting weirdness if one tooltip 
      //  contains another key
      const matchRegex = new RegExp(`(${Object.keys(tooltips).join('|')})`, 'g');
      
      const tooltipSelector = '.tooltip';
      const applyTooltips = element => {
        // Loop over each childNode, which might be a text or element node.
        [...element.childNodes].forEach(child => {
          // If it is a text node, we'll apply the tooltip logic
          if (child.nodeType === Node.TEXT_NODE) {
            // Get the text
            const text = child.wholeText;
            
            // Replace the text with the HTML
            newText = text.replaceAll(matchRegex, key => `<div class="tooltip">${tooltips[key]}</div>`);
            
            // Create a temp element we can assign the
            //  HTML text to to get actual elements
            const temp = document.createElement('div');
            temp.innerHTML = newText;
            
            // Apply each new node before the text child
            [...temp.childNodes].forEach(node => 
              element.insertBefore(node, child)
            );
            
            // Remove the old text child
            element.removeChild(child);
          } else if (!child.matches(tooltipSelector)) {
            // If it is an element that isn't a tooltip element, we'll recurse on it.
            applyTooltips(child);
          }
        });
      };
      .tooltip { color: #F00; }
      <div>
        Test string of text containing foo bar and baz.
        
        This <div class="tooltip">foo</div> is already wrapped and won't get wrapped again.
      </div>
      <button>Apply</button>

      【讨论】:

      • 感谢@samanime,您的回答最接近我的需要。我不太能够让它在我的代码中工作,但它让我得到了这个答案stackoverflow.com/questions/21060932/…,这最终成为了我所需要的。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-06-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-27
      • 1970-01-01
      相关资源
      最近更新 更多