【问题标题】:How to wrap part of text with a <span> or any other HTML tag without new HTML structure being escaped?如何用 <span> 或任何其他 HTML 标记包装部分文本而不转义新的 HTML 结构?
【发布时间】:2019-06-22 16:35:08
【问题描述】:

我正在匹配元素文本中的特定字符串,并希望用 span 包装匹配的文本以便能够选择它并稍后应用修改,但 html 实体正在被转义。有没有办法用 html 标签包装字符串并对其进行转义?

我尝试使用unescapeEntities()​, 方法,但在这种情况下不起作用。 wrap() 效果不佳。 参考这些方法检查https://jsoup.org/apidocs/org/jsoup/parser/Parser.html

当前代码:

for (Element div : doc.select("div")) {
    for (String input : listOfStrings) {
        if (div.ownText().contains(input)) {
            div.text(div.ownText().replaceFirst(input, "<span class=\"select-me\">" + input + "</span>"));
        }
    }
}

期望的输出

&lt;div&gt;some text &lt;span class="select-me"&gt;matched string&lt;/span&gt;&lt;/div&gt;

实际输出

&lt;div&gt;some text &amp;lt;span class=&amp;quot;select-me&amp;quot;&amp;gt;matched string&amp;lt;/span&amp;gt;&lt;/div&gt;

【问题讨论】:

  • 您不想使用text() 方法,而是使用appendElement()after() 等方法添加新元素(使用Node 类型参数)。
  • 好的,但是如何将它附加到所需文本的特定位置?
  • 这取决于原始内容。当您想将它添加到元素文本的末尾时,您可以使用 appendElement() or after()` 之类的方法,具体取决于您调用的 Element 对象。如果要在字符串中插入元素,则需要先将文本拆分为两个文本节点,然后在它们之间添加新的Element
  • 我希望它将匹配的文本包装在它之前或之后。
  • 是否保证您的元素(div)将只包含文本,或者它还可以包含一些内部元素,如&lt;a&gt; 或其他&lt;span&gt;

标签: java jsoup


【解决方案1】:

根据您的问题和 cmets,您似乎只想修改所选元素的直接文本节点而不修改所选文本的潜在内部元素的文本节点,因此在

的情况下
<div>a b <span>b c</span></div> 

如果我们要修改b,我们只修改直接放在&lt;div&gt;中的一个,而不是在&lt;span&gt;中的一个。

<div>a b <span>b c</span></div> 
       ^       ^----don't modify because it is in <span>, not *directly* in <div>
       |
     modify

文本不像&lt;div&gt; &lt;span&gt;等那样被视为ElementNode,但在DOM中它被表示为TextNode,所以如果我们有像&lt;div&gt; a &lt;span&gt;b&lt;/span&gt; c &lt;/div&gt;这样的结构,那么它的DOM表示将是

Element: <div>
├ Text: " a "
├ Element: <span>
│ └ Text: "b"
└ Text: " c "

如果我们想包装部分文本到&lt;span&gt;(或任何其他标签)中,我们实际上是在拆分单个TextNode

├ Text: "foo bar baz"

成系列:

├ Text: "foo "
├ Element: <span>
│ └ Text: "bar"
└ Text: " baz"

要创建使用该想法的解决方案TextNode API 为我们提供了非常有限的工具集,但在可用的方法中我们可以使用

  • splitText(index) 修改原始 TextNode 将拆分的“左侧”保留在其中并返回新的 TextNode 保留拆分的剩余(右侧),就像 TextNode node1 持有 "foo bar" 之后 TextNode node2 = node1.splitText(3); node1 将持有 @ 987654343@ 而node2 将保持" bar" 并将被放置为node1 之后的直接兄弟
  • wrap(htmlElement)(继承自 Node 超类)将 TextNode 包装在表示 htmlElement 的 ElementNode 中,例如 node.wrap("&lt;span class='myClass'&gt;") 将导致 &lt;span class='myClass&gt;text from node&lt;/span&gt;

通过上面的“工具”,我们可以创建类似的方法

static void wrapTextWithElement(TextNode textNode, String strToWrap, String wrapperHTML) {

    while (textNode.text().contains(strToWrap)) {
        // separates part before strToWrap
        // and returns node starting with text we want
        TextNode rightNodeFromSplit = textNode.splitText(textNode.text().indexOf(strToWrap));

        // if there is more text after searched string we need to
        // separate it and handle in next iteration
        if (rightNodeFromSplit.text().length() > strToWrap.length()) {
            textNode = rightNodeFromSplit.splitText(strToWrap.length());
            // after separating remining part rightNodeFromSplit holds
            // only part which we ware looking for so lets wrap it
            rightNodeFromSplit.wrap(wrapperHTML);
        } else { // here we know that node is holding only text to wrap
            rightNodeFromSplit.wrap(wrapperHTML);
            return;// since textNode didn't change but we already handled everything
        }
    }
}

我们可以这样使用:

Document doc = Jsoup.parse("<div>b a b <span>b c</span> d b</div> ");
System.out.println("BEFORE CHANGES:");
System.out.println(doc);

Element id1 = doc.select("div").first();
for (TextNode textNode : id1.textNodes()) {
    wrapTextWithElement(textNode, "b", "<span class='x'>");
}

System.out.println();
System.out.println("AFTER CHANGES");
System.out.println(doc);

结果:

BEFORE CHANGES:
<html>
 <head></head>
 <body>
  <div>
   b a b 
   <span>b c</span> d b
  </div> 
 </body>
</html>

AFTER CHANGES
<html>
 <head></head>
 <body>
  <div>
   <span class="x">b</span> a 
   <span class="x">b</span> 
   <span>b c</span> d 
   <span class="x">b</span>
  </div> 
 </body>
</html>

【讨论】:

    【解决方案2】:

    cmets中的详细解释:

    import java.util.ArrayList;
    import java.util.List;
    
    import org.jsoup.Jsoup;
    import org.jsoup.nodes.Document;
    import org.jsoup.nodes.Element;
    import org.jsoup.nodes.Node;
    import org.jsoup.nodes.TextNode;
    
    public class StackOverflow56717248 {
    
        public static void main(String[] args) {
            List<String> listOfStrings = new ArrayList<>();
            listOfStrings.add("INPUT");
            Document doc = Jsoup.parse(
                    "<div id=\"1\">some text 1</div>" +
                    "<div id=\"2\"> node before <b>xxx</b> this one contains INPUT text <b>xxx</b> node after</div>");
            System.out.println("BEFORE: ");
            System.out.println(doc);
            // iterating over all the divs
            for (Element div : doc.select("div")) {
                // and input texts
                for (String input : listOfStrings) {
                    // to find the one with desired text
                    if (div.ownText().contains(input)) {
                        // when found we have to be aware that this node may not be the only child
                        // so we have to iterate over children nodes
                        for (int i = 0; i < div.childNodeSize(); i++) {
                            Node child = div.childNode(i);
                            // taking into account only TextNodes
                            if (child instanceof TextNode && ((TextNode) child).text().contains(input)) {
                                TextNode textNode = ((TextNode) child);
                                // when found the one matching we can split text node
                                // into two nodes breaking it on position of desired text
                                // which will be inserted as a next sibling node
                                int indexOfInputText = textNode.text().indexOf(input);
                                textNode.splitText(indexOfInputText);
                                // getting the next node (the one newly created!)
                                TextNode nodeWithInput = (TextNode) textNode.nextSibling();
                                // we have to split it again in case there is more text after the input text
                                nodeWithInput.splitText(input.length());
                                // now this node contains only input text so we can wrap it with whatever you want
                                nodeWithInput.wrap("<span class=\"select-me\"></span>");
                                break;
                            }
                        }
                    }
                }
            }
            System.out.println("--------");
            System.out.println("RESULT:");
            System.out.println(doc);
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-07-31
      • 1970-01-01
      • 2012-01-21
      • 1970-01-01
      • 2020-03-09
      • 1970-01-01
      • 2014-08-24
      • 1970-01-01
      相关资源
      最近更新 更多