]>
Commit | Line | Data |
---|---|---|
53e6db90 DC |
1 | ;; jabber-xml.el - XML functions -*- lexical-binding: t; -*- |
2 | ||
3 | ;; Copyright (C) 2003, 2004, 2007, 2008 - Magnus Henoch - mange@freemail.hu | |
4 | ;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net | |
5 | ||
6 | ;; This file is a part of jabber.el. | |
7 | ||
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. | |
12 | ||
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. | |
17 | ||
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 | |
21 | ||
22 | (require 'xml) | |
23 | (require 'jabber-util) | |
24 | (eval-when-compile | |
25 | (require 'cl-lib)) | |
26 | ||
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 \"\\\" | |
30 | specially." | |
31 | (replace-regexp-in-string regexp newtext string t t)) | |
32 | ||
33 | (defun jabber-escape-xml (string) | |
34 | "Escape STRING for XML." | |
35 | (if (stringp string) | |
36 | (let ((newstr (concat string))) | |
37 | ;; Form feeds might appear in code you copy, etc. Nevertheless, | |
38 | ;; it's invalid XML. | |
39 | (setq newstr (jabber-replace-in-string newstr "\f" "\n")) | |
40 | ;; Other control characters are also illegal, except for | |
41 | ;; tab, CR, and LF. | |
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 "\"" """)) | |
48 | newstr) | |
49 | string)) | |
50 | ||
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. | |
55 | (if (stringp string) | |
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 "&" "&")) | |
62 | newstr) | |
63 | string)) | |
64 | ||
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...)" | |
69 | (cond | |
70 | ((stringp sexp) | |
71 | (jabber-escape-xml sexp)) | |
72 | ((listp (car sexp)) | |
73 | (let ((xml "")) | |
74 | (dolist (tag sexp) | |
75 | (setq xml (concat xml (jabber-sexp2xml tag)))) | |
76 | xml)) | |
77 | ;; work around bug in old versions of xml.el, where ("") can appear | |
78 | ;; as children of a node | |
79 | ((and (consp sexp) | |
80 | (stringp (car sexp)) | |
81 | (zerop (length (car sexp)))) | |
82 | "") | |
83 | (t | |
84 | (let ((xml "")) | |
85 | (setq xml (concat "<" | |
86 | (symbol-name (car sexp)))) | |
87 | (dolist (attr (cadr sexp)) | |
88 | (if (consp attr) | |
89 | (setq xml (concat xml | |
90 | (format " %s='%s'" | |
91 | (symbol-name (car attr)) | |
92 | (jabber-escape-xml (cdr attr))))))) | |
93 | (if (cddr sexp) | |
94 | (progn | |
95 | (setq xml (concat xml ">")) | |
96 | (dolist (child (cddr sexp)) | |
97 | (setq xml (concat xml | |
98 | (jabber-sexp2xml child)))) | |
99 | (setq xml (concat xml | |
100 | "</" | |
101 | (symbol-name (car sexp)) | |
102 | ">"))) | |
103 | (setq xml (concat xml | |
104 | "/>"))) | |
105 | xml)))) | |
106 | ||
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 | |
110 | tag with value nil. | |
111 | If DONT-RECURSE-INTO-STREAM is non-nil, stop after an opening | |
112 | <stream:stream> tag. | |
113 | ||
114 | The version of `sgml-skip-tag-forward' in Emacs 21 isn't good | |
115 | enough for us." | |
116 | (skip-chars-forward "^<") | |
117 | (cond | |
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. | |
126 | (cond | |
127 | ((looking-at "/>") | |
128 | (goto-char (match-end 0)) | |
129 | t) | |
130 | ((looking-at ">") | |
131 | (goto-char (match-end 0)) | |
132 | (unless (and dont-recurse-into-stream (equal node-name "stream:stream")) | |
133 | (cl-loop | |
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))) | |
138 | t) | |
139 | (t | |
140 | (throw 'unfinished nil))))) | |
141 | (t | |
142 | (throw 'unfinished nil)))) | |
143 | ||
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)))) | |
154 | ||
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))) | |
159 | ||
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))) | |
164 | ||
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 '((""))) | |
171 | nil | |
172 | children))) | |
173 | ||
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." | |
177 | (let ((match ())) | |
178 | (dolist (child (jabber-xml-node-children node)) | |
179 | (if child | |
180 | (if (equal (jabber-xml-node-name child) child-name) | |
181 | (push child match)))) | |
182 | (nreverse match))) | |
183 | ||
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." | |
189 | (when (consp node) | |
190 | (xml-get-attribute-or-nil node attribute))) | |
191 | ||
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)) | |
195 | ||
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))) | |
207 | (cond | |
208 | ((symbolp step) | |
209 | (setq node (car (jabber-xml-get-children node step)))) | |
210 | ((consp 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. | |
214 | (setq node | |
215 | (cl-dolist (x (jabber-xml-get-children node (intern (cdr step)))) | |
216 | (when (string= (jabber-xml-get-attribute x 'xmlns) | |
217 | (car step)) | |
218 | (cl-return x))))) | |
219 | ((stringp step) | |
220 | (setq node (car (jabber-xml-node-children node))) | |
221 | (unless (stringp node) | |
222 | (setq node nil))) | |
223 | (t | |
224 | (error "Unknown path step: %s" step)))) | |
225 | (setq path (cdr path))) | |
226 | node)) | |
227 | ||
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))) | |
233 | attributes) | |
234 | ,@body)) | |
235 | (put 'jabber-xml-let-attributes 'lisp-indent-function 2) | |
236 | ||
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)) | |
241 | ||
242 | ;; If there is an xmlns attribute, it is the new default | |
243 | ;; namespace. | |
244 | (let ((xmlns (jabber-xml-get-xmlns xml-data))) | |
245 | (when xmlns | |
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))) | |
255 | (if (null ns) | |
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. | |
261 | (mapc (lambda (x) | |
262 | (when (listp x) | |
263 | (jabber-xml-resolve-namespace-prefixes x default-ns prefixes))) | |
264 | (jabber-xml-node-children xml-data)) | |
265 | xml-data)) | |
266 | ||
267 | (defun jabber-xml-merge-namespace-declarations (attrs prefixes) | |
268 | ;; First find any xmlns:foo attributes.. | |
269 | (dolist (attr attrs) | |
270 | (let ((attr-name (symbol-name (car attr)))) | |
271 | (when (string-match "xmlns:" attr-name) | |
272 | (let ((prefix (substring attr-name (match-end 0))) | |
273 | (ns-uri (cdr attr))) | |
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 | |
278 | ;; performance. | |
279 | (setq prefixes | |
280 | (cons (cons prefix ns-uri) | |
281 | (if (assoc prefix prefixes) | |
282 | (remove (assoc prefix prefixes) prefixes) | |
283 | prefixes))))))) | |
284 | prefixes) | |
285 | ||
286 | (provide 'jabber-xml) | |
287 | ||
288 | ;;; arch-tag: ca206e65-7026-4ee8-9af2-ff6a9c5af98a |