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