nokogiri-happymapper 0.6.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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