/
item.rb
165 lines (150 loc) · 4.75 KB
/
item.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
module HappyMapper
class Item
attr_accessor :name, :type, :tag, :options, :namespace
Types = [String, Float, Time, Date, DateTime, Integer, Boolean]
# options:
# :deep => Boolean False to only parse element's children, True to include
# grandchildren and all others down the chain (// in expath)
# :namespace => String Element's namespace if it's not the global or inherited
# default
# :parser => Symbol Class method to use for type coercion.
# :raw => Boolean Use raw node value (inc. tags) when parsing.
# :single => Boolean False if object should be collection, True for single object
# :tag => String Element name if it doesn't match the specified name.
def initialize(name, type, o={})
self.name = name.to_s
self.type = type
self.tag = o.delete(:tag) || name.to_s
self.options = o
@xml_type = self.class.to_s.split('::').last.downcase
end
def constant
@constant ||= constantize(type)
end
def from_xml_node(node, namespace)
if primitive?
find(node, namespace) do |n|
if n.respond_to?(:content)
typecast(n.content)
else
typecast(n.to_s)
end
end
else
if options[:parser]
find(node, namespace) do |n|
if n.respond_to?(:content) && !options[:raw]
value = n.content
else
value = n.to_s
end
begin
constant.send(options[:parser].to_sym, value)
rescue
nil
end
end
else
constant.parse(node, options)
end
end
end
def xpath(namespace = self.namespace)
xpath = ''
xpath += './/' if options[:deep]
xpath += "#{DEFAULT_NS}:" if namespace
xpath += tag
# puts "xpath: #{xpath}"
xpath
end
def primitive?
Types.include?(constant)
end
def element?
@xml_type == 'element'
end
def attribute?
!element?
end
def method_name
@method_name ||= name.tr('-', '_')
end
def typecast(value)
return value if value.kind_of?(constant) || value.nil?
begin
if constant == String then value.to_s
elsif constant == Float then value.to_f
elsif constant == Time then Time.parse(value.to_s)
elsif constant == Date then Date.parse(value.to_s)
elsif constant == DateTime then DateTime.parse(value.to_s)
elsif constant == Boolean then ['true', 't', '1'].include?(value.to_s.downcase)
elsif constant == Integer
# ganked from datamapper
value_to_i = value.to_i
if value_to_i == 0 && value != '0'
value_to_s = value.to_s
begin
Integer(value_to_s =~ /^(\d+)/ ? $1 : value_to_s)
rescue ArgumentError
nil
end
else
value_to_i
end
else
value
end
rescue
value
end
end
private
def constantize(type)
if type.is_a?(String)
names = type.split('::')
constant = Object
names.each do |name|
constant = constant.const_defined?(name) ?
constant.const_get(name) :
constant.const_missing(name)
end
constant
else
type
end
end
def find(node, namespace, &block)
# this node has a custom namespace (that is present in the doc)
if self.namespace
namespace = "#{DEFAULT_NS}:#{self.namespace}"
elsif options[:namespace]
# from an element definition
namespace = "#{DEFAULT_NS}:#{options[:namespace]}"
end
if element?
result = node.find_first(xpath(namespace), namespace)
# puts "vfxn: #{xpath} #{result.inspect}"
if result
value = yield(result)
if options[:attributes].is_a?(Hash)
result.attributes.each do |xml_attribute|
if attribute_options = options[:attributes][xml_attribute.name.to_sym]
attribute_value = Attribute.new(xml_attribute.name.to_sym, *attribute_options).from_xml_node(result, namespace)
result.instance_eval <<-EOV
def value.#{xml_attribute.name}
#{attribute_value.inspect}
end
EOV
end
end
end
value
else
nil
end
else
yield(node[tag])
end
end
end
end