【问题标题】:Building an XML tree from an Array of "strings/that/are/paths" (in Ruby)从“strings/that/are/paths”数组构建 XML 树(在 Ruby 中)
【发布时间】:2009-10-01 00:42:35
【问题描述】:

如果您有一个字符串路径数组,那么在 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] 节点是共同的祖先,但是跟踪它很麻烦,至少我一直在这样做。

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

遇到这个问题时,你们有什么想法或通常会做的事情吗?

谢谢! 兰斯

【问题讨论】:

    标签: xml ruby parsing tree


    【解决方案1】:

    REXML 是你的朋友!您正在获取 XPath,因此请使用它们!

    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

    【讨论】:

    • 非常酷!我最近一直在使用 nokogiri,因为 REXML 有很多负面影响,但这让我想试一试。为什么选择 REXML 而不是 hpricot 或 nokogiri,或者你可以一起使用它们吗?
    • Rexml 附带 Ruby 安装,否则我相信您可以使用 hpricot 或 nokogiri
    • 对,我使用了 REXML,因为您已经拥有它,而且它可以很好地完成手头的任务。没有对 hpricot 或 nokogiri 的判断。
    【解决方案2】:

    这与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
    

    编辑 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 库而不是使用字符串。
    • 我已经修改它以创建一个实际的 Nokogiri XML 文档。希望对您有所帮助。
    • 如果您在 Ruby 1.8x 中执行此操作,则创建中间哈希会丢失任何给定父级之下的子级顺序。所以如果顺序很重要,这个解决方案只适用于 1.9。
    • 这不是问题的要求,但这是一个公平的观点。
    • Edit 2 版本很好。比我的快得多,即使忽略 REXML/Nokogiri 的差异。
    【解决方案3】:

    看起来像 this question 的另一个版本。

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

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

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

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

    而且这个解决方案不是递归的,但我认为问题也不是递归的......(或者我只是不明白,为什么你想要一个递归函数)

    【讨论】:

      猜你喜欢
      • 2011-06-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-11-11
      • 2021-12-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多