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