Заменить строку jsoup только в текстовых частях

Я нашел несколько тем с похожими вопросами и ценными ответами, но я все еще борюсь с этим:

Я хочу проанализировать некоторый html с Jsoup, чтобы заменить, например,

"changeme"

с

<changed>changeme</changed>

но только если он отображается в текстовой части html, нет, если он является частью тега. Итак, начиная с этого html:

<body>
<p><a href="#" onclick="location.href='http://changeme.html'; return false;">test changeme app</a></p>
</BODY>
</HTML>

Я хотел бы получить следующее:

<body>
<p><a href="#" onclick="location.href='http://changeme.html'; return false;">test <changed>changeme</changed> app</a></p>
</BODY>
</HTML>

Я пробовал несколько подходов, и это приближает меня к желаемому результату:

Document doc = null;
try {
    doc = Jsoup.parse(new File("tmp1450348256397.txt"), "UTF-8");
} catch (Exception ex) {
}

Elements els = doc.body().getAllElements();
for (Element e : els) {
    if (e.text().contains("changeme")) {
        e.html(e.html().replaceAll("changeme","<changed>changeme</changed>"));
    }
}
html = doc.toString();
System.out.println(html);

Но при таком подходе я нахожу две проблемы:

<body>
<p><a href="#" onclick="location.href='http://<changed>changeme</changed> .html'; return false;">test
    <changed>
        changeme
    </changed> 
app</a></p>
</BODY>
</HTML>
  • Разрывы строк вставляются до и после нового элемента, который я представляю. Это не настоящая проблема, поскольку я могу избавиться от них, если я использую # changed # для замены и после doc.toString() я снова их заменю на нужное значение (с помощью < > ).

  • Реальная проблема: URL-адрес в href был изменен, и я не хочу, чтобы это произошло.

Идеи? спасибо.

Ответ 1

Вот мое решение:

String html=""
    +"<p><a href=\"http://changeme.html\">"
    +   "test changeme "
    +   "<div class=\"changeme\">"
    +     "inner text changeme"
    +   "</div>"
    +   " app</a>"
    +"</p>";
Document doc = Jsoup.parse(html);
Elements els = doc.body().getAllElements();
for (Element e : els) {
    List<TextNode> tnList = e.textNodes();
    for (TextNode tn : tnList){
        String orig = tn.text();
        tn.text(orig.replaceAll("changeme","<changed>changeme</changed>")); 
    }
}

html = doc.toString();
System.out.println(html);

TextNodes - это всегда листовые узлы, т.е. они не содержат больше элементов HTML. В вашем первоначальном подходе вы заменяете HTML-код элемента новым HTML с замененными строками changme. Вы проверяете, что changeme является частью содержимого TextNodes, но вы заменяете каждое вхождение в HTML-строку элемента, включая все вхождения вне TextNodes.

Мое решение в основном работает как ваше, но я использую метод JSoup textNodes(). Таким образом, мне не нужно вводить тип.

P.S. Конечно, мое решение, а также ваше будет содержать &lt;changed&gt;changeme&lt;/changed&gt; вместо <changed>changeme</changed> в конце. Это может быть или не быть тем, что вы хотите. Если вы этого не хотите, то ваш результат не является более допустимым HTML, так как changed не является допустимым тегом HTML. Jsoup не поможет вам в этом случае. Тем не менее, вы можете, конечно, заменить в результирующей строке все &lt;changed&gt;changeme&lt;/changed&gt; снова - вне JSoup.

Ответ 2

Я думаю, что ваша проблема в том, что вы заменяете элементы html, а не только его текст, меняете:

e.html(e.html().replaceAll("changeme","<changed>changeme</changed>"));

к

e.text(e.text().replaceAll("changeme","<changed>changeme</changed>"));

проблема с разрывами строк, вероятно, может быть решена путем выполнения doc.outputSettings().prettyPrint(false); перед выполнением html = doc.toString();

Ответ 3

Наконец, я попробовал это решение (в конце вопроса), используя TextNodes:

Как я могу заменить текст и quot; в каждом теге с использованием Jsoup

Это результирующий код:

Elements els = doc.body().getAllElements();
for (Element e : els) {
    for (Node child : e.childNodes()){
        if (child instanceof TextNode && !((TextNode) child).isBlank()) {
            ((TextNode)child).text(((TextNode)child).text().replaceAll("changeme","<changed>changeme</changed>"));
        }
    }
}   

Теперь вывод ожидается, и он даже не вводит дополнительные строки прерывания. В этом случае для параметра prettyPrint должно быть установлено значение True.

Единственная проблема заключается в том, что я не совсем понимаю разницу в использовании TextNode vs Element.text(). Если кто-то хочет предоставить некоторую информацию, он будет очень признателен.

Спасибо.