Skip to content

Commit

Permalink
Merged pull request #181 from jkingdon/nokogiri-wsdl-parsing.
Browse files Browse the repository at this point in the history
For parsing WSDL, switch from REXML to Nokogiri and stream parsing to DOM
  • Loading branch information
rubiii committed Apr 27, 2011
2 parents 19b49e8 + 8815cf1 commit 0993dab
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 64 deletions.
6 changes: 3 additions & 3 deletions lib/savon/wsdl/document.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
require "rexml/document"
require "nokogiri"

require "savon/wsdl/request"
require "savon/wsdl/parser"
Expand Down Expand Up @@ -101,8 +101,8 @@ def read_file
# Parses the WSDL document and returns the <tt>Savon::WSDL::Parser</tt>.
def parser
@parser ||= begin
parser = Parser.new
REXML::Document.parse_stream document, parser
parser = Parser.new(Nokogiri::XML(document))
parser.parse
parser
end
end
Expand Down
112 changes: 53 additions & 59 deletions lib/savon/wsdl/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ module WSDL

# = Savon::WSDL::Parser
#
# Serves as a stream listener for parsing WSDL documents.
# Parses WSDL documents and remembers the parts of them we care about.
class Parser

# The main sections of a WSDL document.
Sections = %w(definitions types message portType binding service)

def initialize
def initialize(nokogiri_document)
@document = nokogiri_document
@path = []
@operations = {}
@namespaces = {}
Expand All @@ -31,72 +32,65 @@ def initialize
# Returns the elementFormDefault value.
attr_reader :element_form_default

# Hook method called when the stream parser encounters a starting tag.
def tag_start(tag, attrs)
# read xml namespaces if root element
read_namespaces(attrs) if @path.empty?

tag, namespace = tag.split(":").reverse
@path << tag

if @section == :types && tag == "schema"
@element_form_default = attrs["elementFormDefault"].to_sym if attrs["elementFormDefault"]
end

if @section == :binding && tag == "binding"
# ensure that we are in an wsdl/soap namespace
@section = nil unless @namespaces[namespace].starts_with? "http://schemas.xmlsoap.org/wsdl/soap"
end

@section = tag.to_sym if Sections.include?(tag) && depth <= 2

@namespace ||= attrs["targetNamespace"] if @section == :definitions
@endpoint ||= URI(URI.escape(attrs["location"])) if @section == :service && tag == "address"

operation_from tag, attrs if @section == :binding && tag == "operation"
end

# Returns our current depth in the WSDL document.
def depth
@path.size
def parse
parse_namespaces
parse_endpoint
parse_operations
end

# Reads namespace definitions from a given +attrs+ Hash.
def read_namespaces(attrs)
attrs.each do |key, value|
@namespaces[key.strip_namespace] = value if key.starts_with? "xmlns:"
end
def parse_namespaces
element_form_default = @document.at_xpath(
"s0:definitions/s0:types/xs:schema/@elementFormDefault",
"s0" => "http://schemas.xmlsoap.org/wsdl/",
"xs" => "http://www.w3.org/2001/XMLSchema")
@element_form_default = element_form_default.to_s.to_sym if element_form_default

namespace = @document.at_xpath(
"s0:definitions/@targetNamespace",
"s0" => "http://schemas.xmlsoap.org/wsdl/")
@namespace = namespace.to_s if namespace
end

# Hook method called when the stream parser encounters a closing tag.
def tag_end(tag)
@path.pop

if @section == :binding && @input && tag.strip_namespace == "operation"
# no soapAction attribute found till now
operation_from tag, "soapAction" => @input
end

@section = :definitions if Sections.include?(tag) && depth <= 1
def parse_endpoint
endpoint = @document.at_xpath(
"s0:definitions/s0:service//soap11:address/@location",
"s0" => "http://schemas.xmlsoap.org/wsdl/",
"soap11" => "http://schemas.xmlsoap.org/wsdl/soap/")
endpoint ||= @document.at_xpath(
"s0:definitions/s0:service//soap12:address/@location",
"s0" => "http://schemas.xmlsoap.org/wsdl/",
"soap12" => "http://schemas.xmlsoap.org/wsdl/soap12/")
@endpoint = URI(URI.escape(endpoint.to_s)) if endpoint
end

# Stores available operations from a given tag +name+ and +attrs+.
def operation_from(tag, attrs)
@input = attrs["name"] if attrs["name"]

if attrs["soapAction"]
@action = !attrs["soapAction"].blank? ? attrs["soapAction"] : @input
@input = @action.split("/").last if !@input || @input.empty?

@operations[@input.snakecase.to_sym] = { :action => @action, :input => @input }
@input, @action = nil, nil
def parse_operations
operations = @document.xpath(
"s0:definitions/s0:binding/s0:operation",
"s0" => "http://schemas.xmlsoap.org/wsdl/")
operations.each do |operation|
name = operation.attribute("name").to_s

soap_action = operation.at_xpath(".//soap11:operation/@soapAction",
"soap11" => "http://schemas.xmlsoap.org/wsdl/soap/"
)
soap_action ||= operation.at_xpath(".//soap12:operation/@soapAction",
"soap12" => "http://schemas.xmlsoap.org/wsdl/soap12/"
)
if soap_action
soap_action = soap_action.to_s

action = !soap_action.blank? ? soap_action : name
input = (!name || name.empty?) ? action.split("/").last : name

@operations[input.snakecase.to_sym] =
{ :action => action, :input => input }
elsif !@operations[name.snakecase.to_sym]
@operations[name.snakecase.to_sym] =
{ :action => name, :input => name }
end
end
end

# Catches calls to unimplemented hook methods.
def method_missing(method, *args)
end

end
end
end
1 change: 1 addition & 0 deletions savon.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Gem::Specification.new do |s|
s.add_dependency "crack", "~> 0.1.8"
s.add_dependency "httpi", ">= 0.7.8"
s.add_dependency "gyoku", ">= 0.4.0"
s.add_dependency "nokogiri"

s.add_development_dependency "rspec", "~> 2.4.0"
s.add_development_dependency "autotest"
Expand Down
11 changes: 11 additions & 0 deletions spec/fixtures/wsdl/soap12.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Example of a WSDL with SOAP 1.2 and no SOAP 1.1 endpoint.
Don't know whether this is widespread, but we should allow it. -->
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<service name="Blog">
<port name="BlogSoap12">
<soap12:address location="http://blogsite.example.com/endpoint12"/>
</port>
</service>
</definitions>

12 changes: 10 additions & 2 deletions spec/savon/wsdl/parser_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@
end
end

context "with soap12.xml" do
let(:parser) { new_parser :soap12 }

it "should return the endpoint" do
parser.endpoint.should == URI("http://blogsite.example.com/endpoint12")
end
end

RSpec::Matchers.define :match_operations do |expected|
match do |actual|
actual.should have(expected.keys.size).items
Expand All @@ -91,8 +99,8 @@
end

def new_parser(fixture)
parser = Savon::WSDL::Parser.new
REXML::Document.parse_stream Fixture[:wsdl, fixture], parser
parser = Savon::WSDL::Parser.new(Nokogiri::XML(Fixture[:wsdl, fixture]))
parser.parse
parser
end

Expand Down

0 comments on commit 0993dab

Please sign in to comment.