class Wasabi::Parser

Wasabi::Parser

Parses WSDL documents and remembers their important parts.

Constants

SOAP_1_1
SOAP_1_2
WSDL
XSD

Attributes

deferred_types[RW]

Returns a map of deferred type Proc objects.

document[RW]

Returns the Nokogiri document.

element_form_default[RW]

Returns the elementFormDefault value.

endpoint[RW]

Returns the SOAP endpoint.

namespace[RW]

Returns the target namespace.

namespaces[RW]

Returns a map from namespace identifier to namespace URI.

operations[RW]

Returns the SOAP operations.

service_name[RW]

Returns the SOAP Service Name

types[RW]

Returns a map from a type name to a Hash with type information.

Public Class Methods

new(document) click to toggle source
# File lib/wasabi/parser.rb, line 19
def initialize(document)
  self.document = document
  self.operations = {}
  self.namespaces = {}
  self.service_name = ''
  self.types = {}
  self.deferred_types = []
  self.element_form_default = :unqualified
end

Public Instance Methods

input_for(operation) click to toggle source
# File lib/wasabi/parser.rb, line 234
def input_for(operation)
  input_output_for(operation, 'input')
end
input_output_for(operation, input_output) click to toggle source

@return [namespace_id, message_type]

# File lib/wasabi/parser.rb, line 243
def input_output_for(operation, input_output)
  operation_name = operation['name']

  # Look up the input by walking up to portType, then up to the message.

  binding_type = operation.parent['type'].to_s.split(':').last
  if @port_type_operations[binding_type]
    port_type_operation = @port_type_operations[binding_type][operation_name]
  end

  port_type_input_output = port_type_operation&.element_children&.find { |node| node.name == input_output }

  # find the message for the portType operation
  # if there is no message, we will use the operation name as the message name

  # TODO: Stupid fix for missing support for imports.
  # Sometimes portTypes are actually included in a separate WSDL.
  if port_type_input_output
    # If the message attribute contains a colon, it means the message is namespaced.
    if port_type_input_output.attribute('message').to_s.include? ':'
      port_message_ns_id, port_message_type = port_type_input_output.attribute('message').to_s.split(':')
    else
      port_message_type = port_type_input_output.attribute('message').to_s
    end

    message_ns_id, message_type = nil

    # When there is a parts attribute in soap:body element, we should use that value
    # to look up the message part from messages array.
    input_output_element = operation.element_children.find { |node| node.name == input_output }
    if input_output_element
      soap_body_element = input_output_element.element_children.find { |node| node.name == 'body' }
      soap_body_parts = soap_body_element['parts'] if soap_body_element
    end

    # look for any message part that matches the soap body parts
    message = @messages[port_message_type]
    port_message_part = message&.element_children&.find do |node|
      soap_body_parts.nil? ? (node.name == "part") : (node.name == "part" && node["name"] == soap_body_parts)
    end

    if port_message_part && port_element = port_message_part.attribute('element')
      port_message_part = port_element.to_s
      if port_message_part.include?(':')
        message_ns_id, message_type = port_message_part.split(':')
      else
        message_type = port_message_part
      end
    end

    # If the message is not found, we should use the operation name as the message name for document style operations
    # applies only to output
    if input_output == 'output'
      # if the operation is document style and theres no port_message_part, we should use the operation_name
      soap_operation = operation.element_children.find { |node| node.name == 'operation' }
      if message_type.nil? && (soap_operation.nil? || soap_operation['style'] != 'rpc')
        if port_message_part.nil?
          message_ns_id = port_message_ns_id
          message_type = operation_name
        else
          message_ns_id = port_message_ns_id
          message_type = port_message_type
        end
      end
    end

    # Fall back to the name of the binding operation
    if message_type
      [message_ns_id, message_type]
    else
      [port_message_ns_id, operation_name]
    end
  else
    [nil, operation_name]
  end
end
output_for(operation) click to toggle source
# File lib/wasabi/parser.rb, line 238
def output_for(operation)
  input_output_for(operation, 'output')
end
parse() click to toggle source
# File lib/wasabi/parser.rb, line 56
def parse
  parse_namespaces
  parse_endpoint
  parse_service_name
  parse_messages
  parse_port_types
  parse_port_type_operations
  parse_operations
  parse_operations_parameters
  parse_types
  parse_deferred_types
end
parse_deferred_types() click to toggle source
# File lib/wasabi/parser.rb, line 230
def parse_deferred_types
  deferred_types.each(&:call)
end
parse_endpoint() click to toggle source
# File lib/wasabi/parser.rb, line 82
def parse_endpoint
  if service_node = service
    endpoint = service_node.at_xpath('.//soap11:address/@location', 'soap11' => SOAP_1_1)
    endpoint ||= service_node.at_xpath(service_node, './/soap12:address/@location', 'soap12' => SOAP_1_2)
  end

  @endpoint = parse_url(endpoint) if endpoint
end
parse_messages() click to toggle source
# File lib/wasabi/parser.rb, line 103
def parse_messages
  messages = document.root.element_children.select { |node| node.name == 'message' }
  @messages = Hash[messages.map { |node| [node['name'], node] }]
end
parse_namespaces() click to toggle source
# File lib/wasabi/parser.rb, line 69
def parse_namespaces
  element_form_default = schemas.first && schemas.first['elementFormDefault']
  @element_form_default = element_form_default.to_s.to_sym if element_form_default

  namespace = document.root['targetNamespace']
  @namespace = namespace.to_s if namespace

  @namespaces = @document.namespaces.inject({}) do |memo, (key, value)|
    memo[key.sub('xmlns:', '')] = value
    memo
  end
end
parse_operations() click to toggle source
# File lib/wasabi/parser.rb, line 138
def parse_operations
  operations = document.xpath('wsdl:definitions/wsdl:binding/wsdl:operation', 'wsdl' => WSDL)
  operations.each do |operation|
    name = operation.attribute('name').to_s
    snakecase_name = Wasabi::CoreExt::String.snakecase(name).to_sym

    # TODO: check for soap namespace?
    soap_operation = operation.element_children.find { |node| node.name == 'operation' }
    soap_action = soap_operation['soapAction'] if soap_operation
    soap_document = soap_operation['style'] == 'document'  if soap_operation

    if soap_action || soap_document
      soap_action = soap_action.to_s
      action = soap_action && !soap_action.empty? ? soap_action : name

      # There should be a matching portType for each binding, so we will lookup the input from there.
      namespace_id, output = output_for(operation)
      namespace_id, input = input_for(operation)

      # Store namespace identifier so this operation can be mapped to the proper namespace.
      @operations[snakecase_name] = { :action => action, :input => input, :output => output, :namespace_identifier => namespace_id}
    elsif !@operations[snakecase_name]
      @operations[snakecase_name] = { :action => name, :input => name }
    end
  end
end
parse_operations_parameters() click to toggle source
# File lib/wasabi/parser.rb, line 122
def parse_operations_parameters
   document.xpath("wsdl:definitions/wsdl:types/*[local-name()='schema']/*[local-name()='element']", 'wsdl' => WSDL).each do |element|
    name = Wasabi::CoreExt::String.snakecase(element.attribute('name').to_s).to_sym

    if operation = @operations[name]
      element.xpath("*[local-name() ='complexType']/*[local-name() ='sequence']/*[local-name() ='element']").each do |child_element|
        attr_name = child_element.attribute('name').to_s
        attr_type = (attr_type = child_element.attribute('type').to_s.split(':')).size > 1 ? attr_type[1] : attr_type[0]

        operation[:parameters] ||= {}
        operation[:parameters][attr_name.to_sym] = { :name => attr_name, :type => attr_type }
      end
    end
  end
end
parse_port_type_operations() click to toggle source
# File lib/wasabi/parser.rb, line 113
def parse_port_type_operations
  @port_type_operations = {}

  @port_types.each do |port_type_name, port_type|
    operations = port_type.element_children.select { |node| node.name == 'operation' }
    @port_type_operations[port_type_name] = Hash[operations.map { |node| [node['name'], node] }]
  end
end
parse_port_types() click to toggle source
# File lib/wasabi/parser.rb, line 108
def parse_port_types
  port_types = document.root.element_children.select { |node| node.name == 'portType' }
  @port_types = Hash[port_types.map { |node| [node['name'], node] }]
end
parse_service_name() click to toggle source
# File lib/wasabi/parser.rb, line 98
def parse_service_name
  service_name = document.root['name']
  @service_name = service_name.to_s if service_name
end
parse_types() click to toggle source
# File lib/wasabi/parser.rb, line 165
def parse_types
  schemas.each do |schema|
    schema_namespace = schema['targetNamespace']

    schema.element_children.each do |node|
      namespace = schema_namespace || @namespace

      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
      end
    end
  end
end
parse_url(url) click to toggle source
# File lib/wasabi/parser.rb, line 91
def parse_url(url)
  unescaped_url = Addressable::URI.unescape(url.to_s)
  escaped_url = Addressable::URI.escape(unescaped_url)
  URI(escaped_url)
rescue URI::InvalidURIError, Addressable::URI::InvalidURIError
end
process_type(namespace, type, name) click to toggle source
# File lib/wasabi/parser.rb, line 183
def process_type(namespace, type, name)
  @types[namespace] ||= {}
  @types[namespace][name] ||= { :namespace => namespace }
  @types[namespace][name][:order!] = []

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

    [ :nillable, :minOccurs, :maxOccurs ].each do |attr|
      if v = inner.attribute(attr.to_s)
        @types[namespace][name][element_name][attr] = v.to_s
      end
    end

    @types[namespace][name][:order!] << element_name
  end

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

    @types[namespace][name][:order!] << element_name
  end

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

    if @types[namespace][base]
      # Reverse merge because we don't want subclass attributes to be overriden by base class
      @types[namespace][name] = types[namespace][base].merge(types[namespace][name])
      @types[namespace][name][:order!] = @types[namespace][base][:order!] | @types[namespace][name][:order!]
      @types[namespace][name][:base_type] = base
    else
      p = Proc.new do
        if @types[namespace][base]
          # Reverse merge because we don't want subclass attributes to be overriden by base class
          @types[namespace][name] = @types[namespace][base].merge(@types[namespace][name])
          @types[namespace][name][:order!] = @types[namespace][base][:order!] | @types[namespace][name][:order!]
          @types[namespace][name][:base_type] = base
        end
      end
      deferred_types << p
    end
  end
end
schemas() click to toggle source
# File lib/wasabi/parser.rb, line 320
def schemas
  types = section('types').first
  types ? types.element_children : []
end
section(section_name) click to toggle source
# File lib/wasabi/parser.rb, line 330
def section(section_name)
  sections[section_name] || []
end
sections() click to toggle source
# File lib/wasabi/parser.rb, line 334
def sections
  @sections ||= document.root.element_children.group_by { |node| node.name }
end
service() click to toggle source
# File lib/wasabi/parser.rb, line 325
def service
  services = section('service')
  services.first if services  # service nodes could be imported?
end