【问题标题】:How can one replace an element with text in lxml?如何用 lxml 中的文本替换元素?
【发布时间】:2011-07-22 01:13:53
【问题描述】:

使用 lxml 的 ElementTree API 实现可以很容易地从 XML 文档中完全删除给定元素,但是我看不到用一些文本一致地替换元素的简单方法。例如,给定以下输入:

input = '''<everything>
<m>Some text before <r/></m>
<m><r/> and some text after.</m>
<m><r/></m>
<m>Text before <r/> and after</m>
<m><b/> Text after a sibling <r/> Text before a sibling<b/></m>
</everything>
'''

...您可以使用以下命令轻松删除每个 &lt;r&gt; 元素:

from lxml import etree
f = etree.fromstring(data)
for r in f.xpath('//r'):
    r.getparent().remove(r)
print etree.tostring(f, pretty_print=True)

但是,您将如何用文本替换每个元素以获取输出:

<everything>
<m>Some text before DELETED</m>
<m>DELETED and some text after.</m>
<m>DELETED</m>
<m>Text before DELETED and after</m>
<m><b/>Text after a sibling DELETED Text before a sibling<b/></m>
</everything>

在我看来,因为 ElementTree API 通过每个元素的 .text.tail 属性而不是树中的节点来处理文本,这意味着您必须处理很多不同的情况,具体取决于是否该元素是否具有兄弟元素,现有元素是否具有.tail 属性,等等。我是否错过了一些简单的方法?

【问题讨论】:

  • 如果&lt;r/&gt; 有孩子,您是否也要删除这些孩子?还是合并到&lt;r/&gt; 的父级?
  • 在这种情况下,我只想删除 &lt;r&gt; 节点及其所有子节点,并将其替换为文本字符串。希望这更容易:)

标签: python xml lxml elementtree


【解决方案1】:

我认为 unutbu 的 XSLT 解决方案可能是实现您目标的正确方法。

然而,这里有一个有点老套的方法来实现它,通过修改&lt;r/&gt; 标签的尾部,然后使用etree.strip_elements

from lxml import etree

data = '''<everything>
<m>Some text before <r/></m>
<m><r/> and some text after.</m>
<m><r/></m>
<m>Text before <r/> and after</m>
<m><b/> Text after a sibling <r/> Text before a sibling<b/></m>
</everything>
'''

f = etree.fromstring(data)
for r in f.xpath('//r'):
  r.tail = 'DELETED' + r.tail if r.tail else 'DELETED'

etree.strip_elements(f,'r',with_tail=False)

print etree.tostring(f,pretty_print=True)

给你:

<everything>
<m>Some text before DELETED</m>
<m>DELETED and some text after.</m>
<m>DELETED</m>
<m>Text before DELETED and after</m>
<m><b/> Text after a sibling DELETED Text before a sibling<b/></m>
</everything>

【讨论】:

  • 谢谢,这是一个不错的解决方案 - 我不知道 strip_elementswith_tail optino
  • 想坚持使用 lxml 进行 html 处理。不过可能会改用 Beautifulsoup,它对于基本的 html 修改要直观得多,并且可以使用 lxml 作为解析器...soup = BeautifulSoup(text, "lxml") / soup.find_all('r').replace_with('DELETED')
  • 感谢@benzkij 的提示!太奇怪了,文本有时被视为 ElementTree API 中其他节点的尾部,而不仅仅是 xml 预期的普通文本节点。
【解决方案2】:

使用strip_elements 的缺点是你不能让它保留一些&lt;r&gt; 元素同时替换其他元素。它还要求存在ElementTree 实例(可能不是这种情况)。最后,您不能使用它来替换 XML cmets 或处理指令。 以下应该做你的工作:

for r in f.xpath('//r'):
    text = 'DELETED' + r.tail 
    parent = r.getparent()
    if parent is not None:
        previous = r.getprevious()
        if previous is not None:
            previous.tail = (previous.tail or '') + text
        else:
            parent.text = (parent.text or '') + text
        parent.remove(r)

【讨论】:

  • 我认为text = 'DELETED' + r.tail 应该是text = 'DELETED' + r.tail if r.tail else 'DELETED'
【解决方案3】:

使用ET.XSLT

import io
import lxml.etree as ET

data = '''<everything>
<m>Some text before <r/></m>
<m><r/> and some text after.</m>
<m><r/></m>
<m>Text before <r/> and after</m>
<m><b/> Text after a sibling <r/> Text before a sibling<b/></m>
</everything>
'''

f=ET.fromstring(data)
xslt='''\
    <xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform">    

    <!-- Replace r nodes with DELETED
         http://www.w3schools.com/xsl/el_template.asp -->
    <xsl:template match="r">DELETED</xsl:template>

    <!-- How to copy XML without changes
         http://mrhaki.blogspot.com/2008/07/copy-xml-as-is-with-xslt.html -->    
    <xsl:template match="*">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    <xsl:template match="@*|text()|comment()|processing-instruction">
        <xsl:copy-of select="."/>
    </xsl:template>
    </xsl:stylesheet>
'''

xslt_doc=ET.parse(io.BytesIO(xslt))
transform=ET.XSLT(xslt_doc)
f=transform(f)

print(ET.tostring(f))

产量

<everything>
<m>Some text before DELETED</m>
<m>DELETED and some text after.</m>
<m>DELETED</m>
<m>Text before DELETED and after</m>
<m><b/> Text after a sibling DELETED Text before a sibling<b/></m>
</everything>

【讨论】:

  • +1 这是一个很好的,但非常不明显的答案:) 这个问题出现在我身上是因为我的answer to another question 不足,我希望有比这更简单的方法。即使有这样一个简短的示例,与我的问题中仅删除元素的代码相比,XSLT 也很冗长且难以理解。
猜你喜欢
  • 2015-08-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-10-16
  • 2015-11-16
  • 2011-10-11
  • 2013-01-15
  • 1970-01-01
相关资源
最近更新 更多