我该如何对YAML文件进行排序?

24
我一直在尝试使用Ruby对i18n翻译的YAML文件进行排序,以便更好、更有组织地管理新的翻译内容,但我一直在想是否有什么方法可以简化这个任务。
我找到了一个YAML文件写入器,可以将哈希值写入文件,但我的问题是如何正确地对哈希值进行排序。如果我得到哈希值hh.sort会返回一个数组,但我还没有找到一个简单的方法来做到这一点。
我有像这样的YAML文件:
pt-br:    
  global:
    misc:
      total: "Total"
      all: "Todos"
      close: "Fechar"
      cancel: "Cancelar"

    crud:
      access: "Acessar"
      back: "Voltar"
      edit: "Editar"
      confirm: "Confirmar"
      send: "Enviar"

...

(这些文件比这个要大得多)

但是我想按照以下方式对它们进行排序:

pt-br:    
  global:
    crud:
      access: "Acessar"
      back: "Voltar"
      confirm: "Confirmar"
      edit: "Editar"
      send: "Enviar"

    misc:
      all: "Todos"
      cancel: "Cancelar"
      close: "Fechar"          
      total: "Total"

我认为一些简单的递归方法可以帮助我,像这样:

def translation_sort(h)
  if h.class == Hash
    h = h.sort
    h.each{|item| translation_sort(item)}
  end
  h
end

require "yaml"
h=YAML.load_file(File.open("~/pt-br.sample.yml"))
translation_sort(h)

有没有适用于Sublime的插件? - Amol Pujari
6个回答

11

在我需要对哈希进行深度排序的使用案例中,哈希总是一棵树,其中键是标签,值是(子)树(如果是哈希)或叶子(否则)。我只需对树的标签进行深度排序(而不是值)。

我明白了。

before: {"a":[2,10,{"5":null,"1":null,"3":null}],"x":{"5":null,"1":null,"3":null},"a2":{"5":[2,10,5],"1":null,"3":null}}
after:  {"a":[2,10,{"5":null,"1":null,"3":null}],"a2":{"1":null,"3":null,"5":[2,10,5]},"x":{"1":null,"3":null,"5":null}}

使用这个

    def deeply_sort_hash(object)
      return object unless object.is_a?(Hash)
      hash = Hash.new
      object.each { |k, v| hash[k] = deeply_sort_hash(v) }
      sorted = hash.sort { |a, b| a[0].to_s <=> b[0].to_s }
      hash.class[sorted]
    end

当我在寻找内置方法(或ActiveSupport方法)时,偶然发现了这个答案。由于似乎还没有这样的方法,我很高兴能找到这个答案。我只想在这里留下一个评论,说明如何在新版本的Ruby中用更少的代码行实现相同的功能:def hash_deep_sort(element); return element unless element.is_a?(Hash); hash = element.transform_values { |value| hash_deep_sort(value) }; hash.sort.to_h; end(这不是一行代码,只是stackoverflow强制我不能在评论中使用多行...) - Jay Schneider
已确认在 Ruby 2.7.1 中无需任何更改即可正常工作。 - mmell

9

不要像其他回答中建议的那样使用YAML库。它会破坏长字符串值的格式,删除您的注释,并在使用重音符和特殊字符(您将使用它们,因为您正在进行i18n)时吐出不可读的字符转义。

使用我创建的这个gem:

https://github.com/redealumni/i18n_yaml_sorter

它只会对文件中的行进行排序,因此一切都将保持与原始yaml相同的方式(重音符、您用于输入字符串的YAML构造、缩进等等)。它可以处理深度嵌套的yamls,结果非常可靠。该gem包括测试,适用于ruby 1.8或1.9。

它配有一个TextMate Bundle(Shift + Command + S)和一个Rails rake任务,因此您可以轻松快速地在编辑器中对文件进行排序。速度真的很快。

下面是示例,展示了区别:

原始内容:

  pt-BR:
    # Note how this is a nice way of inputing
    # paragraphs of text in YAML. 
    apples: >
      Maçãs são boas,
      só não coma 
      seus iPods!
    grapes: Não comemos elas.
    bananas: |
      Bananas são "legais":
        - Elas são <b> doces </b>.
        isto: não é chave

      Por isto todos gostam de bananas!

使用YAML::dump得到的结果:

  pt-BR: 
    apples: "Ma\xC3\xA7\xC3\xA3s s\xC3\xA3o boas, s\xC3\xB3 n\xC3\xA3o coma  seus iPods!\n"
    bananas: "Bananas s\xC3\xA3o \"legais\":\n  - Elas s\xC3\xA3o <b> doces </b>.\n  isto: n\xC3\xA3o \xC3\xA9 chave\n\n\ Por isto todos gostam de bananas!\n"
    grapes: "N\xC3\xA3o comemos elas."
由i18n_yaml_sorter产生的结果:
  pt-BR:
    # Note how this is a nice way of inputing
    # paragraphs of text in YAML. 
    apples: >
      Maçãs são boas,
      só não coma 
      seus iPods!
    bananas: |
      Bananas são "legais":
        - Elas são <b> doces </b>.
        isto: não é chave

      Por isto todos gostam de bananas!
    grapes: Não comemos elas.

我刚刚尝试了这个宝石来对一个config.yml文件进行排序,但是它没有起作用。结果被排序了,但是也变得不同了。 - aercolino
1
哎呀,我又踩到这个宝石了,已经好多年了。这个宝石不起作用的原因是它忽略了引用,这意味着依赖关系没有正确排序。为了使您的YAML文件对键排序具有弹性,请以一种使其在被引用之前出现的方式重命名命名键。例如: (1)首先,我将some_key:&some_name重命名为_some_key:&some_name,并添加了some_key:\n<<:*some_name。 (2)然后,我将所有命名键及其值移动到文件顶部,并在需要时为键添加数字。请注意,这种手动调整只需要进行一次。 - aercolino
1
i18n_yaml_sorter似乎不再工作 github.com/redealumni/i18n_yaml_sorter/issues/16 - Tim Hughes
2
整个代码库已被删除或设为私有。 - Joe Atzberger
由于存储库不再可用,我投下反对票... :/ - ZedTuX

5

2014年4月更新:

使用Rails 3.2.13,Ruby 1.9.3p489:

我刚刚使用了i18n_yaml_sorter宝石(https://github.com/redealumni/i18n_yaml_sorter)。

只需将其添加到Gemfile中即可

gem 'i18n_yaml_sorter', group: :development

然后运行 rake 任务来对你的本地化文件进行排序:

rake i18n:sort

完美运行,即使这个宝石已经两年没有更新了。最多只需要5分钟。


2
在 Ruby 1.8 中,哈希没有特定的顺序,因此您不能只是对它们进行排序。
您可以像这样猴子补丁/覆盖 Hash 的 to_yaml 方法:
#!/usr/local/bin/ruby -w

require 'yaml'

class Hash
  def to_yaml(opts = {})
    YAML::quick_emit(self, opts) do |out|
      out.map(taguri, to_yaml_style) do |map|
        keys.sort.each do |k|
          v = self[k]
          map.add(k, v)
        end
      end
    end
  end
end

dict = YAML.load($<.read)

puts dict.to_yaml

当然,具体细节可能取决于您的YAML/Ruby版本。上面的示例适用于Ruby 1.8.6。


YAML::quick_emit已被弃用,不再可用。 - bmurphy1976

1

这里有另一种选择,供其他遇到同样问题的人参考。

require 'yaml'

yaml = YAML.load(IO.read(File.join(File.dirname(__FILE__), 'example.yml')))

@yml_string = "---\n"

def recursive_hash_to_yml_string(hash, depth=0)
  spacer = ""
  depth.times { spacer += "  "}
  hash.keys.sort.each do |sorted_key|
    @yml_string += spacer + sorted_key + ": "
    if hash[sorted_key].is_a?(Hash)
      @yml_string += "\n"
      recursive_hash_to_yml_string(hash[sorted_key], depth+1)
    else
      @yml_string += "#{hash[sorted_key].to_s}\n"
    end
  end
end

recursive_hash_to_yml_string(yaml)

open(File.join(File.dirname(__FILE__), 'example.yml'), 'w') { |f|
  f.write @yml_string
}

0

不幸的是,YAML::quick_emit已被废弃,并且在Psych gem的新版本中不再可用。如果你希望在序列化为yaml时对哈希键进行排序,你需要使用以下猴子补丁:

class Hash
    def to_yaml opts={}
        return Psych.dump(self.clone.sort.to_h)
    end
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接