lolsoap 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md CHANGED
@@ -36,7 +36,9 @@ client = LolSoap::Client.new(File.read('lolapi.wsdl'))
36
36
  request = client.request('getLols')
37
37
 
38
38
  # Populate the request with some data. Namespacing is taken care of
39
- # using the type data from the WSDL.
39
+ # using the type data from the WSDL. The WSDL also tells us whether
40
+ # a given name (e.g. lolFactor below) should be treated as an
41
+ # attribute or a sub-element.
40
42
  request.body do |b|
41
43
  b.lolFactor '11'
42
44
  b.lolDuration 'lolever'
@@ -65,10 +67,11 @@ p response.body_hash
65
67
 
66
68
  ## Bugs/Features ##
67
69
 
68
- * SOAP 1.1 is not supported. Patches to add support will be considered
69
- if they don't add too much extra complexity.
70
70
  * WSSE is not supported.
71
71
  * Assumes that you are able to supply a WSDL document for the service.
72
+ * Some of the finer details of namespace handling may be glossed over.
73
+ This is just pragmatism; patches to improve namespace handling are
74
+ welcome.
72
75
 
73
76
  ## Overview ##
74
77
 
@@ -100,6 +103,24 @@ The others:
100
103
 
101
104
  Development sponsored by [Loco2](http://loco2.com/).
102
105
 
106
+ ## Changelog ##
107
+
108
+ ### 0.2 ###
109
+
110
+ * SOAP 1.1 support
111
+ * Better handling of namespaces in the XML schema. You should now be
112
+ able to have two types a:foo and b:foo and it will Just Work. This
113
+ introduces some API incompatibilities with version 1.1. Specifically,
114
+ `LolSoap::WSDL#types` is now keyed based on a prefixed type name
115
+ rather than an unprefixed type name. This shouldn't affect you if
116
+ you're not using `LolSoap::WSDL#types` directly.
117
+ * Add support for building attributes with `LolSoap::Builder` based on
118
+ the XML Schema information.
119
+
120
+ ### 0.1 ###
121
+
122
+ Initial release
123
+
103
124
  ## License ##
104
125
 
105
126
  (The MIT License)
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.4
1
+ 0.2.0
@@ -55,6 +55,10 @@ module LolSoap
55
55
  __prefixed_tag__(@type.prefix, @type.sub_type(name.to_s), name, *args, &block)
56
56
  end
57
57
 
58
+ def __attribute__(name, value)
59
+ @node[name.to_s] = value.to_s
60
+ end
61
+
58
62
  # @private
59
63
  def __prefixed_tag__(prefix, sub_type, name, *args)
60
64
  sub_node = @node.document.create_element(name.to_s, *args)
@@ -88,6 +92,13 @@ module LolSoap
88
92
 
89
93
  private
90
94
 
91
- alias method_missing __tag__
95
+ # alias method_missing __tag__
96
+ def method_missing(name, *args, &block)
97
+ if @type.has_attribute?(name.to_s)
98
+ __attribute__(name, *args)
99
+ else
100
+ __tag__(name, *args, &block)
101
+ end
102
+ end
92
103
  end
93
104
  end
@@ -6,10 +6,10 @@ module LolSoap
6
6
  attr_reader :wsdl, :operation, :doc
7
7
 
8
8
  # @private
9
- SOAP_PREFIX = 'soap'
9
+ SOAP_1_1 = 'http://schemas.xmlsoap.org/soap/envelope/'
10
10
 
11
11
  # @private
12
- SOAP_NAMESPACE = 'http://www.w3.org/2003/05/soap-envelope'
12
+ SOAP_1_2 = 'http://www.w3.org/2003/05/soap-envelope'
13
13
 
14
14
  def initialize(wsdl, operation, doc = Nokogiri::XML::Document.new)
15
15
  @wsdl = wsdl
@@ -54,16 +54,20 @@ module LolSoap
54
54
  operation.output
55
55
  end
56
56
 
57
- def to_xml
58
- doc.to_xml
57
+ def to_xml(options = {})
58
+ doc.to_xml(options)
59
59
  end
60
60
 
61
61
  def soap_prefix
62
- SOAP_PREFIX
62
+ 'soap'
63
63
  end
64
64
 
65
65
  def soap_namespace
66
- SOAP_NAMESPACE
66
+ soap_version == '1.2' ? SOAP_1_2 : SOAP_1_1
67
+ end
68
+
69
+ def soap_version
70
+ wsdl.soap_version
67
71
  end
68
72
 
69
73
  private
data/lib/lolsoap/fault.rb CHANGED
@@ -11,16 +11,29 @@ module LolSoap
11
11
  request.soap_namespace
12
12
  end
13
13
 
14
+ def soap_version
15
+ request.soap_version
16
+ end
17
+
14
18
  def code
15
- node.at_xpath('./soap:Code/soap:Value', 'soap' => soap_namespace).text.to_s
19
+ node.at_xpath(
20
+ soap_version == '1.2' ? './soap:Code/soap:Value' : './soap:faultcode',
21
+ 'soap' => soap_namespace
22
+ ).text.to_s
16
23
  end
17
24
 
18
25
  def reason
19
- node.at_xpath('./soap:Reason/soap:Text', 'soap' => soap_namespace).text.to_s
26
+ node.at_xpath(
27
+ soap_version == '1.2' ? './soap:Reason/soap:Text' : './soap:faultstring',
28
+ 'soap' => soap_namespace
29
+ ).text.to_s
20
30
  end
21
31
 
22
32
  def detail
23
- node.at_xpath('./soap:Detail/*', 'soap' => soap_namespace).to_xml
33
+ node.at_xpath(
34
+ soap_version == '1.2' ? './soap:Detail/*' : './soap:detail/*',
35
+ 'soap' => soap_namespace
36
+ ).to_xml
24
37
  end
25
38
  end
26
39
  end
@@ -1,10 +1,12 @@
1
1
  module LolSoap
2
2
  # Represents a HTTP request containing a SOAP Envelope
3
3
  class Request
4
- attr_reader :envelope
4
+ attr_reader :envelope
5
+ attr_accessor :xml_options
5
6
 
6
7
  def initialize(envelope)
7
- @envelope = envelope
8
+ @envelope = envelope
9
+ @xml_options = {}
8
10
  end
9
11
 
10
12
  # @see Envelope#body
@@ -22,6 +24,11 @@ module LolSoap
22
24
  envelope.soap_namespace
23
25
  end
24
26
 
27
+ # The SOAP version in use
28
+ def soap_version
29
+ envelope.soap_version
30
+ end
31
+
25
32
  # URL to be POSTed to
26
33
  def url
27
34
  envelope.endpoint
@@ -40,7 +47,11 @@ module LolSoap
40
47
  # The MIME type of the request. This is always application/soap+xml,
41
48
  # but it could be overridden in a subclass.
42
49
  def mime
43
- 'application/soap+xml'
50
+ if soap_version == '1.1'
51
+ 'text/xml'
52
+ else
53
+ 'application/soap+xml'
54
+ end
44
55
  end
45
56
 
46
57
  # The charset of the request. This is always UTF-8, but it could be
@@ -66,7 +77,7 @@ module LolSoap
66
77
 
67
78
  # The content to be sent in the HTTP request
68
79
  def content
69
- @content ||= envelope.to_xml
80
+ @content ||= envelope.to_xml(xml_options)
70
81
  end
71
82
  end
72
83
  end
@@ -9,14 +9,14 @@ class LolSoap::WSDL
9
9
  @singular = singular
10
10
  end
11
11
 
12
- def type
13
- @type ||= wsdl.type(@type_name.split(':').last)
14
- end
15
-
16
12
  def singular?
17
13
  @singular == true
18
14
  end
19
15
 
16
+ def type
17
+ @type ||= wsdl.type(@type_name)
18
+ end
19
+
20
20
  def inspect
21
21
  "<#{self.class} name=#{name.inspect} type=#{@type_name.inspect}>"
22
22
  end
@@ -1,12 +1,14 @@
1
+ require 'set'
2
+
1
3
  class LolSoap::WSDL
2
4
  class Type
3
- attr_reader :name, :namespace
5
+ attr_reader :name, :prefix
4
6
 
5
- def initialize(wsdl, name, namespace, elements)
6
- @wsdl = wsdl
7
- @name = name
8
- @namespace = namespace
9
- @elements = elements
7
+ def initialize(name, prefix, elements, attributes)
8
+ @name = name
9
+ @prefix = prefix
10
+ @elements = elements
11
+ @attributes = Set.new(attributes)
10
12
  end
11
13
 
12
14
  def elements
@@ -21,18 +23,18 @@ class LolSoap::WSDL
21
23
  element(name).type
22
24
  end
23
25
 
24
- def prefix
25
- wsdl.prefixes[namespace]
26
+ def attributes
27
+ @attributes.to_a
26
28
  end
27
29
 
28
- def inspect
29
- "<#{self.class} " \
30
- "name=#{(prefix + ':' + name).inspect} " \
31
- "elements=#{elements.inspect}>"
30
+ def has_attribute?(name)
31
+ @attributes.include?(name)
32
32
  end
33
33
 
34
- private
35
-
36
- def wsdl; @wsdl; end
34
+ def inspect
35
+ "<#{self.class} name=\"#{prefix}:#{name}\" " \
36
+ "elements=#{elements.inspect} " \
37
+ "attributes=#{attributes.inspect}>"
38
+ end
37
39
  end
38
40
  end
data/lib/lolsoap/wsdl.rb CHANGED
@@ -13,75 +13,83 @@ module LolSoap
13
13
  new(WSDLParser.parse(raw))
14
14
  end
15
15
 
16
- attr_reader :parser
16
+ # The SOAP endpoint URL
17
+ attr_reader :endpoint
18
+
19
+ # Hash of namespaces used in the WSDL document (keys are prefixes)
20
+ attr_reader :namespaces
21
+
22
+ # Hash of namespace prefixes used in the WSDL document (keys are namespace URIs)
23
+ attr_reader :prefixes
24
+
25
+ # The version of SOAP detected.
26
+ attr_reader :soap_version
17
27
 
18
28
  def initialize(parser)
19
- @parser = parser
29
+ @types = load_types(parser)
30
+ @operations = load_operations(parser)
31
+ @endpoint = parser.endpoint
32
+ @namespaces = parser.namespaces
33
+ @prefixes = parser.prefixes
34
+ @soap_version = parser.soap_version
20
35
  end
21
36
 
22
37
  # Hash of operations that are supports by the SOAP service
23
38
  def operations
24
- load_operations.dup
39
+ @operations.dup
25
40
  end
26
41
 
27
42
  # Get a single operation
28
43
  def operation(name)
29
- load_operations[name]
44
+ @operations.fetch(name)
30
45
  end
31
46
 
32
47
  # Hash of types declared by the service
33
48
  def types
34
- load_types.dup
49
+ @types.dup
35
50
  end
36
51
 
37
52
  # Get a single type, or a NullType if the type doesn't exist
38
53
  def type(name)
39
- load_types.fetch(name) { NullType.new }
40
- end
41
-
42
- # The SOAP endpoint URL
43
- def endpoint
44
- parser.endpoint
45
- end
46
-
47
- # Hash of namespaces used in the WSDL document (keys are prefixes)
48
- def namespaces
49
- parser.namespaces
50
- end
51
-
52
- # Hash of namespace prefixes used in the WSDL document (keys are namespace URIs)
53
- def prefixes
54
- namespaces.invert
54
+ @types.fetch(name) { NullType.new }
55
55
  end
56
56
 
57
57
  # Namespaces used by the types (a subset of #namespaces)
58
58
  def type_namespaces
59
- Hash[parser.types.map { |k, t| [prefixes[t[:namespace]], t[:namespace]] }]
59
+ Hash[@types.values.map { |type| [type.prefix, namespaces[type.prefix]] }]
60
60
  end
61
61
 
62
62
  def inspect
63
63
  "<#{self.class} " \
64
- "namespaces=#{namespaces.inspect} " \
65
- "operations=#{operations.keys.inspect} " \
66
- "types=#{types.keys.inspect}>"
64
+ "namespaces=#{@namespaces.inspect} " \
65
+ "operations=#{@operations.keys.inspect} " \
66
+ "types=#{@types.keys.inspect}>"
67
67
  end
68
68
 
69
69
  private
70
70
 
71
71
  # @private
72
- def load_operations
73
- @operations ||= Hash[
72
+ def load_operations(parser)
73
+ Hash[
74
74
  parser.operations.map do |k, op|
75
- [k, Operation.new(self, op[:action], type(op[:input][:name]), type(op[:output][:name]))]
75
+ [k, Operation.new(self, op[:action], type(op[:input]), type(op[:output]))]
76
76
  end
77
77
  ]
78
78
  end
79
79
 
80
80
  # @private
81
- def load_types
82
- @types ||= Hash[
83
- parser.types.map do |name, type|
84
- [name, Type.new(self, name, type[:namespace], build_elements(type[:elements]))]
81
+ def load_types(parser)
82
+ Hash[
83
+ parser.types.map do |prefixed_name, type|
84
+ [
85
+ prefixed_name,
86
+ Type.new(
87
+ type[:name],
88
+ type[:prefix],
89
+ build_elements(type[:elements]),
90
+ type[:attributes]
91
+ )
92
+ ]
85
93
  end
86
94
  ]
87
95
  end
@@ -3,11 +3,47 @@ require 'nokogiri'
3
3
  module LolSoap
4
4
  # @private
5
5
  class WSDLParser
6
- NS = {
7
- :wsdl => 'http://schemas.xmlsoap.org/wsdl/',
8
- :soap => 'http://schemas.xmlsoap.org/wsdl/soap12/',
9
- :xmlschema => 'http://www.w3.org/2001/XMLSchema'
10
- }
6
+ class Type
7
+ attr_reader :parser, :node, :target_namespace, :name, :prefix
8
+
9
+ def initialize(parser, node, target_namespace)
10
+ @parser = parser
11
+ @node = node
12
+ @target_namespace = target_namespace
13
+ @prefix, @name = prefix_and_name(node.attr('name'))
14
+ end
15
+
16
+ def name_with_prefix
17
+ "#{prefix}:#{name}"
18
+ end
19
+
20
+ def elements
21
+ Hash[
22
+ node.xpath('.//xs:element', parser.ns).map do |element|
23
+ max_occurs = element.attribute('maxOccurs').to_s
24
+
25
+ [
26
+ prefix_and_name(element.attr('name')).last,
27
+ {
28
+ :type => prefix_and_name(element.attr('type')).join(':'),
29
+ :singular => max_occurs.empty? || max_occurs == '1'
30
+ }
31
+ ]
32
+ end
33
+ ]
34
+ end
35
+
36
+ def attributes
37
+ node.xpath('.//xs:attribute/@name', parser.ns).map(&:text)
38
+ end
39
+
40
+ def prefix_and_name(string)
41
+ parser.prefix_and_name(string, target_namespace)
42
+ end
43
+ end
44
+
45
+ SOAP_1_1 = 'http://schemas.xmlsoap.org/wsdl/soap/'
46
+ SOAP_1_2 = 'http://schemas.xmlsoap.org/wsdl/soap12/'
11
47
 
12
48
  attr_reader :doc
13
49
 
@@ -27,79 +63,76 @@ module LolSoap
27
63
  end
28
64
  end
29
65
 
66
+ # We invert the hash in a deterministic way so that the results are repeatable.
67
+ def prefixes
68
+ @prefixes ||= Hash[namespaces.sort_by { |k, v| k }.uniq { |k, v| v }].invert
69
+ end
70
+
30
71
  def endpoint
31
- @endpoint ||= doc.at_xpath(
32
- '/d:definitions/d:service/d:port/soap:address/@location',
33
- 'd' => NS[:wsdl], 'soap' => NS[:soap]
34
- ).to_s
72
+ @endpoint ||= doc.at_xpath('/d:definitions/d:service/d:port/s:address/@location', ns).to_s
73
+ end
74
+
75
+ def schemas
76
+ doc.xpath('/d:definitions/d:types/xs:schema', ns)
35
77
  end
36
78
 
37
79
  def types
38
80
  @types ||= begin
39
- types = doc.xpath(
40
- '/d:definitions/d:types/s:schema/s:element[@name]',
41
- '/d:definitions/d:types/s:schema/s:complexType[@name]',
42
- 'd' => NS[:wsdl], 's' => NS[:xmlschema]
43
- )
44
- Hash[
45
- types.map do |type|
46
- namespace = type.at_xpath('ancestor::s:schema/@targetNamespace', 's' => NS[:xmlschema]).to_s
47
- elements = type.xpath('.//s:element', 's' => NS[:xmlschema])
48
- name = type.attribute('name').to_s
49
-
50
- [
51
- name,
52
- {
53
- :name => name,
54
- :namespace => namespace,
55
- :elements => Hash[elements.map { |e| [e.attribute('name').to_s, element_hash(e)] }]
56
- }
57
- ]
81
+ types = {}
82
+ schemas.each do |schema|
83
+ target_namespace = schema.attr('targetNamespace').to_s
84
+
85
+ schema.xpath('xs:element[@name] | xs:complexType[@name]', ns).each do |node|
86
+ type = Type.new(self, node, target_namespace)
87
+
88
+ types[type.name_with_prefix] = {
89
+ :name => type.name,
90
+ :prefix => type.prefix,
91
+ :elements => type.elements,
92
+ :attributes => type.attributes
93
+ }
58
94
  end
59
- ]
95
+ end
96
+ types
60
97
  end
61
98
  end
62
99
 
63
100
  def messages
64
101
  @messages ||= Hash[
65
- doc.xpath('/d:definitions/d:message', 'd' => NS[:wsdl]).map do |msg|
66
- element = msg.at_xpath('./d:part/@element', 'd' => NS[:wsdl]).to_s
67
- [msg.attribute('name').to_s, types[element.split(':').last]]
102
+ doc.xpath('/d:definitions/d:message', ns).map do |msg|
103
+ element = msg.at_xpath('./d:part/@element', ns).to_s
104
+ [msg.attribute('name').to_s, prefix_and_name(element).join(':')]
68
105
  end
69
106
  ]
70
107
  end
71
108
 
72
109
  def port_type_operations
73
110
  @port_type_operations ||= Hash[
74
- doc.xpath('/d:definitions/d:portType/d:operation', 'd' => NS[:wsdl]).map do |op|
75
- input = op.at_xpath('./d:input/@message', 'd' => NS[:wsdl]).to_s.split(':').last
76
- output = op.at_xpath('./d:output/@message', 'd' => NS[:wsdl]).to_s.split(':').last
111
+ doc.xpath('/d:definitions/d:portType/d:operation', ns).map do |op|
112
+ input = op.at_xpath('./d:input/@message', ns).to_s.split(':').last
113
+ output = op.at_xpath('./d:output/@message', ns).to_s.split(':').last
77
114
  name = op.attribute('name').to_s
78
115
 
79
- [name, { :name => name, :input => messages[input], :output => messages[output] }]
116
+ [name, { :input => messages.fetch(input), :output => messages.fetch(output) }]
80
117
  end
81
118
  ]
82
119
  end
83
120
 
84
121
  def operations
85
122
  @operations ||= begin
86
- binding = doc.at_xpath(
87
- '/d:definitions/d:service/d:port/soap:address/../@binding',
88
- 'd' => NS[:wsdl], 'soap' => NS[:soap]
89
- ).to_s.split(':').last
123
+ binding = doc.at_xpath('/d:definitions/d:service/d:port/s:address/../@binding', ns).to_s.split(':').last
90
124
 
91
125
  Hash[
92
- doc.xpath("/d:definitions/d:binding[@name='#{binding}']/d:operation", 'd' => NS[:wsdl]).map do |op|
126
+ doc.xpath("/d:definitions/d:binding[@name='#{binding}']/d:operation", ns).map do |op|
93
127
  name = op.attribute('name').to_s
94
- action = op.at_xpath('./soap:operation/@soapAction', 'soap' => NS[:soap]).to_s
128
+ action = op.at_xpath('./s:operation/@soapAction', ns).to_s
95
129
 
96
130
  [
97
131
  name,
98
132
  {
99
- :name => name,
100
133
  :action => action,
101
- :input => port_type_operations[name][:input],
102
- :output => port_type_operations[name][:output]
134
+ :input => port_type_operations.fetch(name)[:input],
135
+ :output => port_type_operations.fetch(name)[:output]
103
136
  }
104
137
  ]
105
138
  end
@@ -107,15 +140,30 @@ module LolSoap
107
140
  end
108
141
  end
109
142
 
110
- private
143
+ def soap_version
144
+ @soap_version ||= namespaces.values.include?(SOAP_1_2) ? '1.2' : '1.1'
145
+ end
111
146
 
112
- def element_hash(el)
113
- max_occurs = el.attribute('maxOccurs').to_s
114
- {
115
- :name => el.attribute('name').to_s,
116
- :type => el.attribute('type').to_s,
117
- :singular => max_occurs.empty? || max_occurs == '1'
147
+ def ns
148
+ @ns ||= {
149
+ 'd' => 'http://schemas.xmlsoap.org/wsdl/',
150
+ 'xs' => 'http://www.w3.org/2001/XMLSchema',
151
+ 's' => soap_version == '1.2' ? SOAP_1_2 : SOAP_1_1
118
152
  }
119
153
  end
154
+
155
+ def prefix_and_name(prefixed_name, default_namespace = nil)
156
+ prefix, name = prefixed_name.to_s.split(':')
157
+
158
+ if name
159
+ # Ensure we always use the same prefix for a given namespace
160
+ prefix = prefixes.fetch(namespaces.fetch(prefix))
161
+ else
162
+ name = prefix
163
+ prefix = prefixes.fetch(default_namespace)
164
+ end
165
+
166
+ [prefix, name]
167
+ end
120
168
  end
121
169
  end
data/lolsoap.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "lolsoap"
8
- s.version = "0.1.4"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jon Leighton"]
12
- s.date = "2012-05-17"
12
+ s.date = "2013-05-15"
13
13
  s.description = "A library for dealing with SOAP requests and responses. We tear our hair out so you don't have to."
14
14
  s.email = "j@jonathanleighton.com"
15
15
  s.extra_rdoc_files = [
@@ -45,6 +45,7 @@ Gem::Specification.new do |s|
45
45
  "lolsoap.gemspec",
46
46
  "test/fixtures/stock_quote.wsdl",
47
47
  "test/fixtures/stock_quote_fault.xml",
48
+ "test/fixtures/stock_quote_fault_soap_1_1.xml",
48
49
  "test/fixtures/stock_quote_response.xml",
49
50
  "test/helper.rb",
50
51
  "test/integration/test_client.rb",
@@ -67,7 +68,7 @@ Gem::Specification.new do |s|
67
68
  s.homepage = "http://github.com/loco2/lolsoap"
68
69
  s.licenses = ["MIT"]
69
70
  s.require_paths = ["lib"]
70
- s.rubygems_version = "1.8.15"
71
+ s.rubygems_version = "1.8.24"
71
72
  s.summary = "A library for dealing with SOAP requests and responses."
72
73
 
73
74
  if s.respond_to? :specification_version then
@@ -6,22 +6,25 @@
6
6
  xmlns:xsd1="http://example.com/stockquote.xsd"
7
7
  xmlns:xsd2="http://example.com/stockquote2.xsd"
8
8
  xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap12/"
9
+ xmlns:xs="http://www.w3.org/2001/XMLSchema"
9
10
  xmlns="http://schemas.xmlsoap.org/wsdl/">
10
11
 
11
12
  <types>
12
13
  <schema targetNamespace="http://example.com/stockquote.xsd"
14
+ xmlns:xsd3="http://example.com/stockquote.xsd"
13
15
  xmlns="http://www.w3.org/2001/XMLSchema">
14
16
  <element name="TradePrice">
15
17
  <complexType>
16
18
  <all>
17
- <element name="price" type="float"/>
19
+ <element name="xsd3:price" type="xs:float"/>
18
20
  </all>
19
21
  </complexType>
20
22
  </element>
21
- <complexType name="TradePriceRequest">
23
+ <complexType name="xsd3:TradePriceRequest">
22
24
  <sequence>
23
- <element name="tickerSymbol" type="string" maxOccurs="5"/>
25
+ <element name="tickerSymbol" type="xs:string" maxOccurs="5"/>
24
26
  <element name="specialTickerSymbol" type="xsd2:TickerSymbol" maxOccurs="unbounded"/>
27
+ <attribute name="id" type="xs:string"/>
25
28
  </sequence>
26
29
  </complexType>
27
30
  </schema>
@@ -30,7 +33,7 @@
30
33
  xmlns="http://www.w3.org/2001/XMLSchema">
31
34
  <complexType name="TickerSymbol">
32
35
  <sequence>
33
- <element name="name" type="string" maxOccurs="1"/>
36
+ <element name="name" type="xs:string" maxOccurs="1"/>
34
37
  </sequence>
35
38
  </complexType>
36
39
  </schema>
@@ -41,7 +44,7 @@
41
44
  </message>
42
45
 
43
46
  <message name="GetLastTradePriceOutput">
44
- <part name="body" element="xsd1:TradePrice"/>
47
+ <part name="body" element="xsd3:TradePrice"/>
45
48
  </message>
46
49
 
47
50
  <portType name="StockQuotePortType">