Class: Wasabi::Parser

Inherits:
Object
  • Object
show all
Defined in:
lib/wasabi/parser.rb

Overview

Wasabi::Parser

Parses WSDL documents and remembers their important parts.

Constant Summary collapse

XSD =
'http://www.w3.org/2001/XMLSchema'
WSDL =
'http://schemas.xmlsoap.org/wsdl/'
SOAP_1_1 =
'http://schemas.xmlsoap.org/wsdl/soap/'
SOAP_1_2 =
'http://schemas.xmlsoap.org/wsdl/soap12/'

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(document) ⇒ Parser

Returns a new instance of Parser.



19
20
21
22
23
24
25
26
27
# 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

Instance Attribute Details

#deferred_typesObject

Returns a map of deferred type Proc objects.



45
46
47
# File 'lib/wasabi/parser.rb', line 45

def deferred_types
  @deferred_types
end

#documentObject

Returns the Nokogiri document.



30
31
32
# File 'lib/wasabi/parser.rb', line 30

def document
  @document
end

#element_form_defaultObject

Returns the elementFormDefault value.



54
55
56
# File 'lib/wasabi/parser.rb', line 54

def element_form_default
  @element_form_default
end

#endpointObject

Returns the SOAP endpoint.



48
49
50
# File 'lib/wasabi/parser.rb', line 48

def endpoint
  @endpoint
end

#namespaceObject

Returns the target namespace.



33
34
35
# File 'lib/wasabi/parser.rb', line 33

def namespace
  @namespace
end

#namespacesObject

Returns a map from namespace identifier to namespace URI.



36
37
38
# File 'lib/wasabi/parser.rb', line 36

def namespaces
  @namespaces
end

#operationsObject

Returns the SOAP operations.



39
40
41
# File 'lib/wasabi/parser.rb', line 39

def operations
  @operations
end

#service_nameObject

Returns the SOAP Service Name



51
52
53
# File 'lib/wasabi/parser.rb', line 51

def service_name
  @service_name
end

#typesObject

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



42
43
44
# File 'lib/wasabi/parser.rb', line 42

def types
  @types
end

Instance Method Details

#input_for(operation) ⇒ Object



234
235
236
# File 'lib/wasabi/parser.rb', line 234

def input_for(operation)
  input_output_for(operation, 'input')
end

#input_output_for(operation, input_output) ⇒ namespace_id, message_type

Returns:

  • (namespace_id, message_type)


243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
# 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) ⇒ Object



238
239
240
# File 'lib/wasabi/parser.rb', line 238

def output_for(operation)
  input_output_for(operation, 'output')
end

#parseObject



56
57
58
59
60
61
62
63
64
65
66
67
# 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_typesObject



230
231
232
# File 'lib/wasabi/parser.rb', line 230

def parse_deferred_types
  deferred_types.each(&:call)
end

#parse_endpointObject



82
83
84
85
86
87
88
89
# 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_messagesObject



103
104
105
106
# 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_namespacesObject



69
70
71
72
73
74
75
76
77
78
79
80
# 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_operationsObject



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# 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_parametersObject



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# 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_operationsObject



113
114
115
116
117
118
119
120
# 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_typesObject



108
109
110
111
# 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_nameObject



98
99
100
101
# 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_typesObject



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
# 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) ⇒ Object



91
92
93
94
95
96
# 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) ⇒ Object



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
# 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

#schemasObject



320
321
322
323
# File 'lib/wasabi/parser.rb', line 320

def schemas
  types = section('types').first
  types ? types.element_children : []
end

#section(section_name) ⇒ Object



330
331
332
# File 'lib/wasabi/parser.rb', line 330

def section(section_name)
  sections[section_name] || []
end

#sectionsObject



334
335
336
# File 'lib/wasabi/parser.rb', line 334

def sections
  @sections ||= document.root.element_children.group_by { |node| node.name }
end

#serviceObject



325
326
327
328
# File 'lib/wasabi/parser.rb', line 325

def service
  services = section('service')
  services.first if services  # service nodes could be imported?
end