我有一个巨大的 xml 文件(1 Gig)。我想将一些元素(条目)移动到具有相同标题和规范的另一个文件。
假设原始文件包含带有标签
<to_move>
: 的条目
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE some SYSTEM "some.dtd">
<some>
...
<to_move date="somedate">
<child>some text</child>
...
...
</to_move>
...
</some>
我使用 lxml.etree.iterparse 来迭代该文件。工作正常。当我找到带有标签
<to_move>
的元素时,我们假设它存储在变量 element
中,我这样做
new_file.write(etree.tostring(element))
但这会导致
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE some SYSTEM "some.dtd">
<some>
...
<to_move xmlns:="some" date="somedate"> # <---- Here is the problem. I don't want the namespace.
<child>some text</child>
...
...
</to_move>
...
</some>
所以问题是:如何告诉 etree.tostring() 不要写
xmlns:="some"
。这可能吗?我在 lxml.etree 的 api 文档中苦苦挣扎,但找不到满意的答案。
这是我找到的
etree.trostring
:
tostring(element_or_tree, encoding=None, method="xml",
xml_declaration=None, pretty_print=False, with_tail=True,
standalone=None, doctype=None, exclusive=False, with_comments=True)
将元素序列化为其 XML 的编码字符串表示形式 树。
对我来说
tostring()
的每一个参数似乎都没有帮助。有什么建议或者更正吗?
我经常抓住一个命名空间来为它创建一个别名,如下所示:
someXML = lxml.etree.XML(someString)
if ns is None:
ns = {"m": someXML.tag.split("}")[0][1:]}
someid = someXML.xpath('.//m:ImportantThing//m:ID', namespaces=ns)
您可以执行类似的操作来获取名称空间,以便创建一个正则表达式,该正则表达式将在使用
tostring
后清理它。
或者您可以清理输入字符串。找到第一个空格,检查其后面是否有xmlns,如果有,则删除整个xmlns位直到下一个空格,如果没有则删除该空格。重复此操作,直到不再有空格或 xmlns 声明。但不要超过第一个
>
。
这更多是对“unutbu”答案的评论,其中需要清理命名空间的建议,但没有给出示例。这可能就是您正在寻找的...
from lxml import objectify
objectify.deannotate(root, cleanup_namespaces=True)
有一种方法可以使用 XSLT 删除名称空间:
import io
import lxml.etree as ET
def remove_namespaces(doc):
# http://wiki.tei-c.org/index.php/Remove-Namespaces.xsl
xslt='''<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="no"/>
<xsl:template match="/|comment()|processing-instruction()">
<xsl:copy>
<xsl:apply-templates/>
</xsl:copy>
</xsl:template>
<xsl:template match="*">
<xsl:element name="{local-name()}">
<xsl:apply-templates select="@*|node()"/>
</xsl:element>
</xsl:template>
<xsl:template match="@*">
<xsl:attribute name="{local-name()}">
<xsl:value-of select="."/>
</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
'''
xslt_doc = ET.parse(io.BytesIO(xslt))
transform = ET.XSLT(xslt_doc)
doc = transform(doc)
return doc
doc = ET.parse('data.xml')
doc = remove_namespaces(doc)
print(ET.tostring(doc))
产量
<some>
<to_move date="somedate">
<child>some text</child>
</to_move>
</some>
这是一个古老的问题,但由于 13 年后 lxml 仍然没有内置这个明显的函数,处理它的最简单方法是使用正则表达式:
def get_text(element: etree.Element) -> str:
s = etree.tostring(element).decode()
if m := re.match(r'^<(\w*)[^>]*>((.|\n)+)<\/\1>', s):
return m.group(2).strip()
return s
这会剥离整个包含元素
<description blah blah blah><div>What we want</div><p>more stuff</p></description>
并仅返回 <description>
内的标记。为了安全起见,它会保存开始元素标记以在末尾进行匹配(使用 \1
反向引用),但寻找结束 <\
效果很好,因为贪婪匹配会跳过内容中嵌入的所有其他元素。
如果匹配失败,引发 ValueError 可能会更好,但这只会返回原始的丑陋字符串、命名空间和所有内容。