Исходя из вашего вопроса и комментариев, похоже, что вы хотите изменить только прямые текстовые узлы выбранного элемента без изменения текстового узла потенциальных внутренних элементов выбранного текста, поэтому в случае
<div>a b <span>b c</span></div>
, если мыхотим изменить b
мы изменяем только один, непосредственно помещенный в <div>
, но не один в <span>
.
<div>a b <span>b c</span></div>
^ ^----don't modify because it is in <span>, not *directly* in <div>
|
modify
Текст не рассматривается как ElementNode
как <div>
<span>
и т. д.,но в DOM это представляется как TextNode
, поэтому, если у нас есть структура, подобная <div> a <span>b</span> c </div>
, тогда ее представление в DOM будет
Element: <div>
├ Text: " a "
├ Element: <span>
│ └ Text: "b"
└ Text: " c "
Если мы хотим обернуть часть некоторого текста в<span>
(или любой другой тег) мы фактически разделяем один TextNode
├ Text: "foo bar baz"
на серии:
├ Text: "foo "
├ Element: <span>
│ └ Text: "bar"
└ Text: " baz"
Чтобы создать решение, использующее эту идею TextNode API предоставляет нам очень ограниченный набор инструментов, но среди доступных методов мы можем использовать
splitText(index)
, который изменяет оригинальный TextNode, оставляя в нем "левую" сторону разделения ивозвращает новый TextNode, который содержит rОставшаяся (правая) сторона разделения, как если TextNode node1
удерживает "foo bar"
после TextNode node2 = node1.splitText(3);
node1
, будет удерживать "foo"
, в то время как node2
будет удерживать " bar"
и будет помещена как непосредственный брат после node1
wrap(htmlElement)
(унаследованный от Node
суперкласса), который оборачивает TextNode в ElementNode, представляющий htmlElement
, например node.wrap("<span class='myClass'>")
, приведет к <span class='myClass>text from node</span>
.
С помощью вышеупомянутых «инструментов» мы можем создать метод, подобный
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>