peanuts 1.0 → 2.0.7

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,49 +1,86 @@
1
- require 'enumerator'
2
- require 'peanuts/backend'
1
+ require 'forwardable'
3
2
  require 'peanuts/converters'
4
3
 
5
4
  module Peanuts
6
- module Mappings
7
- class Mapping
8
- attr_reader :xmlname, :xmlns, :options
5
+ class Mapping
6
+ attr_reader :local_name, :namespace_uri, :prefix, :options
9
7
 
10
- def initialize(xmlname, options)
11
- @xmlname, @xmlns, @options = xmlname.to_s, options.delete(:xmlns), options
12
- end
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
- def initialize(xmlname, options = {})
17
- super
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
- super(options.delete(:xmlname) || name, options)
28
- case type
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
- @converter = Converter.create!(:list, options)
33
- when Class
34
- options[:object_type] = type
52
+ Converter.create!(:list, options)
35
53
  else
36
- @converter = Converter.create!(type, options)
54
+ raise ArgumentError, "invalid value for type: #{type.inspect}"
37
55
  end
38
- @name, @setter, @type = name.to_sym, :"#{name}=", type
56
+ @name, @setter = name.to_sym, :"#{@bare_name}="
39
57
  end
40
58
 
41
- def to_xml(nut, node)
42
- setxml(node, get(nut))
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 from_xml(nut, node)
46
- set(nut, getxml(node))
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 toxml(value)
95
+ def to_xml(value)
59
96
  @converter ? @converter.to_xml(value) : value
60
97
  end
61
98
 
62
- def froxml(text)
99
+ def from_xml(text)
63
100
  @converter ? @converter.from_xml(text) : text
64
101
  end
65
102
 
66
- def each_element(node, &block)
67
- node && backend.each_element(node, xmlname, xmlns, &block)
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
- def add_element(node, value = nil)
72
- backend.add_element(node, xmlname, xmlns, value)
108
+ module SingleMapping
109
+ private
110
+ def read_it(reader, acc)
111
+ read_value(reader)
73
112
  end
74
113
 
75
- def value(node)
76
- backend.value(node)
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
- def parse(node)
80
- type.parse_node(type.new, node)
119
+ module MultiMapping
120
+ def read_it(reader, acc)
121
+ (acc || []) << read_value(reader)
81
122
  end
82
123
 
83
- def build(node, nut, dest_node)
84
- nut && type.build_node(nut, dest_node)
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
- class ElementValue < MemberMapping
129
+ module ValueMapping
89
130
  private
90
- def getxml(node)
91
- each_element(node) {|e| return froxml(value(e)) }
131
+ def read_value(reader)
132
+ from_xml(reader.value)
92
133
  end
93
134
 
94
- def setxml(node, value)
95
- add_element(node, toxml(value))
135
+ def write_value(writer, value)
136
+ writer.write_value(to_xml(value))
96
137
  end
97
138
  end
98
139
 
99
- class Element < MemberMapping
140
+ module ObjectMapping
100
141
  private
101
- def getxml(node)
102
- each_element(node) {|e| return parse(e) }
142
+ def read_value(reader)
143
+ type.mapper.read(type.new, reader)
103
144
  end
104
145
 
105
- def setxml(node, value)
106
- build(node, value, add_element(node))
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
- private
112
- def getxml(node)
113
- froxml(backend.attribute(node, xmlname, xmlns))
114
- end
166
+ include SingleMapping
167
+ include ValueMapping
115
168
 
116
- def setxml(node, value)
117
- backend.set_attribute(node, xmlname, xmlns, toxml(value))
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
- private
123
- def each_value(node)
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
- def setxml(node, values)
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
- private
141
- def each_object(node)
142
- each_element(node) {|e| yield parse(e) }
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 getxml(node)
146
- enum_for(:each_object, node)
200
+ def clear(nut)
201
+ type.mapper.clear(nut)
147
202
  end
148
203
 
149
- def setxml(node, elements)
150
- elements.each {|e| build(node, e, add_element(node)) } if elements
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
@@ -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
data/lib/peanuts.rb CHANGED
@@ -1 +1,7 @@
1
- require 'peanuts/nuts'
1
+ require 'peanuts/mappable'
2
+
3
+ module Peanuts #:nodoc:
4
+ def self.included(other)
5
+ other.send(:include, MappableObject)
6
+ end
7
+ end