/
happymapper.rb
145 lines (114 loc) · 3.71 KB
/
happymapper.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
dir = File.dirname(__FILE__)
$:.unshift(dir) unless $:.include?(dir) || $:.include?(File.expand_path(dir))
require 'date'
require 'time'
require 'rubygems'
gem 'libxml-ruby', '>= 0.9.7'
require 'xml'
require 'libxml_ext/libxml_helper'
class Boolean; end
module HappyMapper
def self.included(base)
base.instance_variable_set("@attributes", {})
base.instance_variable_set("@elements", {})
base.extend ClassMethods
end
module ClassMethods
def attribute(name, type, options={})
attribute = Attribute.new(name, type, options)
@attributes[to_s] ||= []
@attributes[to_s] << attribute
create_accessor(attribute.name)
end
def attributes
@attributes[to_s] || []
end
def element(name, type, options={})
element = Element.new(name, type, options)
@elements[to_s] ||= []
@elements[to_s] << element
create_accessor(element.name)
end
def elements
@elements[to_s] || []
end
def has_one(name, type, options={})
element name, type, {:single => true}.merge(options)
end
def has_many(name, type, options={})
element name, type, {:single => false}.merge(options)
end
def tag(new_tag_name)
@tag_name = new_tag_name.to_s
end
def get_tag_name
@tag_name ||= to_s.downcase
end
def parse(xml, o={})
options = {
:single => false,
:from_root => false,
}.merge(o)
doc = xml.is_a?(LibXML::XML::Node) ? xml : xml.to_libxml_doc
node = doc.respond_to?(:root) ? doc.root : doc
# if doc has a default namespace, turn on ':use_default_namespace' & set default_prefix for LibXML
unless node.namespaces.default.nil?
options[:use_default_namespace] = true
namespace = "default_ns:"
node.namespaces.default_prefix = namespace.chop
warn "Default XML namespace present -- results are unpredictable"
end
# if not using default namespace, get our namespace prefix (if we have one) (thanks to LibXML)
if node.namespaces.to_a.size > 0 && namespace.nil? && !node.namespaces.namespace.nil?
namespace = node.namespaces.namespace.prefix + ":"
end
nodes = if namespace
node = doc.respond_to?(:root) ? doc.root : doc
node.find("#{'/' if options[:from_root]}#{namespace}#{get_tag_name}")
else
doc.find("//#{get_tag_name}")
end
collection = create_collection(nodes, namespace)
# per http://libxml.rubyforge.org/rdoc/classes/LibXML/XML/Document.html#M000354
nodes = nil
GC.start
options[:single] ? collection.first : collection
end
private
def create_collection(nodes, namespace=nil)
nodes.inject([]) do |acc, el|
obj = new
attributes.each { |attr| obj.send("#{normalize_name attr.name}=", attr.from_xml_node(el)) }
elements.each { |elem| obj.send("#{normalize_name elem.name}=", elem.from_xml_node(el, namespace)) }
acc << obj
end
end
def create_getter(name)
name = normalize_name(name)
class_eval <<-EOS, __FILE__, __LINE__
def #{name}
@#{name}
end
EOS
end
def create_setter(name)
name = normalize_name(name)
class_eval <<-EOS, __FILE__, __LINE__
def #{name}=(value)
@#{name} = value
end
EOS
end
def create_accessor(name)
name = normalize_name(name)
create_getter(name)
create_setter(name)
end
def normalize_name(name)
name.gsub('-', '_')
end
end
end
require 'happymapper/item'
require 'happymapper/attribute'
require 'happymapper/element'