Class: XmlMapper

Inherits:
Object
  • Object
show all
Defined in:
lib/xml_mapper.rb,
lib/xml_mapper_hash.rb

Defined Under Namespace

Classes: XmlMapperHash

Constant Summary collapse

TYPE_TO_AFTER_CODE =
{
  :integer => :to_i,
  :boolean => :string_to_boolean
}
MAPPINGS =
{
  "true" => true,
  "false" => false,
  "yes" => true,
  "no" => false,
  "y" => true,
  "n" => false,
  "1" => true,
  "0" => false
}

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeXmlMapper

Returns a new instance of XmlMapper.



87
88
89
90
# File 'lib/xml_mapper.rb', line 87

def initialize
  self.mappings = []
  self.selector_mode = :search
end

Class Attribute Details

.mapperObject

Returns the value of attribute mapper.



10
11
12
# File 'lib/xml_mapper.rb', line 10

def mapper
  @mapper
end

Instance Attribute Details

#after_map_blockObject

Returns the value of attribute after_map_block.



7
8
9
# File 'lib/xml_mapper.rb', line 7

def after_map_block
  @after_map_block
end

#mappingsObject

Returns the value of attribute mappings.



7
8
9
# File 'lib/xml_mapper.rb', line 7

def mappings
  @mappings
end

#selector_modeObject

Returns the value of attribute selector_mode.



7
8
9
# File 'lib/xml_mapper.rb', line 7

def selector_mode
  @selector_mode
end

#within_xpathObject

Returns the value of attribute within_xpath.



7
8
9
# File 'lib/xml_mapper.rb', line 7

def within_xpath
  @within_xpath
end

Class Method Details

.add_mapper_to_args(args, mapper) ⇒ Object



66
67
68
69
70
71
72
# File 'lib/xml_mapper.rb', line 66

def add_mapper_to_args(args, mapper)
  if args.length > 1 && args.last.is_a?(Hash)
    args.last[:mapper] = mapper
  else
    args << { :mapper => mapper }
  end
end

.after_map(&block) ⇒ Object



25
26
27
# File 'lib/xml_mapper.rb', line 25

def after_map(&block)
  mapper.after_map(&block)
end

.attributes_from_superclass(xml, method = :attributes_from_xml) ⇒ Object



39
40
41
42
43
44
45
46
47
# File 'lib/xml_mapper.rb', line 39

def attributes_from_superclass(xml, method = :attributes_from_xml)
  if self.superclass && self.superclass.respond_to?(:mapper)
    attributes = self.superclass.mapper.send(method, xml)
    attributes.delete(:xml_path)
    attributes
  else
    {}
  end
end

.attributes_from_xml(xml) ⇒ Object



35
36
37
# File 'lib/xml_mapper.rb', line 35

def attributes_from_xml(xml)
  attributes_from_superclass(xml, :attributes_from_xml).merge(mapper.attributes_from_xml(xml))
end

.attributes_from_xml_path(path, xml = nil) ⇒ Object



49
50
51
52
53
54
55
# File 'lib/xml_mapper.rb', line 49

def attributes_from_xml_path(path, xml = nil)
  if xml
    attributes_from_superclass(xml, :attributes_from_xml).merge(mapper.attributes_from_xml(xml, path))
  else
    attributes_from_superclass(path, :attributes_from_xml_path).merge(mapper.attributes_from_xml_path(path))
  end
end

.capture_submapping(&block) ⇒ Object



57
58
59
60
61
62
63
64
# File 'lib/xml_mapper.rb', line 57

def capture_submapping(&block)
  saved_mapper = self.mapper
  self.mapper = self.new
  self.instance_eval(&block)
  captured_mapper = self.mapper
  self.mapper = saved_mapper
  captured_mapper
end

.include_mapper(clazz) ⇒ Object



82
83
84
# File 'lib/xml_mapper.rb', line 82

def include_mapper(clazz)
  self.mapper.mappings += clazz.mapper.mappings
end

.many(*args, &block) ⇒ Object



29
30
31
32
33
# File 'lib/xml_mapper.rb', line 29

def many(*args, &block)
  sub_mapper = block_given? ? capture_submapping(&block) : nil
  add_mapper_to_args(args, sub_mapper)
  mapper.add_mapping(:many, *args)
end

.selector_mode(style) ⇒ Object



78
79
80
# File 'lib/xml_mapper.rb', line 78

def selector_mode(style)
  self.mapper.selector_mode = style
end

.within(xpath, &block) ⇒ Object



18
19
20
21
22
23
# File 'lib/xml_mapper.rb', line 18

def within(xpath, &block)
  self.mapper.within_xpath ||= []
  self.mapper.within_xpath << xpath
  self.instance_eval(&block)
  self.mapper.within_xpath.pop
end

Instance Method Details

#add_mapping(type, *args) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
# File 'lib/xml_mapper.rb', line 96

def add_mapping(type, *args)
  options = extract_options_from_args(args)
  if args.first.is_a?(Hash)
    if after_map_method = args.first.delete(:after_map)
      options.merge!(:after_map => after_map_method)
    end
    args.first.map { |xpath, key| add_single_mapping(type, xpath, key, options) }
  else
    args.map { |arg| add_single_mapping(type, arg, arg, options) }
  end
end

#add_single_mapping(type, xpath_or_attribute, key, options = {}) ⇒ Object



112
113
114
115
116
117
118
119
120
# File 'lib/xml_mapper.rb', line 112

def add_single_mapping(type, xpath_or_attribute, key, options = {})
  mappings = { :type => type, :key => key, :options => options }
  xpath = type == :attribute ? nil : xpath_or_attribute
  if type == :attribute
    mappings[:attribute] = xpath_or_attribute.to_s
  end
  mappings[:xpath] = add_with_to_xpath(xpath)
  self.mappings << mappings
end

#add_value_to_hash(hash, key_or_hash, value) ⇒ Object



151
152
153
154
155
156
157
158
159
# File 'lib/xml_mapper.rb', line 151

def add_value_to_hash(hash, key_or_hash, value)
  if key_or_hash.is_a?(Hash)
    hash[key_or_hash.keys.first] ||= Hash.new
    add_value_to_hash(hash[key_or_hash.keys.first], key_or_hash.values.first, value)
  else
    hash.merge!(key_or_hash => value)
  end
  hash
end

#add_with_to_xpath(xpath) ⇒ Object



122
123
124
# File 'lib/xml_mapper.rb', line 122

def add_with_to_xpath(xpath)
  [self.within_xpath, xpath].flatten.compact.join("/")
end

#after_map(&block) ⇒ Object



108
109
110
# File 'lib/xml_mapper.rb', line 108

def after_map(&block)
  self.after_map_block = block
end

#apply_after_map_to_value(value, mapping) ⇒ Object



189
190
191
192
193
194
195
196
197
198
# File 'lib/xml_mapper.rb', line 189

def apply_after_map_to_value(value, mapping)
  after_mappings = [TYPE_TO_AFTER_CODE[mapping[:type]], mapping[:options][:after_map]].compact
  if value
    after_mappings.each do |after_map|
      value = value.send(after_map) if value.respond_to?(after_map)
      value = self.send(after_map, value) if self.respond_to?(after_map)
    end
  end
  value
end

#attributes_from_xml(xml_or_doc, xml_path = nil) ⇒ Object



135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/xml_mapper.rb', line 135

def attributes_from_xml(xml_or_doc, xml_path = nil)
  if xml_or_doc.is_a?(Array)
    xml_or_doc.map { |doc| attributes_from_xml(doc, xml_path) } 
  else
    doc = xml_or_doc.is_a?(Nokogiri::XML::Node) ? xml_or_doc : Nokogiri::XML(xml_or_doc.gsub(/xmlns=[\"\'].*?[\"\']/, "")) # get rid of xml namespaces, quick and dirty
    doc = doc.root if doc.respond_to?(:root)
    atts = self.mappings.inject(XmlMapperHash.from_path_and_node(xml_path, doc)) do |hash, mapping|
      if (value = value_from_doc_and_mapping(doc, mapping, xml_path)) != :not_found
        add_value_to_hash(hash, mapping[:key], value)
      end
    end
    atts.instance_eval(&self.after_map_block) if self.after_map_block
    atts
  end
end

#attributes_from_xml_path(path) ⇒ Object



126
127
128
# File 'lib/xml_mapper.rb', line 126

def attributes_from_xml_path(path)
  attributes_from_xml(File.read(path), path)
end

#extract_options_from_args(args) ⇒ Object



92
93
94
# File 'lib/xml_mapper.rb', line 92

def extract_options_from_args(args)
  args.length > 1 && args.last.is_a?(Hash) ? args.pop : {}
end

#inner_text_for_node(node) ⇒ Object



200
201
202
203
204
# File 'lib/xml_mapper.rb', line 200

def inner_text_for_node(node)
  if node
    node.inner_text.length == 0 ? nil : node.inner_text
  end
end

#parse_date(text) ⇒ Object



232
233
234
235
# File 'lib/xml_mapper.rb', line 232

def parse_date(text)
  text.to_s.strip.length > 0 ? Date.parse(text.to_s.strip) : nil
rescue
end

#parse_duration(string) ⇒ Object



221
222
223
224
225
226
227
228
229
230
# File 'lib/xml_mapper.rb', line 221

def parse_duration(string)
  return string.to_i if string.match(/^\d+$/)
  string = "00:#{string}" if string.match(/^(\d+):(\d+)$/)
  string = string.to_s.gsub(/PT(\d+M.*)/,"PT0H\\1")         # insert 0H into PT3M12S, for example: PT0H3M12S
  if string.match(/^PT(\d+)H(\d+)M(\d+)S$/) || string.match(/^(\d+):(\d+):(\d+)$/)
    $1.to_i * 3600 + $2.to_i * 60 + $3.to_i
  else
    nil
  end
end

#string_to_boolean(value) ⇒ Object



217
218
219
# File 'lib/xml_mapper.rb', line 217

def string_to_boolean(value)
  MAPPINGS[value.to_s.downcase]
end

#value_from_doc_and_mapping(doc, mapping, xml_path = nil) ⇒ Object



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
# File 'lib/xml_mapper.rb', line 161

def value_from_doc_and_mapping(doc, mapping, xml_path = nil)
  if mapping[:type] == :many
    mapping[:options][:mapper].attributes_from_xml(doc.send(self.selector_mode, mapping[:xpath]).to_a, xml_path)
  else
    node = mapping[:xpath].length == 0 ? doc : doc.xpath(mapping[:xpath]).first
    if mapping[:type] == :exists
      !node.nil?
    elsif mapping[:type] == :not_exists
      node.nil?
    else
      value = 
      case mapping[:type]
        when :node_name
          doc.nil? ? nil : doc.name
        when :inner_text
          doc.nil? ? nil : doc.inner_text
        when :node
          node
        when :attribute
          node.nil? ? nil : (node.respond_to?(:root) ? node.root : node)[mapping[:attribute]]
        else
          inner_text_for_node(node)
      end
      apply_after_map_to_value(value, mapping)
    end
  end
end