python-3.x 在ruamel中往返时按原样保留多行字符串

siotufzp  于 2023-03-04  发布在  Python
关注(0)|答案(1)|浏览(92)

假设我有这样一个文件

test:
    long: "This is a sample text
      across two lines."

当我加载该文件并将其转储回来而不对文件进行任何更改时,它会将此文档更改为

test:
    long: "This is a sample text\
      \ across two lines."

虽然这是正确的,并且不会改变实际值,但是对于大型YAML文件,这会产生很多差异,并且很难查看有效的值。
这是我目前使用的代码

import sys
import ruamel.yaml
from pathlib import Path

yaml = ruamel.yaml.YAML()  # defaults to round-trip
yaml.allow_duplicate_keys = True
yaml.preserve_quotes = True
yaml.explicit_start = True
file_name = "ca.yml"

with open(file_name) as fp:
    data = yaml.load(fp)

with open(file_name, 'w') as fp:
    yaml.dump(data, fp)

有人能帮助我了解是否有一些设置,我将能够使用来实现这一点?或者在情况下,它是不可能的任何变通办法来做同样的。

yftpprvb

yftpprvb1#

我无法重新创建输出,所以似乎缺少了一些东西。在我的测试中,反斜杠丢失了,这是我预料到的,因为我不记得有特殊的代码来处理双引号标量中的换行符,AFAICT只为折叠块样式标量添加,但这不是问题所在。
有几件事对我来说很奇怪:

  • 你的输出是缩进的,就好像.indent(mapping=4)是在你的YAML示例上设置的,但是你的代码没有反映出来。
  • 你的代码设置了.explicit_start = True,但是你的输出没有反映出来。
  • 您的输出会换行(围绕第30列),但没有相应的代码。

我可以把.width的值设置为27 - 32来得到输出,如果不设置preserve_quotes,输出就不会得到反斜杠(也不会得到引号):

import sys
import ruamel.yaml

yaml_str = """\
test:
    long: "This is a sample text
      across two lines."
"""

for pq in [True, False]:
    yaml = ruamel.yaml.YAML()  # defaults to round-trip
    yaml.preserve_quotes = pq
    yaml.indent(mapping=4)
    yaml.width = 27
    yaml.allow_duplicate_keys = True
    yaml.explicit_start = True

    data = yaml.load(yaml_str)
    # check there are no hidden spaces or newlines in the loaded data
    assert data["test"]["long"] == 'This is a sample text across two lines.'
    yaml.dump(data, sys.stdout)

其给出:

---
test:
    long: "This is a sample text\
        \ across two lines."
---
test:
    long: This is a sample text
        across two lines.

因此,这似乎与转储样式为"""的字符串的代码有关
顺便说一句,我建议在这种测试过程中不要覆盖输入,如果需要进行文件到文件的加载/转储,请从代码中写入输入,或者使用字符串输入和sys.stdout输出(在进行视觉检查时)。
这些垃圾是由几年前从PyYAML派生的代码引起的:

import sys
import yaml  # PyYAML

data = yaml.safe_load(yaml_str)
assert data["test"]["long"] == 'This is a sample text across two lines.'
yaml.safe_dump(data, sys.stdout, indent=4, width=27, default_style='"')

其给出:

"test":
    "long": "This is a sample\
        \ text across two lines."

这就产生了emitter.pywrite_double_quoted的代码:

class MyEmitter(ruamel.yaml.emitter.Emitter):
    def write_double_quoted(self, text, split=True):
        if self.root_context:
            if self.requested_indent is not None:
                self.write_line_break()
                if self.requested_indent != 0:
                    self.write_indent()
        self.write_indicator(u'"', True)
        start = end = 0
        while end <= len(text):
            ch = None
            if end < len(text):
                ch = text[end]
            if (
                ch is None
                or ch in u'"\\\x85\u2028\u2029\uFEFF'
                or not (
                    u'\x20' <= ch <= u'\x7E'
                    or (
                        self.allow_unicode
                        and (u'\xA0' <= ch <= u'\uD7FF' or u'\uE000' <= ch <= u'\uFFFD')
                    )
                )
            ):
                if start < end:
                    data = text[start:end]
                    self.column += len(data)
                    if bool(self.encoding):
                        data = data.encode(self.encoding)
                    self.stream.write(data)
                    start = end
                if ch is not None:
                    if ch in self.ESCAPE_REPLACEMENTS:
                        data = u'\\' + self.ESCAPE_REPLACEMENTS[ch]
                    elif ch <= u'\xFF':
                        data = u'\\x%02X' % ord(ch)
                    elif ch <= u'\uFFFF':
                        data = u'\\u%04X' % ord(ch)
                    else:
                        data = u'\\U%08X' % ord(ch)
                    self.column += len(data)
                    if bool(self.encoding):
                        data = data.encode(self.encoding)
                    self.stream.write(data)
                    start = end + 1
            if (
                0 < end < len(text) - 1
                and (ch == u' ' or start >= end)
                and self.column + (end - start) > self.best_width
                and split
            ):
                # data = text[start:end] + u'\\'  # <<< replaced with following two lines
                need_backquote = text[end] == u' ' and (len(text) > end) and text[end+1] == u' '
                data = text[start:end] + (u'\\' if need_backquote else u'')
                if start < end:
                    start = end
                self.column += len(data)
                if bool(self.encoding):
                    data = data.encode(self.encoding)
                self.stream.write(data)
                self.write_indent()
                self.whitespace = False
                self.indention = False
                if text[start] == u' ':
                    # data = u'\\'    # <<< replaced with following line
                    data = u'\\' if need_backquote else u''
                    self.column += len(data)
                    if bool(self.encoding):
                        data = data.encode(self.encoding)
                    self.stream.write(data)
            end += 1
        self.write_indicator(u'"', False)

yaml = ruamel.yaml.YAML()
yaml.Emitter = MyEmitter
yaml.preserve_quotes = True
yaml.indent(mapping=4)
yaml.width = 27

data = yaml.load(yaml_str)
assert data["test"]["long"] == 'This is a sample text across two lines.'
yaml.dump(data, sys.stdout)

其给出:

test:
    long: "This is a sample text
         across two lines."

看起来像你想要的。
两个修改行周围的代码块生成了可正确加载的字符串,正如您所注意到的。! t只是非常保守地处理了换行符插入点周围可能存在的多个空格,这对于PyYAML是正确的,PyYAML没有保留原始YAML文档的意图,但对于'ruamel.yaml是不正确的。如果没有这些反斜杠,额外的空格将在加载过程中消失。

yaml_str = 'test:\n    long:\n      "This is a sample text  across two lines."'

yaml = ruamel.yaml.YAML()
yaml.Emitter = MyEmitter
yaml.preserve_quotes = True
yaml.indent(mapping=4)
yaml.width = 27

data = yaml.load(yaml_str)
assert data["test"]["long"] == 'This is a sample text  across two lines.'
yaml.dump(data, sys.stdout)

给出:

test:
    long: "This is a sample text\
        \  across two lines."

因为有两个空格。
看起来上面的药没有其他副作用,但这还没有经过进一步的测试。
你应该小心使用allow_duplicate_keys,如果你有它们,它会改变你的输出,并且可能与加载原始文档的另一个程序的语义不同。
你也应该考虑在包含YAML文档的文件上使用.yaml扩展名,假设使用这个文档的其他程序可以处理这个扩展名。至少从2006年9月起,这就是推荐的扩展名,所以我希望其他一些人从那时起更新他们的代码。

相关问题