Add missing :version tags to new defgroups and defcustoms
[bpt/emacs.git] / lisp / net / soap-client.el
CommitLineData
88ae2870 1;;;; soap-client.el -- Access SOAP web services from Emacs
16d2ff89 2
acaf905b 3;; Copyright (C) 2009-2012 Free Software Foundation, Inc.
16d2ff89 4
d733e817 5;; Author: Alexandru Harsanyi <AlexHarsanyi@gmail.com>
4e358705
GM
6;; Created: December, 2009
7;; Keywords: soap, web-services, comm, hypermedia
d733e817 8;; Package: soap-client
4e358705
GM
9;; Homepage: http://code.google.com/p/emacs-soap-client
10
11;; This file is part of GNU Emacs.
12
13;; GNU Emacs is free software: you can redistribute it and/or modify
16d2ff89
MA
14;; it under the terms of the GNU General Public License as published by
15;; the Free Software Foundation, either version 3 of the License, or
16;; (at your option) any later version.
17
4e358705 18;; GNU Emacs is distributed in the hope that it will be useful,
16d2ff89
MA
19;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21;; GNU General Public License for more details.
22
23;; You should have received a copy of the GNU General Public License
4e358705 24;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
16d2ff89
MA
25
26;;; Commentary:
88ae2870 27;;
16d2ff89
MA
28;; To use the SOAP client, you first need to load the WSDL document for the
29;; service you want to access, using `soap-load-wsdl-from-url'. A WSDL
30;; document describes the available operations of the SOAP service, how their
31;; parameters and responses are encoded. To invoke operations, you use the
32;; `soap-invoke' method passing it the WSDL, the service name, the operation
33;; you wish to invoke and any required parameters.
34;;
f6b1b0a8 35;; Ideally, the service you want to access will have some documentation about
16d2ff89
MA
36;; the operations it supports. If it does not, you can try using
37;; `soap-inspect' to browse the WSDL document and see the available operations
38;; and their parameters.
39;;
40
41;;; Code:
42
43(eval-when-compile (require 'cl))
44
45(require 'xml)
46(require 'warnings)
47(require 'url)
48(require 'url-http)
49(require 'url-util)
50(require 'mm-decode)
51
52(defsubst soap-warning (message &rest args)
53 "Display a warning MESSAGE with ARGS, using the 'soap-client warning type."
54 (display-warning 'soap-client (apply 'format message args) :warning))
55
56(defgroup soap-client nil
57 "Access SOAP web services from Emacs."
2bed3f04 58 :version "24.1"
16d2ff89
MA
59 :group 'tools)
60
61;;;; Support for parsing XML documents with namespaces
62
63;; XML documents with namespaces are difficult to parse because the names of
64;; the nodes depend on what "xmlns" aliases have been defined in the document.
65;; To work with such documents, we introduce a translation layer between a
66;; "well known" namespace tag and the local namespace tag in the document
67;; being parsed.
68
274c2d34 69(defconst soap-well-known-xmlns
16d2ff89
MA
70 '(("apachesoap" . "http://xml.apache.org/xml-soap")
71 ("soapenc" . "http://schemas.xmlsoap.org/soap/encoding/")
72 ("wsdl" . "http://schemas.xmlsoap.org/wsdl/")
73 ("wsdlsoap" . "http://schemas.xmlsoap.org/wsdl/soap/")
74 ("xsd" . "http://www.w3.org/2001/XMLSchema")
75 ("xsi" . "http://www.w3.org/2001/XMLSchema-instance")
76 ("soap" . "http://schemas.xmlsoap.org/soap/envelope/")
77 ("soap12" . "http://schemas.xmlsoap.org/wsdl/soap12/")
78 ("http" . "http://schemas.xmlsoap.org/wsdl/http/")
79 ("mime" . "http://schemas.xmlsoap.org/wsdl/mime/"))
80 "A list of well known xml namespaces and their aliases.")
81
274c2d34 82(defvar soap-local-xmlns nil
16d2ff89
MA
83 "A list of local namespace aliases.
84This is a dynamically bound variable, controlled by
85`soap-with-local-xmlns'.")
86
274c2d34 87(defvar soap-default-xmlns nil
16d2ff89
MA
88 "The default XML namespaces.
89Names in this namespace will be unqualified. This is a
90dynamically bound variable, controlled by
91`soap-with-local-xmlns'")
92
274c2d34 93(defvar soap-target-xmlns nil
16d2ff89
MA
94 "The target XML namespace.
95New XSD elements will be defined in this namespace, unless they
96are fully qualified for a different namespace. This is a
97dynamically bound variable, controlled by
98`soap-with-local-xmlns'")
99
100(defun soap-wk2l (well-known-name)
101 "Return local variant of WELL-KNOWN-NAME.
102This is done by looking up the namespace in the
274c2d34 103`soap-well-known-xmlns' table and resolving the namespace to
16d2ff89 104the local name based on the current local translation table
274c2d34 105`soap-local-xmlns'. See also `soap-with-local-xmlns'."
16d2ff89
MA
106 (let ((wk-name-1 (if (symbolp well-known-name)
107 (symbol-name well-known-name)
108 well-known-name)))
109 (cond
110 ((string-match "^\\(.*\\):\\(.*\\)$" wk-name-1)
111 (let ((ns (match-string 1 wk-name-1))
112 (name (match-string 2 wk-name-1)))
274c2d34
MA
113 (let ((namespace (cdr (assoc ns soap-well-known-xmlns))))
114 (cond ((equal namespace soap-default-xmlns)
16d2ff89
MA
115 ;; Name is unqualified in the default namespace
116 (if (symbolp well-known-name)
117 (intern name)
118 name))
119 (t
274c2d34 120 (let* ((local-ns (car (rassoc namespace soap-local-xmlns)))
16d2ff89
MA
121 (local-name (concat local-ns ":" name)))
122 (if (symbolp well-known-name)
123 (intern local-name)
124 local-name)))))))
125 (t well-known-name))))
126
127(defun soap-l2wk (local-name)
128 "Convert LOCAL-NAME into a well known name.
129The namespace of LOCAL-NAME is looked up in the
274c2d34 130`soap-well-known-xmlns' table and a well known namespace tag is
16d2ff89
MA
131used in the name.
132
133nil is returned if there is no well-known namespace for the
134namespace of LOCAL-NAME."
135 (let ((l-name-1 (if (symbolp local-name)
136 (symbol-name local-name)
137 local-name))
138 namespace name)
139 (cond
140 ((string-match "^\\(.*\\):\\(.*\\)$" l-name-1)
141 (setq name (match-string 2 l-name-1))
142 (let ((ns (match-string 1 l-name-1)))
274c2d34 143 (setq namespace (cdr (assoc ns soap-local-xmlns)))
16d2ff89
MA
144 (unless namespace
145 (error "Soap-l2wk(%s): no namespace for alias %s" local-name ns))))
146 (t
147 (setq name l-name-1)
274c2d34 148 (setq namespace soap-default-xmlns)))
16d2ff89
MA
149
150 (if namespace
274c2d34 151 (let ((well-known-ns (car (rassoc namespace soap-well-known-xmlns))))
16d2ff89
MA
152 (if well-known-ns
153 (let ((well-known-name (concat well-known-ns ":" name)))
154 (if (symbol-name local-name)
155 (intern well-known-name)
156 well-known-name))
157 (progn
158 ;; (soap-warning "soap-l2wk(%s): namespace %s has no well-known tag"
159 ;; local-name namespace)
160 nil)))
161 ;; if no namespace is defined, just return the unqualified name
162 name)))
88ae2870 163
16d2ff89
MA
164
165(defun soap-l2fq (local-name &optional use-tns)
166 "Convert LOCAL-NAME into a fully qualified name.
167A fully qualified name is a cons of the namespace name and the
168name of the element itself. For example \"xsd:string\" is
88ae2870 169converted to \(\"http://www.w3.org/2001/XMLSchema\" . \"string\"\).
16d2ff89
MA
170
171The USE-TNS argument specifies what to do when LOCAL-NAME has no
274c2d34 172namespace tag. If USE-TNS is non-nil, the `soap-target-xmlns'
16d2ff89 173will be used as the element's namespace, otherwise
274c2d34 174`soap-default-xmlns' will be used.
16d2ff89
MA
175
176This is needed because different parts of a WSDL document can use
177different namespace aliases for the same element."
178 (let ((local-name-1 (if (symbolp local-name)
179 (symbol-name local-name)
180 local-name)))
181 (cond ((string-match "^\\(.*\\):\\(.*\\)$" local-name-1)
182 (let ((ns (match-string 1 local-name-1))
183 (name (match-string 2 local-name-1)))
274c2d34 184 (let ((namespace (cdr (assoc ns soap-local-xmlns))))
16d2ff89
MA
185 (if namespace
186 (cons namespace name)
187 (error "Soap-l2fq(%s): unknown alias %s" local-name ns)))))
188 (t
189 (cons (if use-tns
274c2d34
MA
190 soap-target-xmlns
191 soap-default-xmlns)
16d2ff89
MA
192 local-name)))))
193
194(defun soap-extract-xmlns (node &optional xmlns-table)
195 "Return a namespace alias table for NODE by extending XMLNS-TABLE."
196 (let (xmlns default-ns target-ns)
197 (dolist (a (xml-node-attributes node))
198 (let ((name (symbol-name (car a)))
199 (value (cdr a)))
200 (cond ((string= name "targetNamespace")
201 (setq target-ns value))
202 ((string= name "xmlns")
203 (setq default-ns value))
204 ((string-match "^xmlns:\\(.*\\)$" name)
205 (push (cons (match-string 1 name) value) xmlns)))))
88ae2870 206
16d2ff89
MA
207 (let ((tns (assoc "tns" xmlns)))
208 (cond ((and tns target-ns)
88ae2870
MA
209 ;; If a tns alias is defined for this node, it must match
210 ;; the target namespace.
16d2ff89 211 (unless (equal target-ns (cdr tns))
88ae2870
MA
212 (soap-warning
213 "soap-extract-xmlns(%s): tns alias and targetNamespace mismatch"
214 (xml-node-name node))))
16d2ff89
MA
215 ((and tns (not target-ns))
216 (setq target-ns (cdr tns)))
217 ((and (not tns) target-ns)
218 ;; a tns alias was not defined in this node. See if the node has
219 ;; a "targetNamespace" attribute and add an alias to this. Note
220 ;; that we might override an existing tns alias in XMLNS-TABLE,
221 ;; but that is intended.
222 (push (cons "tns" target-ns) xmlns))))
88ae2870 223
16d2ff89
MA
224 (list default-ns target-ns (append xmlns xmlns-table))))
225
226(defmacro soap-with-local-xmlns (node &rest body)
227 "Install a local alias table from NODE and execute BODY."
228 (declare (debug (form &rest form)) (indent 1))
229 (let ((xmlns (make-symbol "xmlns")))
274c2d34
MA
230 `(let ((,xmlns (soap-extract-xmlns ,node soap-local-xmlns)))
231 (let ((soap-default-xmlns (or (nth 0 ,xmlns) soap-default-xmlns))
232 (soap-target-xmlns (or (nth 1 ,xmlns) soap-target-xmlns))
233 (soap-local-xmlns (nth 2 ,xmlns)))
16d2ff89
MA
234 ,@body))))
235
236(defun soap-get-target-namespace (node)
237 "Return the target namespace of NODE.
238This is the namespace in which new elements will be defined."
239 (or (xml-get-attribute-or-nil node 'targetNamespace)
274c2d34
MA
240 (cdr (assoc "tns" soap-local-xmlns))
241 soap-target-xmlns))
16d2ff89
MA
242
243(defun soap-xml-get-children1 (node child-name)
244 "Return the children of NODE named CHILD-NAME.
245This is the same as `xml-get-children', but CHILD-NAME can have
246namespace tag."
247 (let (result)
248 (dolist (c (xml-node-children node))
249 (when (and (consp c)
250 (soap-with-local-xmlns c
251 ;; We use `ignore-errors' here because we want to silently
252 ;; skip nodes for which we cannot convert them to a
253 ;; well-known name.
88ae2870
MA
254 (eq (ignore-errors (soap-l2wk (xml-node-name c)))
255 child-name)))
16d2ff89
MA
256 (push c result)))
257 (nreverse result)))
258
259(defun soap-xml-get-attribute-or-nil1 (node attribute)
260 "Return the NODE's ATTRIBUTE, or nil if it does not exist.
261This is the same as `xml-get-attribute-or-nil', but ATTRIBUTE can
262be tagged with a namespace tag."
263 (catch 'found
264 (soap-with-local-xmlns node
265 (dolist (a (xml-node-attributes node))
266 ;; We use `ignore-errors' here because we want to silently skip
267 ;; attributes for which we cannot convert them to a well-known name.
268 (when (eq (ignore-errors (soap-l2wk (car a))) attribute)
269 (throw 'found (cdr a)))))))
270
271\f
272;;;; XML namespaces
273
274;; An element in an XML namespace, "things" stored in soap-xml-namespaces will
275;; be derived from this object.
276
277(defstruct soap-element
278 name
279 ;; The "well-known" namespace tag for the element. For example, while
280 ;; parsing XML documents, we can have different tags for the XMLSchema
281 ;; namespace, but internally all our XMLSchema elements will have the "xsd"
282 ;; tag.
283 namespace-tag)
284
285(defun soap-element-fq-name (element)
286 "Return a fully qualified name for ELEMENT.
287A fq name is the concatenation of the namespace tag and the
288element name."
289 (concat (soap-element-namespace-tag element)
290 ":" (soap-element-name element)))
291
292;; a namespace link stores an alias for an object in once namespace to a
293;; "target" object possibly in a different namespace
294
295(defstruct (soap-namespace-link (:include soap-element))
296 target)
297
298;; A namespace is a collection of soap-element objects under a name (the name
299;; of the namespace).
300
301(defstruct soap-namespace
302 (name nil :read-only t) ; e.g "http://xml.apache.org/xml-soap"
303 (elements (make-hash-table :test 'equal) :read-only t))
304
305(defun soap-namespace-put (element ns)
306 "Store ELEMENT in NS.
307Multiple elements with the same name can be stored in a
308namespace. When retrieving the element you can specify a
309discriminant predicate to `soap-namespace-get'"
310 (let ((name (soap-element-name element)))
311 (push element (gethash name (soap-namespace-elements ns)))))
312
313(defun soap-namespace-put-link (name target ns &optional replace)
314 "Store a link from NAME to TARGET in NS.
315An error will be signaled if an element by the same name is
316already present in NS, unless REPLACE is non nil.
317
318TARGET can be either a SOAP-ELEMENT or a string denoting an
319element name into another namespace.
320
321If NAME is nil, an element with the same name as TARGET will be
322added to the namespace."
323
324 (unless (and name (not (equal name "")))
325 ;; if name is nil, use TARGET as a name...
326 (cond ((soap-element-p target)
327 (setq name (soap-element-name target)))
d733e817
MA
328 ((consp target) ; a fq name: (namespace . name)
329 (setq name (cdr target)))
16d2ff89
MA
330 ((stringp target)
331 (cond ((string-match "^\\(.*\\):\\(.*\\)$" target)
332 (setq name (match-string 2 target)))
333 (t
334 (setq name target))))))
335
d733e817
MA
336 ;; by now, name should be valid
337 (assert (and name (not (equal name "")))
338 nil
339 "Cannot determine name for namespace link")
16d2ff89
MA
340 (push (make-soap-namespace-link :name name :target target)
341 (gethash name (soap-namespace-elements ns))))
342
343(defun soap-namespace-get (name ns &optional discriminant-predicate)
344 "Retrieve an element with NAME from the namespace NS.
345If multiple elements with the same name exist,
346DISCRIMINANT-PREDICATE is used to pick one of them. This allows
347storing elements of different types (like a message type and a
348binding) but the same name."
349 (assert (stringp name))
350 (let ((elements (gethash name (soap-namespace-elements ns))))
351 (cond (discriminant-predicate
352 (catch 'found
353 (dolist (e elements)
354 (when (funcall discriminant-predicate e)
355 (throw 'found e)))))
356 ((= (length elements) 1) (car elements))
357 ((> (length elements) 1)
88ae2870
MA
358 (error
359 "Soap-namespace-get(%s): multiple elements, discriminant needed"
360 name))
16d2ff89
MA
361 (t
362 nil))))
363
364\f
365;;;; WSDL documents
366;;;;; WSDL document elements
367
368(defstruct (soap-basic-type (:include soap-element))
369 kind ; a symbol of: string, dateTime, long, int
370 )
371
372(defstruct soap-sequence-element
373 name type nillable? multiple?)
374
375(defstruct (soap-sequence-type (:include soap-element))
376 parent ; OPTIONAL WSDL-TYPE name
8350f087 377 elements ; LIST of SOAP-SEQUENCE-ELEMENT
16d2ff89
MA
378 )
379
380(defstruct (soap-array-type (:include soap-element))
381 element-type ; WSDL-TYPE of the array elements
382 )
383
384(defstruct (soap-message (:include soap-element))
385 parts ; ALIST of NAME => WSDL-TYPE name
386 )
387
388(defstruct (soap-operation (:include soap-element))
389 parameter-order
390 input ; (NAME . MESSAGE)
391 output ; (NAME . MESSAGE)
392 faults) ; a list of (NAME . MESSAGE)
393
394(defstruct (soap-port-type (:include soap-element))
395 operations) ; a namespace of operations
396
397;; A bound operation is an operation which has a soap action and a use
398;; method attached -- these are attached as part of a binding and we
399;; can have different bindings for the same operations.
400(defstruct soap-bound-operation
401 operation ; SOAP-OPERATION
402 soap-action ; value for SOAPAction HTTP header
88ae2870
MA
403 use ; 'literal or 'encoded, see
404 ; http://www.w3.org/TR/wsdl#_soap:body
16d2ff89
MA
405 )
406
407(defstruct (soap-binding (:include soap-element))
408 port-type
409 (operations (make-hash-table :test 'equal) :readonly t))
410
411(defstruct (soap-port (:include soap-element))
412 service-url
413 binding)
414
415(defun soap-default-xsd-types ()
416 "Return a namespace containing some of the XMLSchema types."
417 (let ((ns (make-soap-namespace :name "http://www.w3.org/2001/XMLSchema")))
418 (dolist (type '("string" "dateTime" "boolean" "long" "int" "float"
419 "base64Binary" "anyType" "Array" "byte[]"))
420 (soap-namespace-put
421 (make-soap-basic-type :name type :kind (intern type))
422 ns))
423 ns))
424
425(defun soap-default-soapenc-types ()
426 "Return a namespace containing some of the SOAPEnc types."
88ae2870
MA
427 (let ((ns (make-soap-namespace
428 :name "http://schemas.xmlsoap.org/soap/encoding/")))
16d2ff89
MA
429 (dolist (type '("string" "dateTime" "boolean" "long" "int" "float"
430 "base64Binary" "anyType" "Array" "byte[]"))
431 (soap-namespace-put
432 (make-soap-basic-type :name type :kind (intern type))
433 ns))
434 ns))
435
436(defun soap-type-p (element)
437 "Return t if ELEMENT is a SOAP data type (basic or complex)."
438 (or (soap-basic-type-p element)
439 (soap-sequence-type-p element)
440 (soap-array-type-p element)))
88ae2870 441
16d2ff89
MA
442
443;;;;; The WSDL document
444
445;; The WSDL data structure used for encoding/decoding SOAP messages
446(defstruct soap-wsdl
447 origin ; file or URL from which this wsdl was loaded
448 ports ; a list of SOAP-PORT instances
449 alias-table ; a list of namespace aliases
450 namespaces ; a list of namespaces
451 )
452
453(defun soap-wsdl-add-alias (alias name wsdl)
454 "Add a namespace ALIAS for NAME to the WSDL document."
455 (push (cons alias name) (soap-wsdl-alias-table wsdl)))
456
457(defun soap-wsdl-find-namespace (name wsdl)
458 "Find a namespace by NAME in the WSDL document."
459 (catch 'found
460 (dolist (ns (soap-wsdl-namespaces wsdl))
461 (when (equal name (soap-namespace-name ns))
462 (throw 'found ns)))))
463
464(defun soap-wsdl-add-namespace (ns wsdl)
465 "Add the namespace NS to the WSDL document.
466If a namespace by this name already exists in WSDL, individual
467elements will be added to it."
468 (let ((existing (soap-wsdl-find-namespace (soap-namespace-name ns) wsdl)))
469 (if existing
470 ;; Add elements from NS to EXISTING, replacing existing values.
471 (maphash (lambda (key value)
472 (dolist (v value)
473 (soap-namespace-put v existing)))
474 (soap-namespace-elements ns))
475 (push ns (soap-wsdl-namespaces wsdl)))))
476
477(defun soap-wsdl-get (name wsdl &optional predicate use-local-alias-table)
478 "Retrieve element NAME from the WSDL document.
479
480PREDICATE is used to differentiate between elements when NAME
481refers to multiple elements. A typical value for this would be a
482structure predicate for the type of element you want to retrieve.
483For example, to retrieve a message named \"foo\" when other
484elements named \"foo\" exist in the WSDL you could use:
485
486 (soap-wsdl-get \"foo\" WSDL 'soap-message-p)
487
274c2d34 488If USE-LOCAL-ALIAS-TABLE is not nil, `soap-local-xmlns` will be
16d2ff89
MA
489used to resolve the namespace alias."
490 (let ((alias-table (soap-wsdl-alias-table wsdl))
491 namespace element-name element)
492
493 (when (symbolp name)
494 (setq name (symbol-name name)))
495
496 (when use-local-alias-table
274c2d34 497 (setq alias-table (append soap-local-xmlns alias-table)))
88ae2870 498
16d2ff89
MA
499 (cond ((consp name) ; a fully qualified name, as returned by `soap-l2fq'
500 (setq element-name (cdr name))
501 (when (symbolp element-name)
502 (setq element-name (symbol-name element-name)))
503 (setq namespace (soap-wsdl-find-namespace (car name) wsdl))
504 (unless namespace
505 (error "Soap-wsdl-get(%s): unknown namespace: %s" name namespace)))
88ae2870 506
16d2ff89
MA
507 ((string-match "^\\(.*\\):\\(.*\\)$" name)
508 (setq element-name (match-string 2 name))
509
510 (let* ((ns-alias (match-string 1 name))
511 (ns-name (cdr (assoc ns-alias alias-table))))
512 (unless ns-name
88ae2870
MA
513 (error "Soap-wsdl-get(%s): cannot find namespace alias %s"
514 name ns-alias))
515
16d2ff89
MA
516 (setq namespace (soap-wsdl-find-namespace ns-name wsdl))
517 (unless namespace
88ae2870
MA
518 (error
519 "Soap-wsdl-get(%s): unknown namespace %s, referenced by alias %s"
520 name ns-name ns-alias))))
16d2ff89
MA
521 (t
522 (error "Soap-wsdl-get(%s): bad name" name)))
523
524 (setq element (soap-namespace-get
525 element-name namespace
526 (if predicate
527 (lambda (e)
528 (or (funcall 'soap-namespace-link-p e)
529 (funcall predicate e)))
530 nil)))
88ae2870 531
16d2ff89
MA
532 (unless element
533 (error "Soap-wsdl-get(%s): cannot find element" name))
88ae2870 534
16d2ff89
MA
535 (if (soap-namespace-link-p element)
536 ;; NOTE: don't use the local alias table here
537 (soap-wsdl-get (soap-namespace-link-target element) wsdl predicate)
538 element)))
539
540;;;;; Resolving references for wsdl types
541
542;; See `soap-wsdl-resolve-references', which is the main entry point for
543;; resolving references
544
545(defun soap-resolve-references-for-element (element wsdl)
546 "Resolve references in ELEMENT using the WSDL document.
547This is a generic function which invokes a specific function
548depending on the element type.
549
550If ELEMENT has no resolver function, it is silently ignored.
551
552All references are resolved in-place, that is the ELEMENT is
553updated."
554 (let ((resolver (get (aref element 0) 'soap-resolve-references)))
555 (when resolver
556 (funcall resolver element wsdl))))
557
558(defun soap-resolve-references-for-sequence-type (type wsdl)
559 "Resolve references for a sequence TYPE using WSDL document.
560See also `soap-resolve-references-for-element' and
561`soap-wsdl-resolve-references'"
562 (let ((parent (soap-sequence-type-parent type)))
563 (when (or (consp parent) (stringp parent))
564 (setf (soap-sequence-type-parent type)
565 (soap-wsdl-get parent wsdl 'soap-type-p))))
566 (dolist (element (soap-sequence-type-elements type))
567 (let ((element-type (soap-sequence-element-type element)))
568 (cond ((or (consp element-type) (stringp element-type))
569 (setf (soap-sequence-element-type element)
570 (soap-wsdl-get element-type wsdl 'soap-type-p)))
571 ((soap-element-p element-type)
572 ;; since the element already has a child element, it
573 ;; could be an inline structure. we must resolve
574 ;; references in it, because it might not be reached by
575 ;; scanning the wsdl names.
576 (soap-resolve-references-for-element element-type wsdl))))))
577
578(defun soap-resolve-references-for-array-type (type wsdl)
579 "Resolve references for an array TYPE using WSDL.
580See also `soap-resolve-references-for-element' and
581`soap-wsdl-resolve-references'"
582 (let ((element-type (soap-array-type-element-type type)))
583 (when (or (consp element-type) (stringp element-type))
584 (setf (soap-array-type-element-type type)
585 (soap-wsdl-get element-type wsdl 'soap-type-p)))))
586
587(defun soap-resolve-references-for-message (message wsdl)
588 "Resolve references for a MESSAGE type using the WSDL document.
589See also `soap-resolve-references-for-element' and
590`soap-wsdl-resolve-references'"
591 (let (resolved-parts)
592 (dolist (part (soap-message-parts message))
593 (let ((name (car part))
594 (type (cdr part)))
595 (when (stringp name)
596 (setq name (intern name)))
597 (when (or (consp type) (stringp type))
598 (setq type (soap-wsdl-get type wsdl 'soap-type-p)))
599 (push (cons name type) resolved-parts)))
600 (setf (soap-message-parts message) (nreverse resolved-parts))))
601
602(defun soap-resolve-references-for-operation (operation wsdl)
603 "Resolve references for an OPERATION type using the WSDL document.
604See also `soap-resolve-references-for-element' and
605`soap-wsdl-resolve-references'"
606 (let ((input (soap-operation-input operation))
607 (counter 0))
608 (let ((name (car input))
609 (message (cdr input)))
610 ;; Name this part if it was not named
611 (when (or (null name) (equal name ""))
612 (setq name (format "in%d" (incf counter))))
613 (when (or (consp message) (stringp message))
614 (setf (soap-operation-input operation)
88ae2870
MA
615 (cons (intern name)
616 (soap-wsdl-get message wsdl 'soap-message-p))))))
16d2ff89
MA
617
618 (let ((output (soap-operation-output operation))
619 (counter 0))
620 (let ((name (car output))
621 (message (cdr output)))
622 (when (or (null name) (equal name ""))
623 (setq name (format "out%d" (incf counter))))
624 (when (or (consp message) (stringp message))
625 (setf (soap-operation-output operation)
88ae2870
MA
626 (cons (intern name)
627 (soap-wsdl-get message wsdl 'soap-message-p))))))
16d2ff89
MA
628
629 (let ((resolved-faults nil)
630 (counter 0))
631 (dolist (fault (soap-operation-faults operation))
632 (let ((name (car fault))
633 (message (cdr fault)))
634 (when (or (null name) (equal name ""))
635 (setq name (format "fault%d" (incf counter))))
636 (if (or (consp message) (stringp message))
88ae2870
MA
637 (push (cons (intern name)
638 (soap-wsdl-get message wsdl 'soap-message-p))
16d2ff89
MA
639 resolved-faults)
640 (push fault resolved-faults))))
641 (setf (soap-operation-faults operation) resolved-faults))
642
643 (when (= (length (soap-operation-parameter-order operation)) 0)
644 (setf (soap-operation-parameter-order operation)
645 (mapcar 'car (soap-message-parts
646 (cdr (soap-operation-input operation))))))
88ae2870 647
16d2ff89
MA
648 (setf (soap-operation-parameter-order operation)
649 (mapcar (lambda (p)
650 (if (stringp p)
651 (intern p)
652 p))
653 (soap-operation-parameter-order operation))))
654
655(defun soap-resolve-references-for-binding (binding wsdl)
656 "Resolve references for a BINDING type using the WSDL document.
657See also `soap-resolve-references-for-element' and
658`soap-wsdl-resolve-references'"
659 (when (or (consp (soap-binding-port-type binding))
660 (stringp (soap-binding-port-type binding)))
661 (setf (soap-binding-port-type binding)
88ae2870
MA
662 (soap-wsdl-get (soap-binding-port-type binding)
663 wsdl 'soap-port-type-p)))
16d2ff89
MA
664
665 (let ((port-ops (soap-port-type-operations (soap-binding-port-type binding))))
666 (maphash (lambda (k v)
667 (setf (soap-bound-operation-operation v)
668 (soap-namespace-get k port-ops 'soap-operation-p)))
669 (soap-binding-operations binding))))
670
671(defun soap-resolve-references-for-port (port wsdl)
672 "Resolve references for a PORT type using the WSDL document.
673See also `soap-resolve-references-for-element' and
674`soap-wsdl-resolve-references'"
675 (when (or (consp (soap-port-binding port))
676 (stringp (soap-port-binding port)))
677 (setf (soap-port-binding port)
678 (soap-wsdl-get (soap-port-binding port) wsdl 'soap-binding-p))))
679
680;; Install resolvers for our types
681(progn
682 (put (aref (make-soap-sequence-type) 0) 'soap-resolve-references
683 'soap-resolve-references-for-sequence-type)
684 (put (aref (make-soap-array-type) 0) 'soap-resolve-references
685 'soap-resolve-references-for-array-type)
686 (put (aref (make-soap-message) 0) 'soap-resolve-references
687 'soap-resolve-references-for-message)
688 (put (aref (make-soap-operation) 0) 'soap-resolve-references
689 'soap-resolve-references-for-operation)
690 (put (aref (make-soap-binding) 0) 'soap-resolve-references
691 'soap-resolve-references-for-binding)
692 (put (aref (make-soap-port) 0) 'soap-resolve-references
693 'soap-resolve-references-for-port))
694
695(defun soap-wsdl-resolve-references (wsdl)
696 "Resolve all references inside the WSDL structure.
697
698When the WSDL elements are created from the XML document, they
699refer to each other by name. For example, the ELEMENT-TYPE slot
700of an SOAP-ARRAY-TYPE will contain the name of the element and
701the user would have to call `soap-wsdl-get' to obtain the actual
702element.
703
704After the entire document is loaded, we resolve all these
705references to the actual elements they refer to so that at
706runtime, we don't have to call `soap-wsdl-get' each time we
707traverse an element tree."
708 (let ((nprocessed 0)
709 (nstag-id 0)
710 (alias-table (soap-wsdl-alias-table wsdl)))
711 (dolist (ns (soap-wsdl-namespaces wsdl))
712 (let ((nstag (car-safe (rassoc (soap-namespace-name ns) alias-table))))
713 (unless nstag
714 ;; If this namespace does not have an alias, create one for it.
715 (catch 'done
716 (while t
717 (setq nstag (format "ns%d" (incf nstag-id)))
718 (unless (assoc nstag alias-table)
719 (soap-wsdl-add-alias nstag (soap-namespace-name ns) wsdl)
720 (throw 'done t)))))
721
722 (maphash (lambda (name element)
723 (cond ((soap-element-p element) ; skip links
724 (incf nprocessed)
725 (soap-resolve-references-for-element element wsdl)
726 (setf (soap-element-namespace-tag element) nstag))
727 ((listp element)
728 (dolist (e element)
729 (when (soap-element-p e)
730 (incf nprocessed)
731 (soap-resolve-references-for-element e wsdl)
732 (setf (soap-element-namespace-tag e) nstag))))))
c53dc7fc 733 (soap-namespace-elements ns)))))
16d2ff89
MA
734 wsdl)
735
736;;;;; Loading WSDL from XML documents
737
738(defun soap-load-wsdl-from-url (url)
739 "Load a WSDL document from URL and return it.
740The returned WSDL document needs to be used for `soap-invoke'
741calls."
742 (let ((url-request-method "GET")
743 (url-package-name "soap-client.el")
744 (url-package-version "1.0")
745 (url-mime-charset-string "utf-8;q=1, iso-8859-1;q=0.5")
746 (url-request-coding-system 'utf-8)
747 (url-http-attempt-keepalives nil))
748 (let ((buffer (url-retrieve-synchronously url)))
749 (with-current-buffer buffer
750 (declare (special url-http-response-status))
751 (if (> url-http-response-status 299)
752 (error "Error retrieving WSDL: %s" url-http-response-status))
753 (let ((mime-part (mm-dissect-buffer t t)))
754 (unless mime-part
755 (error "Failed to decode response from server"))
756 (unless (equal (car (mm-handle-type mime-part)) "text/xml")
757 (error "Server response is not an XML document"))
758 (with-temp-buffer
759 (mm-insert-part mime-part)
760 (let ((wsdl-xml (car (xml-parse-region (point-min) (point-max)))))
761 (prog1
762 (let ((wsdl (soap-parse-wsdl wsdl-xml)))
763 (setf (soap-wsdl-origin wsdl) url)
764 wsdl)
765 (kill-buffer buffer)))))))))
766
767(defun soap-load-wsdl (file)
768 "Load a WSDL document from FILE and return it."
769 (with-temp-buffer
770 (insert-file-contents file)
771 (let ((xml (car (xml-parse-region (point-min) (point-max)))))
772 (let ((wsdl (soap-parse-wsdl xml)))
773 (setf (soap-wsdl-origin wsdl) file)
774 wsdl))))
775
776(defun soap-parse-wsdl (node)
777 "Construct a WSDL structure from NODE, which is an XML document."
778 (soap-with-local-xmlns node
779
780 (assert (eq (soap-l2wk (xml-node-name node)) 'wsdl:definitions)
781 nil
782 "soap-parse-wsdl: expecting wsdl:definitions node, got %s"
783 (soap-l2wk (xml-node-name node)))
784
785 (let ((wsdl (make-soap-wsdl)))
786
787 ;; Add the local alias table to the wsdl document -- it will be used for
788 ;; all types in this document even after we finish parsing it.
274c2d34 789 (setf (soap-wsdl-alias-table wsdl) soap-local-xmlns)
16d2ff89
MA
790
791 ;; Add the XSD types to the wsdl document
792 (let ((ns (soap-default-xsd-types)))
793 (soap-wsdl-add-namespace ns wsdl)
794 (soap-wsdl-add-alias "xsd" (soap-namespace-name ns) wsdl))
795
796 ;; Add the soapenc types to the wsdl document
797 (let ((ns (soap-default-soapenc-types)))
798 (soap-wsdl-add-namespace ns wsdl)
799 (soap-wsdl-add-alias "soapenc" (soap-namespace-name ns) wsdl))
800
801 ;; Find all the 'xsd:schema nodes which are children of wsdl:types nodes
802 ;; and build our type-library
803
804 (let ((types (car (soap-xml-get-children1 node 'wsdl:types))))
805 (dolist (node (xml-node-children types))
806 ;; We cannot use (xml-get-children node (soap-wk2l 'xsd:schema))
807 ;; because each node can install its own alias type so the schema
808 ;; nodes might have a different prefix.
809 (when (consp node)
810 (soap-with-local-xmlns node
811 (when (eq (soap-l2wk (xml-node-name node)) 'xsd:schema)
812 (soap-wsdl-add-namespace (soap-parse-schema node) wsdl))))))
813
814 (let ((ns (make-soap-namespace :name (soap-get-target-namespace node))))
815 (dolist (node (soap-xml-get-children1 node 'wsdl:message))
816 (soap-namespace-put (soap-parse-message node) ns))
817
818 (dolist (node (soap-xml-get-children1 node 'wsdl:portType))
819 (let ((port-type (soap-parse-port-type node)))
820 (soap-namespace-put port-type ns)
88ae2870
MA
821 (soap-wsdl-add-namespace
822 (soap-port-type-operations port-type) wsdl)))
16d2ff89
MA
823
824 (dolist (node (soap-xml-get-children1 node 'wsdl:binding))
825 (soap-namespace-put (soap-parse-binding node) ns))
826
827 (dolist (node (soap-xml-get-children1 node 'wsdl:service))
828 (dolist (node (soap-xml-get-children1 node 'wsdl:port))
829 (let ((name (xml-get-attribute node 'name))
830 (binding (xml-get-attribute node 'binding))
88ae2870
MA
831 (url (let ((n (car (soap-xml-get-children1
832 node 'wsdlsoap:address))))
16d2ff89
MA
833 (xml-get-attribute n 'location))))
834 (let ((port (make-soap-port
88ae2870
MA
835 :name name :binding (soap-l2fq binding 'tns)
836 :service-url url)))
16d2ff89
MA
837 (soap-namespace-put port ns)
838 (push port (soap-wsdl-ports wsdl))))))
839
840 (soap-wsdl-add-namespace ns wsdl))
841
842 (soap-wsdl-resolve-references wsdl)
843
844 wsdl)))
845
846(defun soap-parse-schema (node)
847 "Parse a schema NODE.
848Return a SOAP-NAMESPACE containing the elements."
849 (soap-with-local-xmlns node
850 (assert (eq (soap-l2wk (xml-node-name node)) 'xsd:schema)
851 nil
852 "soap-parse-schema: expecting an xsd:schema node, got %s"
853 (soap-l2wk (xml-node-name node)))
854 (let ((ns (make-soap-namespace :name (soap-get-target-namespace node))))
855 ;; NOTE: we only extract the complexTypes from the schema, we wouldn't
856 ;; know how to handle basic types beyond the built in ones anyway.
857 (dolist (node (soap-xml-get-children1 node 'xsd:complexType))
858 (soap-namespace-put (soap-parse-complex-type node) ns))
859
860 (dolist (node (soap-xml-get-children1 node 'xsd:element))
861 (soap-namespace-put (soap-parse-schema-element node) ns))
862
863 ns)))
864
865(defun soap-parse-schema-element (node)
866 "Parse NODE and construct a schema element from it."
867 (assert (eq (soap-l2wk (xml-node-name node)) 'xsd:element)
868 nil
869 "soap-parse-schema-element: expecting xsd:element node, got %s"
870 (soap-l2wk (xml-node-name node)))
871 (let ((name (xml-get-attribute-or-nil node 'name))
872 type)
873 ;; A schema element that contains an inline complex type --
874 ;; construct the actual complex type for it.
875 (let ((type-node (soap-xml-get-children1 node 'xsd:complexType)))
876 (when (> (length type-node) 0)
88ae2870
MA
877 (assert (= (length type-node) 1)) ; only one complex type
878 ; definition per element
16d2ff89
MA
879 (setq type (soap-parse-complex-type (car type-node)))))
880 (setf (soap-element-name type) name)
881 type))
882
883(defun soap-parse-complex-type (node)
884 "Parse NODE and construct a complex type from it."
885 (assert (eq (soap-l2wk (xml-node-name node)) 'xsd:complexType)
886 nil
887 "soap-parse-complex-type: expecting xsd:complexType node, got %s"
888 (soap-l2wk (xml-node-name node)))
889 (let ((name (xml-get-attribute-or-nil node 'name))
890 ;; Use a dummy type for the complex type, it will be replaced
891 ;; with the real type below, except when the complex type node
892 ;; is empty...
893 (type (make-soap-sequence-type :elements nil)))
894 (dolist (c (xml-node-children node))
895 (when (consp c) ; skip string nodes, which are whitespace
896 (let ((node-name (soap-l2wk (xml-node-name c))))
897 (cond
d733e817
MA
898 ;; The difference between xsd:all and xsd:sequence is that fields
899 ;; in xsd:all are not ordered and they can occur only once. We
900 ;; don't care about that difference in soap-client.el
901 ((or (eq node-name 'xsd:sequence)
902 (eq node-name 'xsd:all))
16d2ff89
MA
903 (setq type (soap-parse-complex-type-sequence c)))
904 ((eq node-name 'xsd:complexContent)
905 (setq type (soap-parse-complex-type-complex-content c)))
906 ((eq node-name 'xsd:attribute)
907 ;; The name of this node comes from an attribute tag
908 (let ((n (xml-get-attribute-or-nil c 'name)))
909 (setq name n)))
910 (t
911 (error "Unknown node type %s" node-name))))))
912 (setf (soap-element-name type) name)
913 type))
914
915(defun soap-parse-sequence (node)
916 "Parse NODE and a list of sequence elements that it defines.
917NODE is assumed to be an xsd:sequence node. In that case, each
918of its children is assumed to be a sequence element. Each
919sequence element is parsed constructing the corresponding type.
920A list of these types is returned."
d733e817
MA
921 (assert (let ((n (soap-l2wk (xml-node-name node))))
922 (memq n '(xsd:sequence xsd:all)))
16d2ff89 923 nil
d733e817 924 "soap-parse-sequence: expecting xsd:sequence or xsd:all node, got %s"
16d2ff89
MA
925 (soap-l2wk (xml-node-name node)))
926 (let (elements)
927 (dolist (e (soap-xml-get-children1 node 'xsd:element))
928 (let ((name (xml-get-attribute-or-nil e 'name))
929 (type (xml-get-attribute-or-nil e 'type))
930 (nillable? (or (equal (xml-get-attribute-or-nil e 'nillable) "true")
931 (let ((e (xml-get-attribute-or-nil e 'minOccurs)))
932 (and e (equal e "0")))))
933 (multiple? (let ((e (xml-get-attribute-or-nil e 'maxOccurs)))
934 (and e (not (equal e "1"))))))
935 (if type
936 (setq type (soap-l2fq type 'tns))
937
938 ;; The node does not have a type, maybe it has a complexType
939 ;; defined inline...
940 (let ((type-node (soap-xml-get-children1 e 'xsd:complexType)))
941 (when (> (length type-node) 0)
942 (assert (= (length type-node) 1)
943 nil
944 "only one complex type definition per element supported")
945 (setq type (soap-parse-complex-type (car type-node))))))
946
947 (push (make-soap-sequence-element
88ae2870
MA
948 :name (intern name) :type type :nillable? nillable?
949 :multiple? multiple?)
16d2ff89
MA
950 elements)))
951 (nreverse elements)))
952
953(defun soap-parse-complex-type-sequence (node)
954 "Parse NODE as a sequence type."
955 (let ((elements (soap-parse-sequence node)))
956 (make-soap-sequence-type :elements elements)))
957
958(defun soap-parse-complex-type-complex-content (node)
959 "Parse NODE as a xsd:complexContent node.
960A sequence or an array type is returned depending on the actual
961contents."
962 (assert (eq (soap-l2wk (xml-node-name node)) 'xsd:complexContent)
963 nil
964 "soap-parse-complex-type-complex-content: expecting xsd:complexContent node, got %s"
965 (soap-l2wk (xml-node-name node)))
966 (let (array? parent elements)
967 (let ((extension (car-safe (soap-xml-get-children1 node 'xsd:extension)))
88ae2870
MA
968 (restriction (car-safe
969 (soap-xml-get-children1 node 'xsd:restriction))))
16d2ff89
MA
970 ;; a complex content node is either an extension or a restriction
971 (cond (extension
972 (setq parent (xml-get-attribute-or-nil extension 'base))
973 (setq elements (soap-parse-sequence
88ae2870
MA
974 (car (soap-xml-get-children1
975 extension 'xsd:sequence)))))
16d2ff89
MA
976 (restriction
977 (let ((base (xml-get-attribute-or-nil restriction 'base)))
978 (assert (equal base "soapenc:Array")
979 nil
980 "restrictions supported only for soapenc:Array types, this is a %s"
981 base))
982 (setq array? t)
88ae2870
MA
983 (let ((attribute (car (soap-xml-get-children1
984 restriction 'xsd:attribute))))
985 (let ((array-type (soap-xml-get-attribute-or-nil1
986 attribute 'wsdl:arrayType)))
16d2ff89
MA
987 (when (string-match "^\\(.*\\)\\[\\]$" array-type)
988 (setq parent (match-string 1 array-type))))))
989
990 (t
991 (error "Unknown complex type"))))
992
993 (if parent
994 (setq parent (soap-l2fq parent 'tns)))
88ae2870 995
16d2ff89
MA
996 (if array?
997 (make-soap-array-type :element-type parent)
998 (make-soap-sequence-type :parent parent :elements elements))))
999
1000(defun soap-parse-message (node)
1001 "Parse NODE as a wsdl:message and return the corresponding type."
1002 (assert (eq (soap-l2wk (xml-node-name node)) 'wsdl:message)
1003 nil
1004 "soap-parse-message: expecting wsdl:message node, got %s"
1005 (soap-l2wk (xml-node-name node)))
1006 (let ((name (xml-get-attribute-or-nil node 'name))
1007 parts)
1008 (dolist (p (soap-xml-get-children1 node 'wsdl:part))
1009 (let ((name (xml-get-attribute-or-nil p 'name))
1010 (type (xml-get-attribute-or-nil p 'type))
1011 (element (xml-get-attribute-or-nil p 'element)))
1012
1013 (when type
1014 (setq type (soap-l2fq type 'tns)))
1015
1016 (when element
1017 (setq element (soap-l2fq element 'tns)))
1018
1019 (push (cons name (or type element)) parts)))
1020 (make-soap-message :name name :parts (nreverse parts))))
1021
1022(defun soap-parse-port-type (node)
1023 "Parse NODE as a wsdl:portType and return the corresponding port."
1024 (assert (eq (soap-l2wk (xml-node-name node)) 'wsdl:portType)
1025 nil
1026 "soap-parse-port-type: expecting wsdl:portType node got %s"
1027 (soap-l2wk (xml-node-name node)))
1028 (let ((ns (make-soap-namespace
1029 :name (concat "urn:" (xml-get-attribute node 'name)))))
1030 (dolist (node (soap-xml-get-children1 node 'wsdl:operation))
1031 (let ((o (soap-parse-operation node)))
1032
88ae2870
MA
1033 (let ((other-operation (soap-namespace-get
1034 (soap-element-name o) ns 'soap-operation-p)))
16d2ff89
MA
1035 (if other-operation
1036 ;; Unfortunately, the Confluence WSDL defines two operations
1037 ;; named "search" which differ only in parameter names...
88ae2870
MA
1038 (soap-warning "Discarding duplicate operation: %s"
1039 (soap-element-name o))
16d2ff89
MA
1040
1041 (progn
1042 (soap-namespace-put o ns)
1043
1044 ;; link all messages from this namespace, as this namespace
1045 ;; will be used for decoding the response.
1046 (destructuring-bind (name . message) (soap-operation-input o)
1047 (soap-namespace-put-link name message ns))
1048
1049 (destructuring-bind (name . message) (soap-operation-output o)
1050 (soap-namespace-put-link name message ns))
1051
1052 (dolist (fault (soap-operation-faults o))
1053 (destructuring-bind (name . message) fault
1054 (soap-namespace-put-link name message ns 'replace)))
1055
1056 )))))
1057
1058 (make-soap-port-type :name (xml-get-attribute node 'name)
1059 :operations ns)))
1060
1061(defun soap-parse-operation (node)
1062 "Parse NODE as a wsdl:operation and return the corresponding type."
1063 (assert (eq (soap-l2wk (xml-node-name node)) 'wsdl:operation)
1064 nil
1065 "soap-parse-operation: expecting wsdl:operation node, got %s"
1066 (soap-l2wk (xml-node-name node)))
1067 (let ((name (xml-get-attribute node 'name))
88ae2870
MA
1068 (parameter-order (split-string
1069 (xml-get-attribute node 'parameterOrder)))
16d2ff89
MA
1070 input output faults)
1071 (dolist (n (xml-node-children node))
1072 (when (consp n) ; skip string nodes which are whitespace
1073 (let ((node-name (soap-l2wk (xml-node-name n))))
1074 (cond
1075 ((eq node-name 'wsdl:input)
1076 (let ((message (xml-get-attribute n 'message))
1077 (name (xml-get-attribute n 'name)))
1078 (setq input (cons name (soap-l2fq message 'tns)))))
1079 ((eq node-name 'wsdl:output)
1080 (let ((message (xml-get-attribute n 'message))
1081 (name (xml-get-attribute n 'name)))
1082 (setq output (cons name (soap-l2fq message 'tns)))))
1083 ((eq node-name 'wsdl:fault)
1084 (let ((message (xml-get-attribute n 'message))
1085 (name (xml-get-attribute n 'name)))
1086 (push (cons name (soap-l2fq message 'tns)) faults)))))))
1087 (make-soap-operation
1088 :name name
1089 :parameter-order parameter-order
1090 :input input
1091 :output output
1092 :faults (nreverse faults))))
1093
1094(defun soap-parse-binding (node)
1095 "Parse NODE as a wsdl:binding and return the corresponding type."
1096 (assert (eq (soap-l2wk (xml-node-name node)) 'wsdl:binding)
1097 nil
1098 "soap-parse-binding: expecting wsdl:binding node, got %s"
1099 (soap-l2wk (xml-node-name node)))
1100 (let ((name (xml-get-attribute node 'name))
1101 (type (xml-get-attribute node 'type)))
88ae2870
MA
1102 (let ((binding (make-soap-binding :name name
1103 :port-type (soap-l2fq type 'tns))))
16d2ff89
MA
1104 (dolist (wo (soap-xml-get-children1 node 'wsdl:operation))
1105 (let ((name (xml-get-attribute wo 'name))
1106 soap-action
1107 use)
1108 (dolist (so (soap-xml-get-children1 wo 'wsdlsoap:operation))
1109 (setq soap-action (xml-get-attribute-or-nil so 'soapAction)))
1110
1111 ;; Search a wsdlsoap:body node and find a "use" tag. The
1112 ;; same use tag is assumed to be present for both input and
1113 ;; output types (although the WDSL spec allows separate
1114 ;; "use"-s for each of them...
1115
1116 (dolist (i (soap-xml-get-children1 wo 'wsdl:input))
1117 (dolist (b (soap-xml-get-children1 i 'wsdlsoap:body))
1118 (setq use (or use
1119 (xml-get-attribute-or-nil b 'use)))))
1120
1121 (unless use
1122 (dolist (i (soap-xml-get-children1 wo 'wsdl:output))
1123 (dolist (b (soap-xml-get-children1 i 'wsdlsoap:body))
1124 (setq use (or use
1125 (xml-get-attribute-or-nil b 'use))))))
1126
1127 (puthash name (make-soap-bound-operation :operation name
1128 :soap-action soap-action
1129 :use (and use (intern use)))
1130 (soap-binding-operations binding))))
1131 binding)))
1132
1133;;;; SOAP type decoding
1134
274c2d34 1135(defvar soap-multi-refs nil
16d2ff89
MA
1136 "The list of multi-ref nodes in the current SOAP response.
1137This is a dynamically bound variable used during decoding the
1138SOAP response.")
1139
274c2d34 1140(defvar soap-decoded-multi-refs nil
16d2ff89
MA
1141 "List of decoded multi-ref nodes in the current SOAP response.
1142This is a dynamically bound variable used during decoding the
1143SOAP response.")
1144
274c2d34 1145(defvar soap-current-wsdl nil
16d2ff89
MA
1146 "The current WSDL document used when decoding the SOAP response.
1147This is a dynamically bound variable.")
1148
1149(defun soap-decode-type (type node)
1150 "Use TYPE (an xsd type) to decode the contents of NODE.
1151
1152NODE is an XML node, representing some SOAP encoded value or a
1153reference to another XML node (a multiRef). This function will
1154resolve the multiRef reference, if any, than call a TYPE specific
1155decode function to perform the actual decoding."
1156 (let ((href (xml-get-attribute-or-nil node 'href)))
1157 (cond (href
1158 (catch 'done
1159 ;; NODE is actually a HREF, find the target and decode that.
1160 ;; Check first if we already decoded this multiref.
1161
274c2d34 1162 (let ((decoded (cdr (assoc href soap-decoded-multi-refs))))
16d2ff89
MA
1163 (when decoded
1164 (throw 'done decoded)))
1165
1166 (string-match "^#\\(.*\\)$" href) ; TODO: check that it matched
1167
1168 (let ((id (match-string 1 href)))
274c2d34 1169 (dolist (mr soap-multi-refs)
16d2ff89
MA
1170 (let ((mrid (xml-get-attribute mr 'id)))
1171 (when (equal id mrid)
1172 ;; recurse here, in case there are multiple HREF's
1173 (let ((decoded (soap-decode-type type mr)))
274c2d34 1174 (push (cons href decoded) soap-decoded-multi-refs)
16d2ff89
MA
1175 (throw 'done decoded)))))
1176 (error "Cannot find href %s" href))))
1177 (t
1178 (soap-with-local-xmlns node
1179 (if (equal (soap-xml-get-attribute-or-nil1 node 'xsi:nil) "true")
1180 nil
1181 (let ((decoder (get (aref type 0) 'soap-decoder)))
88ae2870
MA
1182 (assert decoder nil "no soap-decoder for %s type"
1183 (aref type 0))
16d2ff89
MA
1184 (funcall decoder type node))))))))
1185
1186(defun soap-decode-any-type (node)
1187 "Decode NODE using type information inside it."
1188 ;; If the NODE has type information, we use that...
1189 (let ((type (soap-xml-get-attribute-or-nil1 node 'xsi:type)))
1190 (if type
274c2d34 1191 (let ((wtype (soap-wsdl-get type soap-current-wsdl 'soap-type-p)))
16d2ff89
MA
1192 (if wtype
1193 (soap-decode-type wtype node)
1194 ;; The node has type info encoded in it, but we don't know how
1195 ;; to decode it...
1196 (error "Soap-decode-any-type: node has unknown type: %s" type)))
1197
1198 ;; No type info in the node...
1199
1200 (let ((contents (xml-node-children node)))
1201 (if (and (= (length contents) 1) (stringp (car contents)))
1202 ;; contents is just a string
1203 (car contents)
1204
1205 ;; we assume the NODE is a sequence with every element a
1206 ;; structure name
1207 (let (result)
1208 (dolist (element contents)
1209 (let ((key (xml-node-name element))
1210 (value (soap-decode-any-type element)))
1211 (push (cons key value) result)))
1212 (nreverse result)))))))
1213
1214(defun soap-decode-array (node)
1215 "Decode NODE as an Array using type information inside it."
1216 (let ((type (soap-xml-get-attribute-or-nil1 node 'soapenc:arrayType))
1217 (wtype nil)
1218 (contents (xml-node-children node))
1219 result)
1220 (when type
1221 ;; Type is in the format "someType[NUM]" where NUM is the number of
1222 ;; elements in the array. We discard the [NUM] part.
1223 (setq type (replace-regexp-in-string "\\[[0-9]+\\]\\'" "" type))
274c2d34 1224 (setq wtype (soap-wsdl-get type soap-current-wsdl 'soap-type-p))
16d2ff89
MA
1225 (unless wtype
1226 ;; The node has type info encoded in it, but we don't know how to
1227 ;; decode it...
1228 (error "Soap-decode-array: node has unknown type: %s" type)))
1229 (dolist (e contents)
1230 (when (consp e)
1231 (push (if wtype
1232 (soap-decode-type wtype e)
1233 (soap-decode-any-type e))
1234 result)))
1235 (nreverse result)))
1236
1237(defun soap-decode-basic-type (type node)
1238 "Use TYPE to decode the contents of NODE.
1239TYPE is a `soap-basic-type' struct, and NODE is an XML document.
1240A LISP value is returned based on the contents of NODE and the
1241type-info stored in TYPE."
1242 (let ((contents (xml-node-children node))
1243 (type-kind (soap-basic-type-kind type)))
1244
1245 (if (null contents)
1246 nil
1247 (ecase type-kind
1248 (string (car contents))
1249 (dateTime (car contents)) ; TODO: convert to a date time
1250 ((long int float) (string-to-number (car contents)))
1251 (boolean (string= (downcase (car contents)) "true"))
1252 (base64Binary (base64-decode-string (car contents)))
1253 (anyType (soap-decode-any-type node))
1254 (Array (soap-decode-array node))))))
1255
1256(defun soap-decode-sequence-type (type node)
1257 "Use TYPE to decode the contents of NODE.
1258TYPE is assumed to be a sequence type and an ALIST with the
1259contents of the NODE is returned."
1260 (let ((result nil)
1261 (parent (soap-sequence-type-parent type)))
1262 (when parent
1263 (setq result (nreverse (soap-decode-type parent node))))
1264 (dolist (element (soap-sequence-type-elements type))
1265 (let ((instance-count 0)
1266 (e-name (soap-sequence-element-name element))
1267 (e-type (soap-sequence-element-type element)))
1268 (dolist (node (xml-get-children node e-name))
1269 (incf instance-count)
1270 (push (cons e-name (soap-decode-type e-type node)) result))
1271 ;; Do some sanity checking
1272 (cond ((and (= instance-count 0)
1273 (not (soap-sequence-element-nillable? element)))
1274 (soap-warning "While decoding %s: missing non-nillable slot %s"
1275 (soap-element-name type) e-name))
1276 ((and (> instance-count 1)
1277 (not (soap-sequence-element-multiple? element)))
1278 (soap-warning "While decoding %s: multiple slots named %s"
1279 (soap-element-name type) e-name)))))
1280 (nreverse result)))
1281
1282(defun soap-decode-array-type (type node)
1283 "Use TYPE to decode the contents of NODE.
1284TYPE is assumed to be an array type. Arrays are decoded as lists.
1285This is because it is easier to work with list results in LISP."
1286 (let ((result nil)
1287 (element-type (soap-array-type-element-type type)))
1288 (dolist (node (xml-node-children node))
1289 (when (consp node)
1290 (push (soap-decode-type element-type node) result)))
1291 (nreverse result)))
1292
1293(progn
1294 (put (aref (make-soap-basic-type) 0)
1295 'soap-decoder 'soap-decode-basic-type)
1296 (put (aref (make-soap-sequence-type) 0)
1297 'soap-decoder 'soap-decode-sequence-type)
1298 (put (aref (make-soap-array-type) 0)
1299 'soap-decoder 'soap-decode-array-type))
1300
1301;;;; Soap Envelope parsing
1302
1303(put 'soap-error
1304 'error-conditions
1305 '(error soap-error))
1306(put 'soap-error 'error-message "SOAP error")
1307
1308(defun soap-parse-envelope (node operation wsdl)
1309 "Parse the SOAP envelope in NODE and return the response.
1310OPERATION is the WSDL operation for which we expect the response,
1311WSDL is used to decode the NODE"
1312 (soap-with-local-xmlns node
1313 (assert (eq (soap-l2wk (xml-node-name node)) 'soap:Envelope)
1314 nil
1315 "soap-parse-envelope: expecting soap:Envelope node, got %s"
1316 (soap-l2wk (xml-node-name node)))
1317 (let ((body (car (soap-xml-get-children1 node 'soap:Body))))
1318
1319 (let ((fault (car (soap-xml-get-children1 body 'soap:Fault))))
1320 (when fault
88ae2870
MA
1321 (let ((fault-code (let ((n (car (xml-get-children
1322 fault 'faultcode))))
16d2ff89 1323 (car-safe (xml-node-children n))))
88ae2870
MA
1324 (fault-string (let ((n (car (xml-get-children
1325 fault 'faultstring))))
16d2ff89
MA
1326 (car-safe (xml-node-children n)))))
1327 (while t
1328 (signal 'soap-error (list fault-code fault-string))))))
1329
1330 ;; First (non string) element of the body is the root node of he
1331 ;; response
1332 (let ((response (if (eq (soap-bound-operation-use operation) 'literal)
1333 ;; For 'literal uses, the response is the actual body
1334 body
1335 ;; ...otherwise the first non string element
1336 ;; of the body is the response
1337 (catch 'found
1338 (dolist (n (xml-node-children body))
1339 (when (consp n)
1340 (throw 'found n)))))))
1341 (soap-parse-response response operation wsdl body)))))
1342
1343(defun soap-parse-response (response-node operation wsdl soap-body)
1344 "Parse RESPONSE-NODE and return the result as a LISP value.
1345OPERATION is the WSDL operation for which we expect the response,
1346WSDL is used to decode the NODE.
1347
1348SOAP-BODY is the body of the SOAP envelope (of which
1349RESPONSE-NODE is a sub-node). It is used in case RESPONSE-NODE
1350reference multiRef parts which are external to RESPONSE-NODE."
274c2d34 1351 (let* ((soap-current-wsdl wsdl)
16d2ff89
MA
1352 (op (soap-bound-operation-operation operation))
1353 (use (soap-bound-operation-use operation))
1354 (message (cdr (soap-operation-output op))))
1355
1356 (soap-with-local-xmlns response-node
1357
1358 (when (eq use 'encoded)
1359 (let* ((received-message-name (soap-l2fq (xml-node-name response-node)))
88ae2870
MA
1360 (received-message (soap-wsdl-get
1361 received-message-name wsdl 'soap-message-p)))
16d2ff89
MA
1362 (unless (eq received-message message)
1363 (error "Unexpected message: got %s, expecting %s"
1364 received-message-name
1365 (soap-element-name message)))))
1366
1367 (let ((decoded-parts nil)
274c2d34
MA
1368 (soap-multi-refs (xml-get-children soap-body 'multiRef))
1369 (soap-decoded-multi-refs nil))
16d2ff89
MA
1370
1371 (dolist (part (soap-message-parts message))
1372 (let ((tag (car part))
1373 (type (cdr part))
1374 node)
1375
1376 (setq node
1377 (cond
1378 ((eq use 'encoded)
1379 (car (xml-get-children response-node tag)))
1380
1381 ((eq use 'literal)
1382 (catch 'found
1383 (let* ((ns-aliases (soap-wsdl-alias-table wsdl))
88ae2870
MA
1384 (ns-name (cdr (assoc
1385 (soap-element-namespace-tag type)
1386 ns-aliases)))
16d2ff89
MA
1387 (fqname (cons ns-name (soap-element-name type))))
1388 (dolist (c (xml-node-children response-node))
1389 (when (consp c)
1390 (soap-with-local-xmlns c
88ae2870
MA
1391 (when (equal (soap-l2fq (xml-node-name c))
1392 fqname)
16d2ff89
MA
1393 (throw 'found c))))))))))
1394
1395 (unless node
1396 (error "Soap-parse-response(%s): cannot find message part %s"
1397 (soap-element-name op) tag))
1398 (push (soap-decode-type type node) decoded-parts)))
1399
1400 decoded-parts))))
1401
1402;;;; SOAP type encoding
1403
274c2d34 1404(defvar soap-encoded-namespaces nil
16d2ff89
MA
1405 "A list of namespace tags used during encoding a message.
1406This list is populated by `soap-encode-value' and used by
1407`soap-create-envelope' to add aliases for these namespace to the
1408XML request.
1409
1410This variable is dynamically bound in `soap-create-envelope'.")
1411
1412(defun soap-encode-value (xml-tag value type)
1413 "Encode inside an XML-TAG the VALUE using TYPE.
1414The resulting XML data is inserted in the current buffer
1415at (point)/
1416
1417TYPE is one of the soap-*-type structures which defines how VALUE
1418is to be encoded. This is a generic function which finds an
1419encoder function based on TYPE and calls that encoder to do the
1420work."
1421 (let ((encoder (get (aref type 0) 'soap-encoder)))
1422 (assert encoder nil "no soap-encoder for %s type" (aref type 0))
1423 ;; XML-TAG can be a string or a symbol, but we pass only string's to the
1424 ;; encoders
1425 (when (symbolp xml-tag)
1426 (setq xml-tag (symbol-name xml-tag)))
1427 (funcall encoder xml-tag value type))
274c2d34 1428 (add-to-list 'soap-encoded-namespaces (soap-element-namespace-tag type)))
16d2ff89
MA
1429
1430(defun soap-encode-basic-type (xml-tag value type)
1431 "Encode inside XML-TAG the LISP VALUE according to TYPE.
1432Do not call this function directly, use `soap-encode-value'
1433instead."
1434 (let ((xsi-type (soap-element-fq-name type))
1435 (basic-type (soap-basic-type-kind type)))
1436
1437 ;; try to classify the type based on the value type and use that type when
1438 ;; encoding
1439 (when (eq basic-type 'anyType)
1440 (cond ((stringp value)
1441 (setq xsi-type "xsd:string" basic-type 'string))
1442 ((integerp value)
1443 (setq xsi-type "xsd:int" basic-type 'int))
1444 ((memq value '(t nil))
1445 (setq xsi-type "xsd:boolean" basic-type 'boolean))
1446 (t
88ae2870
MA
1447 (error
1448 "Soap-encode-basic-type(%s, %s, %s): cannot classify anyType value"
1449 xml-tag value xsi-type))))
16d2ff89
MA
1450
1451 (insert "<" xml-tag " xsi:type=\"" xsi-type "\"")
1452
1453 ;; We have some ambiguity here, as a nil value represents "false" when the
1454 ;; type is boolean, we will never have a "nil" boolean type...
1455
1456 (if (or value (eq basic-type 'boolean))
1457 (progn
1458 (insert ">")
1459 (case basic-type
1460 (string
1461 (unless (stringp value)
1462 (error "Soap-encode-basic-type(%s, %s, %s): not a string value"
1463 xml-tag value xsi-type))
1464 (insert (url-insert-entities-in-string value)))
1465
1466 (dateTime
1467 (cond ((and (consp value) ; is there a time-value-p ?
1468 (>= (length value) 2)
1469 (numberp (nth 0 value))
1470 (numberp (nth 1 value)))
88ae2870
MA
1471 ;; Value is a (current-time) style value, convert
1472 ;; to a string
16d2ff89
MA
1473 (insert (format-time-string "%Y-%m-%dT%H:%M:%S" value)))
1474 ((stringp value)
1475 (insert (url-insert-entities-in-string value)))
1476 (t
88ae2870
MA
1477 (error
1478 "Soap-encode-basic-type(%s, %s, %s): not a dateTime value"
1479 xml-tag value xsi-type))))
16d2ff89
MA
1480
1481 (boolean
1482 (unless (memq value '(t nil))
1483 (error "Soap-encode-basic-type(%s, %s, %s): not a boolean value"
1484 xml-tag value xsi-type))
1485 (insert (if value "true" "false")))
1486
1487 ((long int)
1488 (unless (integerp value)
1489 (error "Soap-encode-basic-type(%s, %s, %s): not an integer value"
1490 xml-tag value xsi-type))
1491 (insert (number-to-string value)))
88ae2870 1492
16d2ff89
MA
1493 (base64Binary
1494 (unless (stringp value)
1495 (error "Soap-encode-basic-type(%s, %s, %s): not a string value"
1496 xml-tag value xsi-type))
1497 (insert (base64-encode-string value)))
1498
1499 (otherwise
88ae2870
MA
1500 (error
1501 "Soap-encode-basic-type(%s, %s, %s): don't know how to encode"
1502 xml-tag value xsi-type))))
1503
16d2ff89
MA
1504 (insert " xsi:nil=\"true\">"))
1505 (insert "</" xml-tag ">\n")))
1506
1507(defun soap-encode-sequence-type (xml-tag value type)
1508 "Encode inside XML-TAG the LISP VALUE according to TYPE.
1509Do not call this function directly, use `soap-encode-value'
1510instead."
1511 (let ((xsi-type (soap-element-fq-name type)))
1512 (insert "<" xml-tag " xsi:type=\"" xsi-type "\"")
1513 (if value
1514 (progn
1515 (insert ">\n")
1516 (let ((parents (list type))
1517 (parent (soap-sequence-type-parent type)))
1518
1519 (while parent
1520 (push parent parents)
1521 (setq parent (soap-sequence-type-parent parent)))
1522
1523 (dolist (type parents)
1524 (dolist (element (soap-sequence-type-elements type))
1525 (let ((instance-count 0)
1526 (e-name (soap-sequence-element-name element))
1527 (e-type (soap-sequence-element-type element)))
1528 (dolist (v value)
1529 (when (equal (car v) e-name)
1530 (incf instance-count)
1531 (soap-encode-value e-name (cdr v) e-type)))
1532
1533 ;; Do some sanity checking
1534 (cond ((and (= instance-count 0)
1535 (not (soap-sequence-element-nillable? element)))
88ae2870
MA
1536 (soap-warning
1537 "While encoding %s: missing non-nillable slot %s"
1538 (soap-element-name type) e-name))
16d2ff89
MA
1539 ((and (> instance-count 1)
1540 (not (soap-sequence-element-multiple? element)))
88ae2870
MA
1541 (soap-warning
1542 "While encoding %s: multiple slots named %s"
1543 (soap-element-name type) e-name))))))))
16d2ff89
MA
1544 (insert " xsi:nil=\"true\">"))
1545 (insert "</" xml-tag ">\n")))
1546
1547(defun soap-encode-array-type (xml-tag value type)
1548 "Encode inside XML-TAG the LISP VALUE according to TYPE.
1549Do not call this function directly, use `soap-encode-value'
1550instead."
1551 (unless (vectorp value)
1552 (error "Soap-encode: %s(%s) expects a vector, got: %s"
1553 xml-tag (soap-element-fq-name type) value))
1554 (let* ((element-type (soap-array-type-element-type type))
1555 (array-type (concat (soap-element-fq-name element-type)
1556 "[" (format "%s" (length value)) "]")))
1557 (insert "<" xml-tag
1558 " soapenc:arrayType=\"" array-type "\" "
1559 " xsi:type=\"soapenc:Array\">\n")
1560 (loop for i below (length value)
1561 do (soap-encode-value xml-tag (aref value i) element-type))
1562 (insert "</" xml-tag ">\n")))
1563
1564(progn
1565 (put (aref (make-soap-basic-type) 0)
1566 'soap-encoder 'soap-encode-basic-type)
1567 (put (aref (make-soap-sequence-type) 0)
1568 'soap-encoder 'soap-encode-sequence-type)
1569 (put (aref (make-soap-array-type) 0)
1570 'soap-encoder 'soap-encode-array-type))
1571
1572(defun soap-encode-body (operation parameters wsdl)
1573 "Create the body of a SOAP request for OPERATION in the current buffer.
1574PARAMETERS is a list of parameters supplied to the OPERATION.
1575
1576The OPERATION and PARAMETERS are encoded according to the WSDL
1577document."
1578 (let* ((op (soap-bound-operation-operation operation))
1579 (use (soap-bound-operation-use operation))
1580 (message (cdr (soap-operation-input op)))
1581 (parameter-order (soap-operation-parameter-order op)))
1582
1583 (unless (= (length parameter-order) (length parameters))
1584 (error "Wrong number of parameters for %s: expected %d, got %s"
1585 (soap-element-name op)
1586 (length parameter-order)
1587 (length parameters)))
1588
1589 (insert "<soap:Body>\n")
1590 (when (eq use 'encoded)
274c2d34 1591 (add-to-list 'soap-encoded-namespaces (soap-element-namespace-tag op))
16d2ff89
MA
1592 (insert "<" (soap-element-fq-name op) ">\n"))
1593
1594 (let ((param-table (loop for formal in parameter-order
1595 for value in parameters
1596 collect (cons formal value))))
1597 (dolist (part (soap-message-parts message))
1598 (let* ((param-name (car part))
1599 (type (cdr part))
1600 (tag-name (if (eq use 'encoded)
1601 param-name
1602 (soap-element-name type)))
1603 (value (cdr (assoc param-name param-table)))
1604 (start-pos (point)))
1605 (soap-encode-value tag-name value type)
1606 (when (eq use 'literal)
1607 ;; hack: add the xmlns attribute to the tag, the only way
1608 ;; ASP.NET web services recognize the namespace of the
1609 ;; element itself...
1610 (save-excursion
1611 (goto-char start-pos)
1612 (when (re-search-forward " ")
1613 (let* ((ns (soap-element-namespace-tag type))
88ae2870
MA
1614 (namespace (cdr (assoc ns
1615 (soap-wsdl-alias-table wsdl)))))
16d2ff89
MA
1616 (when namespace
1617 (insert "xmlns=\"" namespace "\" ")))))))))
1618
1619 (when (eq use 'encoded)
1620 (insert "</" (soap-element-fq-name op) ">\n"))
1621 (insert "</soap:Body>\n")))
1622
1623(defun soap-create-envelope (operation parameters wsdl)
1624 "Create a SOAP request envelope for OPERATION using PARAMETERS.
1625WSDL is the wsdl document used to encode the PARAMETERS."
1626 (with-temp-buffer
274c2d34 1627 (let ((soap-encoded-namespaces '("xsi" "soap" "soapenc"))
16d2ff89
MA
1628 (use (soap-bound-operation-use operation)))
1629
1630 ;; Create the request body
1631 (soap-encode-body operation parameters wsdl)
1632
1633 ;; Put the envelope around the body
1634 (goto-char (point-min))
1635 (insert "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<soap:Envelope\n")
1636 (when (eq use 'encoded)
1637 (insert " soapenc:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\"\n"))
274c2d34 1638 (dolist (nstag soap-encoded-namespaces)
16d2ff89 1639 (insert " xmlns:" nstag "=\"")
274c2d34 1640 (let ((nsname (cdr (assoc nstag soap-well-known-xmlns))))
16d2ff89
MA
1641 (unless nsname
1642 (setq nsname (cdr (assoc nstag (soap-wsdl-alias-table wsdl)))))
1643 (insert nsname)
1644 (insert "\"\n")))
1645 (insert ">\n")
1646 (goto-char (point-max))
1647 (insert "</soap:Envelope>\n"))
1648
1649 (buffer-string)))
1650
1651;;;; invoking soap methods
1652
1653(defcustom soap-debug nil
1654 "When t, enable some debugging facilities."
1655 :type 'boolean
1656 :group 'soap-client)
1657
1658(defun soap-invoke (wsdl service operation-name &rest parameters)
1659 "Invoke a SOAP operation and return the result.
1660
1661WSDL is used for encoding the request and decoding the response.
1662It also contains information about the WEB server address that
1663will service the request.
1664
1665SERVICE is the SOAP service to invoke.
1666
1667OPERATION-NAME is the operation to invoke.
1668
1669PARAMETERS -- the remaining parameters are used as parameters for
1670the SOAP request.
1671
1672NOTE: The SOAP service provider should document the available
1673operations and their parameters for the service. You can also
1674use the `soap-inspect' function to browse the available
1675operations in a WSDL document."
1676 (let ((port (catch 'found
1677 (dolist (p (soap-wsdl-ports wsdl))
1678 (when (equal service (soap-element-name p))
1679 (throw 'found p))))))
1680 (unless port
1681 (error "Unknown SOAP service: %s" service))
1682
1683 (let* ((binding (soap-port-binding port))
88ae2870
MA
1684 (operation (gethash operation-name
1685 (soap-binding-operations binding))))
16d2ff89
MA
1686 (unless operation
1687 (error "No operation %s for SOAP service %s" operation-name service))
1688
1689 (let ((url-request-method "POST")
1690 (url-package-name "soap-client.el")
1691 (url-package-version "1.0")
1692 (url-http-version "1.0")
1693 (url-request-data (soap-create-envelope operation parameters wsdl))
1694 (url-mime-charset-string "utf-8;q=1, iso-8859-1;q=0.5")
1695 (url-request-coding-system 'utf-8)
1696 (url-http-attempt-keepalives t)
1697 (url-request-extra-headers (list
88ae2870
MA
1698 (cons "SOAPAction"
1699 (soap-bound-operation-soap-action
1700 operation))
1701 (cons "Content-Type"
1702 "text/xml; charset=utf-8"))))
1703 (let ((buffer (url-retrieve-synchronously
1704 (soap-port-service-url port))))
16d2ff89
MA
1705 (condition-case err
1706 (with-current-buffer buffer
1707 (declare (special url-http-response-status))
1708 (if (null url-http-response-status)
1709 (error "No HTTP response from server"))
1710 (if (and soap-debug (> url-http-response-status 299))
1711 ;; This is a warning because some SOAP errors come
1712 ;; back with a HTTP response 500 (internal server
1713 ;; error)
88ae2870
MA
1714 (warn "Error in SOAP response: HTTP code %s"
1715 url-http-response-status))
16d2ff89
MA
1716 (let ((mime-part (mm-dissect-buffer t t)))
1717 (unless mime-part
1718 (error "Failed to decode response from server"))
1719 (unless (equal (car (mm-handle-type mime-part)) "text/xml")
1720 (error "Server response is not an XML document"))
1721 (with-temp-buffer
1722 (mm-insert-part mime-part)
88ae2870
MA
1723 (let ((response (car (xml-parse-region
1724 (point-min) (point-max)))))
16d2ff89
MA
1725 (prog1
1726 (soap-parse-envelope response operation wsdl)
1727 (kill-buffer buffer)
1728 (mm-destroy-part mime-part))))))
1729 (soap-error
1730 ;; Propagate soap-errors -- they are error replies of the
1731 ;; SOAP protocol and don't indicate a communication
1732 ;; problem or a bug in this code.
1733 (signal (car err) (cdr err)))
1734 (error
1735 (when soap-debug
1736 (pop-to-buffer buffer))
1737 (error (error-message-string err)))))))))
1738
1739(provide 'soap-client)
1740
1741\f
1742;;; Local Variables:
f0da764a 1743;;; eval: (outline-minor-mode 1)
16d2ff89
MA
1744;;; outline-regexp: ";;;;+"
1745;;; End:
1746
1747;;; soap-client.el ends here