Merge from emacs--rel--22, gnus--devo--0
[bpt/emacs.git] / lisp / nxml / rng-nxml.el
1 ;;; rng-nxml.el --- make nxml-mode take advantage of rng-validate-mode
2
3 ;; Copyright (C) 2003, 2007 Free Software Foundation, Inc.
4
5 ;; Author: James Clark
6 ;; Keywords: XML, RelaxNG
7
8 ;; This file is part of GNU Emacs.
9
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 3, or (at your option)
13 ;; any later version.
14
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING. If not, write to the
22 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 ;; Boston, MA 02110-1301, USA.
24
25 ;;; Commentary:
26
27 ;;; Code:
28
29 (require 'easymenu)
30 (require 'xmltok)
31 (require 'nxml-util)
32 (require 'nxml-ns)
33 (require 'rng-match)
34 (require 'rng-util)
35 (require 'rng-valid)
36 (require 'nxml-mode)
37 (require 'rng-loc)
38
39 (defcustom rng-nxml-auto-validate-flag t
40 "*Non-nil means automatically turn on validation with nxml-mode."
41 :type 'boolean
42 :group 'relax-ng)
43
44 (defvar rng-preferred-prefix-alist-default nil
45 "Default value for variable `rng-preferred-prefix-alist'.")
46
47 (defcustom rng-preferred-prefix-alist rng-preferred-prefix-alist-default
48 "*Alist of namespaces vs preferred prefixes."
49 :type '(repeat (cons :tag "With"
50 (string :tag "this namespace URI")
51 (string :tag "use this prefix")))
52 :group 'relax-ng)
53
54 (defvar rng-complete-end-tags-after-< t
55 "*Non-nil means immediately after < complete on end-tag names.
56 Complete on start-tag names regardless.")
57
58 (defvar rng-nxml-easy-menu
59 '("XML"
60 ["Show Outline Only" nxml-hide-all-text-content]
61 ["Show Everything" nxml-show-all]
62 "---"
63 ["Validation" rng-validate-mode
64 :style toggle
65 :selected rng-validate-mode]
66 "---"
67 ("Set Schema"
68 ["Automatically" rng-auto-set-schema]
69 ("For Document Type"
70 :filter (lambda (menu)
71 (mapcar (lambda (type-id)
72 (vector type-id
73 (list 'rng-set-document-type
74 type-id)))
75 (rng-possible-type-ids))))
76 ["Any Well-Formed XML" rng-set-vacuous-schema]
77 ["File..." rng-set-schema-file])
78 ["Show Schema Location" rng-what-schema]
79 ["Save Schema Location" rng-save-schema-location :help
80 "Save the location of the schema currently being used for this buffer"]
81 "---"
82 ["First Error" rng-first-error :active rng-validate-mode]
83 ["Next Error" rng-next-error :active rng-validate-mode]
84 "---"
85 ["Customize nXML" (customize-group 'nxml)]))
86
87 ;;;###autoload
88 (defun rng-nxml-mode-init ()
89 "Initialize `nxml-mode' to take advantage of `rng-validate-mode'.
90 This is typically called from `nxml-mode-hook'.
91 Validation will be enabled if `rng-nxml-auto-validate-flag' is non-nil."
92 (interactive)
93 (define-key nxml-mode-map "\C-c\C-v" 'rng-validate-mode)
94 (define-key nxml-mode-map "\C-c\C-s\C-w" 'rng-what-schema)
95 (define-key nxml-mode-map "\C-c\C-s\C-a" 'rng-auto-set-schema-and-validate)
96 (define-key nxml-mode-map "\C-c\C-s\C-f" 'rng-set-schema-file-and-validate)
97 (define-key nxml-mode-map "\C-c\C-s\C-l" 'rng-save-schema-location)
98 (define-key nxml-mode-map "\C-c\C-s\C-t" 'rng-set-document-type-and-validate)
99 (define-key nxml-mode-map "\C-c\C-n" 'rng-next-error)
100 (easy-menu-define rng-nxml-menu nxml-mode-map
101 "Menu for nxml-mode used with rng-validate-mode."
102 rng-nxml-easy-menu)
103 (setq mode-line-process
104 '(rng-validate-mode (:eval (rng-compute-mode-line-string))))
105 (cond (rng-nxml-auto-validate-flag
106 (rng-validate-mode 1)
107 (add-hook 'nxml-completion-hook 'rng-complete nil t)
108 (add-hook 'nxml-in-mixed-content-hook 'rng-in-mixed-content-p nil t))
109 (t
110 (rng-validate-mode 0)
111 (remove-hook 'nxml-completion-hook 'rng-complete t)
112 (remove-hook 'nxml-in-mixed-content-hook 'rng-in-mixed-content-p t))))
113
114 (defvar rng-tag-history nil)
115 (defvar rng-attribute-name-history nil)
116 (defvar rng-attribute-value-history nil)
117
118 (defvar rng-complete-target-names nil)
119 (defvar rng-complete-name-attribute-flag nil)
120 (defvar rng-complete-extra-strings nil)
121
122 (defun rng-complete ()
123 "Complete the string before point using the current schema.
124 Return non-nil if in a context it understands."
125 (interactive)
126 (and rng-validate-mode
127 (let ((lt-pos (save-excursion (search-backward "<" nil t)))
128 xmltok-dtd)
129 (and lt-pos
130 (= (rng-set-state-after lt-pos) lt-pos)
131 (or (rng-complete-tag lt-pos)
132 (rng-complete-end-tag lt-pos)
133 (rng-complete-attribute-name lt-pos)
134 (rng-complete-attribute-value lt-pos))))))
135
136 (defconst rng-in-start-tag-name-regex
137 (replace-regexp-in-string
138 "w"
139 xmltok-ncname-regexp
140 "<\\(?:w\\(?::w?\\)?\\)?\\="
141 t
142 t))
143
144 (defun rng-complete-tag (lt-pos)
145 (let (rng-complete-extra-strings)
146 (when (and (= lt-pos (1- (point)))
147 rng-complete-end-tags-after-<
148 rng-open-elements
149 (not (eq (car rng-open-elements) t))
150 (or rng-collecting-text
151 (rng-match-save
152 (rng-match-end-tag))))
153 (setq rng-complete-extra-strings
154 (cons (concat "/"
155 (if (caar rng-open-elements)
156 (concat (caar rng-open-elements)
157 ":"
158 (cdar rng-open-elements))
159 (cdar rng-open-elements)))
160 rng-complete-extra-strings)))
161 (when (save-excursion
162 (re-search-backward rng-in-start-tag-name-regex
163 lt-pos
164 t))
165 (and rng-collecting-text (rng-flush-text))
166 (let ((completion
167 (let ((rng-complete-target-names
168 (rng-match-possible-start-tag-names))
169 (rng-complete-name-attribute-flag nil))
170 (rng-complete-before-point (1+ lt-pos)
171 'rng-complete-qname-function
172 "Tag: "
173 nil
174 'rng-tag-history)))
175 name)
176 (when completion
177 (cond ((rng-qname-p completion)
178 (setq name (rng-expand-qname completion
179 t
180 'rng-start-tag-expand-recover))
181 (when (and name
182 (rng-match-start-tag-open name)
183 (or (not (rng-match-start-tag-close))
184 ;; need a namespace decl on the root element
185 (and (car name)
186 (not rng-open-elements))))
187 ;; attributes are required
188 (insert " ")))
189 ((member completion rng-complete-extra-strings)
190 (insert ">")))))
191 t)))
192
193 (defconst rng-in-end-tag-name-regex
194 (replace-regexp-in-string
195 "w"
196 xmltok-ncname-regexp
197 "</\\(?:w\\(?::w?\\)?\\)?\\="
198 t
199 t))
200
201 (defun rng-complete-end-tag (lt-pos)
202 (when (save-excursion
203 (re-search-backward rng-in-end-tag-name-regex
204 lt-pos
205 t))
206 (cond ((or (not rng-open-elements)
207 (eq (car rng-open-elements) t))
208 (message "No matching start-tag")
209 (ding))
210 (t
211 (let ((start-tag-name
212 (if (caar rng-open-elements)
213 (concat (caar rng-open-elements)
214 ":"
215 (cdar rng-open-elements))
216 (cdar rng-open-elements)))
217 (end-tag-name
218 (buffer-substring-no-properties (+ (match-beginning 0) 2)
219 (point))))
220 (cond ((or (> (length end-tag-name)
221 (length start-tag-name))
222 (not (string= (substring start-tag-name
223 0
224 (length end-tag-name))
225 end-tag-name)))
226 (message "Expected end-tag %s"
227 (rng-quote-string
228 (concat "</" start-tag-name ">")))
229 (ding))
230 (t
231 (delete-region (- (point) (length end-tag-name))
232 (point))
233 (insert start-tag-name ">")
234 (when (not (or rng-collecting-text
235 (rng-match-end-tag)))
236 (message "Element %s is incomplete"
237 (rng-quote-string start-tag-name))))))))
238 t))
239
240 (defconst rng-in-attribute-regex
241 (replace-regexp-in-string
242 "w"
243 xmltok-ncname-regexp
244 "<w\\(?::w\\)?\
245 \\(?:[ \t\r\n]+w\\(?::w\\)?[ \t\r\n]*=\
246 [ \t\r\n]*\\(?:\"[^\"]*\"\\|'[^']*'\\)\\)*\
247 [ \t\r\n]+\\(\\(?:w\\(?::w?\\)?\\)?\\)\\="
248 t
249 t))
250
251 (defvar rng-undeclared-prefixes nil)
252
253 (defun rng-complete-attribute-name (lt-pos)
254 (when (save-excursion
255 (re-search-backward rng-in-attribute-regex lt-pos t))
256 (let ((attribute-start (match-beginning 1))
257 rng-undeclared-prefixes)
258 (and (rng-adjust-state-for-attribute lt-pos
259 attribute-start)
260 (let ((rng-complete-target-names
261 (rng-match-possible-attribute-names))
262 (rng-complete-extra-strings
263 (mapcar (lambda (prefix)
264 (if prefix
265 (concat "xmlns:" prefix)
266 "xmlns"))
267 rng-undeclared-prefixes))
268 (rng-complete-name-attribute-flag t))
269 (rng-complete-before-point attribute-start
270 'rng-complete-qname-function
271 "Attribute: "
272 nil
273 'rng-attribute-name-history))
274 (insert "=\"")))
275 t))
276
277 (defconst rng-in-attribute-value-regex
278 (replace-regexp-in-string
279 "w"
280 xmltok-ncname-regexp
281 "<w\\(?::w\\)?\
282 \\(?:[ \t\r\n]+w\\(?::w\\)?[ \t\r\n]*=\
283 [ \t\r\n]*\\(?:\"[^\"]*\"\\|'[^']*'\\)\\)*\
284 [ \t\r\n]+\\(w\\(:w\\)?\\)[ \t\r\n]*=[ \t\r\n]*\
285 \\(\"[^\"]*\\|'[^']*\\)\\="
286 t
287 t))
288
289 (defun rng-complete-attribute-value (lt-pos)
290 (when (save-excursion
291 (re-search-backward rng-in-attribute-value-regex lt-pos t))
292 (let ((name-start (match-beginning 1))
293 (name-end (match-end 1))
294 (colon (match-beginning 2))
295 (value-start (1+ (match-beginning 3))))
296 (and (rng-adjust-state-for-attribute lt-pos
297 name-start)
298 (if (string= (buffer-substring-no-properties name-start
299 (or colon name-end))
300 "xmlns")
301 (rng-complete-before-point
302 value-start
303 (rng-strings-to-completion-alist
304 (rng-possible-namespace-uris
305 (and colon
306 (buffer-substring-no-properties (1+ colon) name-end))))
307 "Namespace URI: "
308 nil
309 'rng-namespace-uri-history)
310 (rng-adjust-state-for-attribute-value name-start
311 colon
312 name-end)
313 (rng-complete-before-point
314 value-start
315 (rng-strings-to-completion-alist
316 (rng-match-possible-value-strings))
317 "Value: "
318 nil
319 'rng-attribute-value-history))
320 (insert (char-before value-start))))
321 t))
322
323 (defun rng-possible-namespace-uris (prefix)
324 (let ((ns (if prefix (nxml-ns-get-prefix prefix)
325 (nxml-ns-get-default))))
326 (if (and ns (memq prefix (nxml-ns-changed-prefixes)))
327 (list (nxml-namespace-name ns))
328 (mapcar 'nxml-namespace-name
329 (delq nxml-xml-namespace-uri
330 (rng-match-possible-namespace-uris))))))
331
332 (defconst rng-qname-regexp
333 (concat "\\`"
334 xmltok-ncname-regexp
335 "\\(?:" ":" xmltok-ncname-regexp "\\)" "?" "\\'"))
336
337 (defun rng-qname-p (string)
338 (and (string-match rng-qname-regexp string) t))
339
340 (defun rng-expand-qname (qname &optional defaultp recover-fun)
341 (setq qname (rng-split-qname qname))
342 (let ((prefix (car qname)))
343 (if prefix
344 (let ((ns (nxml-ns-get-prefix qname)))
345 (cond (ns (cons ns (cdr qname)))
346 (recover-fun (funcall recover-fun prefix (cdr qname)))))
347 (cons (and defaultp (nxml-ns-get-default)) (cdr qname)))))
348
349 (defun rng-start-tag-expand-recover (prefix local-name)
350 (let ((ns (rng-match-infer-start-tag-namespace local-name)))
351 (and ns
352 (cons ns local-name))))
353
354 (defun rng-split-qname (qname)
355 (if (string-match ":" qname)
356 (cons (substring qname 0 (match-beginning 0))
357 (substring qname (match-end 0)))
358 (cons nil qname)))
359
360 (defun rng-in-mixed-content-p ()
361 "Return non-nil if point is in mixed content.
362 Return nil only if point is definitely not in mixed content.
363 If unsure, return non-nil."
364 (if (eq rng-current-schema rng-any-element)
365 t
366 (rng-set-state-after)
367 (rng-match-mixed-text)))
368
369 (defun rng-set-state-after (&optional pos)
370 "Set the state for after parsing the first token with endpoint >= POS.
371 This does not change the xmltok state or point. However, it does
372 set `xmltok-dtd'. Returns the position of the end of the token."
373 (unless pos (setq pos (point)))
374 (when (< rng-validate-up-to-date-end pos)
375 (message "Parsing...")
376 (while (and (rng-do-some-validation)
377 (< rng-validate-up-to-date-end pos))
378 ;; Display percentage validated.
379 (force-mode-line-update)
380 ;; Force redisplay but don't allow idle timers to run.
381 (let ((timer-idle-list nil))
382 (sit-for 0)))
383 (message "Parsing...done"))
384 (save-excursion
385 (save-restriction
386 (widen)
387 (nxml-with-invisible-motion
388 (if (= pos 1)
389 (rng-set-initial-state)
390 (let ((state (get-text-property (1- pos) 'rng-state)))
391 (cond (state
392 (rng-restore-state state)
393 (goto-char pos))
394 (t
395 (let ((start (previous-single-property-change pos
396 'rng-state)))
397 (cond (start
398 (rng-restore-state (get-text-property (1- start)
399 'rng-state))
400 (goto-char start))
401 (t (rng-set-initial-state))))))))
402 (xmltok-save
403 (if (= (point) 1)
404 (xmltok-forward-prolog)
405 (setq xmltok-dtd rng-dtd))
406 (cond ((and (< pos (point))
407 ;; This handles the case where the prolog ends
408 ;; with a < without any following name-start
409 ;; character. This will be treated by the parser
410 ;; as part of the prolog, but we want to treat
411 ;; it as the start of the instance.
412 (eq (char-after pos) ?<)
413 (<= (point)
414 (save-excursion
415 (goto-char (1+ pos))
416 (skip-chars-forward " \t\r\n")
417 (point))))
418 pos)
419 ((< (point) pos)
420 (let ((rng-dt-namespace-context-getter
421 '(nxml-ns-get-context))
422 (rng-parsing-for-state t))
423 (rng-forward pos))
424 (point))
425 (t pos)))))))
426
427 (defun rng-adjust-state-for-attribute (lt-pos start)
428 (xmltok-save
429 (save-excursion
430 (goto-char lt-pos)
431 (when (memq (xmltok-forward)
432 '(start-tag
433 partial-start-tag
434 empty-element
435 partial-empty-element))
436 (when (< start (point))
437 (setq xmltok-namespace-attributes
438 (rng-prune-attribute-at start
439 xmltok-namespace-attributes))
440 (setq xmltok-attributes
441 (rng-prune-attribute-at start
442 xmltok-attributes)))
443 (let ((rng-parsing-for-state t)
444 (rng-dt-namespace-context-getter '(nxml-ns-get-context)))
445 (rng-process-start-tag 'stop)
446 (rng-find-undeclared-prefixes)
447 t)))))
448
449 (defun rng-find-undeclared-prefixes ()
450 ;; Start with the newly effective namespace declarations.
451 ;; (Includes declarations added during recovery.)
452 (setq rng-undeclared-prefixes (nxml-ns-changed-prefixes))
453 (let ((iter xmltok-attributes)
454 (ns-state (nxml-ns-state))
455 att)
456 ;; Add namespace prefixes used in this tag,
457 ;; but not declared in the parent.
458 (nxml-ns-pop-state)
459 (while iter
460 (setq att (car iter))
461 (let ((prefix (xmltok-attribute-prefix att)))
462 (when (and prefix
463 (not (member prefix rng-undeclared-prefixes))
464 (not (nxml-ns-get-prefix prefix)))
465 (setq rng-undeclared-prefixes
466 (cons prefix rng-undeclared-prefixes))))
467 (setq iter (cdr iter)))
468 (nxml-ns-set-state ns-state)
469 ;; Remove namespace prefixes explicitly declared.
470 (setq iter xmltok-namespace-attributes)
471 (while iter
472 (setq att (car iter))
473 (setq rng-undeclared-prefixes
474 (delete (and (xmltok-attribute-prefix att)
475 (xmltok-attribute-local-name att))
476 rng-undeclared-prefixes))
477 (setq iter (cdr iter)))))
478
479 (defun rng-prune-attribute-at (start atts)
480 (when atts
481 (let ((cur atts))
482 (while (if (eq (xmltok-attribute-name-start (car cur)) start)
483 (progn
484 (setq atts (delq (car cur) atts))
485 nil)
486 (setq cur (cdr cur)))))
487 atts))
488
489 (defun rng-adjust-state-for-attribute-value (name-start
490 colon
491 name-end)
492 (let* ((prefix (if colon
493 (buffer-substring-no-properties name-start colon)
494 nil))
495 (local-name (buffer-substring-no-properties (if colon
496 (1+ colon)
497 name-start)
498 name-end))
499 (ns (and prefix (nxml-ns-get-prefix prefix))))
500 (and (or (not prefix) ns)
501 (rng-match-attribute-name (cons ns local-name)))))
502
503 (defun rng-complete-qname-function (string predicate flag)
504 (let ((alist (mapcar (lambda (name) (cons name nil))
505 (rng-generate-qname-list string))))
506 (cond ((not flag)
507 (try-completion string alist predicate))
508 ((eq flag t)
509 (all-completions string alist predicate))
510 ((eq flag 'lambda)
511 (and (assoc string alist) t)))))
512
513 (defun rng-generate-qname-list (&optional string)
514 (let ((forced-prefix (and string
515 (string-match ":" string)
516 (> (match-beginning 0) 0)
517 (substring string
518 0
519 (match-beginning 0))))
520 (namespaces (mapcar 'car rng-complete-target-names))
521 ns-prefixes-alist ns-prefixes iter ns prefer)
522 (while namespaces
523 (setq ns (car namespaces))
524 (when ns
525 (setq ns-prefixes-alist
526 (cons (cons ns (nxml-ns-prefixes-for
527 ns
528 rng-complete-name-attribute-flag))
529 ns-prefixes-alist)))
530 (setq namespaces (delq ns (cdr namespaces))))
531 (setq iter ns-prefixes-alist)
532 (while iter
533 (setq ns-prefixes (car iter))
534 (setq ns (car ns-prefixes))
535 (when (null (cdr ns-prefixes))
536 ;; No declared prefix for the namespace
537 (if forced-prefix
538 ;; If namespace non-nil and prefix undeclared,
539 ;; use forced prefix.
540 (when (and ns
541 (not (nxml-ns-get-prefix forced-prefix)))
542 (setcdr ns-prefixes (list forced-prefix)))
543 (setq prefer (rng-get-preferred-unused-prefix ns))
544 (when prefer
545 (setcdr ns-prefixes (list prefer)))
546 ;; Unless it's an attribute with a non-nil namespace,
547 ;; allow no prefix for this namespace.
548 (unless rng-complete-name-attribute-flag
549 (setcdr ns-prefixes (cons nil (cdr ns-prefixes))))))
550 (setq iter (cdr iter)))
551 (rng-uniquify-equal
552 (sort (apply 'append
553 (cons rng-complete-extra-strings
554 (mapcar (lambda (name)
555 (if (car name)
556 (mapcar (lambda (prefix)
557 (if prefix
558 (concat prefix
559 ":"
560 (cdr name))
561 (cdr name)))
562 (cdr (assoc (car name)
563 ns-prefixes-alist)))
564 (list (cdr name))))
565 rng-complete-target-names)))
566 'string<))))
567
568 (defun rng-get-preferred-unused-prefix (ns)
569 (let ((ns-prefix (assoc (symbol-name ns) rng-preferred-prefix-alist))
570 iter prefix)
571 (when ns-prefix
572 (setq prefix (cdr ns-prefix))
573 (when (nxml-ns-get-prefix prefix)
574 ;; try to find an unused prefix
575 (setq iter (memq ns-prefix rng-preferred-prefix-alist))
576 (while (and iter
577 (setq ns-prefix (assoc ns iter)))
578 (if (nxml-ns-get-prefix (cdr ns-prefix))
579 (setq iter (memq ns-prefix iter))
580 (setq prefix (cdr ns-prefix))
581 nil))))
582 prefix))
583
584 (defun rng-strings-to-completion-alist (strings)
585 (mapcar (lambda (s) (cons s s))
586 (rng-uniquify-equal (sort (mapcar 'rng-escape-string strings)
587 'string<))))
588
589 (provide 'rng-nxml)
590
591 ;; arch-tag: bec0d6ed-6be1-4540-9c2c-6f56e8e55d8b
592 ;;; rng-nxml.el ends here