使用Python在YAML中保留锚点和别名

时间:2018-01-31 21:11:22

标签: python yaml cross-reference ruamel.yaml

我在Python中编辑了一个包含大量锚点和别名的大型YAML文档。我希望能够根据它引用的节点的数据确定锚的派生方式。

例如,节点有一个'名称'字段,我希望锚点是该字段的值,而不是随机的id号。

PyYAML或ruamel.yaml可以实现吗?

1 个答案:

答案 0 :(得分:3)

有几点需要注意:

  • YAML没有字段。我假设这是您对映射中键的解释,因此您希望与映射关联的锚点与键“name”的值相同
  • 在加载时间期间,遇到锚点时创建的事件不知道它是否是标量,序列或映射上的锚点。更不用说它可以访问“name”的值。
  • 在加载过程中更改锚点非常棘手,因为您必须跟踪引用原始锚点的别名(并将它们映射到新值)
  • 在PyYAML中,锚点名称在dump期间创建,因此在使用PyYAML时必须挂钩。您可以使用ruamel.yaml
  • 执行相同的操作
  • 只有ruamel.yaml才能在往返时保留锚点。即如果您可以让锚定为持久性,即使密钥“name”的值发生更改(假设您在默认生成的表单idNNNN上进行测试)

使用ruamel.yaml时,您可以递归遍历数据结构,跟踪已访问的节点(如果子项包含祖先),遇到ruamel.yaml.comments.CommentedMap时,设置锚点(当前值为ruamel.yaml.comments.Anchor.attrib的属性,即_yaml_anchor)。未经测试的代码:

if isinstance(x, ruamel.yaml.comments.CommentedMap):
    if 'name' in x:
        x.yaml_set_anchor(x['name'])

如果您有可以往返的YAML文件,您可以挂钩代表:

import sys
import ruamel.yaml
from ruamel.yaml.representer import RoundTripRepresenter

yaml_str = """\
# data = [dict(a=1, b=2, name='mydata'), dict(c=3)]
# data.append(data[0])
- &id001
  a: 1
  b: 2
  name: mydata
- c: 3
- *id001
"""

class MyRTR(RoundTripRepresenter):
    def represent_mapping(self, tag, mapping, flow_style=None):
        if 'name' in mapping:
            # if not isinstance(mapping, ruamel.yaml.comments.CommentedMap):
            #     mapping = ruamel.yaml.comments.CommentedMap(mapping)
        mapping.yaml_set_anchor(mapping['name'])

            mapping.yaml_set_anchor(mapping['name'])
        return RoundTripRepresenter.represent_mapping(
            self, tag, mapping, flow_style=flow_style)


yaml = ruamel.yaml.YAML()
yaml.Representer = MyRTR
data = yaml.load(yaml_str)
yaml.dump(data, sys.stdout)

给出:

# data = [dict(a=1, b=2, name='mydata'), dict(c=3)]
# data.append(data[0])
- &mydata a: 1
  b: 2
  name: mydata
- c: 3
- *mydata

但请注意,这假设您加载了数据,并且所有dict实际上都是CommentedMap。如果不是这种情况(即您添加了正常dict s,则取消注释进行转换的两行。