Ruby 在不破坏定位点和别名的情况下读取和写入YAML文件

Ruby 在不破坏定位点和别名的情况下读取和写入YAML文件,ruby,parsing,yaml,psych,emit,Ruby,Parsing,Yaml,Psych,Emit,这个问题以前有人问过: 我想知道如何用许多锚和别名来解决这个问题 感谢这里的问题是Yaml中有一个序列化细节,因此在解析数据后,这些细节不是数据的一部分,因此在将数据写回Yaml时,原始锚名称是未知的。为了在往返时保留锚点名称,您需要在解析时将它们存储在某个位置,以便在以后序列化时可以使用它们。在Ruby中,任何对象都可以有与之关联的实例变量,因此实现这一点的一个简单方法是将锚点名称存储在相关对象的实例变量中 从中的示例继续,对于散列,我们可以更改重新定义的resove_hash方法,这样,如果

这个问题以前有人问过:

我想知道如何用许多锚和别名来解决这个问题

感谢

这里的问题是Yaml中有一个序列化细节,因此在解析数据后,这些细节不是数据的一部分,因此在将数据写回Yaml时,原始锚名称是未知的。为了在往返时保留锚点名称,您需要在解析时将它们存储在某个位置,以便在以后序列化时可以使用它们。在Ruby中,任何对象都可以有与之关联的实例变量,因此实现这一点的一个简单方法是将锚点名称存储在相关对象的实例变量中

从中的示例继续,对于散列,我们可以更改重新定义的
resove_hash
方法,这样,如果散列是一个锚点,那么除了在
@st
变量中记录锚点名称以便以后可以识别alises外,我们还可以将它作为实例变量添加到散列中

class ToRubyNoMerge < Psych::Visitors::ToRuby
  def revive_hash hash, o
    if o.anchor
      @st[o.anchor] = hash
      hash.instance_variable_set "@_yaml_anchor_name", o.anchor
    end

    o.children.each_slice(2) { |k,v|
      key = accept(k)
      hash[key] = accept(v)
    }
    hash
  end
end
现在您可以这样使用它(与前面的问题不同,在这种情况下,您不需要创建自定义发射器):

builder=MyYAMLTree.new

builder这里有一个稍加修改的版本,用于更新版本的psych gem。在它给我以下错误之前:

NoMethodError - undefined method `[]=' for #<Psych::Visitors::YAMLTree::Registrar:0x007fa0db6ba4d0>

我不得不进一步修改@markus发布的代码,以便与Psych v2.0.17一起使用

这就是我的结局。我希望它能帮助其他人节省不少时间。:-)

class ToRubyNoMerge@目标是完美的!这正是我想要的:)我怎样才能不使用$stdout而将输出存储到字符串变量中?我想将输出写入文件。非常感谢你@Max我刚刚查看了源代码,有一种更简单的方法可以使用
builder.yaml.tree
方法创建输出,如果不提供任何输入,该方法将返回字符串。如果需要,还可以提供一个
IO
对象作为参数,这样就可以直接写入文件。看看我最新的答案。你真是个天才!感谢您的帮助,psych真的是一个了不起的工具。参数数量错误(给定为0,预期为2)(ArgumentError)-在
ToRubyNoMerge
@ImranNaqvi中,因此此解决方案仅适用于psych 1.3.4及以下版本。Psych 2.0及以上版本在初始化ToRuby类时需要2个参数。
builder = MyYAMLTree.new
builder << data

tree = builder.tree

puts tree.yaml # returns a string

# alternativelty write direct to file:
File.open('a_file.yml', 'r+') do |f|
  tree.yaml f
end
NoMethodError - undefined method `[]=' for #<Psych::Visitors::YAMLTree::Registrar:0x007fa0db6ba4d0>
class ToRubyNoMerge < Psych::Visitors::ToRuby
  def revive_hash hash, o
    if o.anchor
      @st[o.anchor] = hash
      hash.instance_variable_set "@_yaml_anchor_name", o.anchor
    end

    o.children.each_slice(2) { |k,v|
      key = accept(k)
      hash[key] = accept(v)
    }
    hash
  end
end

class MyYAMLTree < Psych::Visitors::YAMLTree
  class Registrar
    # record object for future, using '@_yaml_anchor_name' rather
    # than object_id if it exists
    def register target, node
      anchor_name = target.instance_variable_get('@_yaml_anchor_name') || target.object_id
      @obj_to_node[anchor_name] = node
    end
  end

  # check to see if this object has been seen before
  def accept target
    if anchor_name = target.instance_variable_get('@_yaml_anchor_name')
      if @st.key? anchor_name
        oid         = anchor_name
        node        = @st[oid]
        anchor      = oid.to_s
        node.anchor = anchor
        return @emitter.alias anchor
      end
    end

    # accept is a pretty big method, call super to avoid copying
    # it all here. super will handle the cases when it's an object
    # that's been seen but doesn't have '@_yaml_anchor_name' set
    super
  end

end
class ToRubyNoMerge < Psych::Visitors::ToRuby
  def revive_hash hash, o
    if o.anchor
      @st[o.anchor] = hash
      hash.instance_variable_set "@_yaml_anchor_name", o.anchor
    end

    o.children.each_slice(2) do |k,v|
      key = accept(k)
      hash[key] = accept(v)
    end
    hash
  end
end

class Psych::Visitors::YAMLTree::Registrar
  # record object for future, using '@_yaml_anchor_name' rather
  # than object_id if it exists
  def register target, node
    @targets << target
    @obj_to_node[_anchor_name(target)] = node
  end

  def key? target
    @obj_to_node.key? _anchor_name(target)
  rescue NoMethodError
    false
  end

  def node_for target
    @obj_to_node[_anchor_name(target)]
  end

  private

  def _anchor_name(target)
    target.instance_variable_get('@_yaml_anchor_name') || target.object_id
  end
end

class MyYAMLTree < Psych::Visitors::YAMLTree
  # check to see if this object has been seen before
  def accept target
    if anchor_name = target.instance_variable_get('@_yaml_anchor_name')
      if @st.key? target
        node        = @st.node_for target
        node.anchor = anchor_name
        return @emitter.alias anchor_name
      end
    end

    # accept is a pretty big method, call super to avoid copying
    # it all here. super will handle the cases when it's an object
    # that's been seen but doesn't have '@_yaml_anchor_name' set
    super
  end

  def visit_String o
    if o == '<<'
      style = Psych::Nodes::Scalar::PLAIN
      tag   = 'tag:yaml.org,2002:str'
      plain = true
      quote = false

      return @emitter.scalar o, nil, tag, plain, quote, style
    end

    # visit_String is a pretty big method, call super to avoid copying it all
    # here. super will handle the cases when it's a string other than '<<'
    super
  end
end