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.
- 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