4

この質問は以前に尋ねられました:アンカーとエイリアスを破壊せずにYAMLファイルを読み書きしますか?

多くのアンカーとエイリアスでその問題を解決する方法を考えていましたか?

ありがとう

4

3 に答える 3

11

ここでの問題は、Yaml のアンカーとエイリアスはシリアル化の詳細であり、解析後のデータの一部ではないため、データを Yaml に書き戻すときに元のアンカー名がわからないことです。ラウンド トリップ時にアンカー名を保持するには、後でシリアル化するときに使用できるように、解析時にアンカー名をどこかに保存する必要があります。Ruby では、任意のオブジェクトにインスタンス変数を関連付けることができるため、これを実現する簡単な方法は、問題のオブジェクトのインスタンス変数にアンカー名を格納することです。

前の質問の例から続けて、ハッシュの場合、再定義されたメソッドを変更revive_hashして、ハッシュがアンカーの場合、アンカー名を@st変数に記録して、後でエイリアスを認識できるようにすることができます。それをインスタンスとして追加します。ハッシュ上の変数。

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

これは、アンカーである yaml マッピングにのみ影響することに注意してください。アンカー名を保持するために他のタイプが必要なpsych/visitors/to_ruby.rb場合は、すべての場合に名前が追加されていることを確認する必要があります。ほとんどのタイプはオーバーライドによって含めることができますregisterが、他にもいくつかあります。を検索し@stます。

ハッシュに必要なアンカー名が関連付けられたので、シリアル化するときに Psych がオブジェクト ID の代わりにそれを使用するようにする必要があります。これは、サブクラス化によって実行できますYAMLTreeYAMLTreeオブジェクトを処理するとき、最初にそのオブジェクトが既に認識されているかどうかを確認し、存在する場合はエイリアスを発行します。新しいオブジェクトについては、後でエイリアスを作成する必要がある場合に備えて、オブジェクトを見たことを記録します。これobject_idでは がキーとして使用されるため、これら 2 つのメソッドをオーバーライドしてインスタンス変数をチェックし、存在する場合は代わりにそれを使用する必要があります。

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? 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

  # record object for future, using '@_yaml_anchor_name' rather
  # than object_id if it exists
  def register target, yaml_obj
    anchor_name = target.instance_variable_get('@_yaml_anchor_name') || target.object_id
    @st[anchor_name] = yaml_obj
    yaml_obj
  end
end

これで、次のように使用できます (前の質問とは異なり、この場合、カスタム エミッターを作成する必要はありません)。

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
于 2012-11-27T16:08:42.757 に答える
1

これは、psych gem の新しいバージョンまでのわずかに変更されたバージョンです。次のエラーが表示される前に:

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

メソッドはのregisterサブクラスに移動したYAMLTreeため、マットが答えで言っていることすべてに関して、これが機能するようになりました。

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
于 2013-03-11T16:10:58.950 に答える
1

Psych v2.0.17 で動作するように、@markus が投稿したコードをさらに変更する必要がありました。

これが私が最終的に得たものです。他の誰かがかなりの時間を節約するのに役立つことを願っています. :-)

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
于 2016-06-14T22:28:56.810 に答える