【问题标题】:Why does ElementTree reject UTF-16 XML declarations with "encoding incorrect"?为什么 ElementTree 拒绝“编码不正确”的 UTF-16 XML 声明?
【发布时间】:2014-07-25 14:27:37
【问题描述】:

在 Python 2.7 中,当将 unicode 字符串传递给 XML 声明中包含 encoding="UTF-16" 的 ElementTree 的 fromstring() 方法时,我收到 ParseError 指出指定的编码不正确:

>>> from xml.etree import ElementTree
>>> data = u'<?xml version="1.0" encoding="utf-16"?><root/>'
>>> ElementTree.fromstring(data)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\Program Files (x86)\Python 2.7\lib\xml\etree\ElementTree.py", line 1300, in XML
    parser.feed(text)
  File "C:\Program Files (x86)\Python 2.7\lib\xml\etree\ElementTree.py", line 1642, in feed
    self._raiseerror(v)
  File "C:\Program Files (x86)\Python 2.7\lib\xml\etree\ElementTree.py", line 1506, in _raiseerror
    raise err
xml.etree.ElementTree.ParseError: encoding specified in XML declaration is incorrect: line 1, column 30

这是什么意思?是什么让 ElementTree 这么想?

毕竟,我传递的是 unicode 代码点,而不是字节字符串。这里不涉及编码。怎么可能不正确?

当然,有人可能会争辩说任何编码都不正确,因为这些 un​​icode 代码点没有被编码。但是,那为什么 UTF-8 不被拒绝为“错误编码”呢?

>>> ElementTree.fromstring(u'<?xml version="1.0" encoding="utf-8"?><root/>')

我可以通过将 unicode 字符串编码为 UTF-16 编码的字节字符串并将其传递给 fromstring() 或将 unicode 字符串中的 encoding="utf-16" 替换为 encoding="utf-8" 来轻松解决此问题,但我想了解为什么会引发该异常。 documentation of ElementTree 没有说明只接受字节字符串。

具体来说,我想避免这些额外的操作,因为我的输入数据可能会变得非常大,并且我想避免它们在内存中两次以及处理它们的 CPU 开销超过绝对必要。

【问题讨论】:

    标签: python-2.7 unicode encoding elementtree python-unicode


    【解决方案1】:

    我不会试图证明这种行为是正当的,而是要解释为什么它实际上会发生在所编写的代码中。

    简而言之:Python 使用的 XML 解析器 expat 对字节而不是 unicode 字符进行操作。在将字符串传递给 ElementTree.fromstring 之前,您必须在字符串上调用 .encode('utf-16-be').encode('utf-16-le')

    ElementTree.fromstring(data.encode('utf-16-be'))
    

    证明:ElementTree.fromstring 最终会调用到pyexpat.xmlparser.Parse,这是在 pyexpat.c 中实现的:

    static PyObject *
    xmlparse_Parse(xmlparseobject *self, PyObject *args)
    {
        char *s;
        int slen;
        int isFinal = 0;
    
        if (!PyArg_ParseTuple(args, "s#|i:Parse", &s, &slen, &isFinal))
            return NULL;
    
        return get_parse_result(self, XML_Parse(self->itself, s, slen, isFinal));
    }
    

    因此,您传入的 unicode 参数将使用 s# 进行转换。 docsPyArg_ParseTuple 说:

    s#(字符串、Unicode 或任何读取缓冲区兼容对象)[const char *, int (or Py_ssize_t, 见下文)] s 上的这个变体存储到两个 C 变量中,第一个是指向字符串的指针,第二个 一个它的长度。在这种情况下,Python 字符串可能包含嵌入的 空字节。 Unicode 对象传回一个指向默认编码的指针 如果可以进行此类转换,则为对象的字符串版本。全部 其他与读取缓冲区兼容的对象传回对原始数据的引用 内部数据表示。

    让我们来看看:

    from xml.etree import ElementTree
    data = u'<?xml version="1.0" encoding="utf-8"?><root>\u2163</root>'
    print ElementTree.fromstring(data)
    

    给出错误:

    UnicodeEncodeError: 'ascii' codec can't encode character u'\u2163' in position 44: ordinal not in range(128)
    

    这意味着当您指定 encoding="utf-8" 时,您很幸运,当 Unicode 字符串被编码为 ASCII 时,您的输入中没有非 ASCII 字符。如果您在解析之前添加以下内容,则 UTF-8 在该示例中按预期工作:

    import sys
    reload(sys).setdefaultencoding('utf8')
    

    但是,将 defaultencoding 设置为 'utf-16-be' 或 'utf-16-le' 不起作用,因为 ElementTree 的 Python 位执行直接字符串比较,这在 UTF-16 领域开始失败.

    【讨论】:

    • 感谢您的广泛分析!听起来像 ET 文档的补丁提交是有序的。 :)
    • 如何从文件而不是字符串中使用它?
    猜你喜欢
    • 1970-01-01
    • 2012-11-07
    • 2022-01-23
    • 2012-03-16
    • 1970-01-01
    • 2011-06-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多