1 ;;; rng-nxml.el --- make nxml-mode take advantage of rng-validate-mode
3 ;; Copyright (C) 2003, 2007 Free Software Foundation, Inc.
6 ;; Keywords: XML, RelaxNG
8 ;; This file is part of GNU Emacs.
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)
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.
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.
39 (defcustom rng-nxml-auto-validate-flag t
40 "*Non-nil means automatically turn on validation with nxml-mode."
44 (defvar rng-preferred-prefix-alist-default nil
45 "Default value for variable `rng-preferred-prefix-alist'.")
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")))
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.")
58 (defvar rng-nxml-easy-menu
60 ["Show Outline Only" nxml-hide-all-text-content
]
61 ["Show Everything" nxml-show-all
]
63 ["Validation" rng-validate-mode
65 :selected rng-validate-mode
]
68 ["Automatically" rng-auto-set-schema
]
70 :filter
(lambda (menu)
71 (mapcar (lambda (type-id)
73 (list 'rng-set-document-type
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"]
82 ["First Error" rng-first-error
:active rng-validate-mode
]
83 ["Next Error" rng-next-error
:active rng-validate-mode
]
85 ["Customize nXML" (customize-group 'nxml
)]))
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."
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."
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
))
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
))))
114 (defvar rng-tag-history nil
)
115 (defvar rng-attribute-name-history nil
)
116 (defvar rng-attribute-value-history nil
)
118 (defvar rng-complete-target-names nil
)
119 (defvar rng-complete-name-attribute-flag nil
)
120 (defvar rng-complete-extra-strings nil
)
122 (defun rng-complete ()
123 "Complete the string before point using the current schema.
124 Return non-nil if in a context it understands."
126 (and rng-validate-mode
127 (let ((lt-pos (save-excursion (search-backward "<" nil t
)))
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
))))))
136 (defconst rng-in-start-tag-name-regex
137 (replace-regexp-in-string
140 "<\\(?:w\\(?::w?\\)?\\)?\\="
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-
<
149 (not (eq (car rng-open-elements
) t
))
150 (or rng-collecting-text
152 (rng-match-end-tag))))
153 (setq rng-complete-extra-strings
155 (if (caar rng-open-elements
)
156 (concat (caar rng-open-elements
)
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
165 (and rng-collecting-text
(rng-flush-text))
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
177 (cond ((rng-qname-p completion
)
178 (setq name
(rng-expand-qname completion
180 'rng-start-tag-expand-recover
))
182 (rng-match-start-tag-open name
)
183 (or (not (rng-match-start-tag-close))
184 ;; need a namespace decl on the root element
186 (not rng-open-elements
))))
187 ;; attributes are required
189 ((member completion rng-complete-extra-strings
)
193 (defconst rng-in-end-tag-name-regex
194 (replace-regexp-in-string
197 "</\\(?:w\\(?::w?\\)?\\)?\\="
201 (defun rng-complete-end-tag (lt-pos)
202 (when (save-excursion
203 (re-search-backward rng-in-end-tag-name-regex
206 (cond ((or (not rng-open-elements
)
207 (eq (car rng-open-elements
) t
))
208 (message "No matching start-tag")
211 (let ((start-tag-name
212 (if (caar rng-open-elements
)
213 (concat (caar rng-open-elements
)
215 (cdar rng-open-elements
))
216 (cdar rng-open-elements
)))
218 (buffer-substring-no-properties (+ (match-beginning 0) 2)
220 (cond ((or (> (length end-tag-name
)
221 (length start-tag-name
))
222 (not (string= (substring start-tag-name
224 (length end-tag-name
))
226 (message "Expected end-tag %s"
228 (concat "</" start-tag-name
">")))
231 (delete-region (- (point) (length end-tag-name
))
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
))))))))
240 (defconst rng-in-attribute-regex
241 (replace-regexp-in-string
245 \\(?:[ \t\r\n]+w\\(?::w\\)?[ \t\r\n]*=\
246 [ \t\r\n]*\\(?:\"[^\"]*\"\\|'[^']*'\\)\\)*\
247 [ \t\r\n]+\\(\\(?:w\\(?::w?\\)?\\)?\\)\\="
251 (defvar rng-undeclared-prefixes nil
)
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
260 (let ((rng-complete-target-names
261 (rng-match-possible-attribute-names))
262 (rng-complete-extra-strings
263 (mapcar (lambda (prefix)
265 (concat "xmlns:" prefix
)
267 rng-undeclared-prefixes
))
268 (rng-complete-name-attribute-flag t
))
269 (rng-complete-before-point attribute-start
270 'rng-complete-qname-function
273 'rng-attribute-name-history
))
277 (defconst rng-in-attribute-value-regex
278 (replace-regexp-in-string
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 \\(\"[^\"]*\\|'[^']*\\)\\="
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
298 (if (string= (buffer-substring-no-properties name-start
301 (rng-complete-before-point
303 (rng-strings-to-completion-alist
304 (rng-possible-namespace-uris
306 (buffer-substring-no-properties (1+ colon
) name-end
))))
309 'rng-namespace-uri-history
)
310 (rng-adjust-state-for-attribute-value name-start
313 (rng-complete-before-point
315 (rng-strings-to-completion-alist
316 (rng-match-possible-value-strings))
319 'rng-attribute-value-history
))
320 (insert (char-before value-start
))))
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))))))
332 (defconst rng-qname-regexp
335 "\\(?:" ":" xmltok-ncname-regexp
"\\)" "?" "\\'"))
337 (defun rng-qname-p (string)
338 (and (string-match rng-qname-regexp string
) t
))
340 (defun rng-expand-qname (qname &optional defaultp recover-fun
)
341 (setq qname
(rng-split-qname qname
))
342 (let ((prefix (car qname
)))
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
)))))
349 (defun rng-start-tag-expand-recover (prefix local-name
)
350 (let ((ns (rng-match-infer-start-tag-namespace local-name
)))
352 (cons ns local-name
))))
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)))
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
)
366 (rng-set-state-after)
367 (rng-match-mixed-text)))
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
))
383 (message "Parsing...done"))
387 (nxml-with-invisible-motion
389 (rng-set-initial-state)
390 (let ((state (get-text-property (1- pos
) 'rng-state
)))
392 (rng-restore-state state
)
395 (let ((start (previous-single-property-change pos
398 (rng-restore-state (get-text-property (1- start
)
401 (t (rng-set-initial-state))))))))
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
) ?
<)
416 (skip-chars-forward " \t\r\n")
420 (let ((rng-dt-namespace-context-getter
421 '(nxml-ns-get-context))
422 (rng-parsing-for-state t
))
427 (defun rng-adjust-state-for-attribute (lt-pos start
)
431 (when (memq (xmltok-forward)
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
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)
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))
456 ;; Add namespace prefixes used in this tag,
457 ;; but not declared in the parent.
460 (setq att
(car iter
))
461 (let ((prefix (xmltok-attribute-prefix att
)))
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
)
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
)))))
479 (defun rng-prune-attribute-at (start atts
)
482 (while (if (eq (xmltok-attribute-name-start (car cur
)) start
)
484 (setq atts
(delq (car cur
) atts
))
486 (setq cur
(cdr cur
)))))
489 (defun rng-adjust-state-for-attribute-value (name-start
492 (let* ((prefix (if colon
493 (buffer-substring-no-properties name-start colon
)
495 (local-name (buffer-substring-no-properties (if colon
499 (ns (and prefix
(nxml-ns-get-prefix prefix
))))
500 (and (or (not prefix
) ns
)
501 (rng-match-attribute-name (cons ns local-name
)))))
503 (defun rng-complete-qname-function (string predicate flag
)
504 (let ((alist (mapcar (lambda (name) (cons name nil
))
505 (rng-generate-qname-list string
))))
507 (try-completion string alist predicate
))
509 (all-completions string alist predicate
))
511 (and (assoc string alist
) t
)))))
513 (defun rng-generate-qname-list (&optional string
)
514 (let ((forced-prefix (and string
515 (string-match ":" string
)
516 (> (match-beginning 0) 0)
519 (match-beginning 0))))
520 (namespaces (mapcar 'car rng-complete-target-names
))
521 ns-prefixes-alist ns-prefixes iter ns prefer
)
523 (setq ns
(car namespaces
))
525 (setq ns-prefixes-alist
526 (cons (cons ns
(nxml-ns-prefixes-for
528 rng-complete-name-attribute-flag
))
530 (setq namespaces
(delq ns
(cdr namespaces
))))
531 (setq iter ns-prefixes-alist
)
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
538 ;; If namespace non-nil and prefix undeclared,
539 ;; use forced prefix.
541 (not (nxml-ns-get-prefix forced-prefix
)))
542 (setcdr ns-prefixes
(list forced-prefix
)))
543 (setq prefer
(rng-get-preferred-unused-prefix ns
))
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
)))
553 (cons rng-complete-extra-strings
554 (mapcar (lambda (name)
556 (mapcar (lambda (prefix)
562 (cdr (assoc (car name
)
565 rng-complete-target-names
)))
568 (defun rng-get-preferred-unused-prefix (ns)
569 (let ((ns-prefix (assoc (symbol-name ns
) rng-preferred-prefix-alist
))
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
))
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
))
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
)
591 ;; arch-tag: bec0d6ed-6be1-4540-9c2c-6f56e8e55d8b
592 ;;; rng-nxml.el ends here