使用PyYAML.dump()生成锚点?

时间:2013-08-05 18:45:25

标签: yaml pyyaml cross-reference

我希望能够在PyYAML的dump()函数生成的YAML中生成锚点。有没有办法做到这一点?理想情况下,锚点的名称与YAML节点的名称相同。

示例:

import yaml
yaml.dump({'a': [1,2,3]})
'a: [1, 2, 3]\n'

我希望能够做的就像生成YAML一样:

import yaml
yaml.dump({'a': [1,2,3]})
'a: &a [1, 2, 3]\n'

我可以编写自定义发射器或转储器来执行此操作吗?还有另一种方式吗?

4 个答案:

答案 0 :(得分:1)

默认情况下,只有在检测到对先前看到的对象的引用时才会发出锚点:

>>> import yaml
>>>
>>> foo = {'a': [1,2,3]}
>>> doc = (foo,foo)
>>>
>>> print yaml.safe_dump(doc, default_flow_style=False)
- &id001
  a:
  - 1
  - 2
  - 3
- *id001

如果要覆盖其命名方式,您必须自定义Dumper class,特别是generate_anchor()功能。 ANCHOR_TEMPLATE也可能有用。

在您的示例中,节点名称很简单,但您需要考虑YAML值的许多可能性,即它可以是序列而不是单个值:

>>> import yaml
>>>
>>> foo = {('a', 'b', 'c'): [1,2,3]}
>>> doc = (foo,foo)
>>>
>>> print yaml.dump(doc, default_flow_style=False)
!!python/tuple
- &id001
  ? !!python/tuple
  - a
  - b
  - c
  : - 1
    - 2
    - 3
- *id001

答案 1 :(得分:1)

这并不容易。除非您要用于锚点的数据是 in 节点。这是因为锚点附加到节点内容,在您的示例中[1,2,3]'并且不知道这个值与关键字' a'相关联。

l = [1, 2, 3]
foo = {'a': l, 'b': l}
class SpecialAnchor(yaml.Dumper):

    def generate_anchor(self, node):
        print('Generating anchor for {}'.format(str(node)))
        anchor =  super().generate_anchor(node)
        print('Generated "{}"'.format(anchor))
        return anchor

y1 = yaml.dump(foo, Dumper=Anchor)

给你:

Generating anchor for SequenceNode(tag='tag:yaml.org,2002:seq', value=[ScalarNode(tag='tag:yaml.org,2002:int', value='1'), ScalarNode(tag='tag:yaml.org,2002:int', value='2'), ScalarNode(tag='tag:yaml.org,2002:int', value='3')])
Generated "id001"
a: &id001 [1, 2, 3]
b: *id001

到目前为止,我还没有找到办法获得钥匙' a'给定节点......

答案 2 :(得分:1)

我写了一个自定义锚类来强制顶级节点的锚值。它不会简单地覆盖锚点(使用generate_anchor),但实际上会强制发出Anchor,即使稍后没有引用该节点:

class CustomAnchor(yaml.Dumper):    
    def __init__(self,*args,**kwargs):
        super(CustomAnchor,self).__init__(*args,**kwargs)
        self.depth = 0
        self.basekey = None
        self.newanchors = {}

    def anchor_node(self, node):        
        self.depth += 1                 
        if self.depth == 2:
            assert isinstance(node,yaml.ScalarNode), "yaml node not a string: %s"%node
            self.basekey = str(node.value)
            node.value = self.basekey+"_ALIAS"
        if self.depth == 3:
            assert self.basekey, "could not find base key for value: %s"%node
            self.newanchors[node] = self.basekey  
        super(CustomAnchor,self).anchor_node(node) 
        if self.newanchors:
            self.anchors.update(self.newanchors)
            self.newanchors.clear()                

请注意,我覆盖节点名称后缀为“_ALIAS”,但您可以删除该行以保留节点名称和锚名称相同,或将其更改为其他名称。

E.g。倾销{'FOO':'BAR'}导致:

FOO_ALIAS:& FOO BAR

另外,我一次只编写它来处理单个顶级键/值对,它只会强制使用顶级键的锚点。如果要将dict转换为YAML文件,其中所有键都是顶级YAML节点,则需要迭代dict并将每个键/值对转储为{key:value},或者重写此类以处理dict有多个键。

答案 3 :(得分:0)

我根本无法获得@beeb的答案,所以我继续尝试推广@ aaa90210的答案

import yaml

class _CustomAnchor(yaml.Dumper):
  anchor_tags = {}
  def __init__(self,*args,**kwargs):
    super().__init__(*args,**kwargs)
    self.new_anchors = {}
    self.anchor_next = None
  def anchor_node(self, node):
    if self.anchor_next is not None:
      self.new_anchors[node] = self.anchor_next
      self.anchor_next = None
    if isinstance(node.value, str) and node.value in self.anchor_tags:
      self.anchor_next = self.anchor_tags[node.value]

    super().anchor_node(node)

    if self.new_anchors:
      self.anchors.update(self.new_anchors)
      self.new_anchors.clear()
def CustomAnchor(tags):
  return type('CustomAnchor', (_CustomAnchor,), {'anchor_tags': tags})

print(yaml.dump(foo, Dumper=CustomAnchor({'a': 'a_name'})))

这没有提供一种方法来区分两个具有相同名称值的节点,这将需要与XML的xpath等效的yaml,我在pyyaml中看不到:(


通过类工厂CustomAnchor,您可以基于节点值传入锚字典。 {value: anchor_name}