Xml Dsl
Xml Dsl adds DSL for defining easy parsers (or mappers) for XML via nokogiri XML objects.
Install
Add xml_dsl
gem to Gemfile
:
gem 'xml_dsl', '~> 0.1.1'
Usage
Let's pretend we have an XML output of a following structure
<root>
<offer>
<id>703134</id>
<distance>10</distance>
<house>38a</house>
<rooms>
<room>1</room>
</rooms>
<prices>
<price>2200000</price>
</prices>
<currency>RUR</currency>
<floor>7</floor>
<nfloor>17</nfloor>
<areas>
<area type="total">37</area>
<area type="live">0</area>
<area type="kitchen">10</area>
</areas>
</offer>
<offer>
<id>703102</id>
<distance>25</distance>
<house>7</house>
<rooms>
<room>1</room>
</rooms>
<prices>
<price>2650000</price>
</prices>
<currency>RUR</currency>
<floor>1</floor>
<nfloor>5</nfloor>
<areas>
<area type="total">30</area>
<area type="live">17</area>
<area type="kitchen">7</area>
</areas>
<magick>woodoo</magick>
</offer>
</root>
Then we can define our mapper as following
class Parser
attr_accessor :xml, :external
def initialize(xml, external)
# Normal iteration goes from xml instance_variable
self.xml = xml
self.external = external
end
# Here we definig parser, which will put attributes to Hash, and look for <offer> nodes inside <root>
# any length of root path can be provided
define_xml_parser Hash, :root, :offer do
# We can define a block to call every time an XmlDsl::ParseError os raised
# it is raised if null: true is passed to field declaration, or manually via raise XmlDsl::ParseError
error_handle do |e, node|
external.notify node
end
# Field declaration
field :id, :id, matcher: :to_i
# We can pass getter to call on Nokogiri::Xml::Element (default :text) and matcher (default :to_s)
field :minutes, :distance, matcher: :to_i
# If null: true (default false) is passed then if field is empty - it will raise XmlDsl::ParseError
# and then triggers error_handle blocks
field :magick, :magick, null: true
# We can pass long xpath to find proper node inside our XML
# like this:
field :amount, [:prices, :price], matcher: :to_i
# or even like this:
field :total_area, [:areas, 'area[type=total]'], matcher: :to_i
# If our logic is more complex we can define it inside block
field :full_area, null: true do |instance, node|
if !instance[:area]
node.search('areas').reduce(0) { |acc,n| acc + n.text.to_i }
else
0
end
end
end
end
Contributing
Feel free to request for some features or fork - implement - pul request.