1 ;; jabber-xml.el - XML functions -*- lexical-binding: t; -*-
3 ;; Copyright (C) 2003, 2004, 2007, 2008 - Magnus Henoch - mange@freemail.hu
4 ;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
6 ;; This file is a part of jabber.el.
8 ;; This program is free software; you can redistribute it and/or modify
9 ;; it under the terms of the GNU General Public License as published by
10 ;; the Free Software Foundation; either version 2 of the License, or
11 ;; (at your option) any later version.
13 ;; This program is distributed in the hope that it will be useful,
14 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ;; GNU General Public License for more details.
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with this program; if not, write to the Free Software
20 ;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 (require 'jabber-util)
27 (defsubst jabber-replace-in-string (string regexp newtext)
28 "Return STRING with all matches for REGEXP replaced with NEWTEXT.
29 NEWTEXT is inserted literally, without changing its case or treating \"\\\"
31 (replace-regexp-in-string regexp newtext string t t))
33 (defun jabber-escape-xml (string)
34 "Escape STRING for XML."
36 (let ((newstr (concat string)))
37 ;; Form feeds might appear in code you copy, etc. Nevertheless,
39 (setq newstr (jabber-replace-in-string newstr "\f" "\n"))
40 ;; Other control characters are also illegal, except for
42 (setq newstr (jabber-replace-in-string newstr "[\000-\010\013\014\016-\037]" " "))
43 (setq newstr (jabber-replace-in-string newstr "&" "&"))
44 (setq newstr (jabber-replace-in-string newstr "<" "<"))
45 (setq newstr (jabber-replace-in-string newstr ">" ">"))
46 (setq newstr (jabber-replace-in-string newstr "'" "'"))
47 (setq newstr (jabber-replace-in-string newstr "\"" """))
51 (defun jabber-unescape-xml (string)
52 "Unescape STRING for XML."
53 ;; Eventually this can be done with `xml-substitute-special', but the
54 ;; version in xml.el of GNU Emacs 21.3 is buggy.
56 (let ((newstr string))
57 (setq newstr (jabber-replace-in-string newstr """ "\""))
58 (setq newstr (jabber-replace-in-string newstr "'" "'"))
59 (setq newstr (jabber-replace-in-string newstr ">" ">"))
60 (setq newstr (jabber-replace-in-string newstr "<" "<"))
61 (setq newstr (jabber-replace-in-string newstr "&" "&"))
65 (defun jabber-sexp2xml (sexp)
66 "Return SEXP as well-formatted XML.
67 SEXP should be in the form:
68 (tagname ((attribute-name . attribute-value)...) children...)"
71 (jabber-escape-xml sexp))
75 (setq xml (concat xml (jabber-sexp2xml tag))))
77 ;; work around bug in old versions of xml.el, where ("") can appear
78 ;; as children of a node
81 (zerop (length (car sexp))))
86 (symbol-name (car sexp))))
87 (dolist (attr (cadr sexp))
91 (symbol-name (car attr))
92 (jabber-escape-xml (cdr attr)))))))
95 (setq xml (concat xml ">"))
96 (dolist (child (cddr sexp))
98 (jabber-sexp2xml child))))
101 (symbol-name (car sexp))
103 (setq xml (concat xml
107 (defun jabber-xml-skip-tag-forward (&optional dont-recurse-into-stream)
108 "Skip to end of tag or matching closing tag if present.
109 Return t iff after a closing tag, otherwise throws an 'unfinished
111 If DONT-RECURSE-INTO-STREAM is non-nil, stop after an opening
114 The version of `sgml-skip-tag-forward' in Emacs 21 isn't good
116 (skip-chars-forward "^<")
118 ((looking-at "<!\\[CDATA\\[")
119 (if (search-forward "]]>" nil t)
120 (goto-char (match-end 0))
121 (throw 'unfinished nil)))
122 ((looking-at "<\\([^[:space:]/>]+\\)\\([[:space:]]+[^=>]+=[[:space:]]*'[^']*'\\|[[:space:]]+[^=>]+=[[:space:]]*\"[^\"]*\"\\)*")
123 (let ((node-name (match-string 1)))
124 (goto-char (match-end 0))
125 (skip-syntax-forward "\s-") ; Skip over trailing white space.
128 (goto-char (match-end 0))
131 (goto-char (match-end 0))
132 (unless (and dont-recurse-into-stream (equal node-name "stream:stream"))
134 do (skip-chars-forward "^<")
135 until (looking-at (regexp-quote (concat "</" node-name ">")))
136 do (jabber-xml-skip-tag-forward))
137 (goto-char (match-end 0)))
140 (throw 'unfinished nil)))))
142 (throw 'unfinished nil))))
144 (defun jabber-xml-parse-next-stanza ()
145 "Parse the first XML stanza in the current buffer.
146 Parse and return the first complete XML element in the buffer,
147 leaving point at the end of it. If there is no complete XML
148 element, return nil."
149 (and (catch 'unfinished
150 (goto-char (point-min))
151 (jabber-xml-skip-tag-forward)
152 (> (point) (point-min)))
153 (xml-parse-region (point-min) (point))))
155 (defsubst jabber-xml-node-name (node)
156 "Return the tag associated with NODE.
157 The tag is a lower-case symbol."
158 (if (listp node) (car node)))
160 (defsubst jabber-xml-node-attributes (node)
161 "Return the list of attributes of NODE.
162 The list can be nil."
163 (if (listp node) (nth 1 node)))
165 (defsubst jabber-xml-node-children (node)
166 "Return the list of children of NODE.
167 This is a list of nodes, and it can be nil."
168 (let ((children (cddr node)))
169 ;; Work around a bug in early versions of xml.el
170 (if (equal children '(("")))
174 (defun jabber-xml-get-children (node child-name)
175 "Return the children of NODE whose tag is CHILD-NAME.
176 CHILD-NAME should be a lower case symbol."
178 (dolist (child (jabber-xml-node-children node))
180 (if (equal (jabber-xml-node-name child) child-name)
181 (push child match))))
184 ;; `xml-get-attribute' returns "" if the attribute is not found, which
185 ;; is not very useful. Therefore, we use `xml-get-attribute-or-nil'.
186 (defsubst jabber-xml-get-attribute (node attribute)
187 "Get from NODE the value of ATTRIBUTE.
188 Return nil if the attribute was not found."
190 (xml-get-attribute-or-nil node attribute)))
192 (defsubst jabber-xml-get-xmlns (node)
193 "Get \"xmlns\" attribute of NODE, or nil if not present."
194 (jabber-xml-get-attribute node 'xmlns))
196 (defun jabber-xml-path (xml-data path)
197 "Find sub-node of XML-DATA according to PATH.
198 PATH is a vaguely XPath-inspired list. Each element can be:
199 a symbol go to first child node with this node name
200 cons cell car is string containing namespace URI,
201 cdr is string containing node name. Find
202 first matching child node.
203 any string character data of this node."
204 (let ((node xml-data))
205 (while (and path node)
206 (let ((step (car path)))
209 (setq node (car (jabber-xml-get-children node step))))
211 ;; This will be easier with namespace-aware use
212 ;; of xml.el. It will also be more correct.
213 ;; Now, it only matches explicit namespace declarations.
215 (cl-dolist (x (jabber-xml-get-children node (intern (cdr step))))
216 (when (string= (jabber-xml-get-attribute x 'xmlns)
220 (setq node (car (jabber-xml-node-children node)))
221 (unless (stringp node)
224 (error "Unknown path step: %s" step))))
225 (setq path (cdr path)))
228 (defmacro jabber-xml-let-attributes (attributes xml-data &rest body)
229 "Evaluate BODY with ATTRIBUTES bound to their values in XML-DATA.
230 ATTRIBUTES must be a list of symbols, as present in XML-DATA."
231 `(let ,(mapcar #'(lambda (attr)
232 (list attr `(jabber-xml-get-attribute ,xml-data ',attr)))
235 (put 'jabber-xml-let-attributes 'lisp-indent-function 2)
237 (defun jabber-xml-resolve-namespace-prefixes (xml-data &optional default-ns prefixes)
238 (let ((node-name (jabber-xml-node-name xml-data))
239 (attrs (jabber-xml-node-attributes xml-data)))
240 (setq prefixes (jabber-xml-merge-namespace-declarations attrs prefixes))
242 ;; If there is an xmlns attribute, it is the new default
244 (let ((xmlns (jabber-xml-get-xmlns xml-data)))
246 (setq default-ns xmlns)))
247 ;; Now, if the node name has a prefix, replace it and add an
248 ;; "xmlns" attribute. Slightly ugly, but avoids the need to
249 ;; change all the rest of jabber.el at once.
250 (let ((node-name-string (symbol-name node-name)))
251 (when (string-match "\\(.*\\):\\(.*\\)" node-name-string)
252 (let* ((prefix (match-string 1 node-name-string))
253 (unprefixed (match-string 2 node-name-string))
254 (ns (assoc prefix prefixes)))
256 ;; This is not supposed to happen...
257 (message "jabber-xml-resolve-namespace-prefixes: Unknown prefix in %s" node-name-string)
258 (setf (car xml-data) (intern unprefixed))
259 (setf (cadr xml-data) (cons (cons 'xmlns (cdr ns)) (delq 'xmlns attrs)))))))
260 ;; And iterate through all child elements.
263 (jabber-xml-resolve-namespace-prefixes x default-ns prefixes)))
264 (jabber-xml-node-children xml-data))
267 (defun jabber-xml-merge-namespace-declarations (attrs prefixes)
268 ;; First find any xmlns:foo attributes..
270 (let ((attr-name (symbol-name (car attr))))
271 (when (string-match "xmlns:" attr-name)
272 (let ((prefix (substring attr-name (match-end 0)))
274 ;; A slightly complicated dance to never change the
275 ;; original value of prefixes (since the caller depends on
276 ;; it), but also to avoid excessive copying (which remove
277 ;; always does). Might need to profile and tweak this for
280 (cons (cons prefix ns-uri)
281 (if (assoc prefix prefixes)
282 (remove (assoc prefix prefixes) prefixes)
286 (provide 'jabber-xml)
288 ;;; arch-tag: ca206e65-7026-4ee8-9af2-ff6a9c5af98a