pipa-xmlnuts 0.0.2 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/xmlnuts/backend.rb +38 -0
- data/lib/xmlnuts/converters.rb +11 -9
- data/lib/xmlnuts/mappings.rb +101 -100
- data/lib/xmlnuts/nuts.rb +175 -36
- data/lib/xmlnuts/rexml.rb +94 -0
- data/lib/xmlnuts.rb +47 -0
- data/test/parsing_test.rb +42 -35
- metadata +4 -2
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'monitor'
|
2
|
+
|
3
|
+
module XmlNuts
|
4
|
+
module XmlBackend
|
5
|
+
extend MonitorMixin
|
6
|
+
|
7
|
+
autoload :REXMLBackend, 'xmlnuts/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[:xmlnuts_xml_backend] || default
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.current=(backend)
|
30
|
+
Thread.current[:xmlnuts_xml_backend] = backend
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def backend #:doc:
|
35
|
+
XmlBackend.current
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/xmlnuts/converters.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
1
3
|
module XmlNuts
|
2
|
-
module
|
4
|
+
module Converter
|
3
5
|
def self.lookup(type)
|
4
6
|
lookup!(type)
|
5
7
|
rescue ArgumentError
|
@@ -22,7 +24,7 @@ module XmlNuts
|
|
22
24
|
lookup!(type).new(options)
|
23
25
|
end
|
24
26
|
|
25
|
-
class Convert_string
|
27
|
+
class Convert_string
|
26
28
|
def initialize(options)
|
27
29
|
@whitespace = options[:whitespace] || :trim
|
28
30
|
end
|
@@ -41,7 +43,7 @@ module XmlNuts
|
|
41
43
|
end
|
42
44
|
end
|
43
45
|
|
44
|
-
class Convert_boolean < Convert_string
|
46
|
+
class Convert_boolean < Convert_string
|
45
47
|
def initialize(options)
|
46
48
|
super
|
47
49
|
@format = options[:format] || :truefalse
|
@@ -68,7 +70,7 @@ module XmlNuts
|
|
68
70
|
end
|
69
71
|
end
|
70
72
|
|
71
|
-
class Convert_integer < Convert_string
|
73
|
+
class Convert_integer < Convert_string
|
72
74
|
def initialize(options)
|
73
75
|
super
|
74
76
|
end
|
@@ -82,7 +84,7 @@ module XmlNuts
|
|
82
84
|
end
|
83
85
|
end
|
84
86
|
|
85
|
-
class Convert_datetime < Convert_string
|
87
|
+
class Convert_datetime < Convert_string
|
86
88
|
def initialize(options)
|
87
89
|
super
|
88
90
|
@fraction_digits = options[:fraction_digits] || 0
|
@@ -92,19 +94,19 @@ module XmlNuts
|
|
92
94
|
time && time.xmlschema(@fraction_digits)
|
93
95
|
end
|
94
96
|
|
95
|
-
def
|
97
|
+
def from_xml(string)
|
96
98
|
string && Time.parse(super(string, options))
|
97
99
|
end
|
98
100
|
end
|
99
101
|
|
100
|
-
class Convert_list
|
102
|
+
class Convert_list
|
101
103
|
def initialize(options)
|
102
104
|
@item_type = options[:item_type] || :string
|
103
|
-
@item_converter =
|
105
|
+
@item_converter = Converter.create!(@item_type, options)
|
104
106
|
end
|
105
107
|
|
106
108
|
def to_xml(array)
|
107
|
-
array.map {|x| @item_converter.to_xml(x) } * ' '
|
109
|
+
array && array.map {|x| @item_converter.to_xml(x) } * ' '
|
108
110
|
end
|
109
111
|
|
110
112
|
def from_xml(string)
|
data/lib/xmlnuts/mappings.rb
CHANGED
@@ -1,137 +1,138 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
def initialize(name, type, options)
|
6
|
-
@name, @xmlname, @type, @options = name.to_sym, (options.delete(:xmlname) || name).to_s, type, options
|
7
|
-
@setter = :"#{name}="
|
8
|
-
@converter = Converters.create(type, options)
|
9
|
-
end
|
1
|
+
require 'enumerator'
|
2
|
+
require 'xmlnuts/backend'
|
3
|
+
require 'xmlnuts/converters'
|
10
4
|
|
11
|
-
|
12
|
-
|
13
|
-
|
5
|
+
module XmlNuts
|
6
|
+
module Mappings
|
7
|
+
class Mapping
|
8
|
+
attr_reader :xmlname, :xmlns, :options
|
14
9
|
|
15
|
-
|
16
|
-
|
10
|
+
def initialize(xmlname, options)
|
11
|
+
@xmlname, @xmlns, @options = xmlname.to_s, options.delete(:xmlns), options
|
12
|
+
end
|
17
13
|
end
|
18
14
|
|
19
|
-
|
20
|
-
|
21
|
-
|
15
|
+
class Root < Mapping
|
16
|
+
def initialize(xmlname, options = {})
|
17
|
+
super
|
18
|
+
end
|
22
19
|
end
|
23
20
|
|
24
|
-
|
25
|
-
|
26
|
-
|
21
|
+
class MemberMapping < Mapping
|
22
|
+
include XmlBackend
|
23
|
+
|
24
|
+
attr_reader :name, :type, :converter
|
25
|
+
|
26
|
+
def initialize(name, type, options)
|
27
|
+
super(options.delete(:xmlname) || name, options)
|
28
|
+
case type
|
29
|
+
when Array
|
30
|
+
raise ArgumentError, "invalid value for type: #{type}" if type.length != 1
|
31
|
+
options[:item_type] = type.first
|
32
|
+
@converter = Converter.create!(:list, options)
|
33
|
+
when Class
|
34
|
+
options[:object_type] = type
|
35
|
+
else
|
36
|
+
@converter = Converter.create!(type, options)
|
37
|
+
end
|
38
|
+
@name, @setter, @type = name.to_sym, :"#{name}=", type
|
39
|
+
end
|
27
40
|
|
28
|
-
|
29
|
-
|
30
|
-
|
41
|
+
def to_xml(nut, node)
|
42
|
+
setxml(node, get(nut))
|
43
|
+
end
|
31
44
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
45
|
+
def from_xml(nut, node)
|
46
|
+
set(nut, getxml(node))
|
47
|
+
end
|
36
48
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
raise ArgumentError, "invalid value for type: #{type}" if type.length != 1
|
41
|
-
type, options[:item_type] = :list, type.first
|
49
|
+
private
|
50
|
+
def get(nut)
|
51
|
+
nut.send(@name)
|
42
52
|
end
|
43
|
-
super
|
44
|
-
raise ArgumentError, "converter absent for type #{type.inspect}" unless converter
|
45
|
-
end
|
46
53
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
end
|
54
|
+
def set(nut, value)
|
55
|
+
nut.send(@setter, value)
|
56
|
+
end
|
51
57
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
end
|
58
|
+
def toxml(value)
|
59
|
+
@converter ? @converter.to_xml(value) : value
|
60
|
+
end
|
56
61
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
(e = node.elements[xmlname]) && e.text
|
62
|
+
def froxml(text)
|
63
|
+
@converter ? @converter.from_xml(text) : text
|
64
|
+
end
|
61
65
|
end
|
62
66
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
+
module NestedMixin #:nodoc:
|
68
|
+
private
|
69
|
+
def parse(node)
|
70
|
+
backend.each_element_with_value(node, xmlname, xmlns) {|el, txt| return type.parse_node(type.new, el) }
|
71
|
+
nil
|
72
|
+
end
|
67
73
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
node.attributes[xmlname]
|
74
|
+
def build(node, nut)
|
75
|
+
nut && type.build_node(nut, backend.add_element(node, xmlname, xmlns, nil))
|
76
|
+
end
|
72
77
|
end
|
73
78
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
79
|
+
module ElementsMixin #:nodoc:
|
80
|
+
private
|
81
|
+
def getxml(node)
|
82
|
+
node && backend.enum_for(:each_element_with_value, node, xmlname, xmlns).map {|el, v| froxml(v) }
|
83
|
+
end
|
78
84
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
nested_nuts && nested_nuts.map {|x| super(x) }
|
83
|
-
end
|
85
|
+
def setxml(node, values)
|
86
|
+
values.each {|x| toxml(node, x) } if values
|
87
|
+
end
|
84
88
|
|
85
|
-
|
86
|
-
|
89
|
+
def toxml(value, node)
|
90
|
+
super(value)
|
91
|
+
end
|
87
92
|
end
|
88
|
-
end
|
89
93
|
|
90
|
-
|
91
|
-
|
94
|
+
class ElementValue < MemberMapping
|
95
|
+
private
|
96
|
+
def getxml(node)
|
97
|
+
backend.each_element_with_value(node, xmlname, xmlns) {|e, v| return froxml(v) }
|
98
|
+
nil
|
99
|
+
end
|
92
100
|
|
93
|
-
|
94
|
-
|
95
|
-
|
101
|
+
def setxml(node, value)
|
102
|
+
backend.add_element(node, xmlname, xmlns, toxml(value))
|
103
|
+
end
|
96
104
|
end
|
97
105
|
|
98
|
-
|
99
|
-
|
100
|
-
end
|
101
|
-
end
|
106
|
+
class Element < MemberMapping
|
107
|
+
include NestedMixin
|
102
108
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
type.build_node(nested_nut, Element.new(xmlname))
|
109
|
+
private
|
110
|
+
alias getxml parse
|
111
|
+
alias setxml build
|
107
112
|
end
|
108
113
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
114
|
+
class Attribute < MemberMapping
|
115
|
+
private
|
116
|
+
def getxml(node)
|
117
|
+
froxml(backend.attribute(node, xmlname, xmlns))
|
118
|
+
end
|
113
119
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
node.elements[xmlname]
|
120
|
+
def setxml(node, value)
|
121
|
+
backend.set_attribute(node, xmlname, xmlns, toxml(value))
|
122
|
+
end
|
118
123
|
end
|
119
124
|
|
120
|
-
|
121
|
-
|
125
|
+
class ElementValues < MemberMapping
|
126
|
+
include ElementsMixin
|
122
127
|
end
|
123
|
-
end
|
124
128
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
private
|
129
|
-
def getxml(node) #:doc:
|
130
|
-
node.get_elements(xmlname)
|
131
|
-
end
|
129
|
+
class Elements < MemberMapping
|
130
|
+
include NestedMixin
|
131
|
+
include ElementsMixin
|
132
132
|
|
133
|
-
|
134
|
-
|
133
|
+
private
|
134
|
+
alias froxml parse
|
135
|
+
alias toxml build
|
135
136
|
end
|
136
137
|
end
|
137
138
|
end
|
data/lib/xmlnuts/nuts.rb
CHANGED
@@ -1,72 +1,211 @@
|
|
1
|
-
require 'rexml/document'
|
2
|
-
require 'time'
|
3
|
-
require 'xmlnuts/converters'
|
4
1
|
require 'xmlnuts/mappings'
|
5
2
|
|
6
3
|
module XmlNuts #:nodoc:
|
4
|
+
# See also +ClassMethods+
|
7
5
|
module Nut
|
6
|
+
include XmlBackend
|
7
|
+
|
8
8
|
def self.included(other) #:nodoc:
|
9
9
|
other.extend(ClassMethods)
|
10
10
|
end
|
11
11
|
|
12
|
+
# See also Nut
|
12
13
|
module ClassMethods
|
14
|
+
include XmlBackend
|
15
|
+
include Mappings
|
16
|
+
|
17
|
+
# namespaces(hash) -> Hash
|
18
|
+
# namespaces -> Hash
|
19
|
+
#
|
20
|
+
# Updates and returns class-level prefix mappings.
|
21
|
+
# When given a hash of mappings merges it over current.
|
22
|
+
# When called withot arguments simply returns current mappings.
|
23
|
+
#
|
24
|
+
# === Example:
|
25
|
+
# class Cat
|
26
|
+
# include XmlNuts::Nut
|
27
|
+
# namespaces :lol => 'urn:lol', ...
|
28
|
+
# ...
|
29
|
+
# end
|
30
|
+
def namespaces(mappings = nil)
|
31
|
+
@namespaces ||= {}
|
32
|
+
mappings ? @namespaces.update(mappings) : @namespaces
|
33
|
+
end
|
34
|
+
|
35
|
+
# root(xmlname[, :xmlns => ...]) -> Mappings::Root
|
36
|
+
# root -> Mappings::Root
|
37
|
+
#
|
38
|
+
# Defines element name.
|
39
|
+
# TODO: moar details
|
40
|
+
#
|
41
|
+
# === Arguments
|
42
|
+
# +xmlname+:: Element name
|
43
|
+
# +options+:: +:xmlns+ => Element namespace
|
44
|
+
#
|
45
|
+
# === Example:
|
46
|
+
# class Cat
|
47
|
+
# include XmlNuts::Nut
|
48
|
+
# ...
|
49
|
+
# root :kitteh, :xmlns => 'urn:lol'
|
50
|
+
# ...
|
51
|
+
# end
|
52
|
+
def root(xmlname = nil, options = {})
|
53
|
+
@root = Root.new(xmlname, prepare_options(options)) if xmlname
|
54
|
+
@root ||= Root.new('root')
|
55
|
+
end
|
56
|
+
|
57
|
+
# element(name, [type[, options]]) -> Mappings::Element or Mappings::ElementValue
|
58
|
+
#
|
59
|
+
# Defines single-element mapping.
|
60
|
+
#
|
61
|
+
# === Arguments
|
62
|
+
# +name+:: Accessor name
|
63
|
+
# +type+:: Element type. +:string+ assumed if omitted (see +Converter+).
|
64
|
+
# +options+:: +:xmlname+, +:xmlns+, converter options (see +Converter+).
|
65
|
+
#
|
66
|
+
# === Example:
|
67
|
+
# class Cat
|
68
|
+
# include XmlNuts::Nut
|
69
|
+
# ...
|
70
|
+
# element :name, :string, :whitespace => :collapse
|
71
|
+
# element :cheeseburger, Cheeseburger, :xmlname => :cheezburger
|
72
|
+
# ...
|
73
|
+
# end
|
13
74
|
def element(name, type = :string, options = {})
|
14
|
-
|
15
|
-
|
75
|
+
define_accessor name
|
76
|
+
(mappings << (type.is_a?(Class) ? Element : ElementValue).new(name, type, prepare_options(options))).last
|
16
77
|
end
|
17
78
|
|
79
|
+
# elements(name, [type[, options]]) -> Mappings::Element or Mappings::ElementValue
|
80
|
+
#
|
81
|
+
# Defines multiple elements mapping.
|
82
|
+
#
|
83
|
+
# === Arguments
|
84
|
+
# +name+:: Accessor name
|
85
|
+
# +type+:: Element type. +:string+ assumed if omitted (see +Converter+).
|
86
|
+
# +options+:: +:xmlname+, +:xmlns+, converter options (see +Converter+).
|
87
|
+
#
|
88
|
+
# === Example:
|
89
|
+
# class RichCat
|
90
|
+
# include XmlNuts::Nut
|
91
|
+
# ...
|
92
|
+
# elements :ration, :string, :whitespace => :collapse
|
93
|
+
# elements :cheeseburgers, Cheeseburger, :xmlname => :cheezburgers
|
94
|
+
# ...
|
95
|
+
# end
|
18
96
|
def elements(name, type = :string, options = {})
|
19
|
-
|
20
|
-
|
97
|
+
define_accessor name
|
98
|
+
(mappings << (type.is_a?(Class) ? Elements : ElementValues).new(name, type, prepare_options(options))).last
|
21
99
|
end
|
22
100
|
|
101
|
+
# attribute(name, [type[, options]]) -> Mappings::Attribute or Mappings::AttributeValue
|
102
|
+
#
|
103
|
+
# Defines attribute mapping.
|
104
|
+
#
|
105
|
+
# === Arguments
|
106
|
+
# +name+:: Accessor name
|
107
|
+
# +type+:: Element type. +:string+ assumed if omitted (see +Converter+).
|
108
|
+
# +options+:: +:xmlname+, +:xmlns+, converter options (see +Converter+).
|
109
|
+
#
|
110
|
+
# === Example:
|
111
|
+
# class Cat
|
112
|
+
# include XmlNuts::Nut
|
113
|
+
# ...
|
114
|
+
# element :name, :string, :whitespace => :collapse
|
115
|
+
# element :cheeseburger, Cheeseburger, :xmlname => :cheezburger
|
116
|
+
# ...
|
117
|
+
# end
|
23
118
|
def attribute(name, type = :string, options = {})
|
24
|
-
|
25
|
-
|
119
|
+
define_accessor name
|
120
|
+
mappings << Attribute.new(name, type, prepare_options(options))
|
26
121
|
end
|
27
122
|
|
123
|
+
# mappings -> Array
|
124
|
+
#
|
125
|
+
# Returns all previously defined XmlNuts mappings on a class.
|
28
126
|
def mappings
|
29
127
|
@mappings ||= []
|
30
128
|
end
|
31
129
|
|
32
|
-
def
|
33
|
-
|
34
|
-
when nil
|
35
|
-
destination = REXML::Document.new
|
36
|
-
e = destination.add_element('root')
|
37
|
-
build_node(nut, e)
|
38
|
-
when REXML::Node
|
39
|
-
build_node(nut, destination)
|
40
|
-
end
|
41
|
-
destination
|
130
|
+
def parse(source, options = {})
|
131
|
+
backend.parse(source, options) {|node| new.parse_node(node) }
|
42
132
|
end
|
43
133
|
|
44
|
-
def
|
45
|
-
|
46
|
-
when nil
|
47
|
-
nil
|
48
|
-
when REXML::Node
|
49
|
-
parse_node(new, source)
|
50
|
-
when String
|
51
|
-
doc = REXML::Document.new(source)
|
52
|
-
(root = doc.root) ? parse_node(new, root) : nil
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
def build_node(nut, node)
|
134
|
+
def build_node(nut, node) #:nodoc:
|
135
|
+
backend.add_namespaces(node, namespaces)
|
57
136
|
callem(:to_xml, nut, node)
|
58
137
|
node
|
59
138
|
end
|
60
139
|
|
61
|
-
def parse_node(nut, node)
|
62
|
-
callem(:from_xml
|
140
|
+
def parse_node(nut, node) #:nodoc:
|
141
|
+
callem(:from_xml, nut, node)
|
63
142
|
nut
|
64
143
|
end
|
65
144
|
|
66
145
|
private
|
67
|
-
def
|
68
|
-
|
146
|
+
def prepare_options(options)
|
147
|
+
ns = options[:xmlns]
|
148
|
+
if ns.is_a?(Symbol)
|
149
|
+
raise ArgumentError, "undefined prefix: #{ns}" unless options[:xmlns] = namespaces[ns]
|
150
|
+
end
|
151
|
+
options
|
69
152
|
end
|
153
|
+
|
154
|
+
def define_accessor(name)
|
155
|
+
if method_defined?(name) || method_defined?("#{name}=")
|
156
|
+
raise ArgumentError, "#{name}: name already defined or reserved"
|
157
|
+
end
|
158
|
+
attr_accessor name
|
159
|
+
end
|
160
|
+
|
161
|
+
def callem(method, *args)
|
162
|
+
mappings.each {|m| m.send(method, *args) }
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def parse(source, options = {})
|
167
|
+
backend.parse(source, options) {|node| parse_node(node) }
|
168
|
+
end
|
169
|
+
|
170
|
+
# build([options]) -> root element or string
|
171
|
+
# build([options]) -> root element or string
|
172
|
+
# build(destination[, options]) -> destination
|
173
|
+
#
|
174
|
+
# Defines attribute mapping.
|
175
|
+
#
|
176
|
+
# === Arguments
|
177
|
+
# +destination+:: Can be given a symbol a backend-specific object,
|
178
|
+
# an instance of String or IO classes.
|
179
|
+
# - +:string+ -> will return an XML string.
|
180
|
+
# - +:document+ -> will return a backend specific document object.
|
181
|
+
# - +:object+ -> will return a backend specific object. New document will be created.
|
182
|
+
# - an instance of +String+: the contents of the string will be replaced with
|
183
|
+
# the generated XML.
|
184
|
+
# - an instance of +IO+: the IO will be written to.
|
185
|
+
# +options+:: Backend-specific options
|
186
|
+
#
|
187
|
+
# === Example:
|
188
|
+
# cat = Cat.new
|
189
|
+
# cat.name = 'Pussy'
|
190
|
+
# puts cat.build
|
191
|
+
# ...
|
192
|
+
# doc = REXML::Document.new
|
193
|
+
# cat.build(doc)
|
194
|
+
# puts doc.to_s
|
195
|
+
def build(result = :string, options = {})
|
196
|
+
options, result = result, :string if result.is_a?(Hash)
|
197
|
+
root = self.class.root
|
198
|
+
options[:xmlname] ||= root.xmlname
|
199
|
+
options[:xmlns_prefix] = self.class.namespaces.invert[options[:xmlns] ||= root.xmlns]
|
200
|
+
backend.build(result, options) {|node| build_node(node) }
|
201
|
+
end
|
202
|
+
|
203
|
+
def parse_node(node) #:nodoc:
|
204
|
+
self.class.parse_node(self, node)
|
205
|
+
end
|
206
|
+
|
207
|
+
def build_node(node) #:nodoc:
|
208
|
+
self.class.build_node(self, node)
|
70
209
|
end
|
71
210
|
end
|
72
211
|
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'rexml/document'
|
2
|
+
require 'xmlnuts/backend'
|
3
|
+
|
4
|
+
class XmlNuts::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 destination'
|
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_with_value(context, name, ns)
|
61
|
+
ns = context.namespace unless ns
|
62
|
+
REXML::XPath.each(context, "ns:#{name}", 'ns' => ns) {|el| yield el, el.text }
|
63
|
+
end
|
64
|
+
|
65
|
+
def attribute(context, name, ns)
|
66
|
+
name, ns = to_prefixed_name(context, name, ns, true)
|
67
|
+
context.attributes[name]
|
68
|
+
end
|
69
|
+
|
70
|
+
def set_attribute(context, name, ns, text)
|
71
|
+
name, ns = to_prefixed_name(context, name, ns, true)
|
72
|
+
context.add_attribute(name, text)
|
73
|
+
end
|
74
|
+
|
75
|
+
def add_element(context, name, ns, text)
|
76
|
+
name, ns = to_prefixed_name(context, name, ns, false)
|
77
|
+
elem = context.add_element(name)
|
78
|
+
elem.add_namespace(ns) if ns
|
79
|
+
elem.text = text if text
|
80
|
+
elem
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
def to_prefixed_name(context, name, ns, prefix_required)
|
85
|
+
if ns
|
86
|
+
if prefix = context.namespaces.invert[ns]
|
87
|
+
name, ns = "#{prefix}:#{name}", nil
|
88
|
+
else
|
89
|
+
raise ArgumentError, "no prefix defined for #{ns}" if prefix_required
|
90
|
+
end
|
91
|
+
end
|
92
|
+
return name, ns
|
93
|
+
end
|
94
|
+
end
|
data/lib/xmlnuts.rb
CHANGED
@@ -1 +1,48 @@
|
|
1
1
|
require 'xmlnuts/nuts'
|
2
|
+
|
3
|
+
#class Cheezburger
|
4
|
+
# include XmlNuts::Nut
|
5
|
+
#
|
6
|
+
# attribute :weight, :integer
|
7
|
+
#end
|
8
|
+
#
|
9
|
+
#class Pet
|
10
|
+
# include XmlNuts::Nut
|
11
|
+
#
|
12
|
+
# namespaces :pipi => 'a', :piz => 'b'
|
13
|
+
#
|
14
|
+
# root 'anus', :xmlns => :piz
|
15
|
+
#
|
16
|
+
# element :eats, [:string], :xmlname => :ration, :xmlns => 'a'
|
17
|
+
# element :species, :string, :whitespace => :collapse
|
18
|
+
# elements :paws, :string, :xmlname => :paw
|
19
|
+
#
|
20
|
+
# attribute :has_tail, :boolean, :xmlname => 'has-tail', :xmlns => 'b'
|
21
|
+
# attribute :height, :integer
|
22
|
+
#
|
23
|
+
# element :cheezburger, Cheezburger
|
24
|
+
#end
|
25
|
+
#
|
26
|
+
#xml_fragment = <<-EOS
|
27
|
+
# <mypet xmlns='lol' xmlns:pizda="b" height=' 12 ' pizda:has-tail=' yes '>
|
28
|
+
# <species>
|
29
|
+
#silly
|
30
|
+
# mouse
|
31
|
+
# </species>
|
32
|
+
# <pi:ration xmlns:pi="a">
|
33
|
+
# tigers
|
34
|
+
# lions
|
35
|
+
# </pi:ration>
|
36
|
+
# <paw> one</paw>
|
37
|
+
# <paw> two </paw>
|
38
|
+
# <paw>three</paw>
|
39
|
+
# <paw>four</paw>
|
40
|
+
# <cheezburger weight='2'>
|
41
|
+
# </cheezburger>
|
42
|
+
# <cub age='4'>
|
43
|
+
# </cub>
|
44
|
+
# </mypet>
|
45
|
+
#EOS
|
46
|
+
#pet = Pet.parse(xml_fragment)
|
47
|
+
#
|
48
|
+
#puts pet.build
|
data/test/parsing_test.rb
CHANGED
@@ -10,30 +10,35 @@ class Cheezburger
|
|
10
10
|
attribute :weight, :integer
|
11
11
|
end
|
12
12
|
|
13
|
-
class
|
13
|
+
class Cat
|
14
14
|
include XmlNuts::Nut
|
15
15
|
|
16
|
-
|
17
|
-
|
16
|
+
namespaces :lol => 'urn:lol', :p => 'b'
|
17
|
+
|
18
|
+
root 'kitteh', :xmlns => 'lol'
|
19
|
+
|
20
|
+
element :eats, [:string], :xmlname => :ration, :xmlns => :lol
|
21
|
+
element :friend, :string, :whitespace => :collapse, :xmlns => 'c'
|
18
22
|
elements :paws, :string, :xmlname => :paw
|
19
23
|
|
20
|
-
attribute :has_tail, :boolean, :xmlname => 'has-tail'
|
24
|
+
attribute :has_tail, :boolean, :xmlname => 'has-tail', :xmlns => 'b'
|
21
25
|
attribute :height, :integer
|
22
26
|
|
23
27
|
element :cheezburger, Cheezburger
|
24
28
|
end
|
25
29
|
|
26
30
|
class ParsingTest < Test::Unit::TestCase
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
31
|
+
def setup
|
32
|
+
@xml_fragment = <<-EOS
|
33
|
+
<mypet xmlns='lol' xmlns:aa='urn:lol' xmlns:bb='b' height=' 12 ' bb:has-tail=' yes '>
|
34
|
+
<friend xmlns='c'>
|
35
|
+
silly
|
32
36
|
mouse
|
33
|
-
</
|
34
|
-
<ration>
|
35
|
-
|
36
|
-
|
37
|
+
</friend>
|
38
|
+
<aa:ration>
|
39
|
+
tigers
|
40
|
+
lions
|
41
|
+
</aa:ration>
|
37
42
|
<paw> one</paw>
|
38
43
|
<paw> two </paw>
|
39
44
|
<paw>three</paw>
|
@@ -43,43 +48,45 @@ class ParsingTest < Test::Unit::TestCase
|
|
43
48
|
<cub age='4'>
|
44
49
|
</cub>
|
45
50
|
</mypet>
|
46
|
-
|
47
|
-
|
48
|
-
|
51
|
+
EOS
|
52
|
+
@cat = Cat.parse(@xml_fragment)
|
53
|
+
end
|
49
54
|
|
50
|
-
|
51
|
-
|
55
|
+
context "A cat" do
|
56
|
+
should 'be a friend of a silly mouse' do
|
57
|
+
assert_equal 'silly mouse', @cat.friend
|
52
58
|
end
|
53
59
|
|
54
60
|
should 'eat tigers and lions' do
|
55
|
-
assert_equal ['tigers', 'lions'], @
|
61
|
+
assert_equal ['tigers', 'lions'], @cat.eats
|
56
62
|
end
|
57
63
|
|
58
64
|
should 'be 12 meters tall' do
|
59
|
-
assert_equal 12, @
|
65
|
+
assert_equal 12, @cat.height
|
60
66
|
end
|
61
67
|
|
62
68
|
should 'have tail' do
|
63
|
-
assert_equal true, @
|
69
|
+
assert_equal true, @cat.has_tail
|
64
70
|
end
|
65
71
|
|
66
72
|
should 'have four paws' do
|
67
|
-
assert_not_nil @
|
68
|
-
assert_equal 4, @
|
69
|
-
assert_equal %w(one two three four), @
|
73
|
+
assert_not_nil @cat.paws
|
74
|
+
assert_equal 4, @cat.paws.length
|
75
|
+
assert_equal %w(one two three four), @cat.paws
|
76
|
+
end
|
77
|
+
|
78
|
+
should 'has cheezburger' do
|
79
|
+
assert_kind_of Cheezburger, @cat.cheezburger
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
context 'A cheezburger' do
|
84
|
+
setup do
|
85
|
+
@burger = @cat.cheezburger
|
70
86
|
end
|
71
87
|
|
72
|
-
|
73
|
-
|
74
|
-
assert_not_nil @burger = @pet.cheezburger
|
75
|
-
assert_kind_of Cheezburger, @pet.cheezburger
|
76
|
-
end
|
77
|
-
|
78
|
-
context 'that' do
|
79
|
-
should 'weigh 2 kg' do
|
80
|
-
assert_equal 2, @burger.weight
|
81
|
-
end
|
82
|
-
end
|
88
|
+
should 'weigh 2 kg' do
|
89
|
+
assert_equal 2, @burger.weight
|
83
90
|
end
|
84
91
|
end
|
85
92
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pipa-xmlnuts
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Igor Gunko
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-12-
|
12
|
+
date: 2008-12-16 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies: []
|
15
15
|
|
@@ -29,6 +29,8 @@ files:
|
|
29
29
|
- lib/xmlnuts/nuts.rb
|
30
30
|
- lib/xmlnuts/mappings.rb
|
31
31
|
- lib/xmlnuts/converters.rb
|
32
|
+
- lib/xmlnuts/backend.rb
|
33
|
+
- lib/xmlnuts/rexml.rb
|
32
34
|
has_rdoc: true
|
33
35
|
homepage: http://github.com/pipa/xmlnuts
|
34
36
|
post_install_message:
|