HiveBrain v1.2.0
Get Started
← Back to all entries
patternrubyMinor

Building XML by enumerating through array & hashes

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
arrayenumeratingbuildingxmlhashesthrough

Problem

data =
[ { 'name' => 'category1',
    'subCategory' => [ {'name' => 'subCategory1',
                        'product' => [ {'name' => 'prodcutName1',
                                        'desc' => 'desc1'},
                                       {'name' => 'prodcutName2',
                                        'desc' => 'desc2'}]
                        } ]

  },
  { 
    #category2 ...and so on 
  }
]


Just recently finished a small project with Ruby. I used the above array of hashes to produce a XML.

I think my solution is quite messy nesting loops and builder tags. Can anyone help me out with a more elegant approach?

builder = Nokogiri::XML::Builder.new { |xml|
data.each { |category| # go through every category in the data array
    xml.category {
        xml.name category['name']
        category['subCategory'].each { |subCategory| # go through each subCategory in the category
            xml.subCategory {
                xml.name subCategory['name']
                subCategory['product'].each { |product| # go though each products and print their data
                    xml.name product['name']
                    xml.desc product['desc']
                }
            }
        }
    }
}

puts builder.to_xml

Solution

Here's a nice recursive solution, that creates value from 'key'=>'value' entries in your hash. If the value is an array, it instead recurses, using the key name as a wrapper element.

require 'nokogiri'

def process_array(label,array,xml)
array.each do |hash|
xml.send(label) do # Create an element named for the label
hash.each do |key,value|
if value.is_a?(Array)
process_array(key,value,xml) # Recurse
else
xml.send(key,value) # Create value (using variables)
end
end
end
end
end

builder = Nokogiri::XML::Builder.new do |xml|
xml.root do # Wrap everything in one element.
process_array('category',data,xml) # Start the recursion with a custom name.
end
end

puts builder.to_xml


When used with this data…

data = [
{ 'name' => 'category1',
'subCategory' => [
{ 'name' => 'subCategory1',
'product' => [
{ 'name' => 'productName1',
'desc' => 'desc1' },
{ 'name' => 'productName2',
'desc' => 'desc2' } ]
} ]
},
{ 'name' => 'category2',
'subCategory' => [
{ 'name' => 'subCategory2.1',
'product' => [
{ 'name' => 'productName2.1.1',
'desc' => 'desc1' },
{ 'name' => 'productName2.1.2',
'desc' => 'desc2' } ]
} ]
},
]


…you get this result:




category1

subCategory1

productName1
desc1


productName2
desc2




category2

subCategory2.1

productName2.1.1
desc1


productName2.1.2
desc2






However, if I had control over the XML schema, I'd do this instead:

require 'nokogiri'

def process_array(label,array,xml)
  array.each do |hash|
    kids,attrs = hash.partition{ |k,v| v.is_a?(Array) }
    xml.send(label,Hash[attrs]) do
      kids.each{ |k,v| process_array(k,v,xml) }
    end
  end
end

builder = Nokogiri::XML::Builder.new do |xml|
  xml.root{ process_array('category',data,xml) }
end

puts builder.to_xml



  
    
      
      
    
  
  
    
      
      
    
  


…but perhaps you're dealing with some terrible XML schema like PList.

Code Snippets

require 'nokogiri'

def process_array(label,array,xml)
  array.each do |hash|
    kids,attrs = hash.partition{ |k,v| v.is_a?(Array) }
    xml.send(label,Hash[attrs]) do
      kids.each{ |k,v| process_array(k,v,xml) }
    end
  end
end

builder = Nokogiri::XML::Builder.new do |xml|
  xml.root{ process_array('category',data,xml) }
end

puts builder.to_xml
<?xml version="1.0"?>
<root>
  <category name="category1">
    <subCategory name="subCategory1">
      <product name="productName1" desc="desc1"/>
      <product name="productName2" desc="desc2"/>
    </subCategory>
  </category>
  <category name="category2">
    <subCategory name="subCategory2.1">
      <product name="productName2.1.1" desc="desc1"/>
      <product name="productName2.1.2" desc="desc2"/>
    </subCategory>
  </category>
</root>

Context

StackExchange Code Review Q#51569, answer score: 8

Revisions (0)

No revisions yet.