如何使用 `lxml` 将所有出现的标签更改为特定文本?

问题描述 投票:0回答:2

我自制的解决方案可能是:

import lxml.etree as ET
def tag2text(node, sar):
    """Replace element in `sar.keys()` to text in `sar.values()`."""
    for elem, text in sar.items():
        for ph in node.xpath(f'.//{elem}'):
            ph.tail = text + ph.tail if ph.tail is not None else text
        ET.strip_elements(node, elem, with_tail=False)

上面的解决方案在工作:

xml = ET.fromstring("""<root><a><c>111</c>
    <b>sdfsf<c>111</c>ddd</b>fff</a>
    <c>111</c><c/><emb><c><c>EMB</c></c></emb></root>""")
tag2text(xml, {'c': '[C]'})

转换此输入:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <a><c>111</c><b>sdfsf<c>111</c>ddd</b>fff</a>
  <c>111</c>
  <c/>
  <emb>
    <c>
      <c>EMB</c>
    </c>
  </emb>
</root>

进入这个输出:

<?xml version="1.0" encoding="utf-8"?>
<root>
  <a>[C]<b>sdfsf[C]ddd</b>fff</a>
  [C]
  [C]
  <emb>[C]</emb>
</root>

看起来不错。

任何更好、更简单、更高效、更 lxml-ic 或更 pythonic 的解决方案?

python lxml
2个回答
0
投票

使用

iter
和单遍替换:

for node in xml.iter(sar):
    r = sar[node.tag] + (node.tail or '')
    if (p := node.getprevious()) is not None:
        p.tail = (p.tail or '') + r
    else:
        p = node.getparent()
        p.text = (p.text or '') + r
    node.getparent().remove(node)

使用

iterparse
并在构建时更新树:

ctx = iterparse(BytesIO(doc), tag= sar)
for _, node in ctx:
    r = sar[node.tag] + (node.tail or '') 
    if (p := node.getprevious()) is not None:
        p.tail = (p.tail or '') + r
    else:
        p = node.getparent()
        p.text = (p.text or '') + r
    node.getparent().remove(node)

几乎唯一的区别是迭代部分。两者都比原来的更复杂,因为它们基本上必须手动完成

strip_elements
的工作。

两者产生相同的结果:

<root>
  <a>[C]<b>sdfsf[C]ddd</b>fff</a>
  [C]
  [C]
  <emb>
    [C]
  </emb>
</root>

0
投票

XML 转换可以由 XSLT 处理,saxonche 有一个用于最新版本 XSLT 3 的 Python 包,它允许基于 XPath 的评估,因此 Python/XSLT 3 版本将是:

from saxonche import *

xslt = '''
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  version="3.0"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  xmlns:map="http://www.w3.org/2005/xpath-functions/map"
  exclude-result-prefixes="#all"
  expand-text="yes">
  
  <xsl:param name="replacement-map" static="yes" as="map(xs:string, xs:string)" select="map { 'c' : '[C]' }"/>

  <xsl:param name="elements-to-replace" as="element()*">
    <xsl:evaluate context-item="." xpath="(($replacement-map => map:keys()) ! ('//' || .)) => string-join(' | ')"/>
  </xsl:param>
  
  <xsl:template match="$elements-to-replace">
    <xsl:sequence select="$replacement-map(local-name())"/>
  </xsl:template>
  
  <xsl:mode on-no-match="shallow-copy"/>

</xsl:stylesheet>
'''
with PySaxonProcessor(license=False) as saxon:

    xslt30_processor = saxon.new_xslt30_processor()

    xslt30_executable = xslt30_processor.compile_stylesheet(stylesheet_text=xslt)

    xslt30_executable.set_cwd('.')

    xslt30_executable.transform_to_file(source_file='sample1.xml', output_file='result1.xml')

我意识到除了 lxml 之外的另一个包不符合主题使用 lxml 的要求,但我认为对于大多数 XML 转换来说值得考虑 XSLT。

© www.soinside.com 2019 - 2024. All rights reserved.