2016-07-29 67 views
1

我正致力于将几个.yaml文件中存在的所有文本置于一个新的单个YAML文件中,该文件将包含可以翻译成西班牙语的英文翻译。返回所有键以及嵌套字典中的值

每个YAML文件都有很多嵌套文本。我想为YAML文件中的每个值打印完整的“路径”,即所有的键以及值。下面是一个.yaml文件住在myproject.section.more_information文件的例子输入:

default: 
    heading: Here’s A Title 
    learn_more: 
     title: Title of Thing 
     url: www.url.com 
     description: description 
     opens_new_window: true 

,这里是所需的输出:

myproject.section.more_information.default.heading: Here’s a Title 
myproject.section.more_information.default.learn_more.title: Title of Thing 
mproject.section.more_information.default.learn_more.url: www.url.com 
myproject.section.more_information.default.learn_more.description: description 
myproject.section.more_information.default.learn_more.opens_new_window: true 

这似乎是一个很好的候选人递归,所以我已经看过例子this answer

但是,我想保留所有导致给定值的键,而不仅仅是一个值中的最后一个键。我目前使用PyYAML来读取/写入YAML。

关于如何保存每个键的任何提示,因为我继续检查该项是否是字典,然后返回与每个值关联的所有键?

回答

0

漫步嵌套字典递归递归,并将“前缀”交给“路径”,这样可以防止您必须对路径段进行任何操作(如@Prune)所暗示的。

有几件事情要记住,使这个问题有意思:

  • 因为你使用多个文件会导致在多个文件中,你需要处理相同的路径(至少投掷一个错误,否则你可能会丢失数据)。在我的例子中,我生成一个值列表。
  • 处理特殊键(非字符串(转换?),空字符串,包含.的键)。我的例子报告这些并退出。

使用ruamel.yaml¹示例代码:

import sys 
import glob 
import ruamel.yaml 
from ruamel.yaml.comments import CommentedMap, CommentedSeq 
from ruamel.yaml.compat import string_types, ordereddict 

class Flatten: 
    def __init__(self, base): 
     self._result = ordereddict() # key to list of tuples of (value, comment) 
     self._base = base 

    def add(self, file_name): 
     data = ruamel.yaml.round_trip_load(open(file_name)) 
     self.walk_tree(data, self._base) 

    def walk_tree(self, data, prefix=None): 
     """ 
     this is based on ruamel.yaml.scalarstring.walk_tree 
     """ 
     if prefix is None: 
      prefix = "" 
     if isinstance(data, dict): 
      for key in data: 
       full_key = self.full_key(key, prefix) 
       value = data[key] 
       if isinstance(value, (dict, list)): 
        self.walk_tree(value, full_key) 
        continue 
       # value is a scalar 
       comment_token = data.ca.items.get(key) 
       comment = comment_token[2].value if comment_token else None 
       self._result.setdefault(full_key, []).append((value, comment)) 
     elif isinstance(base, list): 
      print("don't know how to handle lists", prefix) 
      sys.exit(1) 

    def full_key(self, key, prefix): 
     """ 
     check here for valid keys 
     """ 
     if not isinstance(key, string_types): 
      print('key has to be string', repr(key), prefix) 
      sys.exit(1) 
     if '.' in key: 
      print('dot in key not allowed', repr(key), prefix) 
      sys.exit(1) 
     if key == '': 
      print('empty key not allowed', repr(key), prefix) 
      sys.exit(1) 
     return prefix + '.' + key 

    def dump(self, out): 
     res = CommentedMap() 
     for path in self._result: 
      values = self._result[path] 
      if len(values) == 1: # single value for path 
       res[path] = values[0][0] 
       if values[0][1]: 
        res.yaml_add_eol_comment(values[0][1], key=path) 
       continue 
      res[path] = seq = CommentedSeq() 
      for index, value in enumerate(values): 
       seq.append(value[0]) 
       if values[0][1]: 
        res.yaml_add_eol_comment(values[0][1], key=index) 


     ruamel.yaml.round_trip_dump(res, out) 


flatten = Flatten('myproject.section.more_information') 
for file_name in glob.glob('*.yaml'): 
    flatten.add(file_name) 
flatten.dump(sys.stdout) 

如果你有一个额外的输入文件:

default: 
    learn_more: 
     commented: value # this value has a comment 
     description: another description 

那么结果是:

myproject.section.more_information.default.heading: Here’s A Title 
myproject.section.more_information.default.learn_more.title: Title of Thing 
myproject.section.more_information.default.learn_more.url: www.url.com 
myproject.section.more_information.default.learn_more.description: 
- description 
- another description 
myproject.section.more_information.default.learn_more.opens_new_window: true 
myproject.section.more_information.default.learn_more.commented: value # this value has a comment 
如果

当然你输入没有双重路径,你的输出不会有任何结果名单。

通过使用string_typesordereddictruamel.yaml使这个Python2和Python3兼容(你不指明你正在使用哪个版本)。

ordereddict保留原始的键排序,但这当然取决于文件的处理顺序。如果你想要的路径进行排序,只是改变dump()使用:

 for path in sorted(self._result): 

还要注意,在注释的“注释”字典条目被保留。


¹ ruamel.yaml是YAML 1.2解析器可以保留上往返注释和其他数据(PyYAML确实YAML 1.1的大部分地区)。免责声明:我是ruamel.yaml的作者

+0

对于我的输入可能有的许多边缘情况,这是一个非常深思熟虑的解决方案,谢谢。我特别喜欢它保留评论。 – swellactually

0

你想要做的是拼合嵌套字典。这将是一个很好的开始:Flatten nested Python dictionaries, compressing keys

事实上,我认为如果您只是将sep参数更改为.,那么顶级答案中的代码段将适用于您。

编辑:

检查这一个工作示例基于链接的SO回答http://ideone.com/Sx625B

import collections 

some_dict = { 
    'default': { 
     'heading': 'Here’s A Title', 
     'learn_more': { 
      'title': 'Title of Thing', 
      'url': 'www.url.com', 
      'description': 'description', 
      'opens_new_window': 'true' 
     } 
    } 
} 

def flatten(d, parent_key='', sep='_'): 
    items = [] 
    for k, v in d.items(): 
     new_key = parent_key + sep + k if parent_key else k 
     if isinstance(v, collections.MutableMapping): 
      items.extend(flatten(v, new_key, sep=sep).items()) 
     else: 
      items.append((new_key, v)) 
    return dict(items) 

results = flatten(some_dict, parent_key='', sep='.') 
for item in results: 
    print(item + ': ' + results[item]) 

如果你想为了,但你需要一个OrderedDict。

+0

超级,感谢发送。我看了一下,输出看起来很棒。不幸的是我不得不离开我的电脑,但本周末我会花更多时间浏览它。 – swellactually

+0

尽管这个链接可能回答这个问题,但最好在这里包含答案的基本部分,并提供供参考的链接。如果链接的页面发生更改,则仅链接答案可能会失效,甚至在发生其他StackOverflow答案的链接时也会发生这种情况。 – Anthon

+1

Gotcha @Anthon,我会解决它。 – Michael

0

保留一个简单的字符串列表,作为每个缩进深度处的最新键。当您从一行进行到下一行时,只需更改列表末尾的项目即可。当你“out-dent”时,从列表中弹出最后一项。当你缩进时,追加到列表中。

然后,每个你打一个冒号的时候,相应的关键项是字符串列表中的串联,是这样的:

'.'.join(key_list) 

这是否让你在一个体面的速度移动?

+0

智能设计。我必须离开电脑,所以现在不能尝试,但我喜欢这种方法,谢谢! – swellactually

+0

使用字符串列表会使事情过于复杂。无论如何,您必须使用递归挖掘嵌套字典,因此只需将当前路径添加到“前缀”中,并且递归会处理您建议手动完成的追加/弹出操作。 – Anthon