Le problème ici est que anchors and aliases en YAML sont un détail de sérialisation, et ainsi ne font pas partie des données après qu'il a été analysé, de sorte que le nom d'ancrage d'origine est inconnue lors de l'écriture des données de retour à Yaml. Afin de garder les noms d'ancre lors du déclenchement, vous devez les stocker quelque part lors de l'analyse afin qu'ils soient disponibles plus tard lors de la sérialisation. Dans Ruby, n'importe quel objet peut avoir des variables d'instance qui lui sont associées, donc un moyen facile d'y parvenir serait de stocker le nom de l'ancre dans une variable d'instance de l'objet en question.
suite de l'exemple dans the earlier question, pour hash nous pouvons changer notre redéfinie méthode revive_hash
de sorte que si le hachage est un point d'ancrage alors ainsi que l'enregistrement du nom de l'ancre dans la variable @st
afin Alises plus tard peuvent être reconnus, nous ajoutons la comme une variable d'instance sur le hachage.
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
Notez que cela n'affecte que les mappages yaml qui sont des ancrages. Si vous voulez que d'autres types conservent leur nom d'ancre, vous devrez regarder psych/visitors/to_ruby.rb
et vous assurer que le nom est ajouté dans tous les cas. La plupart des types peuvent être inclus en remplaçant register
mais il y en a quelques autres; recherche @st
. Maintenant que le hash est associé au nom d'ancrage souhaité, vous devez faire en sorte que Psych l'utilise à la place de l'identifiant de l'objet lors de la sérialisation. Cela peut être fait en sous-classant YAMLTree
. Lorsque YAMLTree
traite un objet, il first checks to see if that object has been seen already, and emits an alias for it if it has. Pour tous les nouveaux objets, il records that it has seen the object in case it needs to create an alias later. Le object_id
est utilisé comme clé dans ce domaine, vous avez donc besoin de passer outre ces deux méthodes pour vérifier la variable d'instance, et utiliser à la place si elle existe:
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
Maintenant, vous pouvez l'utiliser comme ceci (contrairement à la question précédente, vous n'avez pas besoin de créer un émetteur personnalisé dans ce cas):
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
c'est parfait! Exactement ce que je cherchais :) Comment puis-je, au lieu d'utiliser $ stdout, stocker la sortie dans une variable de chaîne? Je veux écrire la sortie dans le fichier. Merci beaucoup! – Max
@Max Je viens de jeter un oeil à la source, et il y a une façon plus simple de créer la sortie avec 'builder.yaml.La méthode tree' qui renvoie une chaîne si vous ne fournissez aucune entrée. Vous pouvez également fournir un objet 'IO' comme paramètre si vous le souhaitez, afin que vous puissiez écrire directement dans le fichier de cette façon. Voir ma réponse mise à jour. – matt
vous êtes un génie! Appréciez l'aide, psych est vraiment un outil incroyable. – Max