为什么 lxml.etree.SubElement() 允许创建不可序列化的元素?

The*_*mis 6 python xml lxml xml-namespaces

from lxml import etree

element1 = etree.Element('{j:a}a', nsmap={None: 'j:a'})
etree.SubElement(element1, 'b')

element2 = etree.Element('{j:a}a', nsmap={None: 'j:a'})
etree.SubElement(element2, '{j:a}b')
Run Code Online (Sandbox Code Playgroud)

两个元素序列化为相同的

<a xmlns="j:a"><b/></a>

但两个元素的行为不同

element1.find('b')-> 返回元素

element2.find('b')-> 返回无

如果你反过来做

etree.fromstring("<a xmlns="j:a"><b/></a>")

你从 element2 得到表示,所以

element2.find('b')-> 返回无

<b/>这看起来是一致的,因为树中没有无名称空间,因为<b/>继承了默认名称空间<a/>

那么 element1 中表示的目的是什么?它似乎添加了一个无名称空间子元素<b/>并以这种方式运行。但是当序列化时,元素继承自<a>.

如果它无论如何都不序列化,为什么会存在?

Val*_*ino 1

这一切都归结为命名空间

xml 标签可以(但不得)具有命名空间。所以即使根节点定义了默认命名空间,子节点也允许没有命名空间,这并不等同于在默认命名空间中。

element1这是你的和element2:element1的子元素没有命名空间之间的区别;element2的子元素位于默认命名空间中,因为在创建它时指定了默认命名空间。如果你试试

element2.find("{j:l}b"))-> 返回 element b,或者更准确地说,返回 element {j:a}b

所以是的,命名空间很重要。当您使用 lxml 创建元素时,您可以定义没有名称空间的元素:只是不添加它。

但是序列化呢?

现在我不是 lxml 专家,所以这只是我的猜测。问题是,当您序列化元素时,无法区分真正没有命名空间的元素和默认命名空间中的元素,因此它们以相同的方式表示。

因此,序列化一个元素然后再次解析它,无法给出原始结果。例如,如果使用您的element1您执行以下操作:

sel1 = etree.tostring(element1)
element1s = etree.fromstring(sel1)
Run Code Online (Sandbox Code Playgroud)

事实证明element1s不等于element1,因为子元素b现在是 subelement {j:a}b。解析字符串时,没有命名空间的元素将添加到默认命名空间。

结论

现在,我不知道这是有意为之还是一个错误。据我所知,如果 XML 文档声明了默认命名空间,则所有未明确具有不同命名空间的元素都应被视为默认命名空间。当您使用该函数解析 xml 文档时就会发生这种情况fromstring。仅当未声明默认命名空间时,才可以使用“无命名空间”。
因此,在我看来,您的b子元素element1应该“继承”父节点的名称空间,因为父节点使用 定义了默认名称空间nsmap={None: "j:a"}
但您也可能会被告知,由于您正在使用 lxml 元素构建文档,因此您有责任将每个元素放入正确的命名空间中,这意味着您必须显式添加默认命名空间。

由于在某些情况下 xml 允许没有名称空间的元素,因此当元素没有名称空间时,lxml 不会抱怨。
我认为将默认命名空间自动添加到声明默认命名空间的元素的子元素将是一个很酷的功能,但它并不存在。

  • *“没有办法区分真正没有命名空间的元素和默认命名空间中的元素,因此它们以相同的方式表示。”* - 不,这绝对不是真的。默认命名空间中的元素位于命名空间中。元素 `&lt;foo:element xmlns:foo="some_ns_uri" /&gt;` 和 `&lt;element xmlns="some_ns_uri" /&gt;` 在语义上无法区分。但是 `&lt;element xmlns="some_ns_uri" /&gt;` 和 `&lt;element /&gt;` 是*完全不同的东西*。 (3认同)
  • 但总的来说,你是对的。这显然至少是 lxml 中的一个疏忽,我认为它属于一个错误。XML 中不能有任何元素*既*不声明自己的命名空间,也不继承其作用域的默认命名空间。解析 `&lt;a xmlns="foo"&gt;&lt;b /&gt;&lt;/a&gt;` 和手动构建相同内容之间 lxml 行为的不对称性表明有些事情是不对的。 (2认同)
  • [实现`SubElement`的代码片段](https://github.com/lxml/lxml/blob/ea954da3c87bd8f6874f6bf4203e2ef5269ea383/src/lxml/apihelpers.pxi#L163)没有显示查找和添加父级默认命名空间的迹象。它似乎只查看给定的“nsmap”和给定的前缀。(但我感觉他们的观点是 lxml API 中的所有元素名称都应该是完全限定的。因此,当您给出“b”,并且真正的意思是“{j:a}b”时,那么它就是您的自己的过错。) (2认同)