pipa-xmlnuts 0.0.2 → 0.0.5
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/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:
|