如果你有一个字符串路径数组,在Ruby中构建XML树的最佳方法是什么?
paths = [
"nodeA1",
"nodeA1/nodeB1/nodeC1",
"nodeA1/nodeB1/nodeC1/nodeD1/nodeE1",
"nodeA1/nodeB1/nodeC2",
"nodeA1/nodeB2/nodeC2",
"nodeA3/nodeB2/nodeC3"
]
xml =
<nodeA1>
<nodeB1>
<nodeC1>
<nodeD1>
<nodeE1/>
</nodeD1>
</nodeC1>
<nodeC2/>
</nodeB1>
<nodeB2>
<nodeC2/>
<nodeC3/>
</nodeB2>
</nodeA1>
我的第一个想法是将路径字符串拆分为一个数组,并将其深度和内容与前一个数组进行比较,但是如果我到达路径“nodeA1 / nodeB1 / nodeC1 / nodeD1 / nodeE1”,那么我去回到“nodeA1 / nodeB1 / nodeC2”,[1]节点是共同的祖先,但跟踪它是混乱的,至少我一直这样做。
我也想让它递归,所以我可以在自己的函数中处理每个嵌套级别,但还没有达到任何半通用的解决方案。
当你遇到这个问题时,你们常常做的任何想法或事情?
谢谢! 兰斯
答案 0 :(得分:5)
REXML是你的朋友!你得到了XPath,所以请使用'em!
require 'rexml/document'
paths = [
"nodeA1",
"nodeA1/nodeB1/nodeC1",
"nodeA1/nodeB1/nodeC1/nodeD1/nodeE1",
"nodeA1/nodeB1/nodeC2",
"nodeA1/nodeB2/nodeC2",
"nodeA3/nodeB2/nodeC3"
]
x = REXML::Document.new
x.elements << "xml"
paths.each do |p|
steps = p.split(/\//)
steps.each_index do |i|
unless REXML::XPath.first(x,"/xml/" + steps[0..i]*"/")
REXML::XPath.first(x,"/xml/" + steps[0...i]*"/").elements << steps[i]
end
end
end
puts x.to_s
请注意,您的示例数据在顶层都有nodeA1和nodeA3,所以我在这里开始使用名为“xml”的根。如果“3”是拼写错误,并且nodeA1确实是您的根(正如您的示例XML输出所示),您可以删除'x.elements&lt;&lt; “xml”'行并将所有“/ xml /”更改为“/".
答案 1 :(得分:4)
这与this question非常相似。这是基于sris's answer的修改版本:
paths = [
"nodeA1",
"nodeA1/nodeB1/nodeC1",
"nodeA1/nodeB1/nodeC1/nodeD1/nodeE1",
"nodeA1/nodeB1/nodeC2",
"nodeA1/nodeB2/nodeC2",
"nodeA3/nodeB2/nodeC3"
]
tree = {}
paths.each do |path|
current = tree
path.split("/").inject("") do |sub_path,dir|
sub_path = File.join(sub_path, dir)
current[sub_path] ||= {}
current = current[sub_path]
sub_path
end
end
def make_tree(prefix, node)
tree = ""
node.each_pair do |path, subtree|
tree += "#{prefix}<#{File.basename(path)}"
if subtree.empty?
tree += "/>\n"
else
tree += ">\n"
tree += make_tree(prefix + "\t", subtree) unless subtree.empty?
tree += "#{prefix}</#{File.basename(path)}>\n"
end
end
tree
end
xml = make_tree "", tree
print xml
这是一个使用Nokogiri构建实际XML文档的修改版本。我认为它比字符串版本更容易理解。我还删除了File
的使用,因为您实际上并不需要它来满足您的需求:
require 'nokogiri'
paths = [
"nodeA1",
"nodeA1/nodeB1/nodeC1",
"nodeA1/nodeB1/nodeC1/nodeD1/nodeE1",
"nodeA1/nodeB1/nodeC2",
"nodeA1/nodeB2/nodeC2",
"nodeA3/nodeB2/nodeC3"
]
tree = {}
paths.each do |path|
current = tree
path.split("/").each do |name|
current[name] ||= {}
current = current[name]
end
end
def make_tree(node, curr = nil, doc = Nokogiri::XML::Document.new)
#You need a root node for the XML. Feel free to rename it.
curr ||= doc.root = Nokogiri::XML::Node.new('root', doc)
node.each_pair do |name, subtree|
child = curr << Nokogiri::XML::Node.new(name, doc)
make_tree(subtree, child, doc) unless subtree.empty?
end
doc
end
xml = make_tree tree
print xml
是的,确实在Ruby 1.8哈希值不能保证维持插入顺序。如果这是一个问题,有办法解决它。这是一个保留顺序但不会对递归感到困扰的解决方案,并且更加简单:
require 'nokogiri'
paths = [
"nodeA1",
"nodeA1/nodeB1/nodeC1",
"nodeA1/nodeB1/nodeC1/nodeD1/nodeE1",
"nodeA1/nodeB1/nodeC2",
"nodeA1/nodeB2/nodeC2",
"nodeA3/nodeB2/nodeC3"
]
doc = Nokogiri::XML::Document.new
doc.root = Nokogiri::XML::Node.new('root', doc)
paths.each do |path|
curr = doc.root
path.split("/").each do |name|
curr = curr.xpath(name).first || curr << Nokogiri::XML::Node.new(name, doc)
end
end
print doc
答案 2 :(得分:1)
看起来像this question的另一个版本。
因此,您可以定义树结构并为列表中的每个字符串创建节点。然后编写一个输出方法,将树打印为xml。
如果您不想定义树结构,则必须确保列表按照示例中的顺序排序。然后遍历列表并将每一行与前一行进行比较:
此解决方案无法生成自动关闭标记(“&lt; nodeE1 /&gt;”),因为这需要与上一行和下一行进行比较。
这个解决方案不是递归的,但我认为这个问题不是一个递归的问题......(或者我只是不完全理解,为什么你想要一个递归函数)