javabean_xml 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/javabean_xml.rb +129 -0
  2. data/test/test_javabean_xml.rb +294 -0
  3. metadata +114 -0
@@ -0,0 +1,129 @@
1
+ require "nokogiri"
2
+ require "builder"
3
+
4
+ # TODO : document
5
+
6
+ class JavabeanXml
7
+
8
+ def self.default_transformations
9
+ Hash.new { |hash, key|
10
+ lambda { |value, properties|
11
+ {
12
+ :class => key,
13
+ :value => value,
14
+ :properties => properties
15
+ }.delete_if { |k,v| v.nil? || v.respond_to?(:empty?) && v.empty? }
16
+ }
17
+ }
18
+ end
19
+
20
+ def self.from_xml xml, type_transformations = {}
21
+ nodes = Nokogiri::XML(xml).xpath("/java/object")
22
+ # clean out all empty text-nodes to simplify parsing
23
+ nodes.xpath("//text()").each do |text_node|
24
+ if text_node.content.strip.empty?
25
+ text_node.remove
26
+ end
27
+ end
28
+ JavabeanXml.new(default_transformations.merge(type_transformations)).parse(nodes)
29
+ end
30
+
31
+ def self.to_xml object, type_transformations = {}
32
+ xml = Builder::XmlMarkup.new #:indent => 2
33
+ xml.instruct! :xml, :version=>"1.0", :encoding=>"UTF-8"
34
+ xml.java :version => "1.6.0_26", :class => "java.beans.XMLDecoder" do
35
+ JavabeanXml.new(type_transformations).to_xml(object, xml)
36
+ end
37
+ end
38
+
39
+ def initialize type_transformations
40
+ @type_transformations = type_transformations
41
+ end
42
+
43
+ def parse node
44
+ if node.is_a? Nokogiri::XML::NodeSet
45
+ raise "structure error" if node.length != 1
46
+ node = node.first
47
+ end
48
+ case node.name
49
+ when "object"
50
+ parse_object node
51
+ else
52
+ parse_value node
53
+ end
54
+ end
55
+
56
+ def to_xml object, xml
57
+ if object.is_a? Hash
58
+ case object[:class]
59
+ when Symbol
60
+ to_value_xml object, xml
61
+ else
62
+ to_object_xml object, xml
63
+ end
64
+ else
65
+ # puts "#{object.inspect} (#{object.class})"
66
+ # puts " -- #{@type_transformations.keys.inspect}"
67
+ transformation = @type_transformations[object.class]
68
+ raise "No transformation registered for #{object.class}" unless transformation
69
+ to_xml transformation[object], xml
70
+ end
71
+ end
72
+
73
+ private
74
+ def parse_object obj_node
75
+ klass = obj_node["class"]
76
+ value = []
77
+ properties = {}
78
+ obj_node.children.each do |prop|
79
+ case prop.name
80
+ when "void"
81
+ properties[prop[:property].to_sym] = parse(prop.children)
82
+ when "string", "int", "boolean", "long"
83
+ value << parse_value(prop)
84
+ else
85
+ raise "Unsupported method: #{prop.inspect}"
86
+ end
87
+ end
88
+ value = value.first if value.length == 1
89
+ @type_transformations[klass][value, properties]
90
+ end
91
+
92
+ def parse_value val_node
93
+ case val_node.name
94
+ when "int", "long"
95
+ value = val_node.text.to_i
96
+ when "boolean"
97
+ value = val_node.text.strip == "true"
98
+ else
99
+ value = val_node.text
100
+ end
101
+ klass = val_node.name.to_sym
102
+ @type_transformations[klass][value, nil]
103
+ end
104
+
105
+ def to_value_xml object, xml
106
+ xml.tag! object[:class] do
107
+ xml.text! object[:value].to_s
108
+ end
109
+ end
110
+
111
+ def to_object_xml object, xml
112
+ xml.object :class => object[:class] do
113
+ if value = object[:value]
114
+ value = [value] unless value.is_a? Array
115
+ value.each do |v|
116
+ to_xml v, xml
117
+ end
118
+ end
119
+ if object[:properties]
120
+ object[:properties].each_pair do |p, val|
121
+ xml.void :property => p do
122
+ to_xml val, xml
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ end
@@ -0,0 +1,294 @@
1
+ require "rubygems"
2
+ require "test/unit"
3
+ require "equivalent-xml"
4
+ require "javabean_xml.rb"
5
+ class JavabeanXmlTest < Test::Unit::TestCase
6
+ @@test_xml_1 = <<-XML_END.split(/\n/).map{|l|l.gsub(/^( ){3}/, "")}.join("\n")
7
+ <?xml version="1.0" encoding="UTF-8"?>
8
+ <java version="1.6.0_26" class="java.beans.XMLDecoder">
9
+ <object class="java.SomeClass">
10
+ <void property="myString">
11
+ <string>string1</string>
12
+ </void>
13
+ <void property="myObject">
14
+ <object class="java.NestedObject">
15
+ <string>Initialization string</string>
16
+ </object>
17
+ </void>
18
+ <void property="myDate">
19
+ <object class="java.util.Date">
20
+ <long>1314102227000</long>
21
+ </object>
22
+ </void>
23
+ </object>
24
+ </java>
25
+ XML_END
26
+ @@test_object_1_a = {
27
+ :class => "java.SomeClass",
28
+ :properties => {
29
+ :myString => {
30
+ :class => :string,
31
+ :value => "string1"
32
+ },
33
+ :myObject => {
34
+ :class => "java.NestedObject",
35
+ :value => {
36
+ :class => :string,
37
+ :value => "Initialization string"
38
+ }
39
+ },
40
+ :myDate => {
41
+ :class => "java.util.Date",
42
+ :value => {
43
+ :class => :long,
44
+ :value => 1314102227000
45
+ }
46
+ }
47
+ }
48
+ }
49
+ @@test_object_1_b = {
50
+ :class => "java.SomeClass",
51
+ :properties => {
52
+ :myString => {
53
+ :class => :string,
54
+ :value => "string1"
55
+ },
56
+ :myObject => {
57
+ :class => "java.NestedObject",
58
+ :value => {
59
+ :class => :string,
60
+ :value => "Initialization string"
61
+ }
62
+ },
63
+ :myDate => Time.at(1314102227)
64
+ }
65
+ }
66
+
67
+ @@test_xml_2 = <<-XML_END.split(/\n/).map{|l|l.gsub(/^( ){3}/, "")}.join("\n")
68
+ <?xml version="1.0" encoding="UTF-8"?>
69
+ <java version="1.6.0_26" class="java.beans.XMLDecoder">
70
+ <object class="java.SomeClass">
71
+ <void property="myString">
72
+ <string>string1</string>
73
+ </void>
74
+ <void property="myRectangle">
75
+ <object class="java.awt.Rectangle">
76
+ <int>0</int>
77
+ <int>2</int>
78
+ <int>200</int>
79
+ <int>300</int>
80
+ </object>
81
+ </void>
82
+ <void property="myDate">
83
+ <object class="java.util.Date">
84
+ <long>1314102227000</long>
85
+ </object>
86
+ </void>
87
+ </object>
88
+ </java>
89
+ XML_END
90
+
91
+ @@test_object_2_a = {
92
+ :class => "java.SomeClass",
93
+ :properties => {
94
+ :myString => {
95
+ :class => :string,
96
+ :value => "string1"
97
+ },
98
+ :myRectangle => {
99
+ :class => "java.awt.Rectangle",
100
+ :value => [
101
+ {
102
+ :class => :int,
103
+ :value => 0
104
+ },
105
+ {
106
+ :class => :int,
107
+ :value => 2
108
+ },
109
+ {
110
+ :class => :int,
111
+ :value => 200
112
+ },
113
+ {
114
+ :class => :int,
115
+ :value => 300
116
+ },
117
+ ]
118
+ },
119
+ :myDate => {
120
+ :class => "java.util.Date",
121
+ :value => {
122
+ :class => :long,
123
+ :value => 1314102227000
124
+ }
125
+ }
126
+ }
127
+ }
128
+
129
+ Rect = Struct.new :values
130
+ MyClass = Struct.new :myString, :myRectangle, :myDate
131
+
132
+ @@test_object_2_b = \
133
+ MyClass.new(
134
+ "string1",
135
+ Rect.new([0,2,200,300]),
136
+ Time.at(1314102227)
137
+ )
138
+
139
+ def test_deserialization_simple
140
+ assert_equal(
141
+ @@test_object_1_a,
142
+ JavabeanXml.from_xml(@@test_xml_1),
143
+ "Should parse simple object"
144
+ )
145
+ end
146
+
147
+
148
+ def test_deserialization_multiple_initialization_values
149
+ assert_equal(
150
+ @@test_object_2_a,
151
+ JavabeanXml.from_xml(@@test_xml_2),
152
+ "Should handle several initialization parameters"
153
+ )
154
+ end
155
+
156
+ def test_deserialization_custom_object_types_simple
157
+ assert_equal(
158
+ @@test_object_1_b,
159
+ JavabeanXml.from_xml(
160
+ @@test_xml_1,
161
+ :long => lambda { |value, properties|
162
+ value.to_i
163
+ },
164
+ "java.util.Date" => lambda { |value, properties|
165
+ Time.at(value/1000)
166
+ }
167
+ ),
168
+ "Should parse object and transform them"
169
+ )
170
+ end
171
+
172
+ def test_deserialization_custom_object_types_complex
173
+ assert_equal(
174
+ @@test_object_2_b,
175
+ JavabeanXml.from_xml(
176
+ @@test_xml_2,
177
+ :long => lambda { |value, properties|
178
+ value.to_i
179
+ },
180
+ "java.util.Date" => lambda { |value, properties|
181
+ Time.at(value/1000)
182
+ },
183
+ :int => lambda { |value, properties|
184
+ value.to_i
185
+ },
186
+ :string => lambda { |value, properties|
187
+ value
188
+ },
189
+ "java.awt.Rectangle" => lambda { |value, properties|
190
+ Rect.new value
191
+ },
192
+ "java.SomeClass" => lambda { |value, properties|
193
+ c = MyClass.new
194
+ properties.each_pair do |k, v|
195
+ c.send :"#{k}=", v
196
+ end
197
+ c
198
+ }
199
+ ),
200
+ "Should parse object and transform them"
201
+ )
202
+ end
203
+
204
+ def test_serialization_simple
205
+ assert(
206
+ EquivalentXml.equivalent?(
207
+ @@test_xml_1,
208
+ JavabeanXml.to_xml(@@test_object_1_a),
209
+ :element_order => (RUBY_VERSION =~ /^1.9/),
210
+ :normalize_whitespace => true
211
+ ),
212
+ "Should serialize simple object"
213
+ )
214
+ end
215
+
216
+ def test_serialization_simple_with_transformation
217
+ result = JavabeanXml.to_xml(
218
+ @@test_object_1_b,
219
+ Time => lambda { |value|
220
+ {
221
+ :class => "java.util.Date",
222
+ :value => {
223
+ :class => :long,
224
+ :value => value.to_i * 1000
225
+ }
226
+ }
227
+ }
228
+ )
229
+ unless EquivalentXml.equivalent?(
230
+ @@test_xml_1,
231
+ result,
232
+ :element_order => (RUBY_VERSION =~ /^1.9/),
233
+ :normalize_whitespace => true
234
+ )
235
+ then
236
+ puts "Expected:"
237
+ puts @@test_xml_2
238
+ puts
239
+ puts "Was:"
240
+ puts result
241
+ fail "Should serialize object and transform types"
242
+ end
243
+ end
244
+
245
+ def test_serialization_complex_with_transformation
246
+ result = JavabeanXml.to_xml(
247
+ @@test_object_2_b,
248
+ Rect => lambda { |value|
249
+ {
250
+ :class => "java.awt.Rectangle",
251
+ :value => value.values.map {|v|
252
+ {
253
+ :class => :int,
254
+ :value => v
255
+ }
256
+ }
257
+ }
258
+ },
259
+ String => lambda { |value|
260
+ { :class => :string, :value => value }
261
+ },
262
+ Time => lambda { |value|
263
+ {
264
+ :class => "java.util.Date",
265
+ :value => {
266
+ :class => :long,
267
+ :value => value.to_i * 1000
268
+ }
269
+ }
270
+ },
271
+ MyClass => lambda { |value|
272
+ {
273
+ :class => "java.SomeClass",
274
+ :properties => value.each_pair.inject({}){|h, (k, v)| h[k] = v; h}
275
+ }
276
+ }
277
+ )
278
+ unless EquivalentXml.equivalent?(
279
+ @@test_xml_2,
280
+ result,
281
+ :element_order => (RUBY_VERSION =~ /^1.9/),
282
+ :normalize_whitespace => true
283
+ )
284
+ then
285
+ puts "Expected:"
286
+ puts @@test_xml_2
287
+ puts
288
+ puts "Was:"
289
+ puts result
290
+ fail "Should serialize object and transform types"
291
+ end
292
+ end
293
+
294
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: javabean_xml
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - "Einar Magn\xC3\xBAs Boson"
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-12-22 00:00:00 +01:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: nokogiri
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 5
30
+ segments:
31
+ - 1
32
+ - 5
33
+ version: "1.5"
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: builder
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ~>
43
+ - !ruby/object:Gem::Version
44
+ hash: 7
45
+ segments:
46
+ - 3
47
+ - 0
48
+ version: "3.0"
49
+ type: :runtime
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: equivalent-xml
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ~>
58
+ - !ruby/object:Gem::Version
59
+ hash: 15
60
+ segments:
61
+ - 0
62
+ - 2
63
+ version: "0.2"
64
+ type: :development
65
+ version_requirements: *id003
66
+ description: Small library for encoding and decoding xml the way java.beans.{Encoder,Decoder} does it
67
+ email: einar.boson@gmail.com
68
+ executables: []
69
+
70
+ extensions: []
71
+
72
+ extra_rdoc_files: []
73
+
74
+ files:
75
+ - lib/javabean_xml.rb
76
+ - test/test_javabean_xml.rb
77
+ has_rdoc: true
78
+ homepage: http://github.com/einarmagnus/javabean_xml
79
+ licenses:
80
+ - MIT
81
+ post_install_message:
82
+ rdoc_options: []
83
+
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ hash: 57
92
+ segments:
93
+ - 1
94
+ - 8
95
+ - 7
96
+ version: 1.8.7
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ none: false
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ hash: 3
103
+ segments:
104
+ - 0
105
+ version: "0"
106
+ requirements: []
107
+
108
+ rubyforge_project:
109
+ rubygems_version: 1.6.2
110
+ signing_key:
111
+ specification_version: 3
112
+ summary: Small library for interacting with java.beans.{Encoder,Decoder}
113
+ test_files:
114
+ - test/test_javabean_xml.rb