Skip to content

Commit

Permalink
moved type parsing to the new Type class
Browse files Browse the repository at this point in the history
accidental benefit: types are now parsed "lazily", which should cut some
time from initially parsing the document.
  • Loading branch information
rubiii committed May 1, 2013
1 parent 022a276 commit d2c3506
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 61 deletions.
17 changes: 9 additions & 8 deletions lib/wasabi/document.rb
Original file line number Diff line number Diff line change
Expand Up @@ -83,25 +83,26 @@ def service_name

attr_writer :service_name

# XXX: legacy interface. change savon to use the new types interface.
def type_namespaces
@type_namespaces ||= begin
namespaces = []
parser.types.each do |type, info|
namespaces << [[type], info[:namespace]]
(info.keys - [:namespace]).each { |field| namespaces << [[type, field], info[:namespace]] }
parser.types.each do |name, type|
namespaces << [[name], type.namespace]
type.children.each { |child| namespaces << [[name, child[:name]], type.namespace] }
end if document
namespaces
end
end

# XXX: legacy interface. change savon to use the new types interface.
def type_definitions
@type_definitions ||= begin
result = []
parser.types.each do |type, info|
(info.keys - [:namespace]).each do |field|
field_type = info[field][:type]
tag, namespace = field_type.split(":").reverse
result << [[type, field], tag] if user_defined(namespace)
parser.types.each do |name, type|
type.children.each do |child|
tag, nsid = child[:type].split(":").reverse
result << [[name, child[:name]], tag] if user_defined(nsid)
end
end if document
result
Expand Down
72 changes: 28 additions & 44 deletions lib/wasabi/parser.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require "uri"
require "wasabi/type"
require "wasabi/core_ext/string"

module Wasabi
Expand All @@ -13,14 +14,15 @@ class Parser
SOAP_1_1 = "http://schemas.xmlsoap.org/wsdl/soap/"
SOAP_1_2 = "http://schemas.xmlsoap.org/wsdl/soap12/"

# TODO: parse simple types as well
SCHEMA_CHILD_TYPES = %w[element complexType] # simpleType]

def initialize(document)
self.document = document
self.operations = {}
self.namespaces = {}
self.service_name = ''
self.types = {}
self.deferred_types = []
self.element_form_default = :unqualified
@document = document
@operations = {}
@namespaces = {}
@service_name = ''
@element_form_default = :unqualified
end

# Returns the Nokogiri document.
Expand All @@ -35,11 +37,11 @@ def initialize(document)
# Returns the SOAP operations.
attr_accessor :operations

# Returns a map from a type name to a Hash with type information.
attr_accessor :types
# Returns a map from a type name to an element Type object.
attr_accessor :elements

# Returns a map of deferred type Proc objects.
attr_accessor :deferred_types
# Returns a map from a type name to a complexType Type object.
attr_accessor :complex_types

# Returns the SOAP endpoint.
attr_accessor :endpoint
Expand All @@ -50,6 +52,11 @@ def initialize(document)
# Returns the elementFormDefault value.
attr_accessor :element_form_default

# TODO: this is bad, but it's how this already worked before.
def types
@types ||= @elements.merge(@complex_types)
end

def parse
parse_namespaces
parse_endpoint
Expand All @@ -59,7 +66,6 @@ def parse
parse_port_type_operations
parse_operations
parse_types
parse_deferred_types
end

def parse_namespaces
Expand Down Expand Up @@ -137,50 +143,28 @@ def parse_operations
end

def parse_types
@elements = {}
@complex_types = {}

schemas.each do |schema|
schema_namespace = schema['targetNamespace']

schema.element_children.each do |node|
next unless SCHEMA_CHILD_TYPES.include? node.name

namespace = schema_namespace || @namespace
type_name = node['name']

type = Type.new(self, namespace, node)

case node.name
when 'element'
complex_type = node.at_xpath('./xs:complexType', 'xs' => XSD)
process_type namespace, complex_type, node['name'].to_s if complex_type
when 'complexType'
process_type namespace, node, node['name'].to_s
when 'element' then @elements[type_name] = type
when 'complexType' then @complex_types[type_name] = type
end
end
end
end

def process_type(namespace, type, name)
@types[name] ||= { :namespace => namespace }

type.xpath("./xs:sequence/xs:element", 'xs' => XSD).
each { |inner| @types[name][inner.attribute("name").to_s] = { :type => inner.attribute("type").to_s } }

type.xpath("./xs:complexContent/xs:extension/xs:sequence/xs:element", 'xs' => XSD).each do |inner_element|
@types[name][inner_element.attribute('name').to_s] = {
:type => inner_element.attribute('type').to_s
}
end

type.xpath('./xs:complexContent/xs:extension[@base]', 'xs' => XSD).each do |inherits|
base = inherits.attribute('base').value.match(/\w+$/).to_s

if @types[base]
@types[name].merge! @types[base]
else
deferred_types << Proc.new { @types[name].merge! @types[base] if @types[base] }
end
end
end

def parse_deferred_types
deferred_types.each(&:call)
end

def input_for(operation)
operation_name = operation["name"]

Expand Down
51 changes: 51 additions & 0 deletions lib/wasabi/type.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
module Wasabi
class Type

def initialize(parser, namespace, node)
@parser = parser
@namespace = namespace
@node = node
end

attr_reader :namespace

def children
return @children if @children

case @node.name
when 'element'
first_child = @node.element_children.first

if first_child && first_child.name == 'complexType'
children = process_type first_child, @node['name'].to_s
end
when 'complexType'
children = process_type @node, @node['name'].to_s
end

@children = children || []
end

def process_type(type, name)
children = []

type.xpath("./xs:sequence/xs:element", 'xs' => Parser::XSD).each do |element|
children << { :name => element["name"].to_s, :type => element["type"].to_s }
end

type.xpath("./xs:complexContent/xs:extension/xs:sequence/xs:element", 'xs' => Parser::XSD).each do |element|
children << { :name => element["name"].to_s, :type => element["type"].to_s }
end

type.xpath('./xs:complexContent/xs:extension[@base]', 'xs' => Parser::XSD).each do |extension|
base = extension.attribute('base').value.match(/\w+$/).to_s
base_type = @parser.types.fetch(base) { raise "expected to find extension base #{base} in types" }

children += base_type.children
end

children
end

end
end
11 changes: 7 additions & 4 deletions spec/wasabi/parser/multiple_namespaces_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,22 @@
end

it "records the namespace for each type" do
subject.types["Save"][:namespace].should == "http://example.com/actions"
subject.types["Save"].namespace.should == "http://example.com/actions"
end

it "records the fields under a type" do
subject.types["Save"].keys.should =~ ["article", :namespace]
subject.types["Save"].children.should == [{ :name => "article", :type => "article:Article" }]
end

it "records multiple fields when there are more than one" do
subject.types["Article"].keys.should =~ ["Title", "Author", :namespace]
subject.types["Article"].children.should == [
{ :name => "Author", :type => "s:string" },
{ :name => "Title", :type => "s:string" }
]
end

it "records the type of a field" do
subject.types["Save"]["article"][:type].should == "article:Article"
subject.types["Save"].children.first[:type].should == "article:Article"
subject.namespaces["article"].should == "http://example.com/article"
end

Expand Down
13 changes: 9 additions & 4 deletions spec/wasabi/parser/no_namespace_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,17 @@

let(:xml) { fixture(:no_namespace).read }

it "lists the types" do
subject.types.keys.sort.should == ["McContact", "McContactArray", "MpUser", "MpUserArray"]
it "lists the elements" do
subject.elements.keys.sort.should == []
end

it "ignores xsd:all" do
subject.types["MpUser"].keys.should == [:namespace]
it "lists the complexTypes" do
subject.complex_types.keys.sort.should == ["McContact", "McContactArray", "MpUser", "MpUserArray"]
end

# TODO: this seems to document a lacking feature.
it "ignores xsd:all types" do
subject.types["MpUser"].children.should be_empty
end

end
Expand Down
2 changes: 1 addition & 1 deletion spec/wasabi/parser/no_target_namespace_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
# but I suppose we should do something reasonable if they do.

it "defaults to the target namespace from xs:definitions" do
subject.types["Save"][:namespace].should == "http://def.example.com"
subject.types["Save"].namespace.should == "http://def.example.com"
end

end
Expand Down

0 comments on commit d2c3506

Please sign in to comment.