simcha-mappum 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.
- data/.gitignore +4 -0
- data/LICENSE +15 -0
- data/README +53 -0
- data/Rakefile +46 -0
- data/VERSION +1 -0
- data/bin/mapserver.rb +4 -0
- data/lib/mappum.rb +19 -0
- data/lib/mappum/dsl.rb +186 -0
- data/lib/mappum/map.rb +133 -0
- data/lib/mappum/mapserver/mapgraph.rb +193 -0
- data/lib/mappum/mapserver/mapserver.rb +197 -0
- data/lib/mappum/mapserver/views/transform-ws.wsdl.erb +50 -0
- data/lib/mappum/mapserver/views/ws-error.erb +10 -0
- data/lib/mappum/open_xml_object.rb +61 -0
- data/lib/mappum/ruby_transform.rb +129 -0
- data/lib/mappum/xml_transform.rb +298 -0
- data/mappum.gemspec +89 -0
- data/sample/address_fixture.xml +11 -0
- data/sample/crm.rb +9 -0
- data/sample/crm_client.xsd +28 -0
- data/sample/erp.rb +7 -0
- data/sample/erp_person.xsd +36 -0
- data/sample/example_map.rb +87 -0
- data/sample/example_notypes.rb +61 -0
- data/sample/person_fixture.xml +23 -0
- data/sample/person_fixture_any.xml +23 -0
- data/sample/server/map/example_any.rb +28 -0
- data/sample/server/map/example_soap4r.rb +59 -0
- data/sample/server/mapserver.sh +1 -0
- data/sample/server/schema/crm_client.xsd +29 -0
- data/sample/server/schema/erp/erp_person.xsd +38 -0
- data/test/test_example.rb +137 -0
- data/test/test_openstruct.rb +129 -0
- data/test/test_soap4r.rb +108 -0
- data/test/test_xml_any.rb +62 -0
- metadata +130 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
class SOAP::Mapping::Object
|
2
|
+
def id
|
3
|
+
self[XSD::QName.new(nil, "id")]
|
4
|
+
end
|
5
|
+
def type
|
6
|
+
self[XSD::QName.new(nil, "type")]
|
7
|
+
end
|
8
|
+
|
9
|
+
#
|
10
|
+
# XmlAny element is equal to the other xmlAny element when it
|
11
|
+
# has same elements and attributes regardles of ordering of
|
12
|
+
# elements and attributes.
|
13
|
+
#
|
14
|
+
def == other
|
15
|
+
return false if other.class != self.class
|
16
|
+
return false if @__xmlele - other.__xmlele == []
|
17
|
+
return false if @__xmlattr != other.__xmlattr
|
18
|
+
return true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class OpenXmlObject < SOAP::Mapping::Object
|
23
|
+
def method_missing(sym, *args, &block)
|
24
|
+
if sym.to_s[-1..-1] == "=" then
|
25
|
+
if sym.to_s[0..7] == "xmlattr_"
|
26
|
+
#attribute
|
27
|
+
name = sym.to_s[8..-2]
|
28
|
+
__add_xmlattr_from_method(name, args[0])
|
29
|
+
else
|
30
|
+
#element
|
31
|
+
__add_xmlele_from_method(sym.to_s[0..-2], args[0])
|
32
|
+
end
|
33
|
+
else
|
34
|
+
super(sym, *args, &block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
def id=(value)
|
38
|
+
__add_xmlele_from_method("id", value)
|
39
|
+
end
|
40
|
+
def type=(value)
|
41
|
+
__add_xmlele_from_method("type",value)
|
42
|
+
end
|
43
|
+
private
|
44
|
+
def __add_xmlattr_from_method(name, value)
|
45
|
+
@__xmlattr[XSD::QName.new(nil, name)] = value
|
46
|
+
self.instance_eval <<-EOS
|
47
|
+
def xmlattr_#{name}
|
48
|
+
@__xmlattr[XSD::QName.new(nil, #{name})]
|
49
|
+
end
|
50
|
+
|
51
|
+
def xmlattr_#{name}=(value)
|
52
|
+
@__xmlattr[XSD::QName.new(nil, #{name})] = value
|
53
|
+
end
|
54
|
+
EOS
|
55
|
+
end
|
56
|
+
def __add_xmlele_from_method(name, value)
|
57
|
+
Thread.current[:SOAPMapping] ||= {}
|
58
|
+
Thread.current[:SOAPMapping][:SafeMethodName] ||= {}
|
59
|
+
__add_xmlele_value(XSD::QName.new(nil, name),value)
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
# TODO docs
|
2
|
+
require 'set'
|
3
|
+
require 'mappum'
|
4
|
+
require 'ostruct'
|
5
|
+
|
6
|
+
module Mappum
|
7
|
+
class OpenStruct < OpenStruct
|
8
|
+
def type(*attr)
|
9
|
+
method_missing(:type, *attr)
|
10
|
+
end
|
11
|
+
def id(*attr)
|
12
|
+
method_missing(:id, *attr)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
class MapMissingException < RuntimeError
|
16
|
+
attr_accessor :from
|
17
|
+
def initialize(from, msg=nil)
|
18
|
+
msg ||= "Map for class \"#{from.class}\" not found!"
|
19
|
+
super(msg)
|
20
|
+
@from = from
|
21
|
+
end
|
22
|
+
end
|
23
|
+
class RubyTransform
|
24
|
+
attr_accessor :map_catalogue
|
25
|
+
|
26
|
+
def initialize(map_catalogue = nil, default_struct_class=nil)
|
27
|
+
@map_catalogue = map_catalogue if map_catalogue.kind_of?(Mappum::Map)
|
28
|
+
@map_catalogue ||= Mappum.catalogue(map_catalogue)
|
29
|
+
@default_struct_class = default_struct_class
|
30
|
+
@default_struct_class ||= Mappum::OpenStruct;
|
31
|
+
end
|
32
|
+
def get(object, field)
|
33
|
+
if field.nil?
|
34
|
+
return object
|
35
|
+
elsif not object.kind_of?(@default_struct_class) or
|
36
|
+
object.respond_to?(field)
|
37
|
+
return object.send(field)
|
38
|
+
else
|
39
|
+
#for open structures field will be defined later
|
40
|
+
return nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
def transform(from, map=nil, to=nil)
|
44
|
+
|
45
|
+
map ||= @map_catalogue[from.class]
|
46
|
+
|
47
|
+
raise MapMissingException.new(from) if map.nil?
|
48
|
+
|
49
|
+
to ||= map.to.clazz.new unless map.to.clazz.nil? or map.to.clazz.kind_of?(Symbol)
|
50
|
+
|
51
|
+
all_nils = true
|
52
|
+
|
53
|
+
map.maps.each do |sm|
|
54
|
+
from_value, to_value = nil, nil
|
55
|
+
|
56
|
+
if sm.from.respond_to?(:name)
|
57
|
+
from_value = get(from, sm.from.name)
|
58
|
+
else
|
59
|
+
from_value = sm.from.value
|
60
|
+
end
|
61
|
+
|
62
|
+
if sm.maps.empty?
|
63
|
+
to_value = from_value
|
64
|
+
elsif not from_value.nil?
|
65
|
+
if from_value.instance_of?(Array)
|
66
|
+
sm_v = sm.clone
|
67
|
+
sm_v.from.is_array = false
|
68
|
+
sm_v.to.is_array = false
|
69
|
+
to_value = from_value.collect{|v| transform(v, sm_v)}
|
70
|
+
else
|
71
|
+
to ||= @default_struct_class.new
|
72
|
+
v_to = nil
|
73
|
+
#array values are assigned after return
|
74
|
+
v_to = get(to, sm.to.name) unless sm.to.is_array and not sm.from.is_array
|
75
|
+
to_value = transform(from_value, sm, v_to)
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
unless sm.func.nil? or (not sm.func_on_nil? and to_value.nil?)
|
80
|
+
to_value = sm.func.call(to_value)
|
81
|
+
end
|
82
|
+
unless sm.from.func.nil? or to_value.nil?
|
83
|
+
to_value = to_value.instance_eval(sm.from.func)
|
84
|
+
end
|
85
|
+
unless sm.dict.nil?
|
86
|
+
to_value = sm.dict[to_value]
|
87
|
+
end
|
88
|
+
if sm.to.is_array and not sm.from.is_array
|
89
|
+
to_array = get(to,sm.to.name)
|
90
|
+
to_array ||= []
|
91
|
+
to_array << to_value
|
92
|
+
|
93
|
+
if to_array.empty? and sm.strip_empty?
|
94
|
+
to_array = nil
|
95
|
+
end
|
96
|
+
|
97
|
+
all_nils = false unless to_array.nil?
|
98
|
+
|
99
|
+
if sm.to.name.nil?
|
100
|
+
to = to_array
|
101
|
+
else
|
102
|
+
to ||= @default_struct_class.new
|
103
|
+
to.send("#{sm.to.name}=", to_array) unless to_array.nil?
|
104
|
+
end
|
105
|
+
else
|
106
|
+
|
107
|
+
if to_value.respond_to?(:empty?) and to_value.empty? and sm.strip_empty?
|
108
|
+
to_value = nil
|
109
|
+
end
|
110
|
+
|
111
|
+
all_nils = false unless to_value.nil?
|
112
|
+
|
113
|
+
if sm.to.name.nil?
|
114
|
+
to ||= to_value
|
115
|
+
elsif
|
116
|
+
to ||= @default_struct_class.new
|
117
|
+
|
118
|
+
to.send("#{sm.to.name}=", to_value) unless to_value.nil?
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
if all_nils and map.strip_empty?
|
124
|
+
return nil
|
125
|
+
end
|
126
|
+
return to
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,298 @@
|
|
1
|
+
require 'mappum/ruby_transform'
|
2
|
+
require 'rubygems'
|
3
|
+
gem 'soap4r'
|
4
|
+
require 'soap/marshal'
|
5
|
+
require 'xsd/mapping'
|
6
|
+
require 'wsdl/xmlSchema/xsd2ruby'
|
7
|
+
require 'mappum/open_xml_object'
|
8
|
+
require 'tmpdir'
|
9
|
+
|
10
|
+
class XSD::Mapping::Mapper
|
11
|
+
attr_reader :registry
|
12
|
+
def self.inherited(klass)
|
13
|
+
@@mapper_classes ||= []
|
14
|
+
@@mapper_classes << klass
|
15
|
+
end
|
16
|
+
def self.find_mapper_for_class(klass)
|
17
|
+
klass = const_get(klass) if klass.instance_of?(Symbol)
|
18
|
+
ret_maper=nil
|
19
|
+
#FIXME add cache
|
20
|
+
mappers.each do |mapper|
|
21
|
+
begin
|
22
|
+
sch = mapper.registry.schema_definition_from_class klass
|
23
|
+
ret_maper = mapper unless sch.nil?
|
24
|
+
rescue NoMethodError
|
25
|
+
end
|
26
|
+
end
|
27
|
+
return ret_maper
|
28
|
+
end
|
29
|
+
def self.get_qname_from_class(klass)
|
30
|
+
begin
|
31
|
+
klass = const_get(klass) if klass.instance_of?(Symbol)
|
32
|
+
rescue NameError
|
33
|
+
return nil
|
34
|
+
end
|
35
|
+
ret_qname = nil
|
36
|
+
#FIXME add cache
|
37
|
+
mappers.each do |mapper|
|
38
|
+
begin
|
39
|
+
sch = mapper.registry.schema_definition_from_class klass
|
40
|
+
ret_qname = sch.elename unless sch.nil?
|
41
|
+
rescue NoMethodError
|
42
|
+
end
|
43
|
+
end
|
44
|
+
return ret_qname
|
45
|
+
|
46
|
+
end
|
47
|
+
def self.find_mapper_for_type(qname)
|
48
|
+
ret_maper=nil
|
49
|
+
#FIXME add cache
|
50
|
+
mappers.each do |mapper|
|
51
|
+
begin
|
52
|
+
sch = mapper.registry.schema_definition_from_elename qname
|
53
|
+
ret_maper = mapper unless sch.nil?
|
54
|
+
rescue NoMethodError
|
55
|
+
end
|
56
|
+
end
|
57
|
+
return ret_maper
|
58
|
+
end
|
59
|
+
def obj2soap(obj, elename = nil, io = nil)
|
60
|
+
opt = MAPPING_OPT.dup
|
61
|
+
unless elename
|
62
|
+
if definition = @registry.elename_schema_definition_from_class(obj.class)
|
63
|
+
elename = definition.elename
|
64
|
+
opt[:root_type_hint] = false
|
65
|
+
end
|
66
|
+
end
|
67
|
+
elename = SOAP::Mapping.to_qname(elename) if elename
|
68
|
+
soap = SOAP::Mapping.obj2soap(obj, @registry, elename, opt)
|
69
|
+
if soap.elename.nil? or soap.elename == XSD::QName::EMPTY
|
70
|
+
soap.elename =
|
71
|
+
XSD::QName.new(nil, SOAP::Mapping.name2elename(obj.class.to_s))
|
72
|
+
end
|
73
|
+
return soap
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
def self.mappers
|
78
|
+
@@mappers ||= []
|
79
|
+
@@mapper_classes ||= []
|
80
|
+
if @@mapper_classes.size > @@mappers.size
|
81
|
+
@@mappers = @@mapper_classes.collect{|k|k.new()}
|
82
|
+
end
|
83
|
+
@@mappers
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
module Mappum
|
88
|
+
class XmlTransform
|
89
|
+
def initialize(map_catalogue = nil)
|
90
|
+
@default_mapper = XSD::Mapping::Mapper.new(SOAP::Mapping::LiteralRegistry.new)
|
91
|
+
@ruby_transform = RubyXmlTransform.new(map_catalogue, OpenXmlObject)
|
92
|
+
end
|
93
|
+
def transform(from_xml, map=nil, from_qname=nil, to_qname=nil, handle_soap=true)
|
94
|
+
soap = false
|
95
|
+
|
96
|
+
parser = SOAP::Parser.new(XSD::Mapping::Mapper::MAPPING_OPT)
|
97
|
+
preparsed = parser.parse(from_xml)
|
98
|
+
|
99
|
+
if from_qname.nil?
|
100
|
+
from_qname = preparsed.elename
|
101
|
+
end
|
102
|
+
|
103
|
+
if handle_soap and from_qname == XSD::QName.new("http://schemas.xmlsoap.org/soap/envelope/","Envelope")
|
104
|
+
soap = true
|
105
|
+
#for soap remove envelope
|
106
|
+
preparsed = preparsed.body.root_node
|
107
|
+
from_qname = preparsed.elename
|
108
|
+
end
|
109
|
+
|
110
|
+
|
111
|
+
from_mapper = XSD::Mapping::Mapper.find_mapper_for_type(from_qname)
|
112
|
+
if from_mapper.nil?
|
113
|
+
from_mapper = @default_mapper
|
114
|
+
end
|
115
|
+
|
116
|
+
begin
|
117
|
+
parsed =SOAP::Mapping.soap2obj(preparsed, from_mapper.registry, nil)
|
118
|
+
rescue NoMethodError => e
|
119
|
+
raise ParsingFailedException.new("Parsing failed for xml with root element: #{from_qname}")
|
120
|
+
end
|
121
|
+
|
122
|
+
map ||= @ruby_transform.map_catalogue[from_qname]
|
123
|
+
map ||= @ruby_transform.map_catalogue[from_qname.name.to_sym]
|
124
|
+
if not map.nil? and not map.kind_of?(Map)
|
125
|
+
map = @ruby_transform.map_catalogue[map.to_sym]
|
126
|
+
end
|
127
|
+
|
128
|
+
begin
|
129
|
+
transformed = @ruby_transform.transform(parsed, map)
|
130
|
+
rescue MapMissingException => e
|
131
|
+
if e.from == parsed
|
132
|
+
raise MapMissingException.new(e.from,"Map for element \"#{from_qname}\" not found!")
|
133
|
+
else
|
134
|
+
raise e
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
to_mapper = XSD::Mapping::Mapper.find_mapper_for_class(transformed.class)
|
139
|
+
if to_mapper.nil?
|
140
|
+
to_mapper = @default_mapper
|
141
|
+
end
|
142
|
+
|
143
|
+
if transformed.kind_of?(OpenXmlObject) and to_qname.nil? and not map.nil?
|
144
|
+
to = map.to
|
145
|
+
if to.clazz.kind_of?(XSD::QName)
|
146
|
+
to_qname = to.clazz
|
147
|
+
else
|
148
|
+
to_qname = XSD::QName.new(nil, to.clazz.to_s)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
to_preparsed = to_mapper.obj2soap(transformed,to_qname)
|
152
|
+
if soap == true
|
153
|
+
to_preparsed = SOAP::SOAPEnvelope.new(SOAP::SOAPHeader.new, SOAP::SOAPBody.new(to_preparsed))
|
154
|
+
end
|
155
|
+
generator = SOAP::Generator.new(XSD::Mapping::Mapper::MAPPING_OPT)
|
156
|
+
to_xml = generator.generate(to_preparsed, nil)
|
157
|
+
return to_xml
|
158
|
+
end
|
159
|
+
end
|
160
|
+
class RubyXmlTransform < RubyTransform
|
161
|
+
def initialize(*args)
|
162
|
+
super(*args)
|
163
|
+
end
|
164
|
+
def get(object, field)
|
165
|
+
begin
|
166
|
+
super(object, field)
|
167
|
+
rescue NoMethodError
|
168
|
+
super(object, XSD::CodeGen::GenSupport.safemethodname(field.to_s).to_sym)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
# Class supporting loading working directory of the layout:
|
173
|
+
#
|
174
|
+
# schema/ - Directory containing xsd files
|
175
|
+
#
|
176
|
+
# map/ - directory containing Mappum maps
|
177
|
+
#
|
178
|
+
class WorkdirLoader
|
179
|
+
def initialize(schema_path = "schema", map_dir="maps", basedir=nil)
|
180
|
+
@schema_path = schema_path
|
181
|
+
@basedir = basedir
|
182
|
+
@basedir ||= Dir.mktmpdir
|
183
|
+
@map_dir = map_dir
|
184
|
+
@mapper_scripts = []
|
185
|
+
end
|
186
|
+
def generate_and_require
|
187
|
+
generate_classes
|
188
|
+
require_all
|
189
|
+
end
|
190
|
+
def require_all
|
191
|
+
require_schemas
|
192
|
+
require_maps
|
193
|
+
end
|
194
|
+
# Generate classes from xsd files in shema_path (defaults to schema).
|
195
|
+
# Files containt in subdirectories are generated to modules where module name is
|
196
|
+
# optaind from folder name.
|
197
|
+
# Generated classes are saved to basedir (defaults to tmp)
|
198
|
+
def generate_classes(schema_path=nil, module_path=nil)
|
199
|
+
class_dir = @basedir
|
200
|
+
modname = module_path
|
201
|
+
unless module_path.nil?
|
202
|
+
class_dir = File.join(class_dir, module_path)
|
203
|
+
modname = modname.gsub(File::SEPARATOR, "::")
|
204
|
+
modname = modname.gsub(/^[a-z]|\s+[a-z]|\:\:+[a-z]/) { |a| a.upcase }
|
205
|
+
end
|
206
|
+
|
207
|
+
Dir.mkdir(class_dir) unless File.exist? class_dir
|
208
|
+
|
209
|
+
|
210
|
+
|
211
|
+
schema_path ||= @schema_path
|
212
|
+
|
213
|
+
Dir.foreach(schema_path) do |file_name|
|
214
|
+
full_name = File.join(schema_path,file_name)
|
215
|
+
#when file is a directory
|
216
|
+
#generate classes in module (recursive)
|
217
|
+
if File.directory?(full_name) and not file_name == "." and not file_name == ".."
|
218
|
+
#make directory for future class files
|
219
|
+
module_pth = file_name
|
220
|
+
module_pth = File.join(module_path, module_pth) unless module_path.nil?
|
221
|
+
|
222
|
+
generate_classes(full_name, module_pth)
|
223
|
+
# for XSD files generate classes using XSD2Ruby
|
224
|
+
elsif /.*.xsd/ =~ file_name
|
225
|
+
run_xsd2ruby(full_name, file_name, module_path, modname)
|
226
|
+
@mapper_scripts << class_dir + File::SEPARATOR + file_name[0..-5] + "_mapper.rb"
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
def require_schemas
|
231
|
+
$:.unshift @basedir
|
232
|
+
@mapper_scripts.each do |script|
|
233
|
+
require script
|
234
|
+
end
|
235
|
+
end
|
236
|
+
def require_maps
|
237
|
+
$:.unshift @map_dir
|
238
|
+
Dir.foreach(@map_dir) do |file_name|
|
239
|
+
if /.*.rb/ =~ file_name
|
240
|
+
require File.join(@map_dir, file_name)
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
private
|
245
|
+
def run_xsd2ruby(full_name, file_name, module_path, modname)
|
246
|
+
worker = WSDL::XMLSchema::XSD2Ruby.new
|
247
|
+
worker.location = full_name
|
248
|
+
worker.basedir = @basedir
|
249
|
+
|
250
|
+
classdef = file_name[0..-5]
|
251
|
+
classdef = File.join(module_path,classdef) unless module_path.nil?
|
252
|
+
|
253
|
+
opt = {"classdef" => classdef, "mapping_registry" => "", "mapper" => "", "force" => ""}
|
254
|
+
opt["module_path"] = modname unless modname.nil?
|
255
|
+
worker.opt.update(opt)
|
256
|
+
worker.run
|
257
|
+
end
|
258
|
+
end
|
259
|
+
class XmlSupport
|
260
|
+
#choose parser
|
261
|
+
begin
|
262
|
+
gem 'libxml-ruby'
|
263
|
+
require 'libxml'
|
264
|
+
@@parser = :libxml
|
265
|
+
rescue Gem::LoadError
|
266
|
+
require 'rexml/parsers/sax2parser'
|
267
|
+
@@parser = :rexml
|
268
|
+
end
|
269
|
+
#
|
270
|
+
# Get target namespace from given xsd file
|
271
|
+
#
|
272
|
+
def self.get_target_ns(xsd_file)
|
273
|
+
return get_target_ns_libxml(xsd_file) if @@parser == :libxml
|
274
|
+
return get_target_ns_rexml(xsd_file)
|
275
|
+
end
|
276
|
+
def self.get_target_ns_rexml(xsd_file)
|
277
|
+
reader = REXML::Parsers::SAX2Parser.new(File.new(xsd_file))
|
278
|
+
namespace = nil
|
279
|
+
#FIXME: quit after root
|
280
|
+
reader.listen(:start_element) do |uri, localname, qname, attributes|
|
281
|
+
namespace ||= attributes["targetNamespace"]
|
282
|
+
end
|
283
|
+
|
284
|
+
reader.parse
|
285
|
+
return namespace
|
286
|
+
end
|
287
|
+
def self.get_target_ns_libxml(xsd_file)
|
288
|
+
reader = LibXML::XML::Reader.file(xsd_file)
|
289
|
+
reader.read
|
290
|
+
reader.move_to_attribute("targetNamespace")
|
291
|
+
namespace = reader.value
|
292
|
+
reader.close
|
293
|
+
return namespace
|
294
|
+
end
|
295
|
+
end
|
296
|
+
class ParsingFailedException < RuntimeError
|
297
|
+
end
|
298
|
+
end
|