nokogiri-happymapper 0.6.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +64 -5
  3. data/README.md +296 -192
  4. data/lib/happymapper/anonymous_mapper.rb +46 -43
  5. data/lib/happymapper/attribute.rb +7 -5
  6. data/lib/happymapper/element.rb +19 -22
  7. data/lib/happymapper/item.rb +19 -21
  8. data/lib/happymapper/supported_types.rb +20 -28
  9. data/lib/happymapper/text_node.rb +4 -3
  10. data/lib/happymapper/version.rb +3 -1
  11. data/lib/happymapper.rb +336 -362
  12. data/lib/nokogiri-happymapper.rb +4 -0
  13. metadata +124 -105
  14. data/spec/attribute_default_value_spec.rb +0 -50
  15. data/spec/attributes_spec.rb +0 -36
  16. data/spec/fixtures/address.xml +0 -9
  17. data/spec/fixtures/ambigous_items.xml +0 -22
  18. data/spec/fixtures/analytics.xml +0 -61
  19. data/spec/fixtures/analytics_profile.xml +0 -127
  20. data/spec/fixtures/atom.xml +0 -19
  21. data/spec/fixtures/commit.xml +0 -52
  22. data/spec/fixtures/current_weather.xml +0 -89
  23. data/spec/fixtures/current_weather_missing_elements.xml +0 -18
  24. data/spec/fixtures/default_namespace_combi.xml +0 -6
  25. data/spec/fixtures/dictionary.xml +0 -20
  26. data/spec/fixtures/family_tree.xml +0 -21
  27. data/spec/fixtures/inagy.xml +0 -85
  28. data/spec/fixtures/lastfm.xml +0 -355
  29. data/spec/fixtures/multiple_namespaces.xml +0 -170
  30. data/spec/fixtures/multiple_primitives.xml +0 -5
  31. data/spec/fixtures/optional_attributes.xml +0 -6
  32. data/spec/fixtures/pita.xml +0 -133
  33. data/spec/fixtures/posts.xml +0 -23
  34. data/spec/fixtures/product_default_namespace.xml +0 -18
  35. data/spec/fixtures/product_no_namespace.xml +0 -10
  36. data/spec/fixtures/product_single_namespace.xml +0 -10
  37. data/spec/fixtures/quarters.xml +0 -19
  38. data/spec/fixtures/radar.xml +0 -21
  39. data/spec/fixtures/set_config_options.xml +0 -3
  40. data/spec/fixtures/statuses.xml +0 -422
  41. data/spec/fixtures/subclass_namespace.xml +0 -50
  42. data/spec/fixtures/unformatted_address.xml +0 -1
  43. data/spec/fixtures/wrapper.xml +0 -11
  44. data/spec/happymapper/attribute_spec.rb +0 -12
  45. data/spec/happymapper/element_spec.rb +0 -9
  46. data/spec/happymapper/item_spec.rb +0 -136
  47. data/spec/happymapper/text_node_spec.rb +0 -9
  48. data/spec/happymapper_parse_spec.rb +0 -113
  49. data/spec/happymapper_spec.rb +0 -1129
  50. data/spec/has_many_empty_array_spec.rb +0 -43
  51. data/spec/ignay_spec.rb +0 -95
  52. data/spec/inheritance_spec.rb +0 -107
  53. data/spec/mixed_namespaces_spec.rb +0 -61
  54. data/spec/parse_with_object_to_update_spec.rb +0 -111
  55. data/spec/spec_helper.rb +0 -7
  56. data/spec/to_xml_spec.rb +0 -201
  57. data/spec/to_xml_with_namespaces_spec.rb +0 -232
  58. data/spec/wilcard_tag_name_spec.rb +0 -96
  59. data/spec/wrap_spec.rb +0 -82
  60. data/spec/xpath_spec.rb +0 -89
@@ -1,20 +1,19 @@
1
- module HappyMapper
2
- module AnonymousMapper
1
+ # frozen_string_literal: true
3
2
 
3
+ module HappyMapper
4
+ class AnonymousMapper
4
5
  def parse(xml_content)
5
-
6
6
  # TODO: this should be able to handle all the types of functionality that parse is able
7
7
  # to handle which includes the text, xml document, node, fragment, etc.
8
8
  xml = Nokogiri::XML(xml_content)
9
9
 
10
- happymapper_class = create_happymapper_class_with_element(xml.root)
10
+ klass = create_happymapper_class_from_node(xml.root)
11
11
 
12
12
  # With all the elements and attributes defined on the class it is time
13
13
  # for the class to actually use the normal HappyMapper powers to parse
14
14
  # the content. At this point this code is utilizing all of the existing
15
15
  # code implemented for parsing.
16
- happymapper_class.parse(xml_content, :single => true)
17
-
16
+ klass.parse(xml_content, single: true)
18
17
  end
19
18
 
20
19
  private
@@ -25,9 +24,9 @@ module HappyMapper
25
24
  #
26
25
  def underscore(camel_cased_word)
27
26
  word = camel_cased_word.to_s.dup
28
- word.gsub!(/([A-Z\d]+)([A-Z][a-z])/,'\1_\2')
29
- word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
30
- word.tr!("-", "_")
27
+ word.gsub!(/([A-Z\d]+)([A-Z][a-z])/, '\1_\2')
28
+ word.gsub!(/([a-z\d])([A-Z])/, '\1_\2')
29
+ word.tr!('-', '_')
31
30
  word.downcase!
32
31
  word
33
32
  end
@@ -39,76 +38,80 @@ module HappyMapper
39
38
  # value is set to the one provided.
40
39
  #
41
40
  def create_happymapper_class_with_tag(tag_name)
42
- happymapper_class = Class.new
43
- happymapper_class.class_eval do
41
+ klass = Class.new
42
+ klass.class_eval do
44
43
  include HappyMapper
45
44
  tag tag_name
46
45
  end
47
- happymapper_class
46
+ klass
48
47
  end
49
48
 
50
49
  #
51
50
  # Used internally to create and define the necessary happymapper
52
51
  # elements.
53
52
  #
54
- def create_happymapper_class_with_element(element)
55
- happymapper_class = create_happymapper_class_with_tag(element.name)
53
+ def create_happymapper_class_from_node(node)
54
+ klass = create_happymapper_class_with_tag(node.name)
56
55
 
57
- happymapper_class.namespace element.namespace.prefix if element.namespace
56
+ klass.namespace node.namespace.prefix if node.namespace
58
57
 
59
- element.namespaces.each do |prefix,namespace|
60
- happymapper_class.register_namespace prefix, namespace
58
+ node.namespaces.each do |prefix, namespace|
59
+ klass.register_namespace prefix, namespace
61
60
  end
62
61
 
63
- element.attributes.each do |name,attribute|
64
- define_attribute_on_class(happymapper_class,attribute)
62
+ node.attributes.each_value do |attribute|
63
+ define_attribute_on_class(klass, attribute)
65
64
  end
66
65
 
67
- element.children.each do |child|
68
- define_element_on_class(happymapper_class,child)
66
+ node.children.each do |child|
67
+ define_element_on_class(klass, child)
69
68
  end
70
69
 
71
- happymapper_class
70
+ klass
72
71
  end
73
72
 
74
-
75
73
  #
76
74
  # Define a HappyMapper element on the provided class based on
77
- # the element provided.
75
+ # the node provided.
78
76
  #
79
- def define_element_on_class(class_instance,element)
80
-
81
- # When a text element has been provided create the necessary
82
- # HappyMapper content attribute if the text happens to content
77
+ def define_element_on_class(klass, node)
78
+ # When a text node has been provided create the necessary
79
+ # HappyMapper content attribute if the text happens to contain
83
80
  # some content.
84
81
 
85
- if element.text? and element.content.strip != ""
86
- class_instance.content :content, String
82
+ if node.text?
83
+ klass.content :content, String if node.content.strip != ''
84
+ return
87
85
  end
88
86
 
89
- # When the element has children elements, that are not text
90
- # elements, then we want to recursively define a new HappyMapper
87
+ # When the node has child elements, that are not text
88
+ # nodes, then we want to recursively define a new HappyMapper
91
89
  # class that will have elements and attributes.
92
90
 
93
- element_type = if !element.elements.reject {|e| e.text? }.empty? or !element.attributes.empty?
94
- create_happymapper_class_with_element(element)
95
- else
96
- String
97
- end
91
+ element_type = if node.elements.any? || node.attributes.any?
92
+ create_happymapper_class_from_node(node)
93
+ else
94
+ String
95
+ end
96
+
97
+ element_name = underscore(node.name)
98
+ method = klass.elements.find { |e| e.name == element_name } ? :has_many : :has_one
98
99
 
99
- method = class_instance.elements.find {|e| e.name == element.name } ? :has_many : :has_one
100
+ options = {}
101
+ options[:tag] = node.name
102
+ namespace = node.namespace
103
+ options[:namespace] = namespace.prefix if namespace
104
+ options[:xpath] = './' unless element_type == String
100
105
 
101
- class_instance.send(method,underscore(element.name),element_type)
106
+ klass.send(method, element_name, element_type, options)
102
107
  end
103
108
 
104
109
  #
105
110
  # Define a HappyMapper attribute on the provided class based on
106
111
  # the attribute provided.
107
112
  #
108
- def define_attribute_on_class(class_instance,attribute)
109
- class_instance.attribute underscore(attribute.name), String
113
+ def define_attribute_on_class(klass, attribute)
114
+ klass.attribute underscore(attribute.name), String, tag: attribute.name
110
115
  end
111
-
112
116
  end
113
-
114
117
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HappyMapper
2
4
  class Attribute < Item
3
5
  attr_accessor :default
@@ -5,16 +7,16 @@ module HappyMapper
5
7
  # @see Item#initialize
6
8
  # Additional options:
7
9
  # :default => Object The default value for this
8
- def initialize(name, type, o={})
10
+ def initialize(name, type, options = {})
9
11
  super
10
- self.default = o[:default]
12
+ self.default = options[:default]
11
13
  end
12
14
 
13
- def find(node, namespace, xpath_options)
15
+ def find(node, _namespace, xpath_options)
14
16
  if options[:xpath]
15
- yield(node.xpath(options[:xpath],xpath_options))
17
+ yield(node.xpath(options[:xpath], xpath_options))
16
18
  else
17
- yield(node[tag])
19
+ yield(node.attributes[tag])
18
20
  end
19
21
  end
20
22
  end
@@ -1,6 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HappyMapper
2
4
  class Element < Item
3
-
4
5
  def find(node, namespace, xpath_options)
5
6
  if self.namespace
6
7
  # from the class definition
@@ -10,11 +11,11 @@ module HappyMapper
10
11
  end
11
12
 
12
13
  if options[:single]
13
- if options[:xpath]
14
- result = node.xpath(options[:xpath], xpath_options)
15
- else
16
- result = node.xpath(xpath(namespace), xpath_options)
17
- end
14
+ result = if options[:xpath]
15
+ node.xpath(options[:xpath], xpath_options)
16
+ else
17
+ node.xpath(xpath(namespace), xpath_options)
18
+ end
18
19
 
19
20
  if result
20
21
  value = yield(result.first)
@@ -22,7 +23,7 @@ module HappyMapper
22
23
  value
23
24
  end
24
25
  else
25
- target_path = options[:xpath] ? options[:xpath] : xpath(namespace)
26
+ target_path = options[:xpath] || xpath(namespace)
26
27
  node.xpath(target_path, xpath_options).collect do |item|
27
28
  value = yield(item)
28
29
  handle_attributes_option(item, value, xpath_options)
@@ -32,24 +33,20 @@ module HappyMapper
32
33
  end
33
34
 
34
35
  def handle_attributes_option(result, value, xpath_options)
35
- if options[:attributes].is_a?(Hash)
36
- result = result.first unless result.respond_to?(:attribute_nodes)
36
+ return unless options[:attributes].is_a?(Hash)
37
37
 
38
- return unless result.respond_to?(:attribute_nodes)
38
+ result = result.first unless result.respond_to?(:attribute_nodes)
39
+ return unless result.respond_to?(:attribute_nodes)
39
40
 
40
- result.attribute_nodes.each do |xml_attribute|
41
- if attribute_options = options[:attributes][xml_attribute.name.to_sym]
42
- attribute_value = Attribute.new(xml_attribute.name.to_sym, *attribute_options).from_xml_node(result, namespace, xpath_options)
41
+ result.attribute_nodes.each do |xml_attribute|
42
+ next unless (attribute_options = options[:attributes][xml_attribute.name.to_sym])
43
43
 
44
- result.instance_eval <<-EOV
45
- def value.#{xml_attribute.name.gsub(/\-/, '_')}
46
- #{attribute_value.inspect}
47
- end
48
- EOV
49
- end # if attributes_options
50
- end # attribute_nodes.each
51
- end # if options[:attributes]
52
- end # def handle...
44
+ attribute_value = Attribute.new(xml_attribute.name.to_sym, *attribute_options)
45
+ .from_xml_node(result, namespace, xpath_options)
53
46
 
47
+ method_name = xml_attribute.name.tr('-', '_')
48
+ value.define_singleton_method(method_name) { attribute_value }
49
+ end
50
+ end
54
51
  end
55
52
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HappyMapper
2
4
  class Item
3
5
  attr_accessor :name, :type, :tag, :options, :namespace
@@ -11,12 +13,12 @@ module HappyMapper
11
13
  # :raw => Boolean Use raw node value (inc. tags) when parsing.
12
14
  # :single => Boolean False if object should be collection, True for single object
13
15
  # :tag => String Element name if it doesn't match the specified name.
14
- def initialize(name, type, o={})
16
+ def initialize(name, type, options = {})
15
17
  self.name = name.to_s
16
18
  self.type = type
17
- #self.tag = o.delete(:tag) || name.to_s
18
- self.tag = o[:tag] || name.to_s
19
- self.options = { :single => true }.merge(o.merge(:name => self.name))
19
+ # self.tag = options.delete(:tag) || name.to_s
20
+ self.tag = options[:tag] || name.to_s
21
+ self.options = { single: true }.merge(options.merge(name: self.name))
20
22
 
21
23
  @xml_type = self.class.to_s.split('::').last.downcase
22
24
  end
@@ -26,12 +28,11 @@ module HappyMapper
26
28
  end
27
29
 
28
30
  #
29
- # @param [XMLNode] node the xml node that is being parsed
31
+ # @param [Nokogiri::XML::Element] node the xml node that is being parsed
30
32
  # @param [String] namespace the name of the namespace
31
33
  # @param [Hash] xpath_options additional xpath options
32
34
  #
33
35
  def from_xml_node(node, namespace, xpath_options)
34
-
35
36
  namespace = options[:namespace] if options.key?(:namespace)
36
37
 
37
38
  if suported_type_registered?
@@ -41,9 +42,8 @@ module HappyMapper
41
42
  elsif custom_parser_defined?
42
43
  find(node, namespace, xpath_options) { |n| process_node_with_custom_parser(n) }
43
44
  else
44
- process_node_with_default_parser(node,:namespaces => xpath_options)
45
+ process_node_with_default_parser(node, namespaces: xpath_options)
45
46
  end
46
-
47
47
  end
48
48
 
49
49
  def xpath(namespace = self.namespace)
@@ -51,7 +51,7 @@ module HappyMapper
51
51
  xpath += './/' if options[:deep]
52
52
  xpath += "#{namespace}:" if namespace
53
53
  xpath += tag
54
- #puts "xpath: #{xpath}"
54
+ # puts "xpath: #{xpath}"
55
55
  xpath
56
56
  end
57
57
 
@@ -72,19 +72,18 @@ module HappyMapper
72
72
  typecaster(value).apply(value)
73
73
  end
74
74
 
75
-
76
75
  private
77
76
 
78
77
  # @return [Boolean] true if the type defined for the item is defined in the
79
78
  # list of support types.
80
79
  def suported_type_registered?
81
- SupportedTypes.types.map {|caster| caster.type }.include?(constant)
80
+ SupportedTypes.types.map(&:type).include?(constant)
82
81
  end
83
82
 
84
83
  # @return [#apply] the typecaster object that will be able to convert
85
84
  # the value into a value with the correct type.
86
85
  def typecaster(value)
87
- SupportedTypes.types.find { |caster| caster.apply?(value,constant) }
86
+ SupportedTypes.types.find { |caster| caster.apply?(value, constant) }
88
87
  end
89
88
 
90
89
  #
@@ -113,21 +112,21 @@ module HappyMapper
113
112
  end
114
113
 
115
114
  def process_node_with_custom_parser(node)
116
- if node.respond_to?(:content) && !options[:raw]
117
- value = node.content
118
- else
119
- value = node.to_s
120
- end
115
+ value = if node.respond_to?(:content) && !options[:raw]
116
+ node.content
117
+ else
118
+ node.to_s
119
+ end
121
120
 
122
121
  begin
123
122
  constant.send(options[:parser].to_sym, value)
124
- rescue
123
+ rescue StandardError
125
124
  nil
126
125
  end
127
126
  end
128
127
 
129
- def process_node_with_default_parser(node,parse_options)
130
- constant.parse(node,options.merge(parse_options))
128
+ def process_node_with_default_parser(node, parse_options)
129
+ constant.parse(node, options.merge(parse_options))
131
130
  end
132
131
 
133
132
  #
@@ -155,6 +154,5 @@ module HappyMapper
155
154
  end
156
155
  constant
157
156
  end
158
-
159
157
  end
160
158
  end
@@ -1,6 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HappyMapper
2
4
  module SupportedTypes
3
- extend self
5
+ module_function
4
6
 
5
7
  #
6
8
  # All of the registerd supported types that can be parsed.
@@ -45,8 +47,8 @@ module HappyMapper
45
47
  # DateTime.parse(value,to_s)
46
48
  # end
47
49
  #
48
- def register_type(type,&block)
49
- register CastWhenType.new(type,&block)
50
+ def register_type(type, &block)
51
+ register CastWhenType.new(type, &block)
50
52
  end
51
53
 
52
54
  #
@@ -57,16 +59,16 @@ module HappyMapper
57
59
  class CastWhenType
58
60
  attr_reader :type
59
61
 
60
- def initialize(type,&block)
62
+ def initialize(type, &block)
61
63
  @type = type
62
64
  @apply_block = block || no_operation
63
65
  end
64
66
 
65
67
  def no_operation
66
- lambda {|value| value }
68
+ ->(value) { value }
67
69
  end
68
70
 
69
- def apply?(value,convert_to_type)
71
+ def apply?(_value, convert_to_type)
70
72
  convert_to_type == type
71
73
  end
72
74
 
@@ -81,13 +83,12 @@ module HappyMapper
81
83
  # value simply can be returned.
82
84
  #
83
85
  class NilOrAlreadyConverted
84
-
85
86
  def type
86
87
  NilClass
87
88
  end
88
89
 
89
- def apply?(value,convert_to_type)
90
- value.kind_of?(convert_to_type) || value.nil?
90
+ def apply?(value, convert_to_type)
91
+ value.is_a?(convert_to_type) || value.nil?
91
92
  end
92
93
 
93
94
  def apply(value)
@@ -97,44 +98,35 @@ module HappyMapper
97
98
 
98
99
  register NilOrAlreadyConverted.new
99
100
 
100
- register_type String do |value|
101
- value.to_s
102
- end
101
+ register_type String, &:to_s
103
102
 
104
- register_type Float do |value|
105
- value.to_f
106
- end
103
+ register_type Float, &:to_f
107
104
 
108
105
  register_type Time do |value|
109
- Time.parse(value.to_s) rescue Time.at(value.to_i)
106
+ Time.parse(value.to_s)
107
+ rescue StandardError
108
+ Time.at(value.to_i)
110
109
  end
111
110
 
112
111
  register_type DateTime do |value|
113
- DateTime.parse(value.to_s) if value && !value.empty?
112
+ DateTime.parse(value.to_s, true, Date::ITALY) if value && !value.empty?
114
113
  end
115
114
 
116
115
  register_type Date do |value|
117
- Date.parse(value.to_s) if value && !value.empty?
116
+ Date.parse(value.to_s, true, Date::ITALY) if value && !value.empty?
118
117
  end
119
118
 
120
119
  register_type Boolean do |value|
121
- ['true', 't', '1'].include?(value.to_s.downcase)
120
+ %w(true t 1).include?(value.to_s.downcase)
122
121
  end
123
122
 
124
123
  register_type Integer do |value|
125
124
  value_to_i = value.to_i
126
- if value_to_i == 0 && value != '0'
127
- value_to_s = value.to_s
128
- begin
129
- Integer(value_to_s =~ /^(\d+)/ ? $1 : value_to_s)
130
- rescue ArgumentError
131
- nil
132
- end
125
+ if value_to_i == 0 && !value.to_s.start_with?('0')
126
+ nil
133
127
  else
134
128
  value_to_i
135
129
  end
136
130
  end
137
-
138
131
  end
139
-
140
132
  end
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HappyMapper
2
4
  class TextNode < Item
3
-
4
- def find(node, namespace, xpath_options)
5
- yield(node.children.detect{|c| c.text?})
5
+ def find(node, _namespace, _xpath_options)
6
+ yield(node.children.detect(&:text?))
6
7
  end
7
8
  end
8
9
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HappyMapper
2
- VERSION = "0.6.0"
4
+ VERSION = '0.9.0'
3
5
  end