【问题标题】:How to highlight the search-result of a text-query within an html document ignoring the html tags?如何在忽略 html 标签的 html 文档中突出显示文本查询的搜索结果?
【发布时间】:2020-07-08 17:59:27
【问题描述】:

我有一个字符串,其中包含 html 内容。像这样的

  const text = "My name is Alan and I <span>an</span> <div class="someClass">artist</div>."

我使用dangerouslySetInnerHTML 在反应组件中渲染它。这个文本真的很长,里面有不同类型的 HTML 标签。

我想搜索一个单词并在用户键入时在该文档中突出显示它。该功能类似于浏览器的查找 (cmd + f) 功能。当您键入时,文本应该会突出显示。

这就是我要找的:

 user types `an`
 const text = "My name is Alan and I <span>an</span> <div class="someClass">artist</div>."
result: "My name is Al<mark>an</mark> and I <span><mark>an</mark></span> <div class="someClass">artist</div>."

我尝试使用这个库https://github.com/bvaughn/react-highlight-words,但问题是它也会突出显示标签内的文本并弄乱内容。

result: "My name is Al<mark>an</mark> and I <sp<mark>an</mark>><mark>an</mark></span> <div class="someClass">artist</div>."

然后我会使用我自己的正则表达式并想出这个正则表达式:

const regex = new RegExp(((`${searchedText}`)(?![^<>]*>)))

但是 react(eslint) 在? 抛出这个错误:

This experimental syntax requires enabling the parser plugin: 'partial Application'

这是我的代码:

get highlightedText() {
      if (searchText === '') return self.renderedText;
      const regex = new RegExp((`${searchText}`)((?![^<>]*>)));
      const parts = self.renderedText.split(regex);
      return parts
         .map(part => (regex.test(part) ? `<mark>${part}</mark>` : part))
         .join('');
    },

我不确定我做错了什么。当我使用 regextester.com 测试正则表达式时,正则表达式工作得很好

感谢任何帮助。谢谢!

【问题讨论】:

    标签: javascript regex dom search highlight


    【解决方案1】:

    一种基于正则表达式的方法,在字符串模板级别操作 html 标记,仅适用于严格有效且未嵌套的标记,例如 OP 给出的示例。

    const text = 'My name is Alan and I\'m <span>an</span> <div class="someClass">artist</div>.'
    

    这样的做法will not work for any nested html markup像下面这样...

    const text = 'My name is Alan and I\'m <span><em>an</em></span> <div><em>artist</em></div>.'
    

    至于 OP 提供的用例,为了不意外地操纵任何 html 标记,正则表达式需要匹配并 记住 开始和结束标记以及随附的文本内容。因此需要使用Capturing Groups

    特此提供一个使用Named Groups 的示例正则表达式...

    const test = 'My name is Alan and I\'m <span>an</span> <div class="someClass">artist</div>.'
    
    const regXSimpleMarkup = (/(?<tagStart><[^>]+>)(?<text>[^<]+)(?<tagEnd><\/[^>]+>)/g);
    
    [...test.matchAll(regXSimpleMarkup)].forEach((match, idx) =>
      console.log(`match ${ idx } :: groups : `, match.groups)
    );
    
    console.log([...test.matchAll(regXSimpleMarkup)]);
    .as-console-wrapper { min-height: 100%!important; top: 0; }

    ..,但是从上面的运行代码的结果可以看出,没有匹配/捕获 html 标记之前或之后的所有其他文本内容。因此,应该利用捕获正则表达式和split 的组合...

    const test = 'My name is Alan and I\'m <span>an</span> <div class="someClass">artist</div>.'
    
    // const regXSimpleMarkup = (/(?<tagStart><[^>]+>)(?<text>[^<]+)(?<tagEnd><\/[^>]+>)/g);
    const regXSimpleMarkup = (/(<[^>]+>)([^<]+)(<\/[^>]+>)/g);
    
    console.log(test.split(regXSimpleMarkup));
    .as-console-wrapper { min-height: 100%!important; top: 0; }

    正如上面所证明的,对于 OP 的给定示例,结果是一个干净分离的标记片段列表。现在可以逐步处理此列表,仅对每个检测到的文本内容应用搜索和替换机制(搜索子字符串并创建突出显示标记),而在每个迭代步骤中,新的 html 标记字符串也会以编程方式构建。

    //  How to escape regular expression special characters using javascript?
    //
    //  [https://stackoverflow.com/questions/3115150/how-to-escape-regular-expression-special-characters-using-javascript/9310752#9310752]
    //
    function escapeRegExpSearchString(text) {
      return text.replace(/[-[\]{}()*+?.,\\^$|#\\s]/g, '\\$&');
    }
    
    
    function createTextSearchMarkup(fragment, search, isCaseSensitive) {
      const flags = `g${ !!isCaseSensitive ? '' : 'i' }`;
    
      search = escapeRegExpSearchString(search);
      search = RegExp(`(${ search })`, flags);
    
      return fragment.replace(search, '<mark>$1</mark>');
    }
    
    function concatTextSearchMarkup(collector, fragment) {
      const regXTag = (/^<[^>]+>$/);
    
      if (!regXTag.test(fragment)) {
    
        fragment = createTextSearchMarkup(
          fragment,
          collector.search,
          collector.isCaseSensitive
        );
      }
      collector.markup = [collector.markup, fragment].join(''); // concat.
    
      return collector;
    }
    
    function getHighlightTextSearchMarkup(markup, search, isCaseSensitive) {
    //const regXSimpleMarkup = (/(?<tagStart><[^>]+>)(?<text>[^<]+)(?<tagEnd><\/[^>]+>)/g);
      const regXSimpleMarkup = (/(<[^>]+>)([^<]+)(<\/[^>]+>)/g);
    
      return markup.split(regXSimpleMarkup).reduce(
        concatTextSearchMarkup, {
          isCaseSensitive,
          search,
          markup: ''
        }
      ).markup;
    }
    
    
    const markup = 'My name is Alan and I\'m <span>an</span> <div class="someClass">artist</div>.'
    
    console.log('original markup => ', markup);
    
    console.log(
      'case insensitive search for "an" => ',
      getHighlightTextSearchMarkup(markup, 'an')
    );
    console.log(
      'case insensitive search for "i" => ',
      getHighlightTextSearchMarkup(markup, 'i')
    );
    console.log(
      'case sensitive search for "i" => ',
      getHighlightTextSearchMarkup(markup, 'i', true)
    );
    .as-console-wrapper { min-height: 100%!important; top: 0; }

    注意

    对于 html 模板字符串中的任何嵌套标记,需要一种利用浏览器原生 html 解析/呈现的方法,例如一个 HTML(片段)节点,在任何时候都不是浏览器 DOM 的一部分。

    【讨论】:

    • @pogbamessi ...还有什么问题吗?
    • 我非常感谢您为这个答案付出的所有努力。您的解决方案在我的 react native 项目中非常有效。
    【解决方案2】:

    试试这个:

         function highlightedText(yourText, searchValue) {
         
          if (!searchValue) return yourText;
    
          let rgx = "?![^<>]*>";
          const regex = new RegExp(`(${trim(searchValue)})(${rgx})`, 'gi');
    
          return compose(   
            join(''),
            map(part => (regex.test(part) ? `<span style="background-color: #fff200;">${part}</span>` : part)),
            split(regex)
          )( yourText);
          
        };
        };
    

    请注意,我使用 lodash/fp 中的 {map join trim}。更好的选择是使用 js-coroutines 对较大的文本或大数据集进行数据操作 (http://js-coroutines.com/)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-18
      • 2021-07-24
      • 2018-08-29
      • 2011-02-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多