Как заменить элемент текстом в lxml? - PullRequest
10 голосов
/ 24 марта 2011

Легко полностью удалить данный элемент из XML-документа с помощью lxml реализации ElementTree API, но я не вижу простого способа последовательной замены элемента некоторым текстом. Например, с учетом следующего ввода:

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>
'''

... вы можете легко удалить каждый элемент <r> с помощью:

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 и т. д. Я пропустил какой-то простой способ сделать это?

Ответы [ 3 ]

16 голосов
/ 24 марта 2011

Я думаю, что решение unutbu XSLT, вероятно, является правильным способом достижения вашей цели.

Однако вот несколько хакерский способ добиться этого, изменив хвосты тегов <r/>, а затем используя 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>
7 голосов
/ 09 мая 2012

Использование strip_elements имеет тот недостаток, что вы не можете сохранить некоторые элементы <r> при замене других.Это также требует существования экземпляра ElementTree (что может быть не так).И наконец, вы не можете использовать его для замены комментариев XML или инструкций по обработке.Следующее должно делать вашу работу:

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)
3 голосов
/ 24 марта 2011

Использование 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>
...