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.


16
17
18
19
20
21
22
23
24
# File 'lib/wasabi/parser.rb', line 16

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.


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

def deferred_types
  @deferred_types
end

#documentObject

Returns the Nokogiri document.


27
28
29
# File 'lib/wasabi/parser.rb', line 27

def document
  @document
end

#element_form_defaultObject

Returns the elementFormDefault value.


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

def element_form_default
  @element_form_default
end

#endpointObject

Returns the SOAP endpoint.


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

def endpoint
  @endpoint
end

#namespaceObject

Returns the target namespace.


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

def namespace
  @namespace
end

#namespacesObject

Returns a map from namespace identifier to namespace URI.


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

def namespaces
  @namespaces
end

#operationsObject

Returns the SOAP operations.


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

def operations
  @operations
end

#service_nameObject

Returns the SOAP Service Name


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

def service_name
  @service_name
end

#typesObject

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


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

def types
  @types
end

Instance Method Details

#input_for(operation) ⇒ Object


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

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

#input_output_for(operation, input_output) ⇒ Object


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

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

    # 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


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

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

#parseObject


53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/wasabi/parser.rb', line 53

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


224
225
226
# File 'lib/wasabi/parser.rb', line 224

def parse_deferred_types
  deferred_types.each(&:call)
end

#parse_endpointObject


79
80
81
82
83
84
85
86
# File 'lib/wasabi/parser.rb', line 79

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


100
101
102
103
# File 'lib/wasabi/parser.rb', line 100

def parse_messages
  messages = document.root.element_children.select { |node| node.name == 'message' }
  @messages = Hash[messages.map { |node| [node['name'], node] }]
end

#parse_namespacesObject


66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/wasabi/parser.rb', line 66

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


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

def parse_operations
  operations = document.xpath('wsdl:definitions/wsdl:binding/wsdl:operation', 'wsdl' => WSDL)
  operations.each do |operation|
    name = operation.attribute('name').to_s

    # 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[name.snakecase.to_sym] = { :action => action, :input => input, :output => output, :namespace_identifier => namespace_id}
    elsif !@operations[name.snakecase.to_sym]
      @operations[name.snakecase.to_sym] = { :action => name, :input => name }
    end
  end
end

#parse_operations_parametersObject


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

def parse_operations_parameters
  root_elements = document.xpath("wsdl:definitions/wsdl:types/*[local-name()='schema']/*[local-name()='element']", 'wsdl' => WSDL).each do |element|
    name = element.attribute('name').to_s.snakecase.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


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

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


105
106
107
108
# File 'lib/wasabi/parser.rb', line 105

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


95
96
97
98
# File 'lib/wasabi/parser.rb', line 95

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

#parse_typesObject


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

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


88
89
90
91
92
93
# File 'lib/wasabi/parser.rb', line 88

def parse_url(url)
  unescaped_url = URI.unescape(url.to_s)
  escaped_url   = URI.escape(unescaped_url)
  URI.parse(escaped_url)
rescue URI::InvalidURIError
end

#process_type(namespace, type, name) ⇒ Object


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

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


293
294
295
296
# File 'lib/wasabi/parser.rb', line 293

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

#section(section_name) ⇒ Object


303
304
305
# File 'lib/wasabi/parser.rb', line 303

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

#sectionsObject


307
308
309
310
311
312
313
314
315
316
# File 'lib/wasabi/parser.rb', line 307

def sections
  return @sections if @sections

  sections = {}
  document.root.element_children.each do |node|
    (sections[node.name] ||= []) << node
  end

  @sections = sections
end

#serviceObject


298
299
300
301
# File 'lib/wasabi/parser.rb', line 298

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