假设我有一个YAML文件,如下所示:
en: errors: # Some comment format: "%{attribute} %{message}" # One more comment messages: "1": "Message 1" "2": "Message 2" long_error_message: | This is a multiline message date: format: "YYYY-MM-DD"
我怎么能把这个读成像这样的Ruby Hash?
{ 'en': { 'errors': { 'format': { value: '%{attribute} %{message}',line: 4 } 'messages': { '1': { value: 'Message 1',line: 8 },'2': { value: 'Message 2',line: 9 } } 'long_error_message' : { value: "This is a\nmultiline message",line: 11 } },'date': { 'format': { value: 'YYYY-MM-DD',line: 16 } } } }
我已经尝试使用YAML: Find line number of key?中提到的提示作为起点并实现了一个Psych :: Handler,但感觉我必须从Psych重写大量代码才能使其工作.
我有什么想法可以解决这个问题?
解决方法
看起来您想要采用任何标量值作为映射值,并将其替换为带有包含原始值的值键的哈希值,以及带有行号的行键.
以下几乎可以工作,主要问题是多行字符串,其中给出的行号是Yaml中下一个事物的开始.问题是,当调用处理程序标量方法时,解析器已经超出了感兴趣的标量,因此当标识知道标量已经结束时,标记给出位置的行.在大多数情况下,在您的示例中,这无关紧要,但在多行情况下,它会给出错误的值.在没有进入Psych C代码的情况下,我无法看到任何方式从标记开始获取标量信息.
require 'psych' # Psych's first step is to parse the Yaml into an AST of Node objects # so we open the Node class and add a way to track the line. class Psych::Nodes::Node attr_accessor :line end # We need to provide a handler that will add the line to the node # as it is parsed. TreeBuilder is the "usual" handler,that # creates the AST. class LineNumberHandler < Psych::TreeBuilder # The handler needs access to the parser in order to call mark attr_accessor :parser # We are only interested in scalars,so here we override # the method so that it calls mark and adds the line info # to the node. def scalar value,anchor,tag,plain,quoted,style mark = parser.mark s = super s.line = mark.line s end end # The next step is to convert the AST to a Ruby object. # Psych does this using the visitor pattern with the ToRuby # visitor. Here we patch ToRuby rather than inherit from it # as it makes the last step a little easier. class Psych::Visitors::ToRuby # This is the method for creating hashes. There may be problems # with Yaml mappings that have tags. def revive_hash hash,o o.children.each_slice(2) { |k,v| key = accept(k) val = accept(v) # This is the important bit. If the value is a scalar,# we replace it with the desired hash. if v.is_a? ::Psych::Nodes::Scalar val = { "value" => val,"line" => v.line + 1} # line is 0 based,so + 1 end # Code dealing with << (for merging hashes) omitted. # If you need this you will probably need to copy it # in here. See the method: # https://github.com/tenderlove/psych/blob/v2.0.13/lib/psych/visitors/to_ruby.rb#L333-L365 hash[key] = val } hash end end yaml = get_yaml_from_wherever # Put it all together handler = LineNumberHandler.new parser = Psych::Parser.new(handler) # Provide the handler with a reference to the parser handler.parser = parser # The actual parsing parser.parse yaml # We patched ToRuby rather than inherit so we can use to_ruby here puts handler.root.to_ruby