Building an XML tree from the array "string / that / are / paths" (in Ruby)

What is the best way to create an XML tree in Ruby if you have an array of strings?

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> 

My first thought is to split the path string into an array and compare its depth and contents with the previous array, but then if I get the path "nodeA1 / nodeB1 / nodeC1 / nodeD1 / nodeE1" when I go back to "nodeA1 / nodeB1 / nodeC2 ", [1] node is a common ancestor, but tracking this is useless, as I did, at least.

I would also like to make it recursive so that I can handle each level of the socket in its own function, but has not yet arrived at a semi-universal solution.

Any ideas or things that you guys usually do when you come across this issue?

Thanks! A spear

+4
source share
3 answers

REXML is your friend! You get XPaths, so use 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 

Note that your example data has both nodeA1 and nodeA3 at the top level, so I started with the root named "xml" here. If "3" was a typo and nodeA1 really was your root (as your sample XML output suggests), you can remove the "x.elements <" xml "and change everything" / xml / "to" / ".

+5
source

This is very similar to this question . Here's a modified version based on sris 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 

Edit:

Here is a modified version that creates the actual XML document using Nokogiri. I think this is actually easier than in the string version. I also removed the use of File , because you really don't need this to meet your needs:

 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 

Edit 2:

Yes, it’s true that in Ruby 1.8 the hash is not guaranteed to preserve insertion order. If this is a problem, there are ways around it. Here's a solution that keeps order, but doesn't worry about recursion and is much simpler for it:

 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 
+4
source

Looks like a different version of this question .

So, you can simply define a tree structure and create nodes for each row in the list. Then write an output method that outputs the tree as xml.

If you want to do without defining a tree structure, you must make sure that the list is sorted as in your example. Then collapse the list and compare each line with the previous one:

  • For all nodes in the previous line that are not part of the current one, write the closing tag (in reverse order)
  • For all nodes in the current line that are not part of the previous line, write an opening tag.

This solution cannot create self-closing tags ("<nodeE1 />"), as this requires a comparison with the previous and next lines.

And this solution is not recursive, but I think the problem is not recursive either ... (or I just didn’t understand exactly why you need a recursive function)

+1
source

All Articles