从“strings / that / are / paths”数组构建XML树(在Ruby中)

如果你有一个字符串路径数组,在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”,当我回到“nodeA1 / nodeB1 / nodeC2”,[1]节点是共同的祖先,但跟踪它是混乱的,至少我这样做的方式。

我也想让它递归,所以我可以在它自己的函数中处理每个嵌套级别,但还没有达到任何半通用的解决方案。

当你遇到这个问题时,你们常常做的任何想法或事情?

谢谢! 长矛

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 <<“xml”'行并将所有“/ xml /”更改为只是“/”。

这与这个问题非常相似。 这是基于sris答案的修改版本:

 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}\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 

编辑2:

是的,确实在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 

看起来像这个问题的另一个版本。

因此,您可以定义树结构并为列表中的每个字符串创建节点。 然后编写一个输出方法,将树打印为xml。

如果您不想定义树结构,则必须确保列表按照示例中的顺序排序。 然后遍历列表并将每一行与前一行进行比较:

  • 对于上一行中不属于当前节点的所有节点,请写入结束标记(按相反顺序)
  • 对于当前行中不属于上一行的所有节点,请编写一个开始标记。

此解决方案无法生成自动关闭标签(“”),因为这需要与前一行和下一行进行比较。

这个解决方案不是递归的,但我认为这个问题不是一个递归的问题……(或者我只是不完全理解,为什么你想要一个递归函数)