javabean_xml 0.1.0

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.
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