peanuts 1.0 → 2.0.7
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.
- data/MIT-LICENSE +1 -1
- data/README.rdoc +56 -25
- data/Rakefile +7 -4
- data/lib/peanuts/converters.rb +20 -13
- data/lib/peanuts/mappable.rb +253 -0
- data/lib/peanuts/mapper.rb +95 -0
- data/lib/peanuts/mappings.rb +129 -74
- data/lib/peanuts/xml/libxml.rb +231 -0
- data/lib/peanuts/xml.rb +60 -0
- data/lib/peanuts.rb +7 -1
- data/spec/cat_spec.rb +151 -0
- metadata +19 -8
- data/lib/peanuts/backend.rb +0 -38
- data/lib/peanuts/nuts.rb +0 -219
- data/lib/peanuts/rexml.rb +0 -98
- data/test/parsing_test.rb +0 -115
data/MIT-LICENSE
CHANGED
data/README.rdoc
CHANGED
@@ -1,4 +1,11 @@
|
|
1
1
|
=== Introduction
|
2
|
+
*This is currently undergoing some rewrite. 2.x branch will introduce minor api incompatibilities.*
|
3
|
+
|
4
|
+
+Upd:+ Ok, the thing is out and the example below has been updated for 2.x, but the rdocs are
|
5
|
+
largely out of date and I'm not sure whether I should update them since it seems like nobody's
|
6
|
+
using this lib anyway.
|
7
|
+
|
8
|
+
|
2
9
|
Peanuts is an library that allows for bidirectional mapping between Ruby objects and XML.
|
3
10
|
|
4
11
|
Released under the MIT license.
|
@@ -9,7 +16,8 @@ Released under the MIT license.
|
|
9
16
|
- Pluggable backends to work with different XML APIs (REXML implemented so far).
|
10
17
|
|
11
18
|
=== Installation
|
12
|
-
|
19
|
+
Beta 2.x (recommended) and stable 1.x available from Gemcutter
|
20
|
+
gem install peanuts --source http://gemcutter.org
|
13
21
|
|
14
22
|
=== Usage
|
15
23
|
Please see an example below.
|
@@ -31,7 +39,24 @@ Please report via Github issue tracking.
|
|
31
39
|
class Cheezburger
|
32
40
|
include Peanuts
|
33
41
|
|
34
|
-
attribute :weight, :
|
42
|
+
attribute :weight, :float
|
43
|
+
attribute :price, :decimal
|
44
|
+
|
45
|
+
def initialize(weight = nil, price = nil)
|
46
|
+
@weight, @price = weight, price
|
47
|
+
end
|
48
|
+
|
49
|
+
def eql?(other)
|
50
|
+
other && weight == other.weight && price == other.price
|
51
|
+
end
|
52
|
+
|
53
|
+
alias == eql?
|
54
|
+
end
|
55
|
+
|
56
|
+
class Paws
|
57
|
+
include Peanuts
|
58
|
+
|
59
|
+
elements :paws, :name => :paw, :ns => 'urn:x-lol'
|
35
60
|
end
|
36
61
|
|
37
62
|
class Cat
|
@@ -39,20 +64,24 @@ Please report via Github issue tracking.
|
|
39
64
|
|
40
65
|
namespaces :lol => 'urn:x-lol', :kthnx => 'urn:x-lol:kthnx'
|
41
66
|
|
42
|
-
root 'kitteh', :
|
67
|
+
root 'kitteh', :ns => 'urn:x-lol'
|
43
68
|
|
44
|
-
attribute :has_tail
|
69
|
+
attribute :has_tail?, :boolean, :name => 'has-tail', :ns => :kthnx
|
45
70
|
attribute :ears, :integer
|
46
71
|
|
47
|
-
element :ration, [:string], :
|
48
|
-
element :name, :
|
49
|
-
elements :paws, :string, :xmlname => :paw
|
72
|
+
element :ration, [:string], :name => :eats, :ns => :kthnx
|
73
|
+
element :name, :ns => 'urn:x-lol:kthnx'
|
50
74
|
|
51
|
-
|
52
|
-
|
75
|
+
shallow :paws, Paws
|
76
|
+
|
77
|
+
shallow :pals, :ns => :kthnx do
|
78
|
+
elements :friends, :name => :pal
|
53
79
|
end
|
54
80
|
|
55
81
|
element :cheezburger, Cheezburger
|
82
|
+
element :moar_cheezburgers do
|
83
|
+
elements :cheezburger, Cheezburger
|
84
|
+
end
|
56
85
|
end
|
57
86
|
|
58
87
|
xml_fragment = <<-EOS
|
@@ -65,19 +94,26 @@ Please report via Github issue tracking.
|
|
65
94
|
tigers
|
66
95
|
lions
|
67
96
|
</kthnx:eats>
|
68
|
-
<pals>
|
97
|
+
<kthnx:pals>
|
69
98
|
<pal>Chrissy</pal>
|
70
99
|
<pal>Missy</pal>
|
71
100
|
<pal>Sissy</pal>
|
72
|
-
</pals>
|
73
|
-
<
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
101
|
+
</kthnx:pals>
|
102
|
+
<paws>
|
103
|
+
<paw> one</paw>
|
104
|
+
<paw> two </paw>
|
105
|
+
<paw>three</paw>
|
106
|
+
<paw>four</paw>
|
107
|
+
</paws>
|
108
|
+
<cheezburger price='2.05' weight='14.5547' />
|
109
|
+
<moar_cheezburgers>
|
110
|
+
<cheezburger price='19' weight='685.940' />
|
111
|
+
<cheezburger price='7.40' weight='9356.7' />
|
112
|
+
</moar_cheezburgers>
|
78
113
|
</kitteh>
|
79
114
|
EOS
|
80
|
-
cat = Cat.
|
115
|
+
cat = Cat.restore_from(xml_fragment)
|
116
|
+
cheezburger = cat.cheezburger
|
81
117
|
|
82
118
|
assert_equal 'Silly Tom', cat.name
|
83
119
|
assert_equal %w(tigers lions), cat.ration
|
@@ -88,17 +124,12 @@ Please report via Github issue tracking.
|
|
88
124
|
assert_kind_of Cheezburger, cat.cheezburger
|
89
125
|
assert_equal 2, cat.cheezburger.weight
|
90
126
|
...
|
91
|
-
puts cat.
|
127
|
+
puts cat.save_to(:string)
|
92
128
|
|
93
129
|
|
94
130
|
=== See also
|
95
|
-
* http://github.com/omg/
|
96
|
-
* http://github.com/omg/statelogic --
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
Free hint: If you liek mudkipz^W^Wfeel like generous today you can tip me at http://tipjoy.com/u/pisuka
|
101
|
-
|
131
|
+
* http://github.com/omg/unrest -- REST-client
|
132
|
+
* http://github.com/omg/statelogic -- Simple state machine for ActiveRecord
|
102
133
|
|
103
134
|
Copyright (c) 2009 Igor Gunko, released under the MIT license
|
104
135
|
|
data/Rakefile
CHANGED
@@ -5,7 +5,7 @@ require 'rake'
|
|
5
5
|
require 'rake/clean'
|
6
6
|
require 'rake/gempackagetask'
|
7
7
|
require 'rake/rdoctask'
|
8
|
-
require 'rake/
|
8
|
+
require 'spec/rake/spectask'
|
9
9
|
|
10
10
|
Rake::GemPackageTask.new(Gem::Specification.load('peanuts.gemspec')) do |p|
|
11
11
|
p.need_tar = true
|
@@ -18,9 +18,12 @@ Rake::RDocTask.new do |rdoc|
|
|
18
18
|
rdoc.main = "README.rdoc" # page to start on
|
19
19
|
rdoc.title = "Peanuts Documentation"
|
20
20
|
rdoc.rdoc_dir = 'doc/rdoc' # rdoc output folder
|
21
|
-
rdoc.options << '--line-numbers'
|
21
|
+
rdoc.options << '--line-numbers'
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
24
|
+
desc 'Run specs'
|
25
|
+
task :test => :spec
|
26
|
+
|
27
|
+
Spec::Rake::SpecTask.new do |t|
|
28
|
+
t.spec_files = FileList['spec/**/*.rb']
|
26
29
|
end
|
data/lib/peanuts/converters.rb
CHANGED
@@ -8,7 +8,7 @@ module Peanuts
|
|
8
8
|
# numeric:: see +Convert_integer+, +Convert_decimal+, +Convert_float+
|
9
9
|
# date & time:: see +Convert_datetime+
|
10
10
|
# lists:: see +Convert_list+
|
11
|
-
|
11
|
+
class Converter
|
12
12
|
def self.lookup(type)
|
13
13
|
lookup!(type)
|
14
14
|
rescue ArgumentError
|
@@ -16,9 +16,11 @@ module Peanuts
|
|
16
16
|
end
|
17
17
|
|
18
18
|
def self.lookup!(type)
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
begin
|
20
|
+
const_get("Convert_#{type}")
|
21
|
+
rescue NameError
|
22
|
+
raise ArgumentError, "converter not found for #{type}"
|
23
|
+
end
|
22
24
|
end
|
23
25
|
|
24
26
|
def self.create(type, options)
|
@@ -28,11 +30,16 @@ module Peanuts
|
|
28
30
|
end
|
29
31
|
|
30
32
|
def self.create!(type, options)
|
31
|
-
|
33
|
+
case type
|
34
|
+
when Symbol
|
35
|
+
lookup!(type)
|
36
|
+
else
|
37
|
+
type
|
38
|
+
end.new(options)
|
32
39
|
end
|
33
40
|
|
34
41
|
# Who could have thought... a string.
|
35
|
-
#
|
42
|
+
#
|
36
43
|
# Specifier:: <tt>:string</tt>
|
37
44
|
#
|
38
45
|
# ==== Options:
|
@@ -41,7 +48,7 @@ module Peanuts
|
|
41
48
|
# [<tt>:trim</tt>] Trim whitespace from both ends.
|
42
49
|
# [<tt>:collapse</tt>] Collapse consecutive whitespace + trim as well.
|
43
50
|
# [<tt>:preserve</tt>] Keep'em all.
|
44
|
-
class Convert_string
|
51
|
+
class Convert_string < Converter
|
45
52
|
def initialize(options)
|
46
53
|
@whitespace = options[:whitespace] || :collapse
|
47
54
|
end
|
@@ -83,8 +90,8 @@ module Peanuts
|
|
83
90
|
def to_xml(flag)
|
84
91
|
return nil if flag.nil?
|
85
92
|
string = case @format
|
86
|
-
when :true_false then flag ? 'true' : 'false'
|
87
|
-
when :yes_no then flag ? 'yes' : 'no'
|
93
|
+
when :true_false, :truefalse then flag ? 'true' : 'false'
|
94
|
+
when :yes_no, :yesno then flag ? 'yes' : 'no'
|
88
95
|
when :numeric then flag ? '0' : '1'
|
89
96
|
end
|
90
97
|
super(string)
|
@@ -114,7 +121,7 @@ module Peanuts
|
|
114
121
|
# An integer.
|
115
122
|
#
|
116
123
|
# Specifier:: <tt>:integer</tt>
|
117
|
-
#
|
124
|
+
#
|
118
125
|
# ==== Options
|
119
126
|
# Accepts all options of +Convert_string+.
|
120
127
|
class Convert_integer < Convert_string
|
@@ -135,7 +142,7 @@ module Peanuts
|
|
135
142
|
#
|
136
143
|
# Specifier:: <tt>:decimal</tt>
|
137
144
|
# Ruby type:: +BigDecimal+
|
138
|
-
#
|
145
|
+
#
|
139
146
|
# ==== Options
|
140
147
|
# Accepts all options of +Convert_string+.
|
141
148
|
class Convert_decimal < Convert_string
|
@@ -180,7 +187,7 @@ module Peanuts
|
|
180
187
|
#
|
181
188
|
# Specifier:: <tt>:datetime</tt>
|
182
189
|
# Ruby type:: +Time+
|
183
|
-
#
|
190
|
+
#
|
184
191
|
# ==== Options
|
185
192
|
# Accepts all options of +Convert_string+.
|
186
193
|
class Convert_datetime < Convert_string
|
@@ -206,7 +213,7 @@ module Peanuts
|
|
206
213
|
#
|
207
214
|
# ==== Options
|
208
215
|
# All options will be passed to the underlying type converter.
|
209
|
-
class Convert_list
|
216
|
+
class Convert_list < Converter
|
210
217
|
def initialize(options)
|
211
218
|
@item_type = options[:item_type] || :string
|
212
219
|
@item_converter = Converter.create!(@item_type, options)
|
@@ -0,0 +1,253 @@
|
|
1
|
+
require 'peanuts/xml'
|
2
|
+
require 'peanuts/mappings'
|
3
|
+
require 'peanuts/mapper'
|
4
|
+
|
5
|
+
module Peanuts #:nodoc:
|
6
|
+
# See also +MappableType+
|
7
|
+
module MappableObject
|
8
|
+
def self.included(other) #:nodoc:
|
9
|
+
MappableType.init(other)
|
10
|
+
end
|
11
|
+
|
12
|
+
def from_xml(source, options = {})
|
13
|
+
source = XML::Reader.new(source, options) unless source.is_a?(XML::Reader)
|
14
|
+
e = source.find_element
|
15
|
+
e && self.class.mapper.read(self, source)
|
16
|
+
end
|
17
|
+
|
18
|
+
# save_to(:string|:document[, options]) -> new_string|new_document
|
19
|
+
# save_to(string|iolike|document[, options]) -> string|iolike|document
|
20
|
+
#
|
21
|
+
# Defines attribute mapping.
|
22
|
+
#
|
23
|
+
# [+options+] Backend-specific options
|
24
|
+
#
|
25
|
+
# === Example:
|
26
|
+
# cat = Cat.new
|
27
|
+
# cat.name = 'Pussy'
|
28
|
+
# puts cat.save_to(:string)
|
29
|
+
# ...
|
30
|
+
# doc = LibXML::XML::Document.new
|
31
|
+
# cat.save_to(doc)
|
32
|
+
# puts doc.to_s
|
33
|
+
def to_xml(dest = :string, options = {})
|
34
|
+
dest = XML::Writer.new(dest, options) unless dest.is_a?(XML::Writer)
|
35
|
+
self.class.mapper.write(self, dest)
|
36
|
+
dest.result
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# See also +MappableObject+.
|
41
|
+
module MappableType
|
42
|
+
include Mappings
|
43
|
+
|
44
|
+
def self.init(cls, ns_context = nil, default_ns = nil, &block) #:nodoc:
|
45
|
+
cls.instance_eval do
|
46
|
+
extend MappableType
|
47
|
+
@mapper = Mapper.new(ns_context, default_ns)
|
48
|
+
instance_eval(&block) if block_given?
|
49
|
+
end
|
50
|
+
cls
|
51
|
+
end
|
52
|
+
|
53
|
+
# mapper -> Mapper
|
54
|
+
#
|
55
|
+
# Returns the mapper for the class.
|
56
|
+
attr_reader :mapper
|
57
|
+
|
58
|
+
# namespaces(hash) -> Hash
|
59
|
+
# namespaces -> Hash
|
60
|
+
#
|
61
|
+
# Updates and returns class-level prefix mappings.
|
62
|
+
# When given a hash of mappings merges it over current.
|
63
|
+
# When called withot arguments simply returns current mappings.
|
64
|
+
#
|
65
|
+
# === Example:
|
66
|
+
# class Cat
|
67
|
+
# include Peanuts
|
68
|
+
# namespaces :lol => 'urn:lol', ...
|
69
|
+
# ...
|
70
|
+
# end
|
71
|
+
def namespaces(*args)
|
72
|
+
case args.size
|
73
|
+
when 0
|
74
|
+
mapper.namespaces
|
75
|
+
when 1
|
76
|
+
if args.first.is_a?(Hash)
|
77
|
+
mapper.namespaces.update(args.first)
|
78
|
+
else
|
79
|
+
mapper.default_ns = args.first
|
80
|
+
end
|
81
|
+
when 2
|
82
|
+
mapper.default_ns = args.first
|
83
|
+
mapper.namespaces.update(args[1])
|
84
|
+
else
|
85
|
+
raise ArgumentError, 'bad arguments'
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# root(local_name[, :ns => ...]) -> Mappings::Root
|
90
|
+
# root -> Mappings::Root
|
91
|
+
#
|
92
|
+
# Defines element name.
|
93
|
+
# TODO: moar details
|
94
|
+
#
|
95
|
+
# [+local_name+] Element name
|
96
|
+
# [+options+] <tt>:ns => 'uri'|:prefix</tt> Element namespace
|
97
|
+
#
|
98
|
+
# === Example:
|
99
|
+
# class Cat
|
100
|
+
# include Peanuts
|
101
|
+
# ...
|
102
|
+
# root :kitteh, :ns => 'urn:lol'
|
103
|
+
# ...
|
104
|
+
# end
|
105
|
+
def root(local_name = nil, options = {})
|
106
|
+
mapper.root = Root.new(local_name, prepare_options(:root, options)) if local_name
|
107
|
+
mapper.root
|
108
|
+
end
|
109
|
+
|
110
|
+
# element(name[, type][, options]) -> mapping_object
|
111
|
+
# element(name[, options]) { block } -> mapping_object
|
112
|
+
#
|
113
|
+
# Defines single-element mapping.
|
114
|
+
#
|
115
|
+
# [+name+] Accessor name
|
116
|
+
# [+type+] Element type. <tt>:string</tt> assumed if omitted (see +Converter+).
|
117
|
+
# [+options+] <tt>name</tt>, <tt>:ns</tt>, converter options (see +Converter+).
|
118
|
+
# [+block+] An anonymous class definition.
|
119
|
+
#
|
120
|
+
# === Example:
|
121
|
+
# class Cat
|
122
|
+
# include Peanuts
|
123
|
+
# ...
|
124
|
+
# element :name, :whitespace => :collapse
|
125
|
+
# element :ears, :integer
|
126
|
+
# element :cheeseburger, Cheeseburger, :name => :cheezburger
|
127
|
+
# ...
|
128
|
+
# end
|
129
|
+
def element(name, *args, &block)
|
130
|
+
add_mapping(:element, name, *args, &block)
|
131
|
+
end
|
132
|
+
|
133
|
+
# shallow_element(name, type[, options]) -> mapping_object
|
134
|
+
# shallow_element(name[, options]) { block } -> mapping_object
|
135
|
+
#
|
136
|
+
# Defines single-element shallow mapping.
|
137
|
+
#
|
138
|
+
# [+name+] Accessor name
|
139
|
+
# [+type+] Element type. Either this or _block_ is required.
|
140
|
+
# [+options+] <tt>:name</tt>, <tt>:ns</tt>, converter options (see +Converter+).
|
141
|
+
# [+block+] An anonymous class definition.
|
142
|
+
#
|
143
|
+
# === Example:
|
144
|
+
# class Cat
|
145
|
+
# include Peanuts
|
146
|
+
# ...
|
147
|
+
# shallow :friends do
|
148
|
+
# element :friends, :name => :friend
|
149
|
+
# end
|
150
|
+
# shallow :cheeseburger, Cheeseburger, :name => :cheezburger
|
151
|
+
# ...
|
152
|
+
# end
|
153
|
+
def shallow_element(name, *args, &block)
|
154
|
+
add_mapping(:shallow_element, name, *args, &block)
|
155
|
+
end
|
156
|
+
|
157
|
+
alias shallow shallow_element
|
158
|
+
|
159
|
+
# elements(name[, type][, options]) -> mapping_object
|
160
|
+
# elements(name[, options]) { block } -> mapping_object
|
161
|
+
#
|
162
|
+
# Defines multiple elements mapping.
|
163
|
+
#
|
164
|
+
# [+name+] Accessor name
|
165
|
+
# [+type+] Element type. <tt>:string</tt> assumed if omitted (see +Converter+).
|
166
|
+
# [+options+] <tt>name</tt>, <tt>:ns</tt>, converter options (see +Converter+).
|
167
|
+
# [+block+] An anonymous class definition.
|
168
|
+
#
|
169
|
+
# === Example:
|
170
|
+
# class RichCat
|
171
|
+
# include Peanuts
|
172
|
+
# ...
|
173
|
+
# elements :ration, :string, :whitespace => :collapse
|
174
|
+
# elements :cheeseburgers, Cheeseburger, :name => :cheezburger
|
175
|
+
# ...
|
176
|
+
# end
|
177
|
+
def elements(name, *args, &block)
|
178
|
+
add_mapping(:elements, name, *args, &block)
|
179
|
+
end
|
180
|
+
|
181
|
+
# attribute(name[, type][, options]) -> mapping_object
|
182
|
+
#
|
183
|
+
# Defines attribute mapping.
|
184
|
+
#
|
185
|
+
# [+name+] Accessor name
|
186
|
+
# [+type+] Element type. <tt>:string</tt> assumed if omitted (see +Converter+).
|
187
|
+
# [+options+] <tt>name</tt>, <tt>:ns</tt>, converter options (see +Converter+).
|
188
|
+
#
|
189
|
+
# === Example:
|
190
|
+
# class Cat
|
191
|
+
# include Peanuts
|
192
|
+
# ...
|
193
|
+
# element :name, :string, :whitespace => :collapse
|
194
|
+
# element :cheeseburger, Cheeseburger, :name => :cheezburger
|
195
|
+
# ...
|
196
|
+
# end
|
197
|
+
def attribute(name, *args)
|
198
|
+
add_mapping(:attribute, name, *args)
|
199
|
+
end
|
200
|
+
|
201
|
+
def schema(schema = nil)
|
202
|
+
mapper.schema = schema if schema
|
203
|
+
mapper.schema
|
204
|
+
end
|
205
|
+
|
206
|
+
def from_xml(source, options = {})
|
207
|
+
new.from_xml(source, options)
|
208
|
+
end
|
209
|
+
|
210
|
+
private
|
211
|
+
def object_type?(type)
|
212
|
+
type.is_a?(Class) && !(type < Converter)
|
213
|
+
end
|
214
|
+
|
215
|
+
def add_mapping(node, name, *args, &block)
|
216
|
+
type, options = *args
|
217
|
+
type, options = (block ? Class.new : :string), type if type.nil? || type.is_a?(Hash)
|
218
|
+
|
219
|
+
object_type = object_type?(type)
|
220
|
+
options = prepare_options(node, options || {})
|
221
|
+
|
222
|
+
mapper << m = case node
|
223
|
+
when :element
|
224
|
+
options.delete(:shallow) ? ShallowElement : (object_type ? Element : ElementValue)
|
225
|
+
when :elements
|
226
|
+
object_type ? Elements : ElementValues
|
227
|
+
when :attribute
|
228
|
+
Attribute
|
229
|
+
when :shallow_element
|
230
|
+
ShallowElement
|
231
|
+
end.new(name, type, options)
|
232
|
+
|
233
|
+
raise ArgumentError, 'bad type for shallow element' if !object_type && m.is_a?(ShallowElement)
|
234
|
+
|
235
|
+
default_ns = m.prefix ? mapper.default_ns : m.namespace_uri
|
236
|
+
if object_type && !type.is_a?(MappableType)
|
237
|
+
raise ArgumentError, 'block is required' unless block
|
238
|
+
MappableType.init(type, mapper.namespaces, default_ns, &block)
|
239
|
+
end
|
240
|
+
m.define_accessors(self)
|
241
|
+
m
|
242
|
+
end
|
243
|
+
|
244
|
+
def prepare_options(node, options)
|
245
|
+
ns = options.fetch(:ns) {|k| node == :attribute ? nil : options[k] = mapper.default_ns }
|
246
|
+
if ns.is_a?(Symbol)
|
247
|
+
raise ArgumentError, "undefined prefix: #{ns}" unless options[:ns] = mapper.namespaces[ns]
|
248
|
+
options[:prefix] = ns unless options.include?(:prefix)
|
249
|
+
end
|
250
|
+
options
|
251
|
+
end
|
252
|
+
end
|
253
|
+
end
|
@@ -0,0 +1,95 @@
|
|
1
|
+
require 'enumerator'
|
2
|
+
|
3
|
+
module Peanuts
|
4
|
+
class Mapper
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
attr_reader :root, :namespaces, :ns_context
|
8
|
+
attr_accessor :default_ns, :schema
|
9
|
+
|
10
|
+
def initialize(ns_context = nil, default_ns = nil)
|
11
|
+
@ns_context, @default_ns = ns_context, default_ns
|
12
|
+
@mappings, @footprints = [], {}
|
13
|
+
@namespaces = ns_context ? Hash.new {|h, k| ns_context[k] || raise(IndexError) } : {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def root=(root)
|
17
|
+
raise 'root already defined' if @root
|
18
|
+
# TODO raise 'root in nested scopes not supported' if nested?
|
19
|
+
@default_ns = root.namespace_uri unless root.prefix
|
20
|
+
@root = root
|
21
|
+
end
|
22
|
+
|
23
|
+
def each(&block)
|
24
|
+
@mappings.each(&block)
|
25
|
+
end
|
26
|
+
|
27
|
+
def <<(mapping)
|
28
|
+
fp = MappingFootprint.new(mapping)
|
29
|
+
raise "mapping already defined for #{fp}" if @footprints.include?(fp)
|
30
|
+
@mappings << (@footprints[fp] = mapping)
|
31
|
+
end
|
32
|
+
|
33
|
+
def define_accessors(type)
|
34
|
+
each {|m| m.define_accessors(type) }
|
35
|
+
end
|
36
|
+
|
37
|
+
def read(nut, reader)
|
38
|
+
rdfp = ReaderFootprint.new(reader)
|
39
|
+
reader.each do
|
40
|
+
m = @footprints[rdfp]
|
41
|
+
m.read(nut, reader) if m
|
42
|
+
end
|
43
|
+
nut
|
44
|
+
end
|
45
|
+
|
46
|
+
def write(nut, writer)
|
47
|
+
@root.write(writer) do |w|
|
48
|
+
w.write_namespaces('' => default_ns) if default_ns
|
49
|
+
w.write_namespaces(namespaces)
|
50
|
+
write_children(nut, w)
|
51
|
+
end
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def write_children(nut, writer)
|
56
|
+
each {|m| m.write(nut, writer) } if nut
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
|
60
|
+
def clear(nut)
|
61
|
+
each {|m| m.clear(nut) }
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
class Footprint #:nodoc:
|
66
|
+
extend Forwardable
|
67
|
+
|
68
|
+
def_delegators :@obj, :node_type, :local_name, :namespace_uri
|
69
|
+
|
70
|
+
def initialize(obj)
|
71
|
+
@obj = obj
|
72
|
+
end
|
73
|
+
|
74
|
+
def hash
|
75
|
+
node_type.hash ^ local_name.hash ^ namespace_uri.hash
|
76
|
+
end
|
77
|
+
|
78
|
+
def to_s
|
79
|
+
"#{node_type}(#{local_name}, #{namespace_uri})"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class MappingFootprint < Footprint #:nodoc:
|
84
|
+
def eql?(other)
|
85
|
+
self.equal?(other) || other && @obj.matches?(other)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class ReaderFootprint < Footprint #:nodoc:
|
90
|
+
def eql?(mappingfp)
|
91
|
+
mappingfp.eql?(@obj)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|