【问题标题】:C# parse and change strings in yamlC# 解析和更改 yaml 中的字符串
【发布时间】:2015-08-10 03:38:17
【问题描述】:

我正在寻找一种方法来解析 yaml 文件并更改每个字符串然后保存文件而不更改原始文件的结构。在我看来,我不应该为此使用正则表达式,而是使用某种 yaml 解析器。 示例 yaml 输入如下:

receipt:     Oz-Ware Purchase Invoice
date:        2007-08-06
customer:
    given:   Dorothy

items:
    - part_no:   A4786
      descrip:   Water Bucket (Filled)

    - part_no:   E1628
      descrip:   High Heeled "Ruby" Slippers
      size:      8

bill-to:  &id001
    street: |
            123 Tornado Alley
            Suite 16
    city:   East Centerville
    state:  KS

ship-to:  *id001

specialDelivery:  >
    Follow the Yellow Brick
    Road to the Emerald City.
...

期望的输出:

receipt:     ###Oz-Ware Purchase Invoice###
date:        ###2007-08-06###
customer:
    given:   ###Dorothy###

items:
    - part_no:   ###A4786###
      descrip:   ###Water Bucket (Filled)###

    - part_no:   ###E1628###
      descrip:   ###High Heeled "Ruby" Slippers###
      size:      ###8###

bill-to:  ###&id001###
    street: |
            ###123 Tornado Alley
            Suite 16###
    city:   ###East Centerville###
    state:  ###KS###

ship-to:  ###*id001###

specialDelivery:  >
    ###Follow the Yellow Brick
    Road to the Emerald City.###
...

是否有一个好的 yaml 解析器可以处理复杂的 yaml 文件、更改字符串并将数据保存回来而不影响文档的结构?也许你有其他想法如何解决这个问题。基本上我想从文档顶部遍历每个字符串并对字符串进行一些修改。 任何提示表示赞赏。

【问题讨论】:

  • 你试过 YamlDotNet 吗?它似乎提供了你需要的东西。 github.com/aaubry/YamlDotNet
  • 大多数 YAML 解析器会丢弃值之前的额外空格并丢失所有隐式对齐信息。我知道的解析器还将在读入时解释锚点和引用(并创建对相同数据的引用。我可以向您展示如何在 Python 中完成大部分操作(折叠样式标量是一个问题),如果这是一个选项,但由于这是标记为 C#,除非您确认没问题,否则我不会这样做。
  • @Dreamweaver 非常感谢您的建议,但我找不到任何示例如何迭代/更改每个字符串。
  • @Anthon 虽然我更喜欢使用 C#,但我可以使用 python 解决方案作为替代方案。如果没有用 C# 编写的任何答案,那么我将接受您的解决方案。

标签: c# python parsing yaml yamldotnet


【解决方案1】:

YAML 规范有this to say:

在表示模型中,映射键没有顺序。要序列化映射,有必要对其键进行排序。此顺序是一个序列化细节,不应在组成表示图时使用(因此用于保存应用程序数据)。在节点顺序很重要的每种情况下,都必须使用序列。例如,有序映射可以表示为一系列映射,其中每个映射都是一个键:值对。 YAML 为这种情况提供了方便的紧凑表示法。

所以你真的不应该期望 YAML 在加载和保存文档时保持任何顺序。

话虽如此,我完全理解你来自哪里。由于 YAML 文档是为人类设计的,因此保持一定的顺序绝对是有帮助的。不幸的是,由于规范,大多数实现将使用无序数据结构来表示键/值映射。在 C# 和 Python 中,这将是一个字典;和字典是设计的,没有顺序。

但是 C# 和 Python 都确实有有序字典类型,OrderedDictionaryOrderedDict,至少对于 Python,过去已经做出了一些努力来使用有序字典来维护键顺序:

这就是 Python 方面;我相信 C# 实现也有类似的努力。

【讨论】:

    【解决方案2】:

    大多数 YAML 解析器都是为读取 YAML(由其他程序编写或由人类编辑)而构建的,并且用于编写 YAML 以供其他程序读取。众所周知,缺乏的是解析器编写人类仍然可读的 YAML 的能力:

    • 映射键的顺序未定义
    • cmets 被扔掉了
    • 标量文字块样式(如果有)被删除
    • 标量周围的间距被丢弃
    • 标量折叠信息(如果有)将被删除

    加载已加载的手工制作的 YAML 文件的转储将导致与初始加载相同的内部数据结构,但中间转储通常看起来不像原始(手工制作的)YAML。

    如果你有 Python 程序:

    import ruamel.yaml as yaml
    
    yaml_str = """\
    receipt:     Oz-Ware Purchase Invoice
    date:        2007-08-06
    customer:
        given:   Dorothy
    
    items:
        - part_no:   A4786
          descrip:   Water Bucket (Filled)
    
        - part_no:   E1628
          descrip:   High Heeled "Ruby" Slippers
          size:      8
    
    bill-to:  &id001
        street: |
                123 Tornado Alley
                Suite 16
        city:   East Centerville
        state:  KS
    
    ship-to:  *id001
    
    specialDelivery:  >
        Follow the Yellow Brick
        Road to the Emerald City.
    """
    
    data1 = yaml.load(yaml_str, Loader=yaml.Loader)
    dump_str = yaml.dump(data1, Dumper=yaml.Dumper)
    data2 = yaml.load(dump_str, Loader=yaml.Loader)
    

    那么以下断言成立:

    assert data1 == data2
    assert dump_str != yaml_str
    

    中间的dump_str 看起来像:

    bill-to: &id001 {city: East Centerville, state: KS, street: '123 Tornado Alley
    
        Suite 16
    
        '}
    customer: {given: Dorothy}
    date: 2007-08-06
    items:
    - {descrip: Water Bucket (Filled), part_no: A4786}
    - {descrip: High Heeled "Ruby" Slippers, part_no: E1628, size: 8}
    receipt: Oz-Ware Purchase Invoice
    ship-to: *id001
    specialDelivery: 'Follow the Yellow Brick Road to the Emerald City.
    
      '
    

    以上是ruamel.yamlPyYAML 和许多其他语言的 YAML 解析器和在线 YAML 转换服务的默认行为。对于某些解析器,这是提供的唯一行为。

    我将 ruamel.yaml 作为 PyYAML 的增强功能启动的原因是从手工制作的 YAML 到内部数据,再到 YAML,从而产生更好的人类可读性(我称之为 往返),并保留更多信息(尤其是 cmets)。

    data = yaml.load(yaml_str, Loader=yaml.RoundTripLoader)
    print yaml.dump(data, Dumper=yaml.RoundTripDumper)
    

    给你:

    receipt: Oz-Ware Purchase Invoice
    date: 2007-08-06
    customer:
      given: Dorothy
    items:
    - part_no: A4786
      descrip: Water Bucket (Filled)
    - part_no: E1628
      descrip: High Heeled "Ruby" Slippers
      size: 8
    bill-to: &id001
      street: |
        123 Tornado Alley
        Suite 16
      city: East Centerville
      state: KS
    ship-to: *id001
    specialDelivery: 'Follow the Yellow Brick Road to the Emerald City.
    
      '
    

    我的重点是 cmets、key、order 和文字块样式。标量和折叠标量周围的间距(还)不是特别的。


    从那里开始(您也可以在 PyYAML 中执行此操作,但您不会拥有 ruamel.yaml 键顺序保持的内置增强功能)您可以提供特殊的发射器,或者在较低级别挂钩到系统,覆盖emitter.py 中的一些方法(并确保您可以调用 不需要处理的情况的原件:

    def rewrite_write_plain(self, text, split=True):
        if self.state == self.expect_block_mapping_simple_value:
            text = '###' + text + '###'
            while self.column < 20:
                text = ' ' + text
                self.column += 1
        self._org_write_plain(text, split)
    
    def rewrite_write_literal(self, text):
        if self.state == self.expect_block_mapping_simple_value:
            last_nl = False
            if text and text[-1] == '\n':
                last_nl = True
                text = text[:-1]
            text = '###' + text + '###'
            if False:
                extra_indent = ''
                while self.column < 15:
                    text = ' ' + text
                    extra_indent += ' '
                    self.column += 1
                text = text.replace('\n', '\n' + extra_indent)
            if last_nl:
                text += '\n'
        self._org_write_literal(text)
    
    def rewrite_write_single_quoted(self, text, split=True):
        if self.state == self.expect_block_mapping_simple_value:
            last_nl = False
            if text and text[-1] == u'\n':
                last_nl = True
                text = text[:-1]
            text = u'###' + text + u'###'
            if last_nl:
                text += u'\n'
        self.write_folded(text)
    
    def rewrite_write_indicator(self, indicator, need_whitespace,
                        whitespace=False, indention=False):
        if indicator and indicator[0] in u"*&":
            indicator = u'###' + indicator + u'###'
            while self.column < 20:
                indicator = ' ' + indicator
                self.column += 1
        self._org_write_indicator(indicator, need_whitespace, whitespace,
                                  indention)
    
    dumper._org_write_plain = dumper.write_plain
    dumper.write_plain = rewrite_write_plain
    dumper._org_write_literal = dumper.write_literal
    dumper.write_literal = rewrite_write_literal
    dumper._org_write_single_quoted = dumper.write_single_quoted
    dumper.write_single_quoted = rewrite_write_single_quoted
    dumper._org_write_indicator = dumper.write_indicator
    dumper.write_indicator = rewrite_write_indicator
    
    print yaml.dump(data, Dumper=dumper, indent=4)
    

    给你:

    receipt:             ###Oz-Ware Purchase Invoice###
    date:                ###2007-08-06###
    customer:
        given:           ###Dorothy###
    items:
    -   part_no:         ###A4786###
        descrip:         ###Water Bucket (Filled)###
    -   part_no:         ###E1628###
        descrip:         ###High Heeled "Ruby" Slippers###
        size:            ###8###
    bill-to:             ###&id001###
        street: |
            ###123 Tornado Alley
            Suite 16###
        city:            ###East Centerville###
        state:           ###KS###
    ship-to:             ###*id001###
    specialDelivery: >
        ###Follow the Yellow Brick Road to the Emerald City.###
    

    希望在 C# 中进一步处理是可以接受的

    【讨论】:

    • 感谢您的解决方案,但是我得到了一些更复杂的文件:ruamel.yaml.scanner.ScannerError: while scan a double-quoted scalar in "", line 116, column 17:提示:“更详细的解释... ^ 在“”中发现未知转义字符'&',第 116 行,第 72 列:...平面,\n <a data-toggle=\ “模态”data-target=\ ...
    • 我得到的第二个错误是:ruamel.yaml.scanner.ScannerError: mapping values are not allowed here in "", line 1, column 7: --- en:
    • @wariacik 1) 我可以访问在某处引发错误的确切文件吗? 2) 这是哪个版本的 Python (python --version)?这可能是 Python3 系列的问题。
    • 非常感谢您的回复。给你 - 简化的 yaml gist.github.com/anonymous/758c5fedd339ee061b59 版本 Python 2.6 (r26:66714
    • @wariacik 通常,如果您在 '---' 之后得到一些东西,它必须是一个指导指标(即以下数据是什么类型)。 'en:' 是一个映射键,应该在一个新行上,不缩进。什么程序生成了那个文件?那么您在第一条评论中提到的更复杂的文件呢?
    猜你喜欢
    • 2011-02-18
    • 2020-05-16
    • 1970-01-01
    • 1970-01-01
    • 2011-04-10
    • 2012-11-05
    • 2017-04-05
    • 2011-02-15
    • 1970-01-01
    相关资源
    最近更新 更多