Skip to content

vladson/xml_dsl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages