【问题标题】:How does ruamel.yaml determine the encoding of escaped byte sequences in a string?ruamel.yaml 如何确定字符串中转义字节序列的编码?
【发布时间】:2019-08-22 22:25:44
【问题描述】:

我无法确定在哪里修改或配置 ruamel.yaml 的加载程序,以使其以正确的编码解析一些旧的 YAML。问题的本质是文档中的转义字节序列似乎被解释为 latin1,在经过一些源代码潜水here 之后,我不知道它在哪里执行此操作。这是一个演示该行为的代码示例(特别是在 Python 3.6 中运行):

from ruamel.yaml import YAML
yaml = YAML()
yaml.load('a:\n  b: "\\xE2\\x80\\x99"\n')  # Note that this is a str (that is, unicode) with escapes for the byte escapes in the YAML document
# ordereddict([('a', ordereddict([('b', 'â\x80\x99')]))])

这里是手动解码的相同字节,只是为了显示它应该解析到什么:

>>> b"\xE2\x80\x99".decode('utf8')
'’'

请注意,我对源文档没有任何控制权,因此修改它以使用 ruamel.yaml 生成正确的输出是不可能的。

【问题讨论】:

    标签: python-3.x yaml ruamel.yaml


    【解决方案1】:

    ruamel.yaml 不解释单个字符串,它解释 流它被处理,即.load()的参数。如果说 参数是字节流或类似对象的文件,则其编码为 根据 BOM 确定,默认为 UTF-8。但又一次:那是 在流级别,而不是在单个标量内容之后 解释逃逸。既然你手 .load() Unicode (因为这是 Python 3)“流”不需要进一步解码。 (虽然 与这个问题无关:它是在reader.py:Reader 方法streamdetermine_encoding)

    十六进制转义(\xAB 形式)只会放置一个特定的十六进制 加载器用来构造标量的类型中的值,即 键“b”的值,这是一个普通的 Python 3 str,即 Unicode 在 其内部表示之一。你得到â 在你的 输出是因为您的 Python 配置为如何对其进行解码str 类型。

    所以你不会“找到”ruamel.yaml 解码的地方 字节序列,因为它已经被假定为 Unicode。

    所以要做的是双重解码你的双引号 标量(您只需将它们作为简单的单引号, 文字/折叠标量不能有十六进制转义)。有各种 您可以尝试这样做的点,但我认为 constructor.py:RoundTripConsturtor.construct_scalarscalarstring.py:DoubleQuotedScalarString 是最佳人选。前者可能需要一些挖掘才能找到,但后者实际上是你检查时会得到的类型 添加选项以保留引号时加载后的该字符串:

    yaml = ruamel.yaml.YAML()
    yaml.preserve_quotes = True
    data = yaml.load('a:\n  b: "\\xE2\\x80\\x99"\n')
    print(type(data['a']['b']))
    

    哪个打印:

    <class 'ruamel.yaml.scalarstring.DoubleQuotedScalarString'>
    

    知道你可以检查那个相当简单的包装类:

    class DoubleQuotedScalarString(ScalarString):
        __slots__ = ()
    
        style = '"'
    
        def __new__(cls, value, anchor=None):
            # type: (Text, Any) -> Any
            return ScalarString.__new__(cls, value, anchor=anchor)
    

    “更新”那里唯一的方法 (__new__) 做你的双重 编码(您可能必须进行额外的检查才能不对所有编码进行双重编码 双引号标量0:

    import sys
    import codecs
    import ruamel.yaml
    
    def my_new(cls, value, anchor=None):
        # type information only needed if using mypy
        # value is of type 'str', decode to bytes "without conversion", then encode
        value = value.encode('latin_1').decode('utf-8') 
        return ruamel.yaml.scalarstring.ScalarString.__new__(cls, value, anchor=anchor)
    
    ruamel.yaml.scalarstring.DoubleQuotedScalarString.__new__ = my_new
    
    yaml = ruamel.yaml.YAML()
    yaml.preserve_quotes = True
    data = yaml.load('a:\n  b: "\\xE2\\x80\\x99"\n')
    print(data)
    

    给出:

    ordereddict([('a', ordereddict([('b', '’')]))])
    

    【讨论】:

    • 感谢您的回复。我应该明确表示我不认为 ruamel.yaml 正在解码流(显然不是),但我正在寻找流处理后阶段中发生这种情况的位置。我看到了 DoubleQuotedScalarString 类型,但我无法将解释器级别发生的事情拼凑起来导致编码不正确,因此我感谢您将其深入到 __new__ 函数。不过,一个问题仍然存在:为什么 value 在这个构造函数处被解码为 'latin1'?
    • 到目前为止,我已经在扫描仪中回溯到这一点:bitbucket.org/ruamel/yaml/src/…,我将沿着这条路走得更远,看看是否可以从那里解决。
    • 所以。我所做的是到达here,它显示 Ruamel 将每个 x 解释为一个 unicode 字符。这使我找到了here,它指出 \x 表示一个 8 位 Unicode 序列,这意味着 Ruamel 的行为是正确的,并且无论插入此 YAML 的内容都是错误的。我接受了这个答案,它完成了工作并引导我走上发现之路,让我最终弄清楚发生了什么。谢谢!
    • 我想我没有从头到尾阅读您的评论。很抱歉。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-10-06
    • 2014-02-22
    • 2019-07-18
    • 1970-01-01
    • 2011-01-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多