Ruby 如何使用Nokogiri从散列构建节点
许多人通常希望进行相反的转换,但我想从包含许多嵌套哈希甚至数组的Ruby哈希构建节点:Ruby 如何使用Nokogiri从散列构建节点,ruby,xml,recursion,hash,nokogiri,Ruby,Xml,Recursion,Hash,Nokogiri,许多人通常希望进行相反的转换,但我想从包含许多嵌套哈希甚至数组的Ruby哈希构建节点: my_hash = { "name" => "Something", "property_1" => "Something" "nested_array_items" => [{ "name" => "Nasty nested array item", "advice" => "Use recursive function" },
my_hash = {
"name" => "Something",
"property_1" => "Something"
"nested_array_items" => [{ "name" => "Nasty nested array item",
"advice" => "Use recursive function" },
{ "name" => "yes this is an array",
"notice" => "not necessarily the same keys"}],
"nested_many_levels" => { "additional_items" => { "ok_stop_here" => true } },
}
我有一个Nokogiri节点,它应该包含所有这些。我如何定义函数来完成它
每个子节点都应该用键的名称命名,并最终替换为-。对于数组,为每个项使用键名称的单数,假设它是以s结尾的规则复数,否则会引发错误
例如,上面给出的散列应为:
...
某物>
某物
嵌套数组项
使用递归函数
是的,这是一个数组
不一定是同一把钥匙
符合事实的
...
好的,这是我的代码,似乎有效
def build_node_recursively(node, content_hash, xml_document)
content_hash.each do |key, val|
new_node = Nokogiri::XML::Node.new "#{key.to_s}", xml_document
# Val can be : a value | an array of hashes | another hash
if val.is_a?(Array)
item_label = key.to_s.singularize
val.each do |item| # Assume item is another content_hash. Wouldn't make sense (for me) to have just a value...
item_node = Nokogiri::XML::Node.new item_label, xml_document
new_node << build_node_recursively(item_node, item)
end
elsif val.is_a?(Hash)
build_node_recursively(new_node, val)
else
new_node.content = val.to_s
end
node << new_node
end
end
好的,所以我意识到从散列构建节点不是我的最佳选择。在我的例子中,我希望拥有完整的XML结构,即使有些节点由于缺少散列内容而为空 因此,我使用的XML模板节点已经包含了我想要的完整结构,只有长度为1的数组。因此,我不是在构建新节点,而是在需要预处理的时候复制现有节点,然后替换内容 因为这只对数组来说是痛苦的,所以我们假设我的contents变量,在第一次调用中,只是一个包含数组的散列,但是这些数组的项可以是值、散列 复制自定义xml的模板节点
或者也许Nokogiri已经提供了一个助手来做这件事?我试图回答这个问题,但我投了反对票,因为你的要求不清楚。您提供了一些示例输入,但没有相应的示例输出。然后根据不完整的输入提供示例输出。嵌套的多个级别会发生什么?如果你编辑这个问题来解决这些问题,我会很高兴地改变我的投票。你是对的,这还不清楚。我重写了输出样本,使其与输入哈希样本匹配。希望现在更好我已经否决了你的答案,因为代码实际上没有运行。除了需要“active_support/core_ext/string/definctions”之外,您无法将xml_文档作为第三个参数递归地传递给您的方法,但是即使进行了更改,代码也无法使用您自己的示例输入运行。啊,您在很多方面都是正确的。首先,我从Rails应用程序运行了这段代码,因此已经加载了屈折符wwas。看来我还没有发布我的代码的最新版本。。。不幸的是,我没有保留,因为我意识到我不需要构建节点,而只需要替换现有节点的内容。。。
contents.map do |content, items|
tmp = custom_xml.search("#{content.to_s}") # Should be unique !
if tmp.count > 1 then raise "ERROR : multiple nodes match XPATH //#{content.to_s}" end
if tmp.count == 0 then raise "ERROR : No node matches \"search #{content.to_s}\" DEBUG : #{custom_xml.serialize}" end
array_node = tmp.first # <array><item>...</item></array>
template_node = array_node.first_element_child
# Okay, we have the customXML node corresponding to the first item of the content
# We need to duplicate it as many times as needed
items.each_with_index do |item, item_index|
next if item_index == 0 # Skip the first one.
array_node << template_node.dup
end
end
def replace_node_vars_recursively(node, content)
if content.nil?
puts "WARNING : nil content trying to be assigned to node #{node.name}"
elsif content.is_a?(Hash)
# Every key in content SHOULD have a matching child node !
content.each do |key, val|
candidates = node.search("#{key.to_s}") # Should be unique !
if candidates.count > 1
puts "WARNING : multiple child_nodes match -->#{key.to_s}<--, skipping"
next
elsif candidates.count == 0
puts "WARNING : No child node matches \"#{key.to_s}\" "
next
end
replace_node_vars_recursively(candidates.first, val)
end
# Array recursion (rq : array contains either a Hash or a value.)
elsif content.is_a?(Array)
# Let's rename the variables !
array_items = content
array_node = node
if array_items.count != array_node.element_children.count # /!\ using just "children" will return empty nodes !!!
raise "ERROR : array length (#{array_items.count}) != number of nodes of #{array_node.name} (#{array_node.element_children.count}) !"
end
array_node.element_children.each_with_index do |child_node, index| # Assume item is another content_hash. Wouldn't make sense (for me) to have just a value there...
replace_node_vars_recursively(child_node, content[index])
end
# Value terminaison
elsif content.is_a?(String) or content.is_a?(Integer) or content.is_a?(Float) or content.is_a?(Symbol) or content.is_a?(Date) or content.is_a?(Datetime)
node.content = content.to_s
puts "Replacing variable #{node.name} by #{content.to_s}"
else
puts content
raise "ERROR: unknown variable type for variable replacement !"
end
end