【问题标题】:Insert HTML at given text offsets在给定的文本偏移处插入 HTML
【发布时间】:2021-04-13 00:48:33
【问题描述】:

我正在构建的浏览器扩展需要在某些文本位(通常是单个单词)周围插入 span 元素。

例如,对于某些输入 HTML,例如 <p>This is some text</p>,它可能会生成 <p>This is some <span class="insertedByMe">text</span></p>

REST API 通过字符偏移告诉浏览器扩展将这些span 元素放在哪里。在上面的示例中,单词“text”跨越了字符偏移量[13, 17],因此服务器会告诉扩展程序它应该将范围放在这些偏移量周围。注意:这些字符偏移目前仅与实际文本相关,不包括任何标记,尽管可以在服务器端进行更改。

我的问题:如何在给定的字符偏移处插入 HTML 标签?这是否可能,最好不破坏我要替换的元素上的任何侦听器?另请注意,我不能单独处理文本节点,因为我总是需要至少包含单词的句子的上下文,并且我不能假设单个文本节点总是包含整个句子(因为链接,标记等)。


奖励:上面的例子是一个非常简单的例子。当我处理更多嵌套的 HTML 元素时,事情可能会变得更棘手,或者当在其他已经以某个偏移量开始或结束的 HTML 元素之前或之后放置 span 变得模棱两可时(因为偏移量不计算标记)。请参见下面的示例。非常欢迎任何帮助我处理这些更复杂案例的建议!

<div>
    Some text here without any enclosing p element.
   <p>Some <b>more text</b></p>
</div>

Server response: put spans around offsets [[5, 9], [59, 63]] (the two occurrences of the word "text")

挑战:

  • 第一个跨度将被放入作为div 的直接子级的文本节点中,因此该文本节点的父元素也是包含第二个跨度的文本的祖先。我认为当我替换 div 的整个内部 HTML 时,这可能会出现问题,因为它会重新加载所有子级,可能会破坏侦听器。
  • 第二个 span 的结束标记必须插入 结束 b 标记之前,以保护有效的 HTML,而两个结束标记都位于字符偏移量 63 处。

【问题讨论】:

  • 所有你需要得到一个元素的偏移量来注入另一个元素的代码?这正是你需要的吗?
  • 是的,这有很长的路要走,有点是我目前的实现。但如果可能的话,我想避免重新加载父元素,以免破坏其上的任何侦听器。不过不确定这是否可能。
  • 只要 jquery 让你用 Offset() 注入你的代码我认为它可以做到here 你可以找到一个如何使用 jquery 更改元素偏移量的示例,你可以替换代码为了达到你的目标,如果你失败了,n为你做一个
  • 设置一个元素的 innerHTML 属性,不应该删除任何已经存在的事件处理程序。
  • “非常欢迎任何可以帮助我处理这些更复杂案例的建议!” - 那么您可能应该首先提供此类案例的适当示例。不是口头描述,而是实际代码,以及随之而来的偏移数据。

标签: javascript html jquery dom


【解决方案1】:

这正是我认为你想要的

function addspan(id, offset, length) {
  $("div,p").html(function(i, val) {
    return val.substr(0, offset) +
      "<span>" +
      val.substr(offset, length) +
      "</span>" +
      val.substr(offset + length);
  });
}

addspan("text", 5, 4);
addspan("text", 78, 4);
span {
  background-color: darkblue;
  color:white;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>


<div>Some text here without any enclosing p element.
<p>Some <b> more text </b></p>
</div>

【讨论】:

  • @Joko 希望能满足你的要求,
  • 谢谢,这有很长的路要走。 :) 它仍然不是最优的,因为嵌套的&lt;p&gt; 将由于在父&lt;div&gt; 上设置.html() 而重新加载,可能会破坏处理程序。我想我必须降低到文本节点的级别,并且只替换它才能避免这种情况。
  • 至少你现在得到了提示^^goodluck bud
  • 哦,当然我只会在根元素上执行此操作,而不是所有 "div, p",因为偏移量是相对于根的,而不是相对于单个的 divp
【解决方案2】:

首先,正如我在上面评论的那样,您的 API 在生成偏移量时没有考虑 html。在您的示例中,从技术上讲,“Some”(新行和缩进)之前有 5 个空白字符,因此实际偏移量应该是 [[10, 14], [73, 77]]。我们可以通过编码&lt;div&gt; 元素的内容并将每个字符换行来可视化这一点...

01. %0A
02. 20%
03. 20%
04. 20%
05. 20%
06. S
07. o
08. m
09. e
10. 20%
11. t
12. e
13. x
14. t
15. 20%
16. h
17. e
18. r
19. e
20. 20%
21. w
22. i
23. t
24. h
25. o
26. u
27. t
28. 20%
29. a
30. n
31. y
32. 20%
33. e
34. n
35. c
36. l
37. o
38. s
39. i
40. n
41. g
42. 20%
43. p
44. 20%
45. e
46. l
47. e
48. m
49. e
50. n
51. t
52. .
53. %0A
54. 20%
55. 20%
56. 20%
57. 20%
58. %3C
59. p
60. %3E
61. S
62. o
63. m
64. e
65. 20%
66. %3C
67. b
68. %3E
69. m
70. o
71. r
72. e
73. 20%
74. t
75. e
76. x
77. t
78. %3C
79. %2F
80. b
81. %3E
82. %3C
83. %2F
84. p
85. %3E
86. %0A

现在假设您的 API 返回包括 HTML 字符在内的偏移量,请参阅下面的我的代码实现。

/**
 * -----
 * Core wrapping functions
 * -----
 */
(function($) {
  'use strict';

  /**
   * @param {string|jquery} wrap DOM element used for wraping.
   * @param {integer} start The starting offset for the opening dom tag.
   * @param {integer} end The end offset for the closing dom tag.
   * @return {object}
   */
  $.fn.wrapAt = function(wrap, start, end) {

    const origHtml = this.html();
    const origLength = origHtml.length;

    const firstPart = origHtml.substr(0, start);
    const middlePart = $(wrap).html(origHtml.substr(start, end - start)).prop('outerHTML');
    const endPart = origHtml.substr(end);
    
    const newHtml = `${firstPart}${middlePart}${endPart}`;
    const newLength = newHtml.length;

    this.html(newHtml);

    return {
      original: origLength,
      new: newLength,
      difference: newLength - origLength
    };
  };

  /**
   * @param {string|jquery} wrap DOM element used for wraping.
   * @param {array[]} offsets An array of start and end offsets.
   */
  $.fn.bulkWrapAt = function(wrap, offsets = []) {
    let diff = 0;
    for (let i = 0; i < offsets.length; i++) {
      let start = offsets[i][0];
      let end = offsets[i][1];

      // Modify offsets based on mutated string length difference
      start += diff;
      end += diff;

      const result = this.wrapAt(wrap, start, end);

      diff += result.difference;
    }
  };

})(jQuery);

/**
 * -----
 * Wraping initiator
 * -----
 */
(function($) {

  const apiResponse = [
    [10, 14], //[5, 9],
    [73, 77] //[59, 63]
  ];
  
  $('div').bulkWrapAt('<span>', apiResponse);
  
})(jQuery);
span {
  background-color: darkblue;
  color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>

<div>
    Some text here without any enclosing p element.
    <p>Some <b>more text</b></p>
</div>

“核心包装函数”为您提供了两种使用方法。例如,第一个是单个替换...

$('div').wrapAt('<span>', 10, 14);

第二种方法允许您基于开始和结束偏移的多维数组进行批量包装(如您的 API 响应示例)。

$('div').bulkWrapAt('<span>', [
  [10, 14],
  [73, 77]
]);

每个方法中的第一个参数传递给核心jQuery方法.wrap()意味着你可以添加类,id和其他有效的html,例如...

$('div').wrapAt('<span class="insertedByMe"></span>', 10, 14);

$('div').wrapAt('<div><span></span></div>', 10, 14);

var $el = $('#wrap-template').clone();
$('div').wrapAt($el, 10, 14);

这不是一个完整的答案,但我希望它足以引导您朝着正确的方向前进。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-02
    • 2015-10-20
    相关资源
    最近更新 更多