lib/wsdl/soap/classDefCreator.rb
# encoding: UTF-8
# WSDL4R - Creating class definition from WSDL
# Copyright (C) 2000-2007 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
# 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 <any/> 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