Here's one way:
require 'nokogiri'
doc = Nokogiri::XML(<<EOT)
<?xml version="1.0" encoding="UTF-8"?>
<Document-St-5>
<SDocument>
<ItemList>
<Items_A>
<ItemElem>
<Item_Values>
<ProdA>2</ProdA>
<ProdB>8</ProdB>
</Item_Values>
</ItemElem>
</Items_A>
<Items_B>
<ItemElem>
<Item_Values>
<ProdA>8</ProdA>
<ProdB>9</ProdB>
</Item_Values>
</ItemElem>
</Items_B>
</ItemList>
</SDocument>
</Document-St-5>
EOT
data = doc.search('SDocument').map{ |node|
%w[ProdA ProdB].map{ |n| node.search(n).map(&:text) }
}
data # => [[["2", "8"], ["8", "9"]]]
It results in a bit deeper nesting than you want but it's close.
A little different way, perhaps more easily understood, is:
data = doc.search('SDocument').map{ |node|
%w[A B].map{ |ab|
node.at("Items_#{ ab }").search('ProdA, ProdB').map(&:text)
}
}
The reason the nesting is one-level deeper than you specified is, I'm assuming there will be multiple <SDocument>
tags in the XML. If there won't be, then the code can be modified a bit to return the array as you're asking:
data = doc.search('Items_A, Items_B').map{ |node|
node.search('ProdA, ProdB').map(&:text)
}
data # => [["2", "8"], ["8", "9"]]
Notice I'm using CSS selectors, to make it easy to specify I want the code to look at two different nodes, both for Items_A
and Items_B
, and ProdA
and ProdB
.
Update after the question completely changed:
Here's the set-up:
require 'nokogiri'
doc = Nokogiri::XML(<<EOT)
<?xml version="1.0" encoding="UTF-8"?>
<Document-St-5>
<SDocument>
<StName>WERLJ01</StName>
<StCode>MEKLD</StCode>
<ItemList>
<Items_A>
<ItemElem>
<Item_Values>
<ProdA>2</ProdA>
<ProdB>8</ProdB>
</Item_Values>
</ItemElem>
</Items_A>
<Items_A>
<ItemElem>
<Item_Values>
<ProdA>9</ProdA>
<ProdB>3</ProdB>
</Item_Values>
</ItemElem>
</Items_A>
<Items_B>
<ItemElem>
<Item_Values>
<ProdA>1</ProdA>
<ProdB>17</ProdB>
</Item_Values>
</ItemElem>
</Items_B>
</ItemList>
</SDocument>
</Document-St-5>
EOT
Here's the code:
data = %w[StName StCode].map{ |n| [doc.at(n).text] }
%w[ProdA ProdB].each do |prod|
data << doc.search('Items_A').map{ |item| item.at(prod).text }
end
%w[ProdA ProdB].each do |prod|
data << [doc.at("Items_B #{prod}").text]
end
Here's what was captured:
data # => [["WERLJ01"], ["MEKLD"], ["2", "9"], ["8", "3"], ["1"], ["17"]]