Project

xml_dsl

0.0
No commit activity in last 3 years
No release in over 3 years
DSL designed for easily create XML mappers
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 2.8
~> 0.1
~> 0.2
~> 2.4

Runtime

~> 1.6
 Project Readme

Xml Dsl

Code Climate

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, also attributes full path is also a choise
        # define_xml_parser arbitrary_class, :root, "offer[type=uniq]"
        # You can pass as first argument not only Hash, but any Ruby class capable to set attributes defined in fields
        # define_xml_parser OpenStruct
        # define_xml_parser NiftyActiveRecordModel
        define_xml_parser Hash, :root, :offer do

          # Here is basic validation blocks
          # They MUST return some bool (if block)
          before_parse? do |node|
            !node.search('magick').empty?
          end
          
          # Or just check for must_have key (or path to it)
          before_parse? :jim
          before_parse? [:areas, 'area[type=hobby]']

          # 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
          
          # If something goes wrong - just raise XmlDsl::ParseError manually
          field :fuu do |_, node|
            raise XmlDsl::ParseError, 'FUUU' if node.search(:areas).length > 5
          end
        end
      end

Then you can use your newly defined parser as you wish:

    parser = Parser.new(nifty_xml, some_logger_eg)
    
    # just iterate with block
    parser.iterate do |instance|
        do_stuff_with_newly_mapped_instance_whatever(instance)
    end
    
    # or you can pass some accumulator for your instances to be put into
    parser.iterate acc: []
    
    # or event call a method on instances without getting them actually
    parser.iterate after_method: :save

Contributing

Feel free to request for some features or fork - implement - pul request.