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



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

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

#input_output_for(operation, input_output) ⇒ Object



239
240
241
242
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
# File 'lib/wasabi/parser.rb', line 239

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 &&
    port_type_operation.element_children.find { |node| node.name == input_output }

  # TODO: Stupid fix for missing support for imports.
  # Sometimes portTypes are actually included in a separate WSDL.
  if port_type_input_output
    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

    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 multi part message, return a hash representing
    part_messages = message.element_children.select { |node| node.name == "part" && node.attribute('type') }.size
    if part_messages > 0
      part_messages_hash = {}
      part_messages_hash[operation_name] = {}
      message.element_children.select { |node| node.name == "part" }.each do |node|
        part_message_name = node.attribute('name').value
        part_message_type = node.attribute('type').value.split(':')
        part_messages_hash[operation_name][part_message_name] = part_message_type
      end
      return [port_message_ns_id, part_messages_hash]
    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



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

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



227
228
229
# File 'lib/wasabi/parser.rb', line 227

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



102
103
104
105
# File 'lib/wasabi/parser.rb', line 102

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



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/wasabi/parser.rb', line 137

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

    if soap_action
      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



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/wasabi/parser.rb', line 121

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



112
113
114
115
116
117
118
119
# File 'lib/wasabi/parser.rb', line 112

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



107
108
109
110
# File 'lib/wasabi/parser.rb', line 107

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



97
98
99
100
# File 'lib/wasabi/parser.rb', line 97

def parse_service_name
  service_name = document.root['name']
  @service_name = service_name.to_s if service_name
end

#parse_typesObject



163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/wasabi/parser.rb', line 163

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
# File 'lib/wasabi/parser.rb', line 91

def parse_url(url)
  unescaped_url = Addressable::URI.unescape(url.to_s)
  URI(unescaped_url)
rescue URI::InvalidURIError
end

#process_type(namespace, type, name) ⇒ Object



181
182
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
# File 'lib/wasabi/parser.rb', line 181

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

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

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

    @types[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[name][element_name] = { :type => inner_element.attribute('type').to_s }

    @types[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[base]
      # Reverse merge because we don't want subclass attributes to be overriden by base class
      @types[name] = types[base].merge(types[name])
      @types[name][:order!] = @types[base][:order!] | @types[name][:order!]
      @types[name][:base_type] = base
    else
      p = Proc.new do
        if @types[base]
          # Reverse merge because we don't want subclass attributes to be overriden by base class
          @types[name] = @types[base].merge(@types[name])
          @types[name][:order!] = @types[base][:order!] | @types[name][:order!]
          @types[name][:base_type] = base
        end
      end
      deferred_types << p
    end
  end
end

#schemasObject



309
310
311
312
# File 'lib/wasabi/parser.rb', line 309

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

#section(section_name) ⇒ Object



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

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

#sectionsObject



323
324
325
# File 'lib/wasabi/parser.rb', line 323

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

#serviceObject



314
315
316
317
# File 'lib/wasabi/parser.rb', line 314

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