# Need help in parsing REXML::Document

**URL:** <https://www.ruby-forum.com/t/need-help-in-parsing-rexml-document/113673>\
**Category:** Ruby\
**Created:** [September 3, 2007, 6:52pm UTC](https://www.ruby-forum.com/t/need-help-in-parsing-rexml-document/113673 "2007-09-03T18:52:20Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![Nikolay\_P](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/n/f9ae1b/32.png) [@Nikolay\_P](https://www.ruby-forum.com/u/Nikolay_P)\
**Post date:** [September 3, 2007, 6:52pm UTC](https://www.ruby-forum.com/t/need-help-in-parsing-rexml-document/113673/1 "2007-09-03T18:52:20Z")

</div>

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” 🙂

---

<div class="post-metadata">

**Author:** ![Nikolay\_P](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/n/f9ae1b/32.png) [@Nikolay\_P](https://www.ruby-forum.com/u/Nikolay_P)\
**Post date:** [September 3, 2007, 8:29pm UTC](https://www.ruby-forum.com/t/need-help-in-parsing-rexml-document/113673/2 "2007-09-03T20:29:46Z")

</div>

[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.

---

<div class="post-metadata">

**Author:** ![Nikolay\_P](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/n/f9ae1b/32.png) [@Nikolay\_P](https://www.ruby-forum.com/u/Nikolay_P)\
**Post date:** [September 5, 2007, 2:49pm UTC](https://www.ruby-forum.com/t/need-help-in-parsing-rexml-document/113673/3 "2007-09-05T14:49:42Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![Nikolay\_P](https://www.ruby-forum.com/letter_avatar_proxy/v4/letter/n/f9ae1b/32.png) [@Nikolay\_P](https://www.ruby-forum.com/u/Nikolay_P)\
**Post date:** [September 4, 2007, 7:34pm UTC](https://www.ruby-forum.com/t/need-help-in-parsing-rexml-document/113673/4 "2007-09-04T19:34:13Z")

</div>

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”}]
