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/lib/peanuts/mappings.rb
CHANGED
@@ -1,49 +1,86 @@
|
|
1
|
-
require '
|
2
|
-
require 'peanuts/backend'
|
1
|
+
require 'forwardable'
|
3
2
|
require 'peanuts/converters'
|
4
3
|
|
5
4
|
module Peanuts
|
6
|
-
|
7
|
-
|
8
|
-
attr_reader :xmlname, :xmlns, :options
|
5
|
+
class Mapping
|
6
|
+
attr_reader :local_name, :namespace_uri, :prefix, :options
|
9
7
|
|
10
|
-
|
11
|
-
|
12
|
-
|
8
|
+
def initialize(local_name, options)
|
9
|
+
@local_name, @namespace_uri, @prefix, @options = local_name.to_s, options.delete(:ns), options.delete(:prefix), options
|
10
|
+
end
|
11
|
+
|
12
|
+
def matches?(reader)
|
13
|
+
node_type == reader.node_type &&
|
14
|
+
local_name == reader.local_name &&
|
15
|
+
namespace_uri == reader.namespace_uri
|
13
16
|
end
|
14
17
|
|
18
|
+
def self.node_type(node_type)
|
19
|
+
define_method(:node_type) { node_type }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
module Mappings # :nodoc:
|
15
24
|
class Root < Mapping
|
16
|
-
|
17
|
-
|
25
|
+
node_type :element
|
26
|
+
|
27
|
+
def write(writer, &block)
|
28
|
+
writer.write(node_type, local_name, namespace_uri, prefix, &block)
|
18
29
|
end
|
19
30
|
end
|
20
31
|
|
21
32
|
class MemberMapping < Mapping
|
22
|
-
include XmlBackend
|
23
|
-
|
24
33
|
attr_reader :name, :type, :converter
|
25
34
|
|
26
35
|
def initialize(name, type, options)
|
27
|
-
|
28
|
-
|
36
|
+
@bare_name = name.to_s.sub(/\?\z/, '')
|
37
|
+
|
38
|
+
super(options.delete(:name) || @bare_name, options)
|
39
|
+
|
40
|
+
@type = type
|
41
|
+
@converter = case type
|
42
|
+
when Symbol
|
43
|
+
Converter.create!(type, options)
|
44
|
+
when Class
|
45
|
+
if type < Converter
|
46
|
+
@type = nil
|
47
|
+
Converter.create!(type, options)
|
48
|
+
end
|
29
49
|
when Array
|
30
|
-
raise ArgumentError, "invalid value for type: #{type}" if type.length != 1
|
50
|
+
raise ArgumentError, "invalid value for type: #{type.inspect}" if type.length != 1
|
31
51
|
options[:item_type] = type.first
|
32
|
-
|
33
|
-
when Class
|
34
|
-
options[:object_type] = type
|
52
|
+
Converter.create!(:list, options)
|
35
53
|
else
|
36
|
-
|
54
|
+
raise ArgumentError, "invalid value for type: #{type.inspect}"
|
37
55
|
end
|
38
|
-
@name, @setter
|
56
|
+
@name, @setter = name.to_sym, :"#{@bare_name}="
|
39
57
|
end
|
40
58
|
|
41
|
-
def
|
42
|
-
|
59
|
+
def define_accessors(type)
|
60
|
+
raise ArgumentError, "#{name}: method already defined or reserved" if type.method_defined?(name)
|
61
|
+
raise ArgumentError, "#{@setter}: method already defined or reserved" if type.method_defined?(@setter)
|
62
|
+
|
63
|
+
ivar = :"@#{@bare_name}"
|
64
|
+
raise ArgumentError, "#{ivar}: instance variable already defined" if type.instance_variable_defined?(ivar)
|
65
|
+
|
66
|
+
type.send(:define_method, name) do
|
67
|
+
instance_variable_get(ivar)
|
68
|
+
end
|
69
|
+
type.send(:define_method, @setter) do |value|
|
70
|
+
instance_variable_set(ivar, value)
|
71
|
+
end
|
43
72
|
end
|
44
73
|
|
45
|
-
def
|
46
|
-
set(nut,
|
74
|
+
def read(nut, reader)
|
75
|
+
set(nut, read_it(reader, get(nut)))
|
76
|
+
end
|
77
|
+
|
78
|
+
def write(nut, writer)
|
79
|
+
write_it(writer, get(nut))
|
80
|
+
end
|
81
|
+
|
82
|
+
def clear(nut)
|
83
|
+
set(nut, nil)
|
47
84
|
end
|
48
85
|
|
49
86
|
private
|
@@ -55,99 +92,117 @@ module Peanuts
|
|
55
92
|
nut.send(@setter, value)
|
56
93
|
end
|
57
94
|
|
58
|
-
def
|
95
|
+
def to_xml(value)
|
59
96
|
@converter ? @converter.to_xml(value) : value
|
60
97
|
end
|
61
98
|
|
62
|
-
def
|
99
|
+
def from_xml(text)
|
63
100
|
@converter ? @converter.from_xml(text) : text
|
64
101
|
end
|
65
102
|
|
66
|
-
def
|
67
|
-
|
68
|
-
nil
|
103
|
+
def write_node(writer, &block)
|
104
|
+
writer.write(node_type, local_name, namespace_uri, prefix, &block)
|
69
105
|
end
|
106
|
+
end
|
70
107
|
|
71
|
-
|
72
|
-
|
108
|
+
module SingleMapping
|
109
|
+
private
|
110
|
+
def read_it(reader, acc)
|
111
|
+
read_value(reader)
|
73
112
|
end
|
74
113
|
|
75
|
-
def value
|
76
|
-
|
114
|
+
def write_it(writer, value)
|
115
|
+
write_node(writer) {|w| write_value(w, value) } if value
|
77
116
|
end
|
117
|
+
end
|
78
118
|
|
79
|
-
|
80
|
-
|
119
|
+
module MultiMapping
|
120
|
+
def read_it(reader, acc)
|
121
|
+
(acc || []) << read_value(reader)
|
81
122
|
end
|
82
123
|
|
83
|
-
def
|
84
|
-
|
124
|
+
def write_it(writer, values)
|
125
|
+
values.each {|value| write_node(writer) {|w| write_value(w, value) } } if values
|
85
126
|
end
|
86
127
|
end
|
87
128
|
|
88
|
-
|
129
|
+
module ValueMapping
|
89
130
|
private
|
90
|
-
def
|
91
|
-
|
131
|
+
def read_value(reader)
|
132
|
+
from_xml(reader.value)
|
92
133
|
end
|
93
134
|
|
94
|
-
def
|
95
|
-
|
135
|
+
def write_value(writer, value)
|
136
|
+
writer.write_value(to_xml(value))
|
96
137
|
end
|
97
138
|
end
|
98
139
|
|
99
|
-
|
140
|
+
module ObjectMapping
|
100
141
|
private
|
101
|
-
def
|
102
|
-
|
142
|
+
def read_value(reader)
|
143
|
+
type.mapper.read(type.new, reader)
|
103
144
|
end
|
104
145
|
|
105
|
-
def
|
106
|
-
|
146
|
+
def write_value(writer, value)
|
147
|
+
type.mapper.write_children(value, writer)
|
107
148
|
end
|
108
149
|
end
|
109
150
|
|
151
|
+
class ElementValue < MemberMapping
|
152
|
+
include SingleMapping
|
153
|
+
include ValueMapping
|
154
|
+
|
155
|
+
node_type :element
|
156
|
+
end
|
157
|
+
|
158
|
+
class Element < MemberMapping
|
159
|
+
include SingleMapping
|
160
|
+
include ObjectMapping
|
161
|
+
|
162
|
+
node_type :element
|
163
|
+
end
|
164
|
+
|
110
165
|
class Attribute < MemberMapping
|
111
|
-
|
112
|
-
|
113
|
-
froxml(backend.attribute(node, xmlname, xmlns))
|
114
|
-
end
|
166
|
+
include SingleMapping
|
167
|
+
include ValueMapping
|
115
168
|
|
116
|
-
|
117
|
-
|
169
|
+
node_type :attribute
|
170
|
+
|
171
|
+
def initialize(name, type, options)
|
172
|
+
super
|
173
|
+
raise ArgumentError, 'a namespaced attribute must have namespace prefix' if namespace_uri && !prefix
|
118
174
|
end
|
119
175
|
end
|
120
176
|
|
121
177
|
class ElementValues < MemberMapping
|
122
|
-
|
123
|
-
|
124
|
-
each_element(node) {|x| yield froxml(value(x)) }
|
125
|
-
end
|
126
|
-
|
127
|
-
def getxml(node)
|
128
|
-
enum_for(:each_value, node).to_a
|
129
|
-
end
|
178
|
+
include MultiMapping
|
179
|
+
include ValueMapping
|
130
180
|
|
131
|
-
|
132
|
-
unless node
|
133
|
-
raise 'fuck'
|
134
|
-
end
|
135
|
-
values.each {|v| add_element(node, toxml(v)) } if values
|
136
|
-
end
|
181
|
+
node_type :element
|
137
182
|
end
|
138
183
|
|
139
184
|
class Elements < MemberMapping
|
140
|
-
|
141
|
-
|
142
|
-
|
185
|
+
include MultiMapping
|
186
|
+
include ObjectMapping
|
187
|
+
|
188
|
+
node_type :element
|
189
|
+
end
|
190
|
+
|
191
|
+
class ShallowElement < Element
|
192
|
+
def read(nut, reader)
|
193
|
+
type.mapper.read(nut, reader)
|
194
|
+
end
|
195
|
+
|
196
|
+
def write(nut, writer)
|
197
|
+
write_node(writer) {|w| type.mapper.write_children(nut, w) }
|
143
198
|
end
|
144
199
|
|
145
|
-
def
|
146
|
-
|
200
|
+
def clear(nut)
|
201
|
+
type.mapper.clear(nut)
|
147
202
|
end
|
148
203
|
|
149
|
-
def
|
150
|
-
|
204
|
+
def define_accessors(type)
|
205
|
+
self.type.mapper.define_accessors(type)
|
151
206
|
end
|
152
207
|
end
|
153
208
|
end
|
@@ -0,0 +1,231 @@
|
|
1
|
+
require 'libxml'
|
2
|
+
require 'forwardable'
|
3
|
+
require 'uri'
|
4
|
+
require 'stringio'
|
5
|
+
require 'peanuts/xml'
|
6
|
+
|
7
|
+
module Peanuts
|
8
|
+
module XML
|
9
|
+
module LibXML
|
10
|
+
def self.libxml_opt(options, default = {})
|
11
|
+
h = default.merge(options)
|
12
|
+
h.update(h.from_namespace!(:libxml))
|
13
|
+
end
|
14
|
+
|
15
|
+
class Writer < Peanuts::XML::Writer
|
16
|
+
DEFAULT_OPTIONS = {}
|
17
|
+
|
18
|
+
def initialize(dest, options = {})
|
19
|
+
@dest = case dest
|
20
|
+
when :string
|
21
|
+
''
|
22
|
+
when :document
|
23
|
+
::LibXML::XML::Document.new
|
24
|
+
when ::LibXML::XML::Document
|
25
|
+
dest
|
26
|
+
else
|
27
|
+
raise ArgumentError, "unsupported destination #{dest.inspect}" unless dest.respond_to?(:<<)
|
28
|
+
dest
|
29
|
+
end
|
30
|
+
@options = LibXML.libxml_opt(options, DEFAULT_OPTIONS)
|
31
|
+
end
|
32
|
+
|
33
|
+
def result
|
34
|
+
@dest
|
35
|
+
end
|
36
|
+
|
37
|
+
def write_value(value)
|
38
|
+
case @node
|
39
|
+
when ::LibXML::XML::Attr
|
40
|
+
@node.value = value || ''
|
41
|
+
else
|
42
|
+
@node.content = value || ''
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def write_namespaces(namespaces)
|
47
|
+
for prefix, uri in namespaces
|
48
|
+
mkns(@node, uri, prefix)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def write(node_type, local_name = nil, namespace_uri = nil, prefix = nil)
|
53
|
+
case node_type
|
54
|
+
when :element
|
55
|
+
@node = ::LibXML::XML::Node.new(local_name)
|
56
|
+
@parent << @node if @parent
|
57
|
+
if namespace_uri || prefix
|
58
|
+
unless namespace_uri
|
59
|
+
defns = @node.namespaces.default
|
60
|
+
namespace_uri = defns ? defns.href : ''
|
61
|
+
end
|
62
|
+
@node.namespaces.namespace = mkns(@node, namespace_uri, prefix)
|
63
|
+
end
|
64
|
+
when :attribute
|
65
|
+
@node = ::LibXML::XML::Attr.new(@parent, local_name, '', namespace_uri && mkns(@parent, namespace_uri, prefix))
|
66
|
+
else
|
67
|
+
raise "unsupported node type #{node_type.inspect}"
|
68
|
+
end
|
69
|
+
|
70
|
+
exparent, @parent = @parent, @node
|
71
|
+
|
72
|
+
yield self
|
73
|
+
|
74
|
+
if exparent.nil?
|
75
|
+
case @dest
|
76
|
+
when ::LibXML::XML::Document
|
77
|
+
@dest.root = @parent
|
78
|
+
else
|
79
|
+
@dest << @parent.to_s(@options)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
@parent = exparent
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
def mkns(node, namespace_uri, prefix)
|
88
|
+
prefix = nil if prefix && (prefix = prefix.to_s).empty?
|
89
|
+
newns = proc { ::LibXML::XML::Namespace.new(node, prefix, namespace_uri) }
|
90
|
+
node.namespaces.find(newns) {|ns| ns.prefix == prefix && ns.href == namespace_uri }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
class Reader < Peanuts::XML::Reader
|
95
|
+
extend Forwardable
|
96
|
+
|
97
|
+
SCHEMAS = {:xml_schema => :schema, :relax_ng => :relax_ng}
|
98
|
+
|
99
|
+
RD = ::LibXML::XML::Reader
|
100
|
+
|
101
|
+
NODE_TYPES = [
|
102
|
+
nil,
|
103
|
+
:element,
|
104
|
+
:attribute,
|
105
|
+
:text,
|
106
|
+
:cdata,
|
107
|
+
:entity_reference,
|
108
|
+
:entity,
|
109
|
+
:processing_instruction,
|
110
|
+
:comment,
|
111
|
+
:document,
|
112
|
+
:document_type,
|
113
|
+
:document_fragment,
|
114
|
+
:notation,
|
115
|
+
:whitespace,
|
116
|
+
:significant_whitespace,
|
117
|
+
:end_element,
|
118
|
+
:end_entity,
|
119
|
+
:xml_declaration
|
120
|
+
].freeze
|
121
|
+
|
122
|
+
DEFAULT_OPTIONS = {}
|
123
|
+
|
124
|
+
def initialize(source, options = {})
|
125
|
+
super()
|
126
|
+
options = options.dup
|
127
|
+
@schema = options.delete(:schema)
|
128
|
+
options = LibXML.libxml_opt(options, DEFAULT_OPTIONS)
|
129
|
+
@reader = case source
|
130
|
+
when String
|
131
|
+
RD.string(source.to_s, options)
|
132
|
+
when URI
|
133
|
+
RD.file(source.to_s, options)
|
134
|
+
when ::LibXML::XML::Document
|
135
|
+
RD.document(source)
|
136
|
+
else
|
137
|
+
raise "unsupported source #{source.inspect}" unless source.respond_to?(:read)
|
138
|
+
RD.io(source, options)
|
139
|
+
end
|
140
|
+
@reader.send("#{SCHEMAS[schema.type]}_validate", schema.schema) if @schema
|
141
|
+
end
|
142
|
+
|
143
|
+
def close
|
144
|
+
@reader.close
|
145
|
+
end
|
146
|
+
|
147
|
+
def_delegators :@reader, :name, :local_name, :namespace_uri, :depth
|
148
|
+
|
149
|
+
def node_type
|
150
|
+
NODE_TYPES[@reader.node_type]
|
151
|
+
end
|
152
|
+
|
153
|
+
def value
|
154
|
+
case @reader.node_type
|
155
|
+
when RD::TYPE_ELEMENT
|
156
|
+
@reader.read_string
|
157
|
+
else
|
158
|
+
@reader.has_value? ? @reader.value : nil
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
def each
|
163
|
+
depth = self.depth
|
164
|
+
if read
|
165
|
+
while self.depth > depth
|
166
|
+
yield self
|
167
|
+
break unless next_sibling
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def find_element
|
173
|
+
until @reader.node_type == RD::TYPE_ELEMENT
|
174
|
+
return nil unless read
|
175
|
+
end
|
176
|
+
self
|
177
|
+
end
|
178
|
+
|
179
|
+
private
|
180
|
+
def read
|
181
|
+
case @reader.node_type
|
182
|
+
when RD::TYPE_ATTRIBUTE
|
183
|
+
@reader.move_to_next_attribute > 0 || @reader.read
|
184
|
+
else
|
185
|
+
@reader.move_to_first_attribute > 0 || @reader.read
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def next_sibling
|
190
|
+
case @reader.node_type
|
191
|
+
when RD::TYPE_ATTRIBUTE
|
192
|
+
@reader.move_to_next_attribute > 0 || @reader.read
|
193
|
+
else
|
194
|
+
@reader.next > 0
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def self.schema(schema_type, source)
|
200
|
+
schema_class = case schema_type
|
201
|
+
when :xml_schema
|
202
|
+
::LibXML::XML::Schema
|
203
|
+
when :relax_ng
|
204
|
+
::LibXML::XML::RelaxNG
|
205
|
+
else
|
206
|
+
raise ArgumentError, "unrecognized schema type #{schema_type}"
|
207
|
+
end
|
208
|
+
schema = case source
|
209
|
+
when IO
|
210
|
+
schema_class.string(source.read)
|
211
|
+
when URI
|
212
|
+
schema_class.new(source.to_s)
|
213
|
+
when ::LibXML::XML::Document
|
214
|
+
schema_class.document(source)
|
215
|
+
else
|
216
|
+
schema_class.string(source)
|
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
|
data/lib/peanuts/xml.rb
ADDED
@@ -0,0 +1,60 @@
|
|
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)
|
28
|
+
default.schema(schema_type, source)
|
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)
|
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
|
+
end
|
50
|
+
|
51
|
+
class Writer
|
52
|
+
def self.new(*args, &block)
|
53
|
+
cls = self == Writer ? XML.default::Writer : self
|
54
|
+
obj = cls.allocate
|
55
|
+
obj.send(:initialize, *args, &block)
|
56
|
+
obj
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|