# encoding: ASCII-8BIT # WSDL4R - Creating class definition from WSDL # Copyright (C) 2000-2007 NAKAMURA, Hiroshi . # This program is copyrighted free software by NAKAMURA, Hiroshi. You can # redistribute it and/or modify it under the same terms of Ruby's license; # either the dual license version in 2003, or any later version. require 'wsdl/data' require 'wsdl/soap/classDefCreatorSupport' require 'xsd/codegen' require 'set' module WSDL module SOAP class ClassDefCreator include ClassDefCreatorSupport include XSD::CodeGen def initialize(definitions, name_creator, modulepath = nil) @definitions = definitions @name_creator = name_creator @modulepath = modulepath @elements = definitions.collect_elements @elements.uniq! @attributes = definitions.collect_attributes @attributes.uniq! @simpletypes = definitions.collect_simpletypes @simpletypes.uniq! @complextypes = definitions.collect_complextypes @complextypes.uniq! @modelgroups = definitions.collect_modelgroups @modelgroups.uniq! @faulttypes = nil if definitions.respond_to?(:collect_faulttypes) @faulttypes = definitions.collect_faulttypes end @defined_const = {} end def dump(type = nil) result = "require 'xsd/qname'\n" # cannot use @modulepath because of multiple classes if @modulepath result << "\n" result << modulepath_split(@modulepath).collect { |ele| "module #{ele}" }.join("; ") result << "\n\n" end str = dump_group(type) unless str.empty? result << "\n" unless result.empty? result << str end str = dump_complextype(type) unless str.empty? result << "\n" unless result.empty? result << str end str = dump_simpletype(type) unless str.empty? result << "\n" unless result.empty? result << str end str = dump_element(type) unless str.empty? result << "\n" unless result.empty? result << str end str = dump_attribute(type) unless str.empty? result << "\n" unless result.empty? result << str end if @modulepath result << "\n\n" result << modulepath_split(@modulepath).collect { |ele| "end" }.join("; ") result << "\n" end result end private def dump_element(target = nil) @elements.collect { |ele| next if @complextypes[ele.name] next if target and target != ele.name c = create_elementdef(@modulepath, ele) c ? c.dump : nil }.compact.join("\n") end def dump_attribute(target = nil) @attributes.collect { |attribute| next if target and target != attribute.name if attribute.local_simpletype c = create_simpletypedef(@modulepath, attribute.name, attribute.local_simpletype) end c ? c.dump : nil }.compact.join("\n") end def dump_simpletype(target = nil) @simpletypes.collect { |type| next if target and target != type.name c = create_simpletypedef(@modulepath, type.name, type) c ? c.dump : nil }.compact.join("\n") end def dump_complextype(target = nil) definitions = sort_dependency(@complextypes).collect { |type| next if target and target != type.name c = create_complextypedef(@modulepath, type.name, type) c ? c.dump : nil }.compact.join("\n") end def dump_group(target = nil) definitions = @modelgroups.collect { |group| # TODO: not dumped for now but may be useful in the future }.compact.join("\n") end def create_elementdef(mpath, ele) qualified = (ele.elementform == 'qualified') if ele.local_complextype create_complextypedef(mpath, ele.name, ele.local_complextype, qualified) elsif ele.local_simpletype create_simpletypedef(mpath, ele.name, ele.local_simpletype, qualified) elsif ele.empty? create_simpleclassdef(mpath, ele.name, nil) else # ignores type only element nil end end def create_simpletypedef(mpath, qname, simpletype, qualified = false) if simpletype.restriction create_simpletypedef_restriction(mpath, qname, simpletype, qualified) elsif simpletype.list create_simpletypedef_list(mpath, qname, simpletype, qualified) elsif simpletype.union create_simpletypedef_union(mpath, qname, simpletype, qualified) else raise RuntimeError.new("unknown kind of simpletype: #{simpletype}") end end def create_simpletypedef_restriction(mpath, qname, typedef, qualified) restriction = typedef.restriction unless restriction.enumeration? # not supported. minlength? return nil end classname = mapped_class_basename(qname, mpath) c = ClassDef.new(classname, '::String') c.comment = "#{qname}" define_classenum_restriction(c, classname, restriction.enumeration) c end def create_simpletypedef_list(mpath, qname, typedef, qualified) list = typedef.list classname = mapped_class_basename(qname, mpath) c = ClassDef.new(classname, '::Array') c.comment = "#{qname}" if simpletype = list.local_simpletype if simpletype.restriction.nil? raise RuntimeError.new( "unknown kind of simpletype: #{simpletype}") end define_stringenum_restriction(c, simpletype.restriction.enumeration) c.comment << "\n contains list of #{classname}::*" elsif list.itemtype c.comment << "\n contains list of #{mapped_class_basename(list.itemtype, mpath)}::*" else raise RuntimeError.new("unknown kind of list: #{list}") end c end def create_simpletypedef_union(mpath, qname, typedef, qualified) union = typedef.union classname = mapped_class_basename(qname, mpath) c = ClassDef.new(classname, '::String') c.comment = "#{qname}" if union.member_types # fixme c.comment << "\n any of #{union.member_types}" end c end def define_stringenum_restriction(c, enumeration) const = {} enumeration.each do |value| constname = safeconstname(value) const[constname] ||= 0 if (const[constname] += 1) > 1 constname += "_#{const[constname]}" end c.def_const(constname, ndq(value)) end end def define_classenum_restriction(c, classname, enumeration) const = {} enumeration.each do |value| constname = safeconstname(value) const[constname] ||= 0 if (const[constname] += 1) > 1 constname += "_#{const[constname]}" end c.def_const(constname, "new(#{ndq(value)})") end end def create_simpleclassdef(mpath, qname, type_or_element) classname = mapped_class_basename(qname, mpath) c = ClassDef.new(classname, '::String') c.comment = "#{qname}" init_lines = [] if type_or_element and !type_or_element.attributes.empty? define_attribute(c, type_or_element.attributes) init_lines << "@__xmlattr = {}" end c.def_method('initialize', '*arg') do "super\n" + init_lines.join("\n") end c end def create_complextypedef(mpath, qname, type, qualified = false) case type.compoundtype when :TYPE_STRUCT, :TYPE_EMPTY create_structdef(mpath, qname, type, qualified) when :TYPE_ARRAY create_arraydef(mpath, qname, type) when :TYPE_SIMPLE create_simpleclassdef(mpath, qname, type) when :TYPE_MAP # mapped as a general Hash nil else raise RuntimeError.new( "unknown kind of complexContent: #{type.compoundtype}") end end def create_structdef(mpath, qname, typedef, qualified = false) classname = mapped_class_basename(qname, mpath) baseclassname = nil if typedef.complexcontent if base = typedef.complexcontent.base # :TYPE_ARRAY must not be derived (#424) basedef = @complextypes[base] if basedef and basedef.compoundtype != :TYPE_ARRAY # baseclass should be a toplevel complexType baseclassname = mapped_class_basename(base, @modulepath) end end end if @faulttypes and @faulttypes.index(qname) c = ClassDef.new(classname, '::StandardError') else c = ClassDef.new(classname, baseclassname) end c.comment = "#{qname}" c.comment << "\nabstract" if typedef.abstract parentmodule = mapped_class_name(qname, mpath) init_lines, init_params = parse_elements(c, typedef.elements, qname.namespace, parentmodule) unless typedef.attributes.empty? define_attribute(c, typedef.attributes) init_lines << "@__xmlattr = {}" end c.def_method('initialize', *init_params) do init_lines.join("\n") end c end def parse_elements(c, elements, base_namespace, mpath, as_array = false) init_lines = [] init_params = [] any = false elements.each do |element| case element when XMLSchema::Any # only 1 is allowed for now. raise RuntimeError.new("duplicated 'any'") if any any = true attrname = '__xmlele_any' c.def_attr(attrname, false, attrname) c.def_method('set_any', 'elements') do '@__xmlele_any = elements' end init_lines << "@__xmlele_any = nil" when XMLSchema::Element next if element.ref == SchemaName name = name_element(element).name typebase = @modulepath if element.anonymous_type? inner = create_elementdef(mpath, element) unless as_array inner.comment = "inner class for member: #{name}\n" + inner.comment end c.innermodule << inner typebase = mpath end unless as_array attrname = safemethodname(name) varname = safevarname(name) c.def_attr(attrname, true, varname) init_lines << "@#{varname} = #{varname}" if element.map_as_array? init_params << "#{varname} = []" else init_params << "#{varname} = nil" end c.comment << "\n #{attrname} - #{create_type_name(typebase, element) || '(any)'}" end when WSDL::XMLSchema::Sequence child_init_lines, child_init_params = parse_elements(c, element.elements, base_namespace, mpath, as_array) init_lines.concat(child_init_lines) init_params.concat(child_init_params) when WSDL::XMLSchema::Choice child_init_lines, child_init_params = parse_elements(c, element.elements, base_namespace, mpath, as_array) init_lines.concat(child_init_lines) init_params.concat(child_init_params) when WSDL::XMLSchema::Group if element.content.nil? warn("no group definition found: #{element}") next end child_init_lines, child_init_params = parse_elements(c, element.content.elements, base_namespace, mpath, as_array) init_lines.concat(child_init_lines) init_params.concat(child_init_params) else raise RuntimeError.new("unknown type: #{element}") end end [init_lines, init_params] end def define_attribute(c, attributes) const = {} unless attributes.empty? c.def_method("__xmlattr") do <<-__EOD__ @__xmlattr ||= {} __EOD__ end end attributes.each do |attribute| name = name_attribute(attribute) methodname = safemethodname('xmlattr_' + name.name) constname = 'Attr' + safeconstname(name.name) const[constname] ||= 0 if (const[constname] += 1) > 1 constname += "_#{const[constname]}" end c.def_const(constname, dqname(name)) c.def_method(methodname) do <<-__EOD__ __xmlattr[#{constname}] __EOD__ end c.def_method(methodname + '=', 'value') do <<-__EOD__ __xmlattr[#{constname}] = value __EOD__ end c.comment << "\n #{methodname} - #{attribute_basetype(attribute) || '(any)'}" end end def create_arraydef(mpath, qname, typedef) classname = mapped_class_basename(qname, mpath) c = ClassDef.new(classname, '::Array') c.comment = "#{qname}" parentmodule = mapped_class_name(qname, mpath) parse_elements(c, typedef.elements, qname.namespace, parentmodule, true) c end def sort_dependency(types) dep = {} root = [] types.each do |type| if type.complexcontent and (base = type.complexcontent.base) dep[base] ||= [] dep[base] << type else root << type end end sorted = [] root.each do |type| sorted.concat(collect_dependency(type, dep)) end sorted.concat(dep.values.flatten) sorted end # removes collected key from dep def collect_dependency(type, dep) result = [type] return result unless dep.key?(type.name) dep[type.name].each do |deptype| result.concat(collect_dependency(deptype, dep)) end dep.delete(type.name) result end def modulepath_split(modulepath) if modulepath.is_a?(::Array) modulepath else modulepath.to_s.split('::') end end end end end