【问题标题】:lxml, xi:include, and original filelxml、xi:include 和原始文件
【发布时间】:2013-03-23 05:42:41
【问题描述】:
我正在使用 lxml 解析包含 xi:include 元素的文件,并且我正在使用 xinclude() 解析包含。
给定一个元素,有没有办法识别该元素最初出现的文件和源代码行?
例如:
from lxml import etree
doc = etree.parse('file.xml')
doc.xinclude()
xpath_expression = ...
elt = doc.xpath(xpath_expression)
# Print file name and source line of `elt` location
【问题讨论】:
标签:
python
xml
lxml
xinclude
【解决方案1】:
xinclude 扩展将为顶级扩展元素添加一个 xml:base 属性,
并且 elt.base 和 elt.sourceline 也为子节点更新,所以:
print elt.base, elt.sourceline
会给你你想要的。
如果 elt 不是 xinclude 扩展的一部分,那么 elt.base 将指向基础
document ('file.xml') 和 elt.sourceline 将是该文件中的行号。
(请注意,sourceline 通常似乎实际上指向元素标记所在的行
结束,而不是开始的那一行,如果元素在多行上,就像
验证错误消息通常指向发生错误的结束标记。 )
您可以找到最初的 xincluded 元素并通过以下方式检查:
xels = doc.xpath( '//*[@xml:base] )
for x in xels:
print x.tag, x.base, x.sourceline
for c in x.getchildren():
print c.tag, c.base, c.sourceline
【解决方案2】:
遗憾的是,当前版本的 lxml 不再包含此功能。但是,我使用简单的自定义加载程序开发了一种解决方法。这是一个测试脚本,它演示了上述方法中的错误以及解决方法。请注意,此方法仅更新包含文档的根标记的xml:base 属性。
程序的输出(使用Python 3.9.1,lxml 4.6.3):
Included file was source.xml; xinclude reports it as document.xml
Included file was source.xml; workaround reports it as source.xml
这是示例程序。
# Includes
# ========
from pathlib import Path
from textwrap import dedent
from lxml import etree as ElementTree
from lxml import ElementInclude
# Setup
# =====
# Create a sample document, taken from the `Python stdlib
# <https://docs.python.org/3/library/xml.etree.elementtree.html#id3>`_...
Path("document.xml").write_text(
dedent(
"""\
<?xml version="1.0"?>
<document xmlns:xi="http://www.w3.org/2001/XInclude">
<xi:include href="source.xml" parse="xml" />
</document>
"""
)
)
# ...and the associated include file.
Path("source.xml").write_text("<para>This is a paragraph.</para>")
# Failing xinclude case
# =====================
# Load and xinclude this.
tree = ElementTree.parse("document.xml")
tree.xinclude()
# Show that the ``base`` attribute refers to the top-level
# ``document.xml``, instead of the xincluded ``source.xml``.
root = tree.getroot()
print(f"Included file was source.xml; xinclude reports it as {root[0].base}")
# Workaround
# ==========
# As a workaround, define a loader which sets the ``xml:base`` of an
# xincluded element. While lxml evidently used to do this, a change
# eliminated this ability per some `discussion
# <https://mail.gnome.org/archives/xml/2014-April/msg00015.html>`_,
# which included a rejected patch fixing this problem. `Current source
# <https://github.com/GNOME/libxml2/blob/master/xinclude.c#L1689>`_
# lacks this patch.
def my_loader(href, parse, encoding=None, parser=None):
ret = ElementInclude._lxml_default_loader(href, parse, encoding, parser)
ret.attrib["{http://www.w3.org/XML/1998/namespace}base"] = href
return ret
new_tree = ElementTree.parse("document.xml")
ElementInclude.include(new_tree, loader=my_loader)
new_root = new_tree.getroot()
print(f"Included file was source.xml; workaround reports it as {new_root[0].base}")