2017-05-03 232 views
2

我想创建一个YAML过滤器读取YAML文件,处理它事后转储它。PyYAML:装载和卸载YAML文件并保留标签(CustomTag!)

必须解决所有的别名(即很好的工作已经开箱):

>>> yaml.dump(yaml.load(""" 
Foo: &bar 
    name: bar 
Foo2: 
    <<: *bar 
""")) 

'Foo: {name: bar}\nFoo2: {name: bar}\n' 

但它也应保留任何​​表达,如:

>>> yaml.dump(yaml.load("Name: !Foo bar ")) 
yaml.constructor.ConstructorError: could not determine a constructor for the tag '!Foo' in "<unicode string>", line 1, column 7: 
Name: !Foo bar 
    ^

我读pyYAML Errors on "!" in a string,这是接近我所需要的,但它分析和OUTP UTS的自定义标签为引用字符串,因此它不是一个标签了:

>>> def default_ctor(loader, tag_suffix, node): 
... return tag_suffix + ' ' + node.value 

>>> yaml.add_multi_constructor('', default_ctor) 
>>> yaml.dump(yaml.load("Name: !Foo bar "), default_flow_style=False) 
"Name: '!Foo bar'\n" 

我想没有太多的缺失,但什么?我如何加载包含任何标签的文件并在之后转储它们?

+0

使用'yaml.load()'是不安全的,因为如果有人可以控制YAML文件就可以执行任意代码,并且PyYAML不会警告其危险。 – Anthon

回答

3

由于default_ctor()返回一个字符串(这仅仅是一个标签和标量的串联),这就是被抛弃。并且因为标记以!开头,所以将该字符串转储为标量将会引起报价。

如果你想一般保存你需要存储那些在一个特殊的类型(而不是一个“正常”的Python字符串),并提供了申述的标签和值(即反倾销程序),该类型:

import sys 
import yaml 

yaml_str = """\ 
Name: !Foo bar 
Alt: !Bar foo 
""" 


class GenericScalar: 
    def __init__(self, value, tag, style=None): 
     self._value = value 
     self._tag = tag 
     self._style = style 

    @staticmethod 
    def to_yaml(dumper, data): 
     # data is a GenericScalar 
     return dumper.represent_scalar(data._tag, data._value, style=data._style) 


def default_constructor(loader, tag_suffix, node): 
    if isinstance(node, yaml.ScalarNode): 
     return GenericScalar(node.value, tag_suffix, style=node.style) 
    else: 
     raise NotImplementedError('Node: ' + str(type(node))) 


yaml.add_multi_constructor('', default_constructor, Loader=yaml.SafeLoader) 

yaml.add_representer(GenericScalar, GenericScalar.to_yaml, Dumper=yaml.SafeDumper) 

data = yaml.safe_load(yaml_str) 
yaml.safe_dump(data, sys.stdout, default_flow_style=False, allow_unicode=True) 

这给:

Alt: !Bar 'foo' 
Name: !Foo 'bar' 

注:

  • 它是不安全的使用PyYAML的load()不要使用它,这是没有必要的(因为我的代码显示)。更糟糕的是PyYAML没有反馈说有危险。
  • PyYAML转储是有引号标记,即使你保留节点的风格,因为我做的(或力为空字符串)所有标量。为了防止发生这种情况,您必须深入挖掘节点的序列化。我一直在我的ruamel.yaml软件包中为此进行修复,因为引号通常不是必需的。
  • 您的锚和别名无法解析。只是PyYAML不够聪明,无法做任何事情,只能在加载时扩大merge key。如果你在你的YAML中有一个正常的自引用,你会在你倾倒的YAML中得到一个锚点和别名。
  • 上面将很好地产生一个错误,如果您的节点,所述标签之后,是什么,但标量(即,映射或序列)。也可以一般地加载/转储它们。通过只添加一些类型并将default_constructor与一些elif isinstance(node, yaml.MappingNode)elif isinstance(node, yaml.SequenceNode)延伸。我会做那些创建不同类型(即像一个字典RESP清单),如果你走这条路,你应该知道,构建这些将需要两个步骤的过程中发生(yield构造的对象,则获得子节点值并填充对象),否则不能使用自引用结构(即节点内的别名)。
  • PyYAML不保留元素的顺序在映射
  • 你可以有一个冒号结束标签!CustomTag:,但我觉得不是这样的人友好的阅读​​,因为这看起来非常像一个块样式映射中的键值对。