From db9445103c082a306ba085f7677da02ea94b8841 Mon Sep 17 00:00:00 2001 From: nahi Date: Wed, 24 Sep 2003 15:18:44 +0000 Subject: * lib/soap/* (29 files): SOAP4R added. * lib/wsdl/* (42 files): WSDL4R added. * lib/xsd/* (12 files): XSD4R added. * test/soap/* (16 files): added. * test/wsdl/* (2 files): added. * test/xsd/* (3 files): added. * sample/soap/* (27 files): added. * sample/wsdl/* (13 files): added. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@4591 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- lib/soap/mapping/factory.rb | 310 +++++++++++++++++++++++++ lib/soap/mapping/mapping.rb | 218 ++++++++++++++++++ lib/soap/mapping/registry.rb | 408 +++++++++++++++++++++++++++++++++ lib/soap/mapping/rubytypeFactory.rb | 437 ++++++++++++++++++++++++++++++++++++ lib/soap/mapping/typeMap.rb | 52 +++++ lib/soap/mapping/wsdlRegistry.rb | 146 ++++++++++++ 6 files changed, 1571 insertions(+) create mode 100644 lib/soap/mapping/factory.rb create mode 100644 lib/soap/mapping/mapping.rb create mode 100644 lib/soap/mapping/registry.rb create mode 100644 lib/soap/mapping/rubytypeFactory.rb create mode 100644 lib/soap/mapping/typeMap.rb create mode 100644 lib/soap/mapping/wsdlRegistry.rb (limited to 'lib/soap/mapping') diff --git a/lib/soap/mapping/factory.rb b/lib/soap/mapping/factory.rb new file mode 100644 index 0000000000..2e5ddc1f15 --- /dev/null +++ b/lib/soap/mapping/factory.rb @@ -0,0 +1,310 @@ +=begin +SOAP4R - Mapping factory. +Copyright (C) 2000, 2001, 2002, 2003 NAKAMURA, Hiroshi. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PRATICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 675 Mass +Ave, Cambridge, MA 02139, USA. +=end + + +module SOAP +module Mapping + + +class Factory + include TraverseSupport + + def obj2soap(soap_class, obj, info, map) + raise NotImplementError.new + # return soap_obj + end + + def soap2obj(obj_class, node, info, map) + raise NotImplementError.new + # return convert_succeeded_or_not, obj + end + + if Object.respond_to?(:allocate) + # ruby/1.7 or later. + def create_empty_object(klass) + klass.allocate + end + else + def create_empty_object(klass) + name = klass.name + # Below line is from TANAKA, Akira's amarshal.rb. + # See http://cvs.m17n.org/cgi-bin/viewcvs/amarshal/?cvsroot=ruby + ::Marshal.load(sprintf("\004\006o:%c%s\000", name.length + 5, name)) + end + end + + def set_instance_vars(obj, values) + values.each do |name, value| + setter = name + "=" + if obj.respond_to?(setter) + obj.__send__(setter, value) + else + obj.instance_eval("@#{ name } = value") + end + end + end + + def setiv2obj(obj, node, map) + return if node.nil? + vars = {} + node.each do |name, value| + vars[Mapping.elename2name(name)] = Mapping._soap2obj(value, map) + end + set_instance_vars(obj, vars) + end + + def setiv2soap(node, obj, map) + obj.instance_variables.each do |var| + name = var.sub(/^@/, '') + node.add(Mapping.name2elename(name), + Mapping._obj2soap(obj.instance_eval(var), map)) + end + end + + def addiv2soap(node, obj, map) + return if obj.instance_variables.empty? + ivars = SOAPStruct.new # Undefined type. + setiv2soap(ivars, obj, map) + node.add('ivars', ivars) + end + + # It breaks Thread.current[:SOAPMarshalDataKey]. + def mark_marshalled_obj(obj, soap_obj) + Thread.current[:SOAPMarshalDataKey][obj.__id__] = soap_obj + end + + # It breaks Thread.current[:SOAPMarshalDataKey]. + def mark_unmarshalled_obj(node, obj) + Thread.current[:SOAPMarshalDataKey][node.id] = obj + end + + def name2typename(name) + capitalize(name) + end + + def capitalize(target) + target.gsub(/^([a-z])/) { $1.tr!('[a-z]', '[A-Z]') } + end +end + +class StringFactory_ < Factory + def obj2soap(soap_class, obj, info, map) + begin + if XSD::Charset.is_ces(obj, $KCODE) + encoded = XSD::Charset.encoding_conv(obj, $KCODE, XSD::Charset.encoding) + soap_obj = soap_class.new(encoded) + else + return nil + end + rescue XSD::ValueSpaceError + return nil + end + mark_marshalled_obj(obj, soap_obj) + soap_obj + end + + def soap2obj(obj_class, node, info, map) + obj = XSD::Charset.encoding_conv(node.data, XSD::Charset.encoding, $KCODE) + mark_unmarshalled_obj(node, obj) + return true, obj + end +end + +class BasetypeFactory_ < Factory + def obj2soap(soap_class, obj, info, map) + soap_obj = nil + begin + soap_obj = soap_class.new(obj) + rescue XSD::ValueSpaceError + return nil + end + # Basetype except String should not be multiref-ed in SOAP/1.1. + soap_obj + end + + def soap2obj(obj_class, node, info, map) + obj = node.data + mark_unmarshalled_obj(node, obj) + return true, obj + end +end + +class DateTimeFactory_ < Factory + def obj2soap(soap_class, obj, info, map) + soap_obj = nil + begin + soap_obj = soap_class.new(obj) + rescue XSD::ValueSpaceError + return nil + end + mark_marshalled_obj(obj, soap_obj) + soap_obj + end + + def soap2obj(obj_class, node, info, map) + obj = nil + if obj_class == Time + obj = node.to_time + if obj.nil? + # Is out of range as a Time + return false + end + elsif obj_class == Date + obj = node.data + else + return false + end + mark_unmarshalled_obj(node, obj) + return true, obj + end +end + +class Base64Factory_ < Factory + def obj2soap(soap_class, obj, info, map) + soap_obj = soap_class.new(obj) + mark_marshalled_obj(obj, soap_obj) if soap_obj + soap_obj + end + + def soap2obj(obj_class, node, info, map) + obj = node.string + mark_unmarshalled_obj(node, obj) + return true, obj + end +end + +class ArrayFactory_ < Factory + # [[1], [2]] is converted to Array of Array, not 2-D Array. + # To create M-D Array, you must call Mapping.ary2md. + def obj2soap(soap_class, obj, info, map) + arytype = Mapping.obj2element(obj) + if arytype.name + arytype.namespace ||= RubyTypeNamespace + else + arytype = XSD::AnyTypeName + end + param = SOAPArray.new(ValueArrayName, 1, arytype) + mark_marshalled_obj(obj, param) + obj.each do |var| + param.add(Mapping._obj2soap(var, map)) + end + param + end + + def soap2obj(obj_class, node, info, map) + obj = create_empty_object(obj_class) + mark_unmarshalled_obj(node, obj) + node.soap2array(obj) do |elem| + elem ? Mapping._soap2obj(elem, map) : nil + end + return true, obj + end +end + +class TypedArrayFactory_ < Factory + def obj2soap(soap_class, obj, info, map) + arytype = info[:type] || info[0] + param = SOAPArray.new(ValueArrayName, 1, arytype) + mark_marshalled_obj(obj, param) + obj.each do |var| + param.add(Mapping._obj2soap(var, map)) + end + param + end + + def soap2obj(obj_class, node, info, map) + if node.rank > 1 + return false + end + arytype = info[:type] || info[0] + unless node.arytype == arytype + return false + end + obj = create_empty_object(obj_class) + mark_unmarshalled_obj(node, obj) + node.soap2array(obj) do |elem| + elem ? Mapping._soap2obj(elem, map) : nil + end + return true, obj + end +end + +class TypedStructFactory_ < Factory + def obj2soap(soap_class, obj, info, map) + type = info[:type] || info[0] + param = soap_class.new(type) + mark_marshalled_obj(obj, param) + if obj.class <= SOAP::Marshallable + setiv2soap(param, obj, map) + else + setiv2soap(param, obj, map) + end + param + end + + def soap2obj(obj_class, node, info, map) + type = info[:type] || info[0] + unless node.type == type + return false + end + obj = create_empty_object(obj_class) + mark_unmarshalled_obj(node, obj) + setiv2obj(obj, node, map) + return true, obj + end +end + +MapQName = XSD::QName.new(ApacheSOAPTypeNamespace, 'Map') +class HashFactory_ < Factory + def obj2soap(soap_class, obj, info, map) + if obj.default or + (obj.respond_to?(:default_proc) and obj.default_proc) + return nil + end + param = SOAPStruct.new(MapQName) + mark_marshalled_obj(obj, param) + obj.each do |key, value| + elem = SOAPStruct.new + elem.add("key", Mapping._obj2soap(key, map)) + elem.add("value", Mapping._obj2soap(value, map)) + # ApacheAxis allows only 'item' here. + param.add("item", elem) + end + param + end + + def soap2obj(obj_class, node, info, map) + unless node.type == MapQName + return false + end + if node.key?('default') + return false + end + obj = create_empty_object(obj_class) + mark_unmarshalled_obj(node, obj) + node.each do |key, value| + obj[Mapping._soap2obj(value['key'], map)] = + Mapping._soap2obj(value['value'], map) + end + return true, obj + end +end + + +end +end diff --git a/lib/soap/mapping/mapping.rb b/lib/soap/mapping/mapping.rb new file mode 100644 index 0000000000..19eca5dab0 --- /dev/null +++ b/lib/soap/mapping/mapping.rb @@ -0,0 +1,218 @@ +=begin +SOAP4R - Ruby type mapping utility. +Copyright (C) 2000, 2001, 2003 NAKAMURA Hiroshi. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PRATICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 675 Mass +Ave, Cambridge, MA 02139, USA. +=end + + +module SOAP + + +module Mapping + RubyTypeNamespace = 'http://www.ruby-lang.org/xmlns/ruby/type/1.6' + RubyTypeInstanceNamespace = + 'http://www.ruby-lang.org/xmlns/ruby/type-instance' + RubyCustomTypeNamespace = 'http://www.ruby-lang.org/xmlns/ruby/type/custom' + ApacheSOAPTypeNamespace = 'http://xml.apache.org/xml-soap' + + + # TraverseSupport breaks Thread.current[:SOAPMarshalDataKey]. + module TraverseSupport + def mark_marshalled_obj(obj, soap_obj) + Thread.current[:SOAPMarshalDataKey][obj.__id__] = soap_obj + end + + def mark_unmarshalled_obj(node, obj) + # node.id is not Object#id but SOAPReference#id + Thread.current[:SOAPMarshalDataKey][node.id] = obj + end + end + + + def self.obj2soap(obj, registry = nil, type = nil) + registry ||= Mapping::DefaultRegistry + Thread.current[:SOAPMarshalDataKey] = {} + soap_obj = _obj2soap(obj, registry, type) + Thread.current[:SOAPMarshalDataKey] = nil + soap_obj + end + + def self.soap2obj(node, registry = nil) + registry ||= Mapping::DefaultRegistry + Thread.current[:SOAPMarshalDataKey] = {} + obj = _soap2obj(node, registry) + Thread.current[:SOAPMarshalDataKey] = nil + obj + end + + def self.ary2soap(ary, type_ns = XSD::Namespace, typename = XSD::AnyTypeLiteral, registry = nil) + registry ||= Mapping::DefaultRegistry + type = XSD::QName.new(type_ns, typename) + soap_ary = SOAPArray.new(ValueArrayName, 1, type) + Thread.current[:SOAPMarshalDataKey] = {} + ary.each do |ele| + soap_ary.add(_obj2soap(ele, registry, type)) + end + Thread.current[:SOAPMarshalDataKey] = nil + soap_ary + end + + def self.ary2md(ary, rank, type_ns = XSD::Namespace, typename = XSD::AnyTypeLiteral, registry = nil) + registry ||= Mapping::DefaultRegistry + type = XSD::QName.new(type_ns, typename) + md_ary = SOAPArray.new(ValueArrayName, rank, type) + Thread.current[:SOAPMarshalDataKey] = {} + add_md_ary(md_ary, ary, [], registry) + Thread.current[:SOAPMarshalDataKey] = nil + md_ary + end + + def self.fault2exception(e, registry = nil) + registry ||= Mapping::DefaultRegistry + detail = if e.detail + soap2obj(e.detail, registry) || "" + else + "" + end + if detail.is_a?(Mapping::SOAPException) + begin + raise detail.to_e + rescue Exception => e2 + detail.set_backtrace(e2) + raise + end + else + e.detail = detail + e.set_backtrace( + if detail.is_a?(Array) + detail + else + [detail.to_s] + end + ) + raise + end + end + + def self._obj2soap(obj, registry, type = nil) + if referent = Thread.current[:SOAPMarshalDataKey][obj.__id__] + soap_obj = SOAPReference.new + soap_obj.__setobj__(referent) + soap_obj + else + registry.obj2soap(obj.class, obj, type) + end + end + + def self._soap2obj(node, registry) + if node.is_a?(SOAPReference) + target = node.__getobj__ + # target.id is not Object#id but SOAPReference#id + if referent = Thread.current[:SOAPMarshalDataKey][target.id] + return referent + else + return _soap2obj(target, registry) + end + end + return registry.soap2obj(node.class, node) + end + + + # Allow only (Letter | '_') (Letter | Digit | '-' | '_')* here. + # Caution: '.' is not allowed here. + # To follow XML spec., it should be NCName. + # (denied chars) => .[0-F][0-F] + # ex. a.b => a.2eb + # + def self.name2elename(name) + name.gsub(/([^a-zA-Z0-9:_\-]+)/n) { + '.' << $1.unpack('H2' * $1.size).join('.') + }.gsub(/::/n, '..') + end + + def self.elename2name(name) + name.gsub(/\.\./n, '::').gsub(/((?:\.[0-9a-fA-F]{2})+)/n) { + [$1.delete('.')].pack('H*') + } + end + + def self.class_from_name(name) + if /^[A-Z]/ !~ name + return nil + end + klass = ::Object + name.split('::').each do |klass_str| + if klass.const_defined?(klass_str) + klass = klass.const_get(klass_str) + else + return nil + end + end + klass + end + + def self.class2qname(klass) + name = if klass.class_variables.include?("@@schema_type") + klass.class_eval("@@schema_type") + else + nil + end + namespace = if klass.class_variables.include?("@@schema_ns") + klass.class_eval("@@schema_ns") + else + nil + end + XSD::QName.new(namespace, name) + end + + def self.class2element(klass) + type = Mapping.class2qname(klass) + type.name ||= Mapping.name2elename(klass.name) + type.namespace ||= RubyCustomTypeNamespace + type + end + + def self.obj2element(obj) + name = namespace = nil + ivars = obj.instance_variables + if ivars.include?("@schema_type") + name = obj.instance_eval("@schema_type") + end + if ivars.include?("@schema_ns") + namespace = obj.instance_eval("@schema_ns") + end + if !name or !namespace + class2qname(obj.class) + else + XSD::QName.new(namespace, name) + end + end + + class << Mapping + private + def add_md_ary(md_ary, ary, indices, registry) + for idx in 0..(ary.size - 1) + if ary[idx].is_a?(Array) + add_md_ary(md_ary, ary[idx], indices + [idx], registry) + else + md_ary[*(indices + [idx])] = _obj2soap(ary[idx], registry) + end + end + end + end +end + + +end diff --git a/lib/soap/mapping/registry.rb b/lib/soap/mapping/registry.rb new file mode 100644 index 0000000000..568f34ac3e --- /dev/null +++ b/lib/soap/mapping/registry.rb @@ -0,0 +1,408 @@ +=begin +SOAP4R - Mapping registry. +Copyright (C) 2000, 2001, 2002, 2003 NAKAMURA, Hiroshi. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PRATICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 675 Mass +Ave, Cambridge, MA 02139, USA. +=end + + +require 'soap/baseData' +require 'soap/mapping/mapping' +require 'soap/mapping/typeMap' +require 'soap/mapping/factory' +require 'soap/mapping/rubytypeFactory' + + +module SOAP + + +module Marshallable + # @@type_ns = Mapping::RubyCustomTypeNamespace +end + + +module Mapping + + +module MappedException; end + + +RubyTypeName = XSD::QName.new(RubyTypeInstanceNamespace, 'rubyType') + + +# Inner class to pass an exception. +class SOAPException; include Marshallable + attr_reader :excn_type_name, :message, :backtrace, :cause + def initialize(e) + @excn_type_name = Mapping.name2elename(e.class.to_s) + @message = e.message + @backtrace = e.backtrace + @cause = e + end + + def to_e + if @cause.is_a?(::Exception) + @cause.extend(::SOAP::Mapping::MappedException) + return @cause + end + klass = Mapping.class_from_name( + Mapping.elename2name(@excn_type_name.to_s)) + if klass.nil? + raise RuntimeError.new(@message) + end + unless klass <= ::Exception + raise NameError.new + end + obj = klass.new(@message) + obj.extend(::SOAP::Mapping::MappedException) + obj + end + + def set_backtrace(e) + e.set_backtrace( + if @backtrace.is_a?(Array) + @backtrace + else + [@backtrace.inspect] + end + ) + end +end + + +# For anyType object: SOAP::Mapping::Object not ::Object +class Object; include Marshallable + def set_property(name, value) + var_name = name + begin + instance_eval <<-EOS + def #{ var_name } + @#{ var_name } + end + + def #{ var_name }=(value) + @#{ var_name } = value + end + EOS + self.send(var_name + '=', value) + rescue SyntaxError + var_name = safe_name(var_name) + retry + end + + var_name + end + + def members + instance_variables.collect { |str| str[1..-1] } + end + + def [](name) + if self.respond_to?(name) + self.send(name) + else + self.send(safe_name(name)) + end + end + + def []=(name, value) + if self.respond_to?(name) + self.send(name + '=', value) + else + self.send(safe_name(name) + '=', value) + end + end + +private + + def safe_name(name) + require 'md5' + "var_" << MD5.new(name).hexdigest + end +end + + +class MappingError < Error; end + + +class Registry + class Map + def initialize(registry) + @map = [] + @registry = registry + end + + def obj2soap(klass, obj) + @map.each do |obj_class, soap_class, factory, info| + if klass == obj_class or + (info[:derived_class] and klass <= obj_class) + ret = factory.obj2soap(soap_class, obj, info, @registry) + return ret if ret + end + end + nil + end + + def soap2obj(klass, node) + @map.each do |obj_class, soap_class, factory, info| + if klass == soap_class or + (info[:derived_class] and klass <= soap_class) + conv, obj = factory.soap2obj(obj_class, node, info, @registry) + return true, obj if conv + end + end + return false + end + + # Give priority to former entry. + def init(init_map = []) + clear + init_map.reverse_each do |obj_class, soap_class, factory, info| + add(obj_class, soap_class, factory, info) + end + end + + # Give priority to latter entry. + def add(obj_class, soap_class, factory, info) + info ||= {} + @map.unshift([obj_class, soap_class, factory, info]) + end + + def clear + @map.clear + end + + def find_mapped_soap_class(target_obj_class) + @map.each do |obj_class, soap_class, factory, info| + if obj_class == target_obj_class + return soap_class + end + end + nil + end + + def find_mapped_obj_class(target_soap_class) + @map.each do |obj_class, soap_class, factory, info| + if soap_class == target_soap_class + return obj_class + end + end + nil + end + end + + StringFactory = StringFactory_.new + BasetypeFactory = BasetypeFactory_.new + DateTimeFactory = DateTimeFactory_.new + ArrayFactory = ArrayFactory_.new + Base64Factory = Base64Factory_.new + TypedArrayFactory = TypedArrayFactory_.new + TypedStructFactory = TypedStructFactory_.new + + HashFactory = HashFactory_.new + + SOAPBaseMap = [ + [::NilClass, ::SOAP::SOAPNil, BasetypeFactory], + [::TrueClass, ::SOAP::SOAPBoolean, BasetypeFactory], + [::FalseClass, ::SOAP::SOAPBoolean, BasetypeFactory], + [::String, ::SOAP::SOAPString, StringFactory], + [::DateTime, ::SOAP::SOAPDateTime, BasetypeFactory], + [::Date, ::SOAP::SOAPDateTime, BasetypeFactory], + [::Date, ::SOAP::SOAPDate, BasetypeFactory], + [::Time, ::SOAP::SOAPDateTime, BasetypeFactory], + [::Time, ::SOAP::SOAPTime, BasetypeFactory], + [::Float, ::SOAP::SOAPDouble, BasetypeFactory, + {:derived_class => true}], + [::Float, ::SOAP::SOAPFloat, BasetypeFactory, + {:derived_class => true}], + [::Integer, ::SOAP::SOAPInt, BasetypeFactory, + {:derived_class => true}], + [::Integer, ::SOAP::SOAPLong, BasetypeFactory, + {:derived_class => true}], + [::Integer, ::SOAP::SOAPInteger, BasetypeFactory, + {:derived_class => true}], + [::Integer, ::SOAP::SOAPShort, BasetypeFactory, + {:derived_class => true}], + [::URI::Generic, ::SOAP::SOAPAnyURI, BasetypeFactory, + {:derived_class => true}], + [::String, ::SOAP::SOAPBase64, Base64Factory], + [::String, ::SOAP::SOAPHexBinary, Base64Factory], + [::String, ::SOAP::SOAPDecimal, BasetypeFactory], + [::String, ::SOAP::SOAPDuration, BasetypeFactory], + [::String, ::SOAP::SOAPGYearMonth, BasetypeFactory], + [::String, ::SOAP::SOAPGYear, BasetypeFactory], + [::String, ::SOAP::SOAPGMonthDay, BasetypeFactory], + [::String, ::SOAP::SOAPGDay, BasetypeFactory], + [::String, ::SOAP::SOAPGMonth, BasetypeFactory], + [::String, ::SOAP::SOAPQName, BasetypeFactory], + + [::Array, ::SOAP::SOAPArray, ArrayFactory, + {:derived_class => true}], + + [::Hash, ::SOAP::SOAPStruct, HashFactory], + [::SOAP::Mapping::SOAPException, + ::SOAP::SOAPStruct, TypedStructFactory, + {:type => XSD::QName.new(RubyCustomTypeNamespace, "SOAPException")}], + ] + + RubyOriginalMap = [ + [::NilClass, ::SOAP::SOAPNil, BasetypeFactory], + [::TrueClass, ::SOAP::SOAPBoolean, BasetypeFactory], + [::FalseClass, ::SOAP::SOAPBoolean, BasetypeFactory], + [::String, ::SOAP::SOAPString, StringFactory], + [::DateTime, ::SOAP::SOAPDateTime, BasetypeFactory], + [::Date, ::SOAP::SOAPDateTime, BasetypeFactory], + [::Date, ::SOAP::SOAPDate, BasetypeFactory], + [::Time, ::SOAP::SOAPDateTime, BasetypeFactory], + [::Time, ::SOAP::SOAPTime, BasetypeFactory], + [::Float, ::SOAP::SOAPDouble, BasetypeFactory, + {:derived_class => true}], + [::Float, ::SOAP::SOAPFloat, BasetypeFactory, + {:derived_class => true}], + [::Integer, ::SOAP::SOAPInt, BasetypeFactory, + {:derived_class => true}], + [::Integer, ::SOAP::SOAPLong, BasetypeFactory, + {:derived_class => true}], + [::Integer, ::SOAP::SOAPInteger, BasetypeFactory, + {:derived_class => true}], + [::Integer, ::SOAP::SOAPShort, BasetypeFactory, + {:derived_class => true}], + [::URI::Generic, ::SOAP::SOAPAnyURI, BasetypeFactory, + {:derived_class => true}], + [::String, ::SOAP::SOAPBase64, Base64Factory], + [::String, ::SOAP::SOAPHexBinary, Base64Factory], + [::String, ::SOAP::SOAPDecimal, BasetypeFactory], + [::String, ::SOAP::SOAPDuration, BasetypeFactory], + [::String, ::SOAP::SOAPGYearMonth, BasetypeFactory], + [::String, ::SOAP::SOAPGYear, BasetypeFactory], + [::String, ::SOAP::SOAPGMonthDay, BasetypeFactory], + [::String, ::SOAP::SOAPGDay, BasetypeFactory], + [::String, ::SOAP::SOAPGMonth, BasetypeFactory], + [::String, ::SOAP::SOAPQName, BasetypeFactory], + + # Does not allow Array's subclass here. + [::Array, ::SOAP::SOAPArray, ArrayFactory], + + [::Hash, ::SOAP::SOAPStruct, HashFactory], + [::SOAP::Mapping::SOAPException, + ::SOAP::SOAPStruct, TypedStructFactory, + {:type => XSD::QName.new(RubyCustomTypeNamespace, "SOAPException")}], + ] + + def initialize(config = {}) + @config = config + @map = Map.new(self) + if @config[:allow_original_mapping] + allow_original_mapping = true + @map.init(RubyOriginalMap) + else + allow_original_mapping = false + @map.init(SOAPBaseMap) + end + + allow_untyped_struct = @config.key?(:allow_untyped_struct) ? + @config[:allow_untyped_struct] : true + @rubytype_factory = RubytypeFactory.new( + :allow_untyped_struct => allow_untyped_struct, + :allow_original_mapping => allow_original_mapping + ) + @default_factory = @rubytype_factory + @excn_handler_obj2soap = nil + @excn_handler_soap2obj = nil + end + + def add(obj_class, soap_class, factory, info = nil) + @map.add(obj_class, soap_class, factory, info) + end + alias :set :add + + # This mapping registry ignores type hint. + def obj2soap(klass, obj, type = nil) + ret = nil + if obj.is_a?(SOAPStruct) || obj.is_a?(SOAPArray) + obj.replace do |ele| + Mapping._obj2soap(ele, self) + end + return obj + elsif obj.is_a?(SOAPBasetype) + return obj + end + begin + ret = @map.obj2soap(klass, obj) || + @default_factory.obj2soap(klass, obj, nil, self) + rescue MappingError + end + return ret if ret + + if @excn_handler_obj2soap + ret = @excn_handler_obj2soap.call(obj) { |yield_obj| + Mapping._obj2soap(yield_obj, self) + } + end + return ret if ret + + raise MappingError.new("Cannot map #{ klass.name } to SOAP/OM.") + end + + def soap2obj(klass, node) + if node.extraattr.key?(RubyTypeName) + conv, obj = @rubytype_factory.soap2obj(klass, node, nil, self) + return obj if conv + else + conv, obj = @map.soap2obj(klass, node) + return obj if conv + conv, obj = @default_factory.soap2obj(klass, node, nil, self) + return obj if conv + end + + if @excn_handler_soap2obj + begin + return @excn_handler_soap2obj.call(node) { |yield_node| + Mapping._soap2obj(yield_node, self) + } + rescue Exception + end + end + + raise MappingError.new("Cannot map #{ node.type.name } to Ruby object.") + end + + def default_factory=(factory) + @default_factory = factory + end + + def excn_handler_obj2soap=(handler) + @excn_handler_obj2soap = handler + end + + def excn_handler_soap2obj=(handler) + @excn_handler_soap2obj = handler + end + + def find_mapped_soap_class(obj_class) + @map.find_mapped_soap_class(obj_class) + end + + def find_mapped_obj_class(soap_class) + @map.find_mapped_obj_class(soap_class) + end +end + + +DefaultRegistry = Registry.new +RubyOriginalRegistry = Registry.new(:allow_original_mapping => true) + + +end +end diff --git a/lib/soap/mapping/rubytypeFactory.rb b/lib/soap/mapping/rubytypeFactory.rb new file mode 100644 index 0000000000..0a3f502dfe --- /dev/null +++ b/lib/soap/mapping/rubytypeFactory.rb @@ -0,0 +1,437 @@ +=begin +SOAP4R - Ruby type mapping factory. +Copyright (C) 2000, 2001, 2002, 2003 NAKAMURA, Hiroshi. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PRATICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 675 Mass +Ave, Cambridge, MA 02139, USA. +=end + + +module SOAP +module Mapping + + +class RubytypeFactory < Factory + TYPE_STRING = 'String' + TYPE_ARRAY = 'Array' + TYPE_REGEXP = 'Regexp' + TYPE_RANGE = 'Range' + TYPE_CLASS = 'Class' + TYPE_MODULE = 'Module' + TYPE_SYMBOL = 'Symbol' + TYPE_STRUCT = 'Struct' + TYPE_HASH = 'Map' + + def initialize(config = {}) + @config = config + @allow_untyped_struct = @config.key?(:allow_untyped_struct) ? + @config[:allow_untyped_struct] : true + @allow_original_mapping = @config.key?(:allow_original_mapping) ? + @config[:allow_original_mapping] : false + end + + def obj2soap(soap_class, obj, info, map) + param = nil + case obj + when String + unless @allow_original_mapping + return nil + end + unless XSD::Charset.is_ces(obj, $KCODE) + return nil + end + encoded = XSD::Charset.encoding_conv(obj, $KCODE, XSD::Charset.encoding) + param = SOAPStruct.new(XSD::QName.new(RubyTypeNamespace, TYPE_STRING)) + mark_marshalled_obj(obj, param) + param.add('string', SOAPString.new(encoded)) + if obj.class != String + param.extraattr[RubyTypeName] = obj.class.name + end + addiv2soap(param, obj, map) + when Array + unless @allow_original_mapping + return nil + end + arytype = Mapping.obj2element(obj) + if arytype.name + arytype.namespace ||= RubyTypeNamespace + else + arytype = XSD::AnyTypeName + end + if obj.instance_variables.empty? + param = SOAPArray.new(ValueArrayName, 1, arytype) + mark_marshalled_obj(obj, param) + obj.each do |var| + param.add(Mapping._obj2soap(var, map)) + end + else + param = SOAPStruct.new(XSD::QName.new(RubyTypeNamespace, TYPE_ARRAY)) + mark_marshalled_obj(obj, param) + ary = SOAPArray.new(ValueArrayName, 1, arytype) + obj.each do |var| + ary.add(Mapping._obj2soap(var, map)) + end + param.add('array', ary) + addiv2soap(param, obj, map) + end + if obj.class != Array + param.extraattr[RubyTypeName] = obj.class.name + end + when Regexp + param = SOAPStruct.new(XSD::QName.new(RubyTypeNamespace, TYPE_REGEXP)) + mark_marshalled_obj(obj, param) + if obj.class != Regexp + param.extraattr[RubyTypeName] = obj.class.name + end + param.add('source', SOAPBase64.new(obj.source)) + if obj.respond_to?('options') + # Regexp#options is from Ruby/1.7 + options = obj.options + else + options = 0 + obj.inspect.sub(/^.*\//, '').each_byte do |c| + options += case c + when ?i + 1 + when ?x + 2 + when ?m + 4 + when ?n + 16 + when ?e + 32 + when ?s + 48 + when ?u + 64 + end + end + end + param.add('options', SOAPInt.new(options)) + addiv2soap(param, obj, map) + when Range + param = SOAPStruct.new(XSD::QName.new(RubyTypeNamespace, TYPE_RANGE)) + mark_marshalled_obj(obj, param) + if obj.class != Range + param.extraattr[RubyTypeName] = obj.class.name + end + param.add('begin', Mapping._obj2soap(obj.begin, map)) + param.add('end', Mapping._obj2soap(obj.end, map)) + param.add('exclude_end', SOAP::SOAPBoolean.new(obj.exclude_end?)) + addiv2soap(param, obj, map) + when Hash + unless @allow_original_mapping + return nil + end + if obj.respond_to?(:default_proc) && obj.default_proc + raise TypeError.new("cannot dump hash with default proc") + end + param = SOAPStruct.new(XSD::QName.new(RubyTypeNamespace, TYPE_HASH)) + mark_marshalled_obj(obj, param) + if obj.class != Hash + param.extraattr[RubyTypeName] = obj.class.name + end + obj.each do |key, value| + elem = SOAPStruct.new # Undefined type. + elem.add("key", Mapping._obj2soap(key, map)) + elem.add("value", Mapping._obj2soap(value, map)) + param.add("item", elem) + end + param.add('default', Mapping._obj2soap(obj.default, map)) + addiv2soap(param, obj, map) + when Class + if obj.name.empty? + raise TypeError.new("Can't dump anonymous class #{ obj }.") + end + param = SOAPStruct.new(XSD::QName.new(RubyTypeNamespace, TYPE_CLASS)) + mark_marshalled_obj(obj, param) + param.add('name', SOAPString.new(obj.name)) + addiv2soap(param, obj, map) + when Module + if obj.name.empty? + raise TypeError.new("Can't dump anonymous module #{ obj }.") + end + param = SOAPStruct.new(XSD::QName.new(RubyTypeNamespace, TYPE_MODULE)) + mark_marshalled_obj(obj, param) + param.add('name', SOAPString.new(obj.name)) + addiv2soap(param, obj, map) + when Symbol + param = SOAPStruct.new(XSD::QName.new(RubyTypeNamespace, TYPE_SYMBOL)) + mark_marshalled_obj(obj, param) + param.add('id', SOAPString.new(obj.id2name)) + addiv2soap(param, obj, map) + when Exception + typestr = Mapping.name2elename(obj.class.to_s) + param = SOAPStruct.new(XSD::QName.new(RubyTypeNamespace, typestr)) + mark_marshalled_obj(obj, param) + param.add('message', Mapping._obj2soap(obj.message, map)) + param.add('backtrace', Mapping._obj2soap(obj.backtrace, map)) + addiv2soap(param, obj, map) + when Struct + param = SOAPStruct.new(XSD::QName.new(RubyTypeNamespace, TYPE_STRUCT)) + mark_marshalled_obj(obj, param) + param.add('type', ele_type = SOAPString.new(obj.class.to_s)) + ele_member = SOAPStruct.new + obj.members.each do |member| + ele_member.add(Mapping.name2elename(member), + Mapping._obj2soap(obj[member], map)) + end + param.add('member', ele_member) + addiv2soap(param, obj, map) + when IO, Binding, Continuation, Data, Dir, File::Stat, MatchData, Method, + Proc, Thread, ThreadGroup + return nil + when ::SOAP::Mapping::Object + param = SOAPStruct.new(XSD::AnyTypeName) + mark_marshalled_obj(obj, param) + setiv2soap(param, obj, map) # addiv2soap? + else + if obj.class.name.empty? + raise TypeError.new("Can't dump anonymous class #{ obj }.") + end + if check_singleton(obj) + raise TypeError.new("singleton can't be dumped #{ obj }") + end + type = Mapping.class2element(obj.class) + param = SOAPStruct.new(type) + mark_marshalled_obj(obj, param) + if obj.class <= Marshallable + setiv2soap(param, obj, map) + else + setiv2soap(param, obj, map) # Should not be marshalled? + end + end + param + end + + def soap2obj(obj_class, node, info, map) + rubytype = node.extraattr[RubyTypeName] + if rubytype or node.type.namespace == RubyTypeNamespace + rubytype2obj(node, map, rubytype) + elsif node.type == XSD::AnyTypeName or node.type == XSD::AnySimpleTypeName + anytype2obj(node, map) + else + unknowntype2obj(node, map) + end + end + +private + + def check_singleton(obj) + unless singleton_methods_true(obj).empty? + return true + end + singleton_class = class << obj; self; end + if !singleton_class.instance_variables.empty? or + !(singleton_class.ancestors - obj.class.ancestors).empty? + return true + end + false + end + + if RUBY_VERSION >= '1.8.0' + def singleton_methods_true(obj) + obj.singleton_methods(true) + end + else + def singleton_methods_true(obj) + obj.singleton_methods + end + end + + def rubytype2obj(node, map, rubytype) + obj = nil + case node.class + when SOAPString + obj = string2obj(node, map, rubytype) + obj.replace(node.data) + return true, obj + when SOAPArray + obj = array2obj(node, map, rubytype) + node.soap2array(obj) do |elem| + elem ? Mapping._soap2obj(elem, map) : nil + end + return true, obj + end + + case node.type.name + when TYPE_STRING + obj = string2obj(node, map, rubytype) + obj.replace(node['string'].data) + setiv2obj(obj, node['ivars'], map) + when TYPE_ARRAY + obj = array2obj(node, map, rubytype) + node['array'].soap2array(obj) do |elem| + elem ? Mapping._soap2obj(elem, map) : nil + end + setiv2obj(obj, node['ivars'], map) + when TYPE_REGEXP + klass = rubytype ? Mapping.class_from_name(rubytype) : Regexp + obj = create_empty_object(klass) + mark_unmarshalled_obj(node, obj) + source = node['source'].string + options = node['options'].data || 0 + obj.instance_eval { initialize(source, options) } + setiv2obj(obj, node['ivars'], map) + when TYPE_RANGE + klass = rubytype ? Mapping.class_from_name(rubytype) : Range + obj = create_empty_object(klass) + mark_unmarshalled_obj(node, obj) + first = Mapping._soap2obj(node['begin'], map) + last = Mapping._soap2obj(node['end'], map) + exclude_end = node['exclude_end'].data + obj.instance_eval { initialize(first, last, exclude_end) } + setiv2obj(obj, node['ivars'], map) + when TYPE_HASH + unless @allow_original_mapping + return false + end + klass = rubytype ? Mapping.class_from_name(rubytype) : Hash + obj = create_empty_object(klass) + mark_unmarshalled_obj(node, obj) + node.each do |key, value| + next unless key == 'item' + obj[Mapping._soap2obj(value['key'], map)] = + Mapping._soap2obj(value['value'], map) + end + if node.key?('default') + obj.default = Mapping._soap2obj(node['default'], map) + end + setiv2obj(obj, node['ivars'], map) + when TYPE_CLASS + obj = Mapping.class_from_name(node['name'].data) + setiv2obj(obj, node['ivars'], map) + when TYPE_MODULE + obj = Mapping.class_from_name(node['name'].data) + setiv2obj(obj, node['ivars'], map) + when TYPE_SYMBOL + obj = node['id'].data.intern + setiv2obj(obj, node['ivars'], map) + when TYPE_STRUCT + typestr = Mapping.elename2name(node['type'].data) + klass = Mapping.class_from_name(typestr) + if klass.nil? + klass = Mapping.class_from_name(name2typename(typestr)) + end + if klass.nil? + return false + end + unless klass <= ::Struct + return false + end + obj = create_empty_object(klass) + mark_unmarshalled_obj(node, obj) + node['member'].each do |name, value| + obj[Mapping.elename2name(name)] = + Mapping._soap2obj(value, map) + end + setiv2obj(obj, node['ivars'], map) + else + conv, obj = exception2obj(node, map) + unless conv + return false + end + setiv2obj(obj, node['ivars'], map) + end + return true, obj + end + + def exception2obj(node, map) + typestr = Mapping.elename2name(node.type.name) + klass = Mapping.class_from_name(typestr) + if klass.nil? + return false + end + unless klass <= Exception + return false + end + message = Mapping._soap2obj(node['message'], map) + backtrace = Mapping._soap2obj(node['backtrace'], map) + obj = create_empty_object(klass) + obj = obj.exception(message) + mark_unmarshalled_obj(node, obj) + obj.set_backtrace(backtrace) + setiv2obj(obj, node['ivars'], map) + return true, obj + end + + def anytype2obj(node, map) + case node + when SOAPBasetype + return true, node.data + when SOAPStruct + klass = ::SOAP::Mapping::Object + obj = klass.new + mark_unmarshalled_obj(node, obj) + node.each do |name, value| + obj.set_property(name, Mapping._soap2obj(value, map)) + end + return true, obj + else + return false + end + end + + def unknowntype2obj(node, map) + if node.is_a?(SOAPStruct) + obj = struct2obj(node, map) + return true, obj if obj + if !@allow_untyped_struct + return false + end + return anytype2obj(node, map) + else + # Basetype which is not defined... + return false + end + end + + def struct2obj(node, map) + obj = nil + typestr = Mapping.elename2name(node.type.name) + klass = Mapping.class_from_name(typestr) + if klass.nil? + klass = Mapping.class_from_name(name2typename(typestr)) + end + if klass.nil? + return nil + end + klass_type = Mapping.class2qname(klass) + return nil unless node.type.match(klass_type) + obj = create_empty_object(klass) + mark_unmarshalled_obj(node, obj) + setiv2obj(obj, node, map) + obj + end + + # Only creates empty array. Do String#replace it with real string. + def array2obj(node, map, rubytype) + klass = rubytype ? Mapping.class_from_name(rubytype) : Array + obj = create_empty_object(klass) + mark_unmarshalled_obj(node, obj) + obj + end + + # Only creates empty string. Do String#replace it with real string. + def string2obj(node, map, rubytype) + klass = rubytype ? Mapping.class_from_name(rubytype) : String + obj = create_empty_object(klass) + mark_unmarshalled_obj(node, obj) + obj + end +end + + +end +end diff --git a/lib/soap/mapping/typeMap.rb b/lib/soap/mapping/typeMap.rb new file mode 100644 index 0000000000..c62f1482c1 --- /dev/null +++ b/lib/soap/mapping/typeMap.rb @@ -0,0 +1,52 @@ +=begin +SOAP4R - Base type mapping definition +Copyright (C) 2000, 2001, 2002, 2003 NAKAMURA, Hiroshi. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PRATICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 675 Mass +Ave, Cambridge, MA 02139, USA. +=end + + +module SOAP + + +TypeMap = { + XSD::XSDAnySimpleType::Type => SOAPAnySimpleType, + XSD::XSDString::Type => SOAPString, + XSD::XSDBoolean::Type => SOAPBoolean, + XSD::XSDDecimal::Type => SOAPDecimal, + XSD::XSDFloat::Type => SOAPFloat, + XSD::XSDDouble::Type => SOAPDouble, + XSD::XSDDuration::Type => SOAPDuration, + XSD::XSDDateTime::Type => SOAPDateTime, + XSD::XSDTime::Type => SOAPTime, + XSD::XSDDate::Type => SOAPDate, + XSD::XSDGYearMonth::Type => SOAPGYearMonth, + XSD::XSDGYear::Type => SOAPGYear, + XSD::XSDGMonthDay::Type => SOAPGMonthDay, + XSD::XSDGDay::Type => SOAPGDay, + XSD::XSDGMonth::Type => SOAPGMonth, + XSD::XSDHexBinary::Type => SOAPHexBinary, + XSD::XSDBase64Binary::Type => SOAPBase64, + XSD::XSDAnyURI::Type => SOAPAnyURI, + XSD::XSDQName::Type => SOAPQName, + XSD::XSDInteger::Type => SOAPInteger, + XSD::XSDLong::Type => SOAPLong, + XSD::XSDInt::Type => SOAPInt, + XSD::XSDShort::Type => SOAPShort, + + SOAP::SOAPBase64::Type => SOAPBase64, +} + + +end diff --git a/lib/soap/mapping/wsdlRegistry.rb b/lib/soap/mapping/wsdlRegistry.rb new file mode 100644 index 0000000000..4b5beabe72 --- /dev/null +++ b/lib/soap/mapping/wsdlRegistry.rb @@ -0,0 +1,146 @@ +=begin +SOAP4R - WSDL mapping registry. +Copyright (C) 2000, 2001, 2002, 2003 NAKAMURA, Hiroshi. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation; either version 2 of the License, or (at your option) any later +version. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PRATICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., 675 Mass +Ave, Cambridge, MA 02139, USA. +=end + + +require 'soap/baseData' +require 'soap/mapping/mapping' +require 'soap/mapping/typeMap' + + +module SOAP +module Mapping + + +class WSDLRegistry + include TraverseSupport + + attr_reader :complextypes + + def initialize(complextypes, config = {}) + @complextypes = complextypes + @config = config + @excn_handler_obj2soap = nil + # For mapping AnyType element. + @rubytype_factory = RubytypeFactory.new( + :allow_untyped_struct => true, + :allow_original_mapping => true + ) + end + + def obj2soap(klass, obj, type_qname) + soap_obj = nil + if obj.nil? + soap_obj = SOAPNil.new + elsif obj.is_a?(XSD::NSDBase) + soap_obj = soap2soap(obj, type_qname) + elsif (type = @complextypes[type_qname]) + case type.compoundtype + when :TYPE_STRUCT + soap_obj = struct2soap(obj, type_qname, type) + when :TYPE_ARRAY + soap_obj = array2soap(obj, type_qname, type) + end + elsif (type = TypeMap[type_qname]) + soap_obj = base2soap(obj, type) + elsif type_qname == XSD::AnyTypeName + soap_obj = @rubytype_factory.obj2soap(nil, obj, nil, nil) + end + return soap_obj if soap_obj + + if @excn_handler_obj2soap + soap_obj = @excn_handler_obj2soap.call(obj) { |yield_obj| + Mapping._obj2soap(yield_obj, self) + } + end + return soap_obj if soap_obj + + raise MappingError.new("Cannot map #{ klass.name } to SOAP/OM.") + end + + def soap2obj(klass, node) + raise RuntimeError.new("#{ self } is for obj2soap only.") + end + + def excn_handler_obj2soap=(handler) + @excn_handler_obj2soap = handler + end + +private + + def soap2soap(obj, type_qname) + if obj.is_a?(SOAPBasetype) + obj + elsif obj.is_a?(SOAPStruct) && (type = @complextypes[type_qname]) + soap_obj = obj + mark_marshalled_obj(obj, soap_obj) + elements2soap(obj, soap_obj, type.content.elements) + soap_obj + elsif obj.is_a?(SOAPArray) && (type = @complextypes[type_qname]) + soap_obj = obj + contenttype = type.child_type + mark_marshalled_obj(obj, soap_obj) + obj.replace do |ele| + Mapping._obj2soap(ele, self, contenttype) + end + soap_obj + else + nil + end + end + + def base2soap(obj, type) + soap_obj = nil + if type <= XSD::XSDString + soap_obj = type.new(XSD::Charset.is_ces(obj, $KCODE) ? + XSD::Charset.encoding_conv(obj, $KCODE, XSD::Charset.encoding) : obj) + mark_marshalled_obj(obj, soap_obj) + else + soap_obj = type.new(obj) + end + soap_obj + end + + def struct2soap(obj, type_qname, type) + soap_obj = SOAPStruct.new(type_qname) + mark_marshalled_obj(obj, soap_obj) + elements2soap(obj, soap_obj, type.content.elements) + soap_obj + end + + def array2soap(obj, type_qname, type) + contenttype = type.child_type + soap_obj = SOAPArray.new(ValueArrayName, 1, contenttype) + mark_marshalled_obj(obj, soap_obj) + obj.each do |item| + soap_obj.add(Mapping._obj2soap(item, self, contenttype)) + end + soap_obj + end + + def elements2soap(obj, soap_obj, elements) + elements.each do |element| + name = element.name.name + child_obj = obj.instance_eval("@#{ name }") + soap_obj.add(name, Mapping._obj2soap(child_obj, self, element.type)) + end + end +end + + +end +end -- cgit v1.2.3