使用 lxml 标记文本的一部分

Using lxml to tag parts of a text

我正在使用 python lxml 库与 XML 合作。

我有一段文字是这样的,

<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer facilisis elit eget
condimentum efficitur. Donec eu dignissim lectus. Integer tortor
lacus, porttitor at ipsum quis, tempus dignissim dui. Curabitur cursus
quis arcu in pellentesque. Aenean volutpat, tortor a commodo interdum,
lorem est convallis dui, sodales imperdiet ligula ligula non felis.</p>

假设我想标记一个特定的文本位,例如“tortor lacus, porttitor at ipsum quis, tempus”,它存在于上面的段落中,带有标记。我将如何使用 lxml 执行此操作。现在我正在使用文本替换,但我觉得这不是解决这个问题的正确方法。

即我正在寻找的结果是

<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer facilisis elit eget
condimentum efficitur. Donec eu dignissim lectus. Integer <foobar>tortor
lacus, porttitor at ipsum quis, tempus</foobar> dignissim dui. Curabitur cursus 
quis arcu in pellentesque. Aenean volutpat, tortor a commodo interdum,
lorem est convallis dui, sodales imperdiet ligula ligula non felis.</p>

有没有children:

可以这样查
from lxml import etree

root = etree.parse("test.xml").getroot()
paragraphs = root.findall("p")

print(f"Found {len(paragraphs)} paragraphs")

for i in range(len(paragraphs)):
    if len(list(paragraphs[i])) > 0:
        print(f"Paragraph {i} has children")
    else:
        print(f"Paragraph {i} has no children")

首先代码过滤所有段落,然后查看段落是否有 children.

现在如果你没有 children 你可以像以前一样替换文本,如果你有 children 你可以替换整个 child

如果 <p> 标签不会嵌套在另一个 <p> 中,您可以考虑使用正则表达式替换

import re

a="""
other lines here that may contain foo
<p>
this is a foo inside para
and this is new line in this foo para
</p>
excess lines here that also may contain foo in it.
"""

search="foo"
newtagname="bar"

b=re.sub("("+search+")(?=[^><]*?</p>)","<"+newtagname+">\1</"+newtagname+">",a)

print(b)

这会打印

other lines here that may contain foo
<p>
this is a <bar>foo</bar> inside para
and this is new line in this <bar>foo</bar> para
</p>
excess lines here that also may contain foo in it.

用实际元素替换文本在 lxml 中很棘手;特别是如果你有混合内容(文本和子元素的混合)。

棘手的部分是知道如何处理剩余的文本以及在何处插入元素。剩余的文本应该是父 .text 的一部分吗?它应该是前一个兄弟姐妹的 .tail 的一部分吗?它应该是新元素 .tail 的一部分吗?

我过去所做的是处理所有 text() 节点并将占位符字符串添加到文本(无论是 .text 还是 .tail)。然后我将树序列化为一个字符串,并在占位符上进行搜索和替换。之后,我要么将字符串解析为 XML 以构建一棵新树(用于进一步处理、验证、分析等),要么将其写入文件。

请参阅我的 /answer 以获取有关此上下文中 .text/.tail 的更多信息。

这是一个基于我在上述问题中的回答的示例。

备注:

  • 我添加了 gotcha 元素来展示它如何处理混合内容。
  • 我添加了第二个搜索字符串 (Aenean volutpat) 以显示替换多个字符串。
  • 在这个例子中,我只处理作为 p.
  • 子节点的 text() 节点

Python

import re
from lxml import etree

xml = """<doc>
<p>Lorem ipsum dolor <gotcha>sit amet</gotcha>, consectetur adipiscing elit. Integer facilisis elit eget
condimentum efficitur. Donec eu dignissim lectus. Integer tortor
lacus, porttitor at ipsum quis, tempus dignissim dui. Curabitur cursus
quis arcu <gotcha>in pellentesque</gotcha>. Aenean volutpat, tortor a commodo interdum,
lorem est convallis dui, sodales imperdiet ligula ligula non felis.</p>
</doc>
"""


def update_text(orig_text, phrase_list, elemname):
    new_text = orig_text
    for phrase in phrase_list:
        if phrase in new_text:
            # Add placeholders for the new start/end tags.
            new_text = new_text.replace(phrase, f"[elemstart:{elemname}]{phrase}[elemend:{elemname}]")
        else:
            new_text = new_text
    return new_text


root = etree.fromstring(xml)

foobar_phrases = {"tortor lacus, porttitor at ipsum quis, tempus", "Aenean volutpat"}

for text in root.xpath("//p/text()"):
    parent = text.getparent()
    updated_text = update_text(text.replace("\n", " "), foobar_phrases, "foobar")
    if text.is_text:
        parent.text = updated_text
    elif text.is_tail:
        parent.tail = updated_text

# Serialze the tree to a string so we can replace the placeholders with proper tags.
serialized_tree = etree.tostring(root, encoding="utf-8").decode()
serialized_tree = re.sub(r"\[elemstart:([^\]]+)\]", r"<>", serialized_tree)
serialized_tree = re.sub(r"\[elemend:([^\]]+)\]", r"</>", serialized_tree)

# Now we can either parse the string back into a tree (for additional processing, validation, etc.),
# print it, write it to a file, etc.
print(serialized_tree)

打印输出(添加换行符以提高可读性)

<doc>
<p>Lorem ipsum dolor <gotcha>sit amet</gotcha>, consectetur adipiscing elit. 
Integer facilisis elit eget condimentum efficitur. Donec eu dignissim lectus.
Integer <foobar>tortor lacus, porttitor at ipsum quis, tempus</foobar> dignissim dui.
Curabitur cursus quis arcu <gotcha>in pellentesque</gotcha>. <foobar>Aenean volutpat</foobar>, 
tortor a commodo interdum, lorem est convallis dui, sodales imperdiet ligula ligula non felis.</p>
</doc>