Need help in parsing REXML::Document

Hello all.
I have this XML output:

<operation id="other id" sid="other sid" ...

Could someone point me to the way on how i can get construction like
this
one from the XML above:

{:num => value,
:token => value,
:operation => [{:id => value,
:sid => value,
:trans => value,
:data => value },
{:id => value,
:sid => value,
:trans => value,
:data => value}]
}

The initial hash part was trivial for me, but while i am thinking about
an
array part my brain says me “stack level too deep” :slight_smile:

[Please post raw text; my newsreader chokes on whatever you posted…]

Nikolay P. wrote:

I have this XML output:

<operation id="other id" sid="other sid" ...

Could someone point me to the way on how i can get construction like this
one from the XML above:

{:num => value,
:token => value,
:operation => [{:id => value,
:sid => value,

I get the idea you want a generic system to turn any stereotypical XML
to a
hash. The XML is too varied to just hard-code the transformation, but
the
XML doesn’t contain advanced features like nodes containing both text
and
sub-nodes.

def git_riddim(node)
hash = {}
REXML::XPath.each node, ‘*’ do |node|
contents = (node.text || ‘’).strip
(hash[node.name] ||= []) << (contents.blank? ? git_riddim(node) :
node.text)
end
return hash
end

def test_recursive_xml_reader
xml = ’
v1
v2

v3
v4






doc = REXML::Document.new(xml)
hash = git_riddim(doc)
assert_equal({“response”=>[{“token”=>[“v2”], “num”=>[“v1”],
“operation”=>[{“trans”=>[“v3”], “data”=>[“v4”]}, {“trans”=>[{}],
“data”=>[{}]}]}]}), hash
end

Now here’s the head-hurting part. We need arrays to turn into objects if
they only contain one element.

def reduce_dimensions(hash)
return unless hash.kind_of?(Hash)
hash.each do |k,v|
v.each do |q| reduce_dimensions(q) end
hash[k] = *v
end
end

reduce_dimensions(hash)

assert_equal({"response"=>
              {"token"=>"v2",
              "num"=>"v1",
 "operation"=>[{"trans"=>"v3", "data"=>"v4"}, {"trans"=>{},

“data”=>{}}]}},
hash)

We used * to turn arrays of none or one into nil or an object.

Outside of that trick, my code is much more flabby that usual for Hash
contortions. Someone might be able to use .inject or something to get it
down to fewer lines, in one pass.

OK. I have come up to the final version that do what i want.
Here it is:

def parse_to_hash(node)
normalize(xml_to_hash_of_arrays(node))
end

def xml_to_hash_of_arrays(node)
hash = {}
REXML::XPath.each node, ‘*’ do |node|
if node.parent.has_attributes?
node.parent.attributes.each_attribute do |attr|
hash[attr.name.underscore.to_sym] = attr.value
end
end
(hash[node.name.underscore.to_sym] ||= []) << if node.has_elements?
xml_to_hash_of_arrays(node)
else
(node.text || ‘’).strip
end
end
return hash
end

def normalize(hash)
return unless hash.kind_of?(Hash)
hash.each do |key, value|
value.each { |val| normalize(val) }
hash[key] = *value
end
end

Many thanks Philip to your suggestions.

On Monday 03 September 2007 21:28:57 Phlip wrote:

Outside of that trick, my code is much more flabby that usual for Hash
contortions. Someone might be able to use .inject or something to get it
down to fewer lines, in one pass.

Thanks Phlip. With your suggestions i have come to some thing like this
at
the end:

def parse_to_hash(node)
normalize(xml_to_hash_of_arrays(node))
end

def xml_to_hash_of_arrays(node)
hash = {}
REXML::XPath.each node, ‘*’ do |node|
(hash[node.name.underscore.to_sym] ||= []) << if node.has_elements?
parse_node(node)
else
(node.text || ‘’).strip
end
end
end

def normalize(hash)
return unless hash.kind_of?(Hash)
hash.each do |key, value|
value.each { |val| reduce(val) }
hash[key] = *value
end
end

But one thing is still missed. In my original example i want to move the
attributes of the nodes (that hash_elements? == true) to the array, for
example:

         <operation id="some id" sid="some sid">
           <trans>v3</trans>
           <data>v4</data>
         </operation>

Should produce:

[{:id => “some id”, :sid => “some sid”, :trans => “v4”, :data => “v4”}]