【问题标题】:Why does lxml.etree.SubElement() allow making elements which are not serialisable?为什么 lxml.etree.SubElement() 允许制作不可序列化的元素?
【发布时间】:2021-12-21 14:32:00
【问题描述】:
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')

两个元素都序列化为相同的

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

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

【问题讨论】:

  • 你使用的是哪个lxml版本?
  • lxml 版本为 4.6.3
  • 您认为 lxml 不应该允许的究竟是什么?你期待什么?
  • @mzjn 预期的行为是所有未指定自己的命名空间的元素都将继承在其位置有效的默认命名空间。这就是 lxml 的文档解析的工作方式(根据规范),但它不是 lxml 的文档生成的工作方式。这至少是“令人惊讶的 API 行为”,它会生成不存在且行为不正常的文档树。在我的书中,这被认为是一个错误。
  • @mzjn 这当然是在这个问题上的一个立场,但我认为这是从“API 实现者的便利”的角度来看的。这样做当然更容易。但是元素要么在命名空间中,要么不在。没有歧义需要解决。在第一种情况下,正确的做法是生成<a xmlns="j:a"><b xmlns="" /></a>。这将是一致的,它会生成一个合法的 XML 树,它在解析后会表现相同,并且它会立即表明 API 需要像 .SubElement(element2, '{j:a}b') 中的显式命名空间。

标签: python xml lxml xml-namespaces


【解决方案1】:

这一切都归结为命名空间

xml 标签可以(但不能)有命名空间。所以即使根节点定义了默认命名空间,子节点也可以没有命名空间,这不等价在默认命名空间中。

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

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

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

但是序列化呢?

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

因此,序列化一个元素然后再次解析它,无法给出原始结果。例如,如果使用您的element1,您会这样做:

sel1 = etree.tostring(element1)
element1s = etree.fromstring(sel1)

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

结论

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

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

【讨论】:

  • “没有办法区分真正没有命名空间的元素和默认命名空间中的元素,因此它们以相同的方式表示。” - 不,那是绝对不是真的。默认命名空间中的元素位于命名空间中。元素 <foo:element xmlns:foo="some_ns_uri" /><element xmlns="some_ns_uri" /> 在语义上无法区分。但是<element xmlns="some_ns_uri" /><element />完全不同的东西
  • 但总的来说,你是对的。这显然至少是 lxml 中的一个疏忽,我会说它有资格作为一个错误。 XML 中的元素不能 不声明自己的命名空间,也不继承其作用域的默认命名空间。 lxml 在解析 <a xmlns="foo"><b /></a> 和手动构建相同的东西之间的不对称行为表明有些事情是不对的。
  • piece of code that implements SubElement 没有显示查找和添加父级默认命名空间的迹象。它似乎只查看给定的nsmap 和给定的前缀。 (但我感觉他们的观点是,lxml API 中的所有元素名称都应该是完全限定的。所以当你给b,真正的意思是{j:a}b,那么这是你自己的错。)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-01-09
  • 2015-12-11
  • 2017-02-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多