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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +64 -5
- data/README.md +296 -192
- data/lib/happymapper/anonymous_mapper.rb +46 -43
- data/lib/happymapper/attribute.rb +7 -5
- data/lib/happymapper/element.rb +19 -22
- data/lib/happymapper/item.rb +19 -21
- data/lib/happymapper/supported_types.rb +20 -28
- data/lib/happymapper/text_node.rb +4 -3
- data/lib/happymapper/version.rb +3 -1
- data/lib/happymapper.rb +336 -362
- data/lib/nokogiri-happymapper.rb +4 -0
- metadata +124 -105
- data/spec/attribute_default_value_spec.rb +0 -50
- data/spec/attributes_spec.rb +0 -36
- data/spec/fixtures/address.xml +0 -9
- data/spec/fixtures/ambigous_items.xml +0 -22
- data/spec/fixtures/analytics.xml +0 -61
- data/spec/fixtures/analytics_profile.xml +0 -127
- data/spec/fixtures/atom.xml +0 -19
- data/spec/fixtures/commit.xml +0 -52
- data/spec/fixtures/current_weather.xml +0 -89
- data/spec/fixtures/current_weather_missing_elements.xml +0 -18
- data/spec/fixtures/default_namespace_combi.xml +0 -6
- data/spec/fixtures/dictionary.xml +0 -20
- data/spec/fixtures/family_tree.xml +0 -21
- data/spec/fixtures/inagy.xml +0 -85
- data/spec/fixtures/lastfm.xml +0 -355
- data/spec/fixtures/multiple_namespaces.xml +0 -170
- data/spec/fixtures/multiple_primitives.xml +0 -5
- data/spec/fixtures/optional_attributes.xml +0 -6
- data/spec/fixtures/pita.xml +0 -133
- data/spec/fixtures/posts.xml +0 -23
- data/spec/fixtures/product_default_namespace.xml +0 -18
- data/spec/fixtures/product_no_namespace.xml +0 -10
- data/spec/fixtures/product_single_namespace.xml +0 -10
- data/spec/fixtures/quarters.xml +0 -19
- data/spec/fixtures/radar.xml +0 -21
- data/spec/fixtures/set_config_options.xml +0 -3
- data/spec/fixtures/statuses.xml +0 -422
- data/spec/fixtures/subclass_namespace.xml +0 -50
- data/spec/fixtures/unformatted_address.xml +0 -1
- data/spec/fixtures/wrapper.xml +0 -11
- data/spec/happymapper/attribute_spec.rb +0 -12
- data/spec/happymapper/element_spec.rb +0 -9
- data/spec/happymapper/item_spec.rb +0 -136
- data/spec/happymapper/text_node_spec.rb +0 -9
- data/spec/happymapper_parse_spec.rb +0 -113
- data/spec/happymapper_spec.rb +0 -1129
- data/spec/has_many_empty_array_spec.rb +0 -43
- data/spec/ignay_spec.rb +0 -95
- data/spec/inheritance_spec.rb +0 -107
- data/spec/mixed_namespaces_spec.rb +0 -61
- data/spec/parse_with_object_to_update_spec.rb +0 -111
- data/spec/spec_helper.rb +0 -7
- data/spec/to_xml_spec.rb +0 -201
- data/spec/to_xml_with_namespaces_spec.rb +0 -232
- data/spec/wilcard_tag_name_spec.rb +0 -96
- data/spec/wrap_spec.rb +0 -82
- data/spec/xpath_spec.rb +0 -89
@@ -1,20 +1,19 @@
|
|
1
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
43
|
-
|
41
|
+
klass = Class.new
|
42
|
+
klass.class_eval do
|
44
43
|
include HappyMapper
|
45
44
|
tag tag_name
|
46
45
|
end
|
47
|
-
|
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
|
55
|
-
|
53
|
+
def create_happymapper_class_from_node(node)
|
54
|
+
klass = create_happymapper_class_with_tag(node.name)
|
56
55
|
|
57
|
-
|
56
|
+
klass.namespace node.namespace.prefix if node.namespace
|
58
57
|
|
59
|
-
|
60
|
-
|
58
|
+
node.namespaces.each do |prefix, namespace|
|
59
|
+
klass.register_namespace prefix, namespace
|
61
60
|
end
|
62
61
|
|
63
|
-
|
64
|
-
define_attribute_on_class(
|
62
|
+
node.attributes.each_value do |attribute|
|
63
|
+
define_attribute_on_class(klass, attribute)
|
65
64
|
end
|
66
65
|
|
67
|
-
|
68
|
-
define_element_on_class(
|
66
|
+
node.children.each do |child|
|
67
|
+
define_element_on_class(klass, child)
|
69
68
|
end
|
70
69
|
|
71
|
-
|
70
|
+
klass
|
72
71
|
end
|
73
72
|
|
74
|
-
|
75
73
|
#
|
76
74
|
# Define a HappyMapper element on the provided class based on
|
77
|
-
# the
|
75
|
+
# the node provided.
|
78
76
|
#
|
79
|
-
def define_element_on_class(
|
80
|
-
|
81
|
-
#
|
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
|
86
|
-
|
82
|
+
if node.text?
|
83
|
+
klass.content :content, String if node.content.strip != ''
|
84
|
+
return
|
87
85
|
end
|
88
86
|
|
89
|
-
# When the
|
90
|
-
#
|
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
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
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
|
-
|
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(
|
109
|
-
|
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,
|
10
|
+
def initialize(name, type, options = {})
|
9
11
|
super
|
10
|
-
self.default =
|
12
|
+
self.default = options[:default]
|
11
13
|
end
|
12
14
|
|
13
|
-
def find(node,
|
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
|
data/lib/happymapper/element.rb
CHANGED
@@ -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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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]
|
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
|
-
|
36
|
-
result = result.first unless result.respond_to?(:attribute_nodes)
|
36
|
+
return unless options[:attributes].is_a?(Hash)
|
37
37
|
|
38
|
-
|
38
|
+
result = result.first unless result.respond_to?(:attribute_nodes)
|
39
|
+
return unless result.respond_to?(:attribute_nodes)
|
39
40
|
|
40
|
-
|
41
|
-
|
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
|
-
|
45
|
-
|
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
|
data/lib/happymapper/item.rb
CHANGED
@@ -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,
|
16
|
+
def initialize(name, type, options = {})
|
15
17
|
self.name = name.to_s
|
16
18
|
self.type = type
|
17
|
-
#self.tag =
|
18
|
-
self.tag =
|
19
|
-
self.options = { :
|
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 [
|
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
|
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
|
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
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
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
|
-
|
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
|
49
|
-
register CastWhenType.new(type
|
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
|
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
|
-
|
68
|
+
->(value) { value }
|
67
69
|
end
|
68
70
|
|
69
|
-
def apply?(
|
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.
|
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
|
101
|
-
value.to_s
|
102
|
-
end
|
101
|
+
register_type String, &:to_s
|
103
102
|
|
104
|
-
register_type Float
|
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)
|
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
|
-
|
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
|
127
|
-
|
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
|
-
|
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
|
data/lib/happymapper/version.rb
CHANGED