omg-peanuts 1.0 → 2.0.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.
- data/README.rdoc +6 -5
- data/lib/peanuts/mapper.rb +110 -0
- data/lib/peanuts/mappings.rb +62 -49
- data/lib/peanuts/nuts.rb +81 -37
- data/lib/peanuts/xml/libxml.rb +231 -0
- data/lib/peanuts/xml/reader.rb +78 -0
- data/lib/peanuts/xml.rb +1 -0
- data/lib/peanuts.rb +1 -0
- data/test/parsing_test.rb +2 -2
- metadata +18 -5
- data/lib/peanuts/backend.rb +0 -38
- data/lib/peanuts/rexml.rb +0 -98
data/README.rdoc
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
=== Introduction
|
2
|
+
*This is currently undergoing some rewrite. 2.x branch will introduce minor api incompatibilities.*
|
3
|
+
|
2
4
|
Peanuts is an library that allows for bidirectional mapping between Ruby objects and XML.
|
3
5
|
|
4
6
|
Released under the MIT license.
|
@@ -9,6 +11,10 @@ Released under the MIT license.
|
|
9
11
|
- Pluggable backends to work with different XML APIs (REXML implemented so far).
|
10
12
|
|
11
13
|
=== Installation
|
14
|
+
Stable branch 1.x available from Gemcutter
|
15
|
+
gem install peanuts --source http://gemcutter.org
|
16
|
+
|
17
|
+
Development branch 2.x available from Github. buggy/broken/missing docs
|
12
18
|
gem install omg-peanuts --source http://gems.github.com
|
13
19
|
|
14
20
|
=== Usage
|
@@ -96,9 +102,4 @@ Please report via Github issue tracking.
|
|
96
102
|
* http://github.com/omg/statelogic -- A simple state machine for ActiveRecord
|
97
103
|
|
98
104
|
|
99
|
-
|
100
|
-
Free hint: If you liek mudkipz^W^Wfeel like generous today you can tip me at http://tipjoy.com/u/pisuka
|
101
|
-
|
102
|
-
|
103
105
|
Copyright (c) 2009 Igor Gunko, released under the MIT license
|
104
|
-
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'enumerator'
|
2
|
+
|
3
|
+
module Peanuts
|
4
|
+
class Mapper
|
5
|
+
include Enumerable
|
6
|
+
|
7
|
+
attr_reader :root, :namespaces, :nscontext, :container
|
8
|
+
attr_accessor :schema
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@mappings, @footprints = [], {}
|
12
|
+
@namespaces = Hash.new do |h, k|
|
13
|
+
nscontext && nscontext[k] || raise(IndexError)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def root=(root)
|
18
|
+
raise 'root already defined' if @root
|
19
|
+
raise 'root in nested scopes not supported' if nested?
|
20
|
+
@root = root
|
21
|
+
end
|
22
|
+
|
23
|
+
def set_context(container, nscontext)
|
24
|
+
@container, @nscontext = container, nscontext
|
25
|
+
end
|
26
|
+
|
27
|
+
def nested?
|
28
|
+
!!container
|
29
|
+
end
|
30
|
+
|
31
|
+
def each(&block)
|
32
|
+
@mappings.each(&block)
|
33
|
+
end
|
34
|
+
|
35
|
+
def <<(mapping)
|
36
|
+
fp = MappingFootprint.new(mapping)
|
37
|
+
raise "mapping already defined for #{fp}" if @footprints.include?(fp)
|
38
|
+
@mappings << (@footprints[fp] = mapping)
|
39
|
+
end
|
40
|
+
|
41
|
+
def parse(nut, reader)
|
42
|
+
rdfp = ReaderFootprint.new(reader)
|
43
|
+
reader.each do
|
44
|
+
m = @footprints[rdfp]
|
45
|
+
m.from_xml(nut, reader) if m
|
46
|
+
end
|
47
|
+
nut
|
48
|
+
end
|
49
|
+
|
50
|
+
def build(nut, writer)
|
51
|
+
if @root
|
52
|
+
@root.to_xml(writer) do
|
53
|
+
_save(nut, writer)
|
54
|
+
end
|
55
|
+
else
|
56
|
+
_save(nut, writer)
|
57
|
+
end
|
58
|
+
writer.result
|
59
|
+
end
|
60
|
+
|
61
|
+
def clear(nut)
|
62
|
+
@mappings.each {|m| m.clear(nut) }
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
def _save(nut, writer)
|
67
|
+
@mappings.each {|m| m.to_xml(nut, writer) }
|
68
|
+
end
|
69
|
+
|
70
|
+
class Footprint
|
71
|
+
def ==(other)
|
72
|
+
self.equal?(other) || other && node_type == other.node_type && name == other.name && ns == other.ns
|
73
|
+
end
|
74
|
+
|
75
|
+
alias eql? ==
|
76
|
+
|
77
|
+
def hash
|
78
|
+
node_type.hash ^ name.hash ^ ns.hash
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_s
|
82
|
+
"#{node_type}(#{name}, #{ns})"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
class MappingFootprint < Footprint
|
87
|
+
extend Forwardable
|
88
|
+
|
89
|
+
def initialize(mapping)
|
90
|
+
@mapping = mapping
|
91
|
+
end
|
92
|
+
|
93
|
+
def_delegator :@mapping, :node_type
|
94
|
+
def_delegator :@mapping, :xmlname, :name
|
95
|
+
def_delegator :@mapping, :xmlns, :ns
|
96
|
+
end
|
97
|
+
|
98
|
+
class ReaderFootprint < Footprint
|
99
|
+
extend Forwardable
|
100
|
+
|
101
|
+
def initialize(reader)
|
102
|
+
@reader = reader
|
103
|
+
end
|
104
|
+
|
105
|
+
def_delegator :@reader, :node_type
|
106
|
+
def_delegator :@reader, :local_name, :name
|
107
|
+
def_delegator :@reader, :namespace_uri, :ns
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
data/lib/peanuts/mappings.rb
CHANGED
@@ -1,26 +1,36 @@
|
|
1
|
-
require '
|
2
|
-
require 'peanuts/backend'
|
1
|
+
require 'forwardable'
|
3
2
|
require 'peanuts/converters'
|
4
3
|
|
5
4
|
module Peanuts
|
6
|
-
|
7
|
-
|
8
|
-
|
5
|
+
class Mapping
|
6
|
+
attr_reader :xmlname, :xmlns, :prefix, :options
|
7
|
+
|
8
|
+
def initialize(xmlname, options)
|
9
|
+
@xmlname, @xmlns, @prefix, @options = xmlname.to_s, options.delete(:xmlns), options.delete(:prefix), options
|
10
|
+
end
|
11
|
+
|
12
|
+
def node_type
|
13
|
+
self.class.node_type
|
14
|
+
end
|
9
15
|
|
10
|
-
|
11
|
-
|
16
|
+
class << self
|
17
|
+
def node_type(node_type = nil)
|
18
|
+
@node_type = node_type if node_type
|
19
|
+
@node_type
|
12
20
|
end
|
13
21
|
end
|
22
|
+
end
|
14
23
|
|
24
|
+
module Mappings
|
15
25
|
class Root < Mapping
|
16
|
-
|
17
|
-
|
26
|
+
node_type :element
|
27
|
+
|
28
|
+
def to_xml(writer, &block)
|
29
|
+
writer.write(node_type, xmlname, xmlns, prefix, &block)
|
18
30
|
end
|
19
31
|
end
|
20
32
|
|
21
33
|
class MemberMapping < Mapping
|
22
|
-
include XmlBackend
|
23
|
-
|
24
34
|
attr_reader :name, :type, :converter
|
25
35
|
|
26
36
|
def initialize(name, type, options)
|
@@ -43,7 +53,11 @@ module Peanuts
|
|
43
53
|
end
|
44
54
|
|
45
55
|
def from_xml(nut, node)
|
46
|
-
set(nut,
|
56
|
+
set(nut, getxml2(node, get(nut)))
|
57
|
+
end
|
58
|
+
|
59
|
+
def clear(nut)
|
60
|
+
set(nut, nil)
|
47
61
|
end
|
48
62
|
|
49
63
|
private
|
@@ -63,54 +77,60 @@ module Peanuts
|
|
63
77
|
@converter ? @converter.from_xml(text) : text
|
64
78
|
end
|
65
79
|
|
66
|
-
def
|
67
|
-
|
68
|
-
nil
|
69
|
-
end
|
70
|
-
|
71
|
-
def add_element(node, value = nil)
|
72
|
-
backend.add_element(node, xmlname, xmlns, value)
|
80
|
+
def getxml2(node, acc)
|
81
|
+
getxml(node)
|
73
82
|
end
|
74
83
|
|
75
|
-
def
|
76
|
-
|
84
|
+
def parse(events)
|
85
|
+
type.send(:_restore, events)
|
77
86
|
end
|
78
87
|
|
79
|
-
def
|
80
|
-
type.
|
81
|
-
end
|
82
|
-
|
83
|
-
def build(node, nut, dest_node)
|
84
|
-
nut && type.build_node(nut, dest_node)
|
88
|
+
def build(nut, writer)
|
89
|
+
type.send(:_save, nut, writer)
|
85
90
|
end
|
86
91
|
end
|
87
92
|
|
88
93
|
class ElementValue < MemberMapping
|
94
|
+
node_type :element
|
95
|
+
|
89
96
|
private
|
90
97
|
def getxml(node)
|
91
|
-
|
98
|
+
froxml(node.value)
|
92
99
|
end
|
93
100
|
|
94
|
-
def setxml(
|
95
|
-
|
101
|
+
def setxml(writer, value)
|
102
|
+
writer.write(node_type, xmlname, xmlns, prefix) do |w|
|
103
|
+
w.value = toxml(value)
|
104
|
+
end
|
96
105
|
end
|
97
106
|
end
|
98
107
|
|
99
108
|
class Element < MemberMapping
|
109
|
+
node_type :element
|
110
|
+
|
100
111
|
private
|
101
112
|
def getxml(node)
|
102
|
-
|
113
|
+
parse(node)
|
103
114
|
end
|
104
115
|
|
105
|
-
def setxml(
|
106
|
-
|
116
|
+
def setxml(writer, value)
|
117
|
+
writer.write(node_type, xmlname, xmlns, prefix) do |w|
|
118
|
+
build(value, w)
|
119
|
+
end
|
107
120
|
end
|
108
121
|
end
|
109
122
|
|
110
123
|
class Attribute < MemberMapping
|
124
|
+
node_type :attribute
|
125
|
+
|
126
|
+
def initialize(name, type, options)
|
127
|
+
super
|
128
|
+
raise ArgumentError, 'a namespaced attribute must have namespace prefix' if xmlns && !prefix
|
129
|
+
end
|
130
|
+
|
111
131
|
private
|
112
132
|
def getxml(node)
|
113
|
-
froxml(
|
133
|
+
froxml(node.value)
|
114
134
|
end
|
115
135
|
|
116
136
|
def setxml(node, value)
|
@@ -119,31 +139,24 @@ module Peanuts
|
|
119
139
|
end
|
120
140
|
|
121
141
|
class ElementValues < MemberMapping
|
122
|
-
|
123
|
-
def each_value(node)
|
124
|
-
each_element(node) {|x| yield froxml(value(x)) }
|
125
|
-
end
|
142
|
+
node_type :element
|
126
143
|
|
127
|
-
|
128
|
-
|
144
|
+
private
|
145
|
+
def getxml2(node, acc)
|
146
|
+
(acc || []) << froxml(node.value)
|
129
147
|
end
|
130
148
|
|
131
149
|
def setxml(node, values)
|
132
|
-
unless node
|
133
|
-
raise 'fuck'
|
134
|
-
end
|
135
150
|
values.each {|v| add_element(node, toxml(v)) } if values
|
136
151
|
end
|
137
152
|
end
|
138
153
|
|
139
154
|
class Elements < MemberMapping
|
140
|
-
|
141
|
-
def each_object(node)
|
142
|
-
each_element(node) {|e| yield parse(e) }
|
143
|
-
end
|
155
|
+
node_type :element
|
144
156
|
|
145
|
-
|
146
|
-
|
157
|
+
private
|
158
|
+
def getxml2(node, acc)
|
159
|
+
(acc || []) << parse(node)
|
147
160
|
end
|
148
161
|
|
149
162
|
def setxml(node, elements)
|
data/lib/peanuts/nuts.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require 'peanuts/mappings'
|
2
|
+
require 'peanuts/mapper'
|
3
|
+
require 'peanuts/xml/reader'
|
2
4
|
|
3
5
|
module Peanuts #:nodoc:
|
4
6
|
# See also +ClassMethods+
|
@@ -8,9 +10,19 @@ module Peanuts #:nodoc:
|
|
8
10
|
|
9
11
|
# See also +PeaNuts+.
|
10
12
|
module ClassMethods
|
11
|
-
include XmlBackend
|
12
13
|
include Mappings
|
13
14
|
|
15
|
+
def self.extended(other)
|
16
|
+
other.instance_eval do
|
17
|
+
@mappings = Mapper.new
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# mappings -> Mapper
|
22
|
+
#
|
23
|
+
# Returns all mappings defined on a class.
|
24
|
+
attr_reader :mappings
|
25
|
+
|
14
26
|
# namespaces(hash) -> Hash
|
15
27
|
# namespaces -> Hash
|
16
28
|
#
|
@@ -20,13 +32,12 @@ module Peanuts #:nodoc:
|
|
20
32
|
#
|
21
33
|
# === Example:
|
22
34
|
# class Cat
|
23
|
-
# include
|
35
|
+
# include Peanuts
|
24
36
|
# namespaces :lol => 'urn:lol', ...
|
25
37
|
# ...
|
26
38
|
# end
|
27
39
|
def namespaces(mappings = nil)
|
28
|
-
@namespaces
|
29
|
-
mappings ? @namespaces.update(mappings) : @namespaces
|
40
|
+
mappings ? @mappings.namespaces.update(mappings) : @mappings.namespaces
|
30
41
|
end
|
31
42
|
|
32
43
|
# root(xmlname[, :xmlns => ...]) -> Mappings::Root
|
@@ -47,8 +58,8 @@ module Peanuts #:nodoc:
|
|
47
58
|
# ...
|
48
59
|
# end
|
49
60
|
def root(xmlname = nil, options = {})
|
50
|
-
@root = Root.new(xmlname, prepare_options(options)) if xmlname
|
51
|
-
@
|
61
|
+
@mappings.root = Root.new(xmlname, prepare_options(options)) if xmlname
|
62
|
+
@mappings.root
|
52
63
|
end
|
53
64
|
|
54
65
|
# element(name, [type[, options]]) -> Mappings::Element or Mappings::ElementValue
|
@@ -71,9 +82,10 @@ module Peanuts #:nodoc:
|
|
71
82
|
# ...
|
72
83
|
# end
|
73
84
|
def element(name, type = :string, options = {}, &block)
|
74
|
-
type, options
|
75
|
-
|
76
|
-
|
85
|
+
prepare_args(type, options, block) do |type, options|
|
86
|
+
define_accessor name
|
87
|
+
(@mappings << (type.is_a?(Class) ? Element : ElementValue).new(name, type, options)).last
|
88
|
+
end
|
77
89
|
end
|
78
90
|
|
79
91
|
# elements(name, [type[, options]]) -> Mappings::Element or Mappings::ElementValue
|
@@ -96,9 +108,10 @@ module Peanuts #:nodoc:
|
|
96
108
|
# ...
|
97
109
|
# end
|
98
110
|
def elements(name, type = :string, options = {}, &block)
|
99
|
-
type, options
|
100
|
-
|
101
|
-
|
111
|
+
prepare_args(type, options, block) do |type, options|
|
112
|
+
define_accessor name
|
113
|
+
(@mappings << (type.is_a?(Class) ? Elements : ElementValues).new(name, type, options)).last
|
114
|
+
end
|
102
115
|
end
|
103
116
|
|
104
117
|
# attribute(name, [type[, options]]) -> Mappings::Attribute or Mappings::AttributeValue
|
@@ -120,18 +133,27 @@ module Peanuts #:nodoc:
|
|
120
133
|
# end
|
121
134
|
def attribute(name, type = :string, options = {})
|
122
135
|
define_accessor name
|
123
|
-
mappings << Attribute.new(name, type, prepare_options(options))
|
136
|
+
@mappings << Attribute.new(name, type, prepare_options({:xmlns => nil}.merge(options)))
|
124
137
|
end
|
125
138
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
139
|
+
def schema(schema = nil)
|
140
|
+
@mappings.schema = schema if schema
|
141
|
+
@mappings.schema
|
142
|
+
end
|
143
|
+
|
144
|
+
def restore(reader)
|
145
|
+
e = reader.find_element
|
146
|
+
e && _restore(e)
|
131
147
|
end
|
132
148
|
|
133
|
-
def
|
134
|
-
|
149
|
+
def restore_from(source_or_type = nil, source = nil, options = {})
|
150
|
+
_source_or_dest(source_or_type, source) do |source_type, source|
|
151
|
+
restore(XML::Reader.new(source, source_type, options))
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def save(nut, writer)
|
156
|
+
_save(nut, writer)
|
135
157
|
end
|
136
158
|
|
137
159
|
def build(nut, result = :string, options = {})
|
@@ -147,28 +169,48 @@ module Peanuts #:nodoc:
|
|
147
169
|
node
|
148
170
|
end
|
149
171
|
|
150
|
-
def
|
151
|
-
|
152
|
-
|
172
|
+
def parse_events(nut, events) #:nodoc:
|
173
|
+
@mappings.parse(nut, events)
|
174
|
+
end
|
175
|
+
|
176
|
+
def _source_or_dest(a, b)
|
177
|
+
a, b = :string, a unless a.is_a?(Symbol)
|
178
|
+
yield a, b
|
153
179
|
end
|
154
180
|
|
155
181
|
private
|
156
|
-
def
|
157
|
-
|
158
|
-
|
182
|
+
def _restore(events)
|
183
|
+
nut = new
|
184
|
+
@mappings.parse(nut, events)
|
185
|
+
nut
|
186
|
+
end
|
187
|
+
|
188
|
+
def _save(nut, events)
|
189
|
+
@mappings.build(nut, events)
|
190
|
+
end
|
191
|
+
|
192
|
+
def prepare_args(type, options, blk)
|
193
|
+
if blk
|
194
|
+
options = prepare_options(type) if type.is_a?(Hash)
|
159
195
|
type = Class.new
|
160
|
-
type.
|
161
|
-
|
162
|
-
|
196
|
+
yield(type, options).tap do |m|
|
197
|
+
type.instance_eval do
|
198
|
+
include Peanuts
|
199
|
+
mappings.set_context(m, namespaces)
|
200
|
+
instance_eval(&blk)
|
201
|
+
end
|
163
202
|
end
|
203
|
+
else
|
204
|
+
options = prepare_options(options)
|
205
|
+
yield type, options
|
164
206
|
end
|
165
|
-
return type, prepare_options(options)
|
166
207
|
end
|
167
208
|
|
168
209
|
def prepare_options(options)
|
169
|
-
ns = options
|
210
|
+
ns = options.fetch(:xmlns) {|k| options[k] = root && root.xmlns || @mappings.container && @mappings.container.xmlns }
|
170
211
|
if ns.is_a?(Symbol)
|
171
212
|
raise ArgumentError, "undefined prefix: #{ns}" unless options[:xmlns] = namespaces[ns]
|
213
|
+
options[:prefix] = ns
|
172
214
|
end
|
173
215
|
options
|
174
216
|
end
|
@@ -179,16 +221,16 @@ module Peanuts #:nodoc:
|
|
179
221
|
end
|
180
222
|
attr_accessor name
|
181
223
|
end
|
182
|
-
|
183
|
-
def callem(method, *args)
|
184
|
-
mappings.each {|m| m.send(method, *args) }
|
185
|
-
end
|
186
224
|
end
|
187
225
|
|
188
226
|
def parse(source, options = {})
|
189
227
|
backend.parse(source, options) {|node| parse_node(node) }
|
190
228
|
end
|
191
229
|
|
230
|
+
def save(writer)
|
231
|
+
self.class.save(self, writer)
|
232
|
+
end
|
233
|
+
|
192
234
|
# build([options]) -> root element or string
|
193
235
|
# build([options]) -> root element or string
|
194
236
|
# build(destination[, options]) -> destination
|
@@ -213,7 +255,9 @@ module Peanuts #:nodoc:
|
|
213
255
|
# doc = REXML::Document.new
|
214
256
|
# cat.build(doc)
|
215
257
|
# puts doc.to_s
|
216
|
-
def
|
217
|
-
self.class.
|
258
|
+
def save_to(dest_or_type = :string, dest = nil, options = {})
|
259
|
+
self.class._source_or_dest(dest_or_type, dest) do |dest_type, dest|
|
260
|
+
save(XML::Writer.new(dest, dest_type, options))
|
261
|
+
end
|
218
262
|
end
|
219
263
|
end
|
@@ -0,0 +1,231 @@
|
|
1
|
+
require 'libxml'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'peanuts/xml/reader'
|
4
|
+
|
5
|
+
module Peanuts
|
6
|
+
module XML
|
7
|
+
module LibXML
|
8
|
+
class Writer < Peanuts::XML::Writer
|
9
|
+
def initialize(dest, dest_type, options = {})
|
10
|
+
@dest_type = dest_type
|
11
|
+
@dest = case dest_type
|
12
|
+
when :string
|
13
|
+
dest || ''
|
14
|
+
when :io
|
15
|
+
dest
|
16
|
+
when :document
|
17
|
+
dest || LibXML::XML::Document.new
|
18
|
+
else
|
19
|
+
raise ArgumentError, "unrecognized destination type #{dest_type.inspect}"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def result
|
24
|
+
@dest
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_writer :value
|
28
|
+
|
29
|
+
def clear
|
30
|
+
@node_type = @local_name = @namespace_uri = @prefix = @value = nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def write(node_type, local_name = nil, namespace_uri = nil, prefix = nil)
|
34
|
+
if @node_type
|
35
|
+
mknode
|
36
|
+
else
|
37
|
+
@node = nil
|
38
|
+
end
|
39
|
+
|
40
|
+
@node_type = node_type
|
41
|
+
@local_name, @namespace_uri, @prefix = local_name, namespace_uri, prefix && prefix.to_s
|
42
|
+
|
43
|
+
exparent, @parent = @parent, @node
|
44
|
+
|
45
|
+
yield self
|
46
|
+
|
47
|
+
if @node_type
|
48
|
+
mknode
|
49
|
+
end
|
50
|
+
@node = nil
|
51
|
+
|
52
|
+
if exparent.nil?
|
53
|
+
case @dest_type
|
54
|
+
when :string, :io
|
55
|
+
@dest << @parent.to_s
|
56
|
+
when :document
|
57
|
+
@dest.root = @parent
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
@parent = exparent
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
def mknode
|
66
|
+
@node = case @node_type
|
67
|
+
when :element
|
68
|
+
n = ::LibXML::XML::Node.new(@local_name, @value)
|
69
|
+
if @namespace_uri
|
70
|
+
ns = @parent && @parent.namespaces.find_by_href(@namespace_uri)
|
71
|
+
n.namespaces.namespace = ns && ns.prefix == @prefix ? ns : ::LibXML::XML::Namespace.new(n, @prefix, @namespace_uri)
|
72
|
+
end
|
73
|
+
@parent << n if @parent
|
74
|
+
n
|
75
|
+
when :attribute
|
76
|
+
::LibXML::XML::Attr.new(@parent, @local_name, @value, @namespace_uri)
|
77
|
+
else
|
78
|
+
raise "unsupported node type #{@node_type.inspect}"
|
79
|
+
end
|
80
|
+
clear
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
class Reader < Peanuts::XML::Reader
|
85
|
+
extend Forwardable
|
86
|
+
|
87
|
+
SCHEMAS = {:xml_schema => :schema, :relax_ng => :relax_ng}
|
88
|
+
|
89
|
+
RD = ::LibXML::XML::Reader
|
90
|
+
|
91
|
+
NODE_TYPES = [
|
92
|
+
nil,
|
93
|
+
:element,
|
94
|
+
:attribute,
|
95
|
+
:text,
|
96
|
+
:cdata,
|
97
|
+
:entity_reference,
|
98
|
+
:entity,
|
99
|
+
:processing_instruction,
|
100
|
+
:comment,
|
101
|
+
:document,
|
102
|
+
:document_type,
|
103
|
+
:document_fragment,
|
104
|
+
:notation,
|
105
|
+
:whitespace,
|
106
|
+
:significant_whitespace,
|
107
|
+
:end_element,
|
108
|
+
:end_entity,
|
109
|
+
:xml_declaration
|
110
|
+
].freeze
|
111
|
+
|
112
|
+
DEFAULT_PARSER_OPTIONS = {
|
113
|
+
:libxml_encoding => ::LibXML::XML::Encoding::UTF_8,
|
114
|
+
:libxml_options => ::LibXML::XML::Parser::Options::NOENT
|
115
|
+
}
|
116
|
+
|
117
|
+
def initialize(source, source_type, options = {})
|
118
|
+
super()
|
119
|
+
options = options.dup
|
120
|
+
@schema = options.delete(:schema)
|
121
|
+
@reader = case source_type
|
122
|
+
when :string
|
123
|
+
RD.string(source, parser_opt(options))
|
124
|
+
when :io
|
125
|
+
RD.io(source, parser_opt(options))
|
126
|
+
when :uri
|
127
|
+
RD.file(source, parser_opt(options))
|
128
|
+
when :document
|
129
|
+
RD.document(source)
|
130
|
+
else
|
131
|
+
raise ArgumentError, "unrecognized source type #{source_type}"
|
132
|
+
end
|
133
|
+
@reader.send("#{SCHEMAS[schema.type]}_validate", schema.schema) if @schema
|
134
|
+
end
|
135
|
+
|
136
|
+
def close
|
137
|
+
@reader.close
|
138
|
+
end
|
139
|
+
|
140
|
+
def_delegators :@reader, :name, :local_name, :namespace_uri, :depth
|
141
|
+
|
142
|
+
def node_type
|
143
|
+
NODE_TYPES[@reader.node_type]
|
144
|
+
end
|
145
|
+
|
146
|
+
def value
|
147
|
+
case @reader.node_type
|
148
|
+
when RD::TYPE_ELEMENT
|
149
|
+
@reader.read_string
|
150
|
+
else
|
151
|
+
@reader.has_value? ? @reader.value : nil
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def each
|
156
|
+
depth = self.depth
|
157
|
+
if read
|
158
|
+
while self.depth > depth
|
159
|
+
yield self
|
160
|
+
break unless next_sibling
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def find_element
|
166
|
+
until @reader.node_type == RD::TYPE_ELEMENT
|
167
|
+
return nil unless read
|
168
|
+
end
|
169
|
+
self
|
170
|
+
end
|
171
|
+
|
172
|
+
private
|
173
|
+
def parser_opt(options)
|
174
|
+
h = DEFAULT_PARSER_OPTIONS.merge(options)
|
175
|
+
h.merge(h.from_namespace!(:libxml))
|
176
|
+
end
|
177
|
+
|
178
|
+
def read
|
179
|
+
case @reader.node_type
|
180
|
+
when RD::TYPE_ATTRIBUTE
|
181
|
+
@reader.move_to_next_attribute > 0 || @reader.read
|
182
|
+
else
|
183
|
+
@reader.move_to_first_attribute > 0 || @reader.read
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def next_sibling
|
188
|
+
case @reader.node_type
|
189
|
+
when RD::TYPE_ATTRIBUTE
|
190
|
+
@reader.move_to_next_attribute > 0 || @reader.read
|
191
|
+
else
|
192
|
+
@reader.next > 0
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def self.schema(schema_type, source, source_type = :string)
|
198
|
+
schema_class = case schema_type
|
199
|
+
when :xml_schema
|
200
|
+
::LibXML::XML::Schema
|
201
|
+
when :relax_ng
|
202
|
+
::LibXML::XML::RelaxNG
|
203
|
+
else
|
204
|
+
raise ArgumentError, "unrecognized schema type #{schema_type}"
|
205
|
+
end
|
206
|
+
schema = case source_type
|
207
|
+
when :string
|
208
|
+
schema_class.string(source)
|
209
|
+
when :io
|
210
|
+
schema_class.string(source.read)
|
211
|
+
when :uri
|
212
|
+
schema_class.new(source)
|
213
|
+
when :document
|
214
|
+
schema_class.document(source)
|
215
|
+
else
|
216
|
+
raise ArgumentError, "unrecognized source type #{source_type}"
|
217
|
+
end
|
218
|
+
|
219
|
+
Schema.new(schema_type, schema)
|
220
|
+
end
|
221
|
+
|
222
|
+
class Schema
|
223
|
+
attr_reader :type, :handle
|
224
|
+
|
225
|
+
def initialize(type, handle)
|
226
|
+
@type, @handle = type, handle
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'enumerator'
|
2
|
+
|
3
|
+
class Hash
|
4
|
+
def from_namespace(ns)
|
5
|
+
rx = /^#{Regexp.quote(ns.to_s)}_(.*)$/
|
6
|
+
inject({}) do |a, p|
|
7
|
+
a[$1.to_sym] = p[1] if p[0].to_s =~ rx
|
8
|
+
a
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def from_namespace!(ns)
|
13
|
+
h = from_namespace(ns)
|
14
|
+
h.each_key {|k| delete(:"#{ns}_#{k}") }
|
15
|
+
h
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module Peanuts
|
20
|
+
module XML
|
21
|
+
autoload :LibXML, 'peanuts/xml/libxml'
|
22
|
+
|
23
|
+
def self.default
|
24
|
+
@@default ||= LibXML
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.schema(schema_type, source, source_type = :string)
|
28
|
+
default.schema(schema_type, source, source_type)
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.method_missing(method, *args, &block)
|
32
|
+
case method.to_s
|
33
|
+
when /^(.*)_schema_from_(.*)$/
|
34
|
+
XML.schema($1.to_sym, args.first, $2.to_sym)
|
35
|
+
else
|
36
|
+
super
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class Reader
|
41
|
+
include Enumerable
|
42
|
+
|
43
|
+
def self.new(*args, &block)
|
44
|
+
cls = self == Reader ? XML.default::Reader : self
|
45
|
+
obj = cls.allocate
|
46
|
+
obj.send(:initialize, *args, &block)
|
47
|
+
obj
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.method_missing(method, *args, &block)
|
51
|
+
case method.to_s
|
52
|
+
when /^from_(.*)$/
|
53
|
+
new(args.first, $1.to_sym, &block)
|
54
|
+
else
|
55
|
+
super
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Writer
|
61
|
+
def self.new(*args, &block)
|
62
|
+
cls = self == Writer ? XML.default::Writer : self
|
63
|
+
obj = cls.allocate
|
64
|
+
obj.send(:initialize, *args, &block)
|
65
|
+
obj
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.method_missing(method, *args, &block)
|
69
|
+
case method.to_s
|
70
|
+
when /^from_(.*)$/
|
71
|
+
new(args.first, $1.to_sym, &block)
|
72
|
+
else
|
73
|
+
super
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/peanuts/xml.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'peanuts/xml/reader'
|
data/lib/peanuts.rb
CHANGED
data/test/parsing_test.rb
CHANGED
@@ -21,7 +21,7 @@ class Cat
|
|
21
21
|
|
22
22
|
root 'kitteh', :xmlns => :lol
|
23
23
|
|
24
|
-
attribute :has_tail, :boolean, :xmlname => 'has-tail', :xmlns =>
|
24
|
+
attribute :has_tail, :boolean, :xmlname => 'has-tail', :xmlns => :kthnx
|
25
25
|
attribute :ears, :integer
|
26
26
|
|
27
27
|
element :ration, [:string], :xmlname => :eats, :xmlns => :kthnx
|
@@ -66,7 +66,7 @@ class ParsingTest < Test::Unit::TestCase
|
|
66
66
|
</moar_cheezburgers>
|
67
67
|
</kitteh>
|
68
68
|
EOS
|
69
|
-
@cat = Cat.
|
69
|
+
@cat = Cat.restore_from(@xml_fragment)
|
70
70
|
end
|
71
71
|
|
72
72
|
context "A cat" do
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: omg-peanuts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Igor Gunko
|
@@ -9,9 +9,19 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-
|
12
|
+
date: 2009-09-24 00:00:00 -07:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: libxml-ruby
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.1.3
|
24
|
+
version:
|
15
25
|
- !ruby/object:Gem::Dependency
|
16
26
|
name: thoughtbot-shoulda
|
17
27
|
type: :development
|
@@ -40,10 +50,13 @@ files:
|
|
40
50
|
- lib/peanuts/nuts.rb
|
41
51
|
- lib/peanuts/mappings.rb
|
42
52
|
- lib/peanuts/converters.rb
|
43
|
-
- lib/peanuts/
|
44
|
-
- lib/peanuts/
|
53
|
+
- lib/peanuts/mapper.rb
|
54
|
+
- lib/peanuts/xml.rb
|
55
|
+
- lib/peanuts/xml/reader.rb
|
56
|
+
- lib/peanuts/xml/libxml.rb
|
45
57
|
has_rdoc: true
|
46
58
|
homepage: http://github.com/omg/peanuts
|
59
|
+
licenses:
|
47
60
|
post_install_message:
|
48
61
|
rdoc_options:
|
49
62
|
- --line-numbers
|
@@ -66,7 +79,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
66
79
|
requirements: []
|
67
80
|
|
68
81
|
rubyforge_project:
|
69
|
-
rubygems_version: 1.
|
82
|
+
rubygems_version: 1.3.5
|
70
83
|
signing_key:
|
71
84
|
specification_version: 2
|
72
85
|
summary: Making XML <-> Ruby binding easy
|
data/lib/peanuts/backend.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
require 'monitor'
|
2
|
-
|
3
|
-
module Peanuts
|
4
|
-
module XmlBackend
|
5
|
-
extend MonitorMixin
|
6
|
-
|
7
|
-
autoload :REXMLBackend, 'peanuts/rexml'
|
8
|
-
|
9
|
-
def self.default
|
10
|
-
synchronize do
|
11
|
-
unless defined? @@default
|
12
|
-
@@default = REXMLBackend.new
|
13
|
-
def self.default #:nodoc:
|
14
|
-
@@default
|
15
|
-
end
|
16
|
-
@@default
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.default=(backend)
|
22
|
-
@@default = backend
|
23
|
-
end
|
24
|
-
|
25
|
-
def self.current
|
26
|
-
Thread.current[XmlBackend.name] || default
|
27
|
-
end
|
28
|
-
|
29
|
-
def self.current=(backend)
|
30
|
-
Thread.current[XmlBackend.name] = backend
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
def backend #:doc:
|
35
|
-
XmlBackend.current
|
36
|
-
end
|
37
|
-
end
|
38
|
-
end
|
data/lib/peanuts/rexml.rb
DELETED
@@ -1,98 +0,0 @@
|
|
1
|
-
require 'rexml/document'
|
2
|
-
require 'peanuts/backend'
|
3
|
-
|
4
|
-
class Peanuts::XmlBackend::REXMLBackend #:nodoc:
|
5
|
-
def parse(source, options)
|
6
|
-
case source
|
7
|
-
when nil
|
8
|
-
return nil
|
9
|
-
when REXML::Document
|
10
|
-
node = source.root
|
11
|
-
when REXML::Node
|
12
|
-
node = source
|
13
|
-
when String, IO
|
14
|
-
node = REXML::Document.new(source).root
|
15
|
-
else
|
16
|
-
raise ArgumentError, 'invalid source'
|
17
|
-
end
|
18
|
-
node && yield(node)
|
19
|
-
end
|
20
|
-
|
21
|
-
def build(result, options)
|
22
|
-
case result
|
23
|
-
when :string, :document, :object, String, IO
|
24
|
-
doc = REXML::Document.new
|
25
|
-
when REXML::Document
|
26
|
-
doc = result
|
27
|
-
when REXML::Node
|
28
|
-
node, doc = result, result.document
|
29
|
-
else
|
30
|
-
raise ArgumentError, 'invalid destination'
|
31
|
-
end
|
32
|
-
node ||= doc.root
|
33
|
-
unless node
|
34
|
-
name, ns, prefix = options[:xmlname], options[:xmlns], options[:xmlns_prefix]
|
35
|
-
name, ns = "#{prefix}:#{name}", nil if prefix
|
36
|
-
node = add_element(doc, name, ns, nil)
|
37
|
-
end
|
38
|
-
|
39
|
-
yield node
|
40
|
-
|
41
|
-
case result
|
42
|
-
when :string
|
43
|
-
doc.to_s
|
44
|
-
when String
|
45
|
-
result.replace(doc.to_s)
|
46
|
-
when IO
|
47
|
-
doc.write(result)
|
48
|
-
result
|
49
|
-
when REXML::Document, :document
|
50
|
-
doc
|
51
|
-
when REXML::Node, :object
|
52
|
-
node
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def add_namespaces(context, namespaces)
|
57
|
-
namespaces.each {|prefix, uri| context.add_namespace(prefix.to_s, uri) }
|
58
|
-
end
|
59
|
-
|
60
|
-
def each_element(context, name, ns, &block)
|
61
|
-
ns = context.namespace unless ns
|
62
|
-
REXML::XPath.each(context, "ns:#{name}", 'ns' => ns, &block)
|
63
|
-
end
|
64
|
-
|
65
|
-
def value(node)
|
66
|
-
node.text
|
67
|
-
end
|
68
|
-
|
69
|
-
def attribute(context, name, ns)
|
70
|
-
name, ns = to_prefixed_name(context, name, ns, true)
|
71
|
-
context.attributes[name]
|
72
|
-
end
|
73
|
-
|
74
|
-
def set_attribute(context, name, ns, text)
|
75
|
-
name, ns = to_prefixed_name(context, name, ns, true)
|
76
|
-
context.add_attribute(name, text)
|
77
|
-
end
|
78
|
-
|
79
|
-
def add_element(context, name, ns, text)
|
80
|
-
name, ns = to_prefixed_name(context, name, ns, false)
|
81
|
-
elem = context.add_element(name)
|
82
|
-
elem.add_namespace(ns) if ns
|
83
|
-
elem.text = text if text
|
84
|
-
elem
|
85
|
-
end
|
86
|
-
|
87
|
-
private
|
88
|
-
def to_prefixed_name(context, name, ns, prefix_required)
|
89
|
-
if ns
|
90
|
-
if prefix = context.namespaces.invert[ns]
|
91
|
-
name, ns = "#{prefix}:#{name}", nil
|
92
|
-
else
|
93
|
-
raise ArgumentError, "no prefix defined for #{ns}" if prefix_required
|
94
|
-
end
|
95
|
-
end
|
96
|
-
return name, ns
|
97
|
-
end
|
98
|
-
end
|