Refill some copyright headers.
[bpt/emacs.git] / lisp / nxml / nxml-mode.el
1 ;;; nxml-mode.el --- a new XML mode
2
3 ;; Copyright (C) 2003, 2004, 2007, 2008, 2009, 2010, 2011
4 ;; Free Software Foundation, Inc.
5
6 ;; Author: James Clark
7 ;; Keywords: XML
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 ;; See nxml-rap.el for description of parsing strategy.
27
28 ;;; Code:
29
30 (when (featurep 'mucs)
31 (error "nxml-mode is not compatible with Mule-UCS"))
32
33 (eval-when-compile (require 'cl)) ; for assert
34
35 (require 'xmltok)
36 (require 'nxml-enc)
37 (require 'nxml-glyph)
38 (require 'nxml-util)
39 (require 'nxml-rap)
40 (require 'nxml-outln)
41 ;; nxml-mode calls rng-nxml-mode-init, which is autoloaded from rng-nxml.
42 ;; So we might as well just require it and silence the compiler.
43 (provide 'nxml-mode) ; avoid recursive require
44 (require 'rng-nxml)
45
46 ;;; Customization
47
48 (defgroup nxml nil
49 "New XML editing mode."
50 :group 'languages)
51
52 (defgroup nxml-faces nil
53 "Faces for XML syntax highlighting."
54 :group 'nxml)
55
56 (defcustom nxml-char-ref-display-glyph-flag t
57 "Non-nil means display glyph following character reference.
58 The glyph is displayed in face `nxml-glyph'. The hook
59 `nxml-glyph-set-hook' can be used to customize for which characters
60 glyphs are displayed."
61 :group 'nxml
62 :type 'boolean)
63
64 (defcustom nxml-sexp-element-flag nil
65 "Non-nil means sexp commands treat an element as a single expression."
66 :group 'nxml
67 :type 'boolean)
68
69 (defcustom nxml-slash-auto-complete-flag nil
70 "Non-nil means typing a slash automatically completes the end-tag.
71 This is used by `nxml-electric-slash'."
72 :group 'nxml
73 :type 'boolean)
74
75 (defcustom nxml-child-indent 2
76 "Indentation for the children of an element relative to the start-tag.
77 This only applies when the line or lines containing the start-tag contains
78 nothing else other than that start-tag."
79 :group 'nxml
80 :type 'integer)
81
82 (defcustom nxml-attribute-indent 4
83 "Indentation for the attributes of an element relative to the start-tag.
84 This only applies when the first attribute of a tag starts a line.
85 In other cases, the first attribute on one line is indented the same
86 as the first attribute on the previous line."
87 :group 'nxml
88 :type 'integer)
89
90 (defcustom nxml-bind-meta-tab-to-complete-flag (not window-system)
91 "Non-nil means bind M-TAB in `nxml-mode-map' to `nxml-complete'.
92 C-return will be bound to `nxml-complete' in any case.
93 M-TAB gets swallowed by many window systems/managers, and
94 `documentation' will show M-TAB rather than C-return as the
95 binding for `nxml-complete' when both are bound. So it's better
96 to bind M-TAB only when it will work."
97 :group 'nxml
98 :set (lambda (sym flag)
99 (set-default sym flag)
100 (when (and (boundp 'nxml-mode-map) nxml-mode-map)
101 (define-key nxml-mode-map "\M-\t" (and flag 'nxml-complete))))
102 :type 'boolean)
103
104 (defcustom nxml-prefer-utf-16-to-utf-8-flag nil
105 "Non-nil means prefer UTF-16 to UTF-8 when saving a buffer.
106 This is used only when a buffer does not contain an encoding declaration
107 and when its current `buffer-file-coding-system' specifies neither UTF-16
108 nor UTF-8."
109 :group 'nxml
110 :type 'boolean)
111
112 (defcustom nxml-prefer-utf-16-little-to-big-endian-flag (eq system-type
113 'windows-nt)
114 "Non-nil means prefer little-endian to big-endian byte-order for UTF-16.
115 This is used only for saving a buffer; when reading the byte-order is
116 auto-detected. It may be relevant both when there is no encoding declaration
117 and when the encoding declaration specifies `UTF-16'."
118 :group 'nxml
119 :type 'boolean)
120
121 (defcustom nxml-default-buffer-file-coding-system nil
122 "Default value for `buffer-file-coding-system' for a buffer for a new file.
123 A value of nil means use the default value of `buffer-file-coding-system' as normal.
124 A buffer's `buffer-file-coding-system' affects what \\[nxml-insert-xml-declaration] inserts."
125 :group 'nxml
126 :type 'coding-system)
127
128 (defcustom nxml-auto-insert-xml-declaration-flag nil
129 "Non-nil means automatically insert an XML declaration in a new file.
130 The XML declaration is inserted using `nxml-insert-xml-declaration'."
131 :group 'nxml
132 :type 'boolean)
133
134 (defface nxml-delimited-data
135 '((t (:inherit font-lock-doc-face)))
136 "Face used to highlight data enclosed between delimiters.
137 This is not used directly, but only via inheritance by other faces."
138 :group 'nxml-faces)
139
140 (defface nxml-name
141 '((t (:inherit font-lock-builtin-face)))
142 "Face used to highlight various names.
143 This includes element and attribute names, processing
144 instruction targets and the CDATA keyword in a CDATA section.
145 This is not used directly, but only via inheritance by other faces."
146 :group 'nxml-faces)
147
148 (defface nxml-ref
149 '((t (:inherit font-lock-constant-face)))
150 "Face used to highlight character and entity references.
151 This is not used directly, but only via inheritance by other faces."
152 :group 'nxml-faces)
153
154 (defface nxml-delimiter
155 nil
156 "Face used to highlight delimiters.
157 This is not used directly, but only via inheritance by other faces."
158 :group 'nxml-faces)
159
160 (defface nxml-text
161 nil
162 "Face used to highlight text."
163 :group 'nxml-faces)
164
165 (defface nxml-comment-content
166 '((t (:inherit font-lock-comment-face)))
167 "Face used to highlight the content of comments."
168 :group 'nxml-faces)
169
170 (defface nxml-comment-delimiter
171 '((t (:inherit font-lock-comment-delimiter-face)))
172 "Face used for the delimiters of comments, i.e <!-- and -->."
173 :group 'nxml-faces)
174
175 (defface nxml-processing-instruction-delimiter
176 '((t (:inherit nxml-delimiter)))
177 "Face used for the delimiters of processing instructions, i.e <? and ?>."
178 :group 'nxml-faces)
179
180 (defface nxml-processing-instruction-target
181 '((t (:inherit font-lock-keyword-face)))
182 "Face used for the target of processing instructions."
183 :group 'nxml-faces)
184
185 (defface nxml-processing-instruction-content
186 '((t (:inherit nxml-delimited-data)))
187 "Face used for the content of processing instructions."
188 :group 'nxml-faces)
189
190 (defface nxml-cdata-section-delimiter
191 '((t (:inherit nxml-delimiter)))
192 "Face used for the delimiters of CDATA sections, i.e <![, [, and ]]>."
193 :group 'nxml-faces)
194
195 (defface nxml-cdata-section-CDATA
196 '((t (:inherit nxml-name)))
197 "Face used for the CDATA keyword in CDATA sections."
198 :group 'nxml-faces)
199
200 (defface nxml-cdata-section-content
201 '((t (:inherit nxml-text)))
202 "Face used for the content of CDATA sections."
203 :group 'nxml-faces)
204
205 (defface nxml-char-ref-number
206 '((t (:inherit nxml-ref)))
207 "Face used for the number in character references.
208 This includes ths `x' in hex references."
209 :group 'nxml-faces)
210
211 (defface nxml-char-ref-delimiter
212 '((t (:inherit nxml-ref)))
213 "Face used for the delimiters of character references, i.e &# and ;."
214 :group 'nxml-faces)
215
216 (defface nxml-entity-ref-name
217 '((t (:inherit nxml-ref)))
218 "Face used for the entity name in general entity references."
219 :group 'nxml-faces)
220
221 (defface nxml-entity-ref-delimiter
222 '((t (:inherit nxml-ref)))
223 "Face used for the delimiters of entity references, i.e & and ;."
224 :group 'nxml-faces)
225
226 (defface nxml-tag-delimiter
227 '((t (:inherit nxml-delimiter)))
228 "Face used for the angle brackets delimiting tags.
229 `nxml-tag-slash' is used for slashes."
230 :group 'nxml-faces)
231
232 (defface nxml-tag-slash
233 '((t (:inherit nxml-tag-delimiter)))
234 "Face used for slashes in tags, both in end-tags and empty-elements."
235 :group 'nxml-faces)
236
237 (defface nxml-element-prefix
238 '((t (:inherit nxml-name)))
239 "Face used for the prefix of elements."
240 :group 'nxml-faces)
241
242 (defface nxml-element-colon
243 nil
244 "Face used for the colon in element names."
245 :group 'nxml-faces)
246
247 (defface nxml-element-local-name
248 '((t (:inherit font-lock-function-name-face)))
249 "Face used for the local name of elements."
250 :group 'nxml-faces)
251
252 (defface nxml-attribute-prefix
253 '((t (:inherit nxml-name)))
254 "Face used for the prefix of attributes."
255 :group 'nxml-faces)
256
257 (defface nxml-attribute-colon
258 '((t (:inherit nxml-delimiter)))
259 "Face used for the colon in attribute names."
260 :group 'nxml-faces)
261
262 (defface nxml-attribute-local-name
263 '((t (:inherit font-lock-variable-name-face)))
264 "Face used for the local name of attributes."
265 :group 'nxml-faces)
266
267 (defface nxml-namespace-attribute-xmlns
268 '((t (:inherit nxml-attribute-prefix)))
269 "Face used for `xmlns' in namespace attributes."
270 :group 'nxml-faces)
271
272 (defface nxml-namespace-attribute-colon
273 '((t (:inherit nxml-attribute-colon)))
274 "Face used for the colon in namespace attributes."
275 :group 'nxml-faces)
276
277 (defface nxml-namespace-attribute-prefix
278 '((t (:inherit nxml-attribute-local-name)))
279 "Face used for the prefix declared in namespace attributes."
280 :group 'nxml-faces)
281
282 (defface nxml-attribute-value
283 '((t (:inherit font-lock-string-face)))
284 "Face used for the value of attributes."
285 :group 'nxml-faces)
286
287 (defface nxml-attribute-value-delimiter
288 '((t (:inherit nxml-attribute-value)))
289 "Face used for the delimiters of attribute values."
290 :group 'nxml-faces)
291
292 (defface nxml-namespace-attribute-value
293 '((t (:inherit nxml-attribute-value)))
294 "Face used for the value of namespace attributes."
295 :group 'nxml-faces)
296
297 (defface nxml-namespace-attribute-value-delimiter
298 '((t (:inherit nxml-attribute-value-delimiter)))
299 "Face used for the delimiters of namespace attribute values."
300 :group 'nxml-faces)
301
302 (defface nxml-prolog-literal-delimiter
303 '((t (:inherit nxml-delimited-data)))
304 "Face used for the delimiters of literals in the prolog."
305 :group 'nxml-faces)
306
307 (defface nxml-prolog-literal-content
308 '((t (:inherit nxml-delimited-data)))
309 "Face used for the content of literals in the prolog."
310 :group 'nxml-faces)
311
312 (defface nxml-prolog-keyword
313 '((t (:inherit font-lock-keyword-face)))
314 "Face used for keywords in the prolog."
315 :group 'nxml-faces)
316
317 (defface nxml-markup-declaration-delimiter
318 '((t (:inherit nxml-delimiter)))
319 "Face used for the delimiters of markup declarations in the prolog.
320 The delimiters are <! and >."
321 :group 'nxml-faces)
322
323 (defface nxml-hash
324 '((t (:inherit nxml-name)))
325 "Face used for # before a name in the prolog."
326 :group 'nxml-faces)
327
328 (defface nxml-glyph
329 '((((type x))
330 (:family
331 "misc-fixed"
332 :background
333 "light grey"
334 :foreground
335 "black"
336 :weight
337 normal
338 :slant
339 normal))
340 (t
341 (:background
342 "light grey"
343 :foreground
344 "black"
345 :weight
346 normal
347 :slant
348 normal)))
349 "Face used for glyph for char references."
350 :group 'nxml-faces)
351
352 ;;; Global variables
353
354 (defvar nxml-parent-document nil
355 "The parent document for a part of a modular document.
356 Use `nxml-parent-document-set' to set it.")
357 (make-variable-buffer-local 'nxml-parent-document)
358 (put 'nxml-parent-document 'safe-local-variable 'stringp)
359
360 (defvar nxml-prolog-regions nil
361 "List of regions in the prolog to be fontified.
362 See the function `xmltok-forward-prolog' for more information.")
363 (make-variable-buffer-local 'nxml-prolog-regions)
364
365 (defvar nxml-last-fontify-end nil
366 "Position where fontification last ended.
367 It is nil if the buffer changed since the last fontification.")
368 (make-variable-buffer-local 'nxml-last-fontify-end)
369
370 (defvar nxml-degraded nil
371 "Non-nil if currently operating in degraded mode.
372 Degraded mode is enabled when an internal error is encountered in the
373 fontification or after-change functions.")
374 (make-variable-buffer-local 'nxml-degraded)
375
376 (defvar nxml-completion-hook nil
377 "Hook run by `nxml-complete'.
378 This hook is run until success.")
379
380 (defvar nxml-in-mixed-content-hook nil
381 "Hook to determine whether point is in mixed content.
382 The hook is called without arguments. It should return nil if it is
383 definitely not mixed; non-nil otherwise. The hook will be run until
384 one of the functions returns nil.")
385
386 (defvar nxml-mixed-scan-distance 4000
387 "Maximum distance from point to scan when checking for mixed content.")
388
389 (defvar nxml-end-tag-indent-scan-distance 4000
390 "Maximum distance from point to scan backwards when indenting end-tag.")
391
392 (defvar nxml-char-ref-extra-display t
393 "Non-nil means display extra information for character references.
394 The extra information consists of a tooltip with the character name
395 and, if `nxml-char-ref-display-glyph-flag' is non-nil, a glyph
396 corresponding to the referenced character following the character
397 reference.")
398 (make-variable-buffer-local 'nxml-char-ref-extra-display)
399
400 (defvar nxml-mode-map
401 (let ((map (make-sparse-keymap)))
402 (define-key map "\M-\C-u" 'nxml-backward-up-element)
403 (define-key map "\M-\C-d" 'nxml-down-element)
404 (define-key map "\M-\C-n" 'nxml-forward-element)
405 (define-key map "\M-\C-p" 'nxml-backward-element)
406 (define-key map "\M-{" 'nxml-backward-paragraph)
407 (define-key map "\M-}" 'nxml-forward-paragraph)
408 (define-key map "\M-h" 'nxml-mark-paragraph)
409 (define-key map "\C-c\C-f" 'nxml-finish-element)
410 (define-key map "\C-c]" 'nxml-finish-element)
411 (define-key map "\C-c/" 'nxml-finish-element)
412 (define-key map "\C-c\C-m" 'nxml-split-element)
413 (define-key map "\C-c\C-b" 'nxml-balanced-close-start-tag-block)
414 (define-key map "\C-c\C-i" 'nxml-balanced-close-start-tag-inline)
415 (define-key map "\C-c\C-x" 'nxml-insert-xml-declaration)
416 (define-key map "\C-c\C-d" 'nxml-dynamic-markup-word)
417 ;; u is for Unicode
418 (define-key map "\C-c\C-u" 'nxml-insert-named-char)
419 (define-key map "\C-c\C-o" nxml-outline-prefix-map)
420 (define-key map [S-mouse-2] 'nxml-mouse-hide-direct-text-content)
421 (define-key map "/" 'nxml-electric-slash)
422 (define-key map [C-return] 'nxml-complete)
423 (when nxml-bind-meta-tab-to-complete-flag
424 (define-key map "\M-\t" 'nxml-complete))
425 map)
426 "Keymap for nxml-mode.")
427
428 (defvar nxml-font-lock-keywords
429 '(nxml-fontify-matcher)
430 "Default font lock keywords for nxml-mode.")
431
432 (defsubst nxml-set-face (start end face)
433 (when (and face (< start end))
434 (font-lock-append-text-property start end 'face face)))
435
436 (defun nxml-parent-document-set (parent-document)
437 "Set `nxml-parent-document' and inherit the DTD &c."
438 ;; FIXME: this does not work.
439 ;; the idea is that by inheriting some variables from the parent,
440 ;; `rng-validate-mode' will validate entities declared in the parent.
441 ;; alas, the most interesting variables (`rng-compile-table' et al)
442 ;; are circular and cannot be printed even with `print-circle'.
443 (interactive "fParent document")
444 (let (dtd current-schema current-schema-file-name compile-table
445 ipattern-table last-ipattern-index)
446 (when (string= (file-truename parent-document)
447 (file-truename buffer-file-name))
448 (error "Parent document cannot be the same as the document"))
449 (with-current-buffer (find-file-noselect parent-document)
450 (setq dtd rng-dtd
451 current-schema rng-current-schema
452 current-schema-file-name rng-current-schema-file-name
453 compile-table rng-compile-table
454 ipattern-table rng-ipattern-table
455 last-ipattern-index rng-last-ipattern-index
456 parent-document buffer-file-name))
457 (setq rng-dtd dtd
458 rng-current-schema current-schema
459 rng-current-schema-file-name current-schema-file-name
460 rng-compile-table compile-table
461 rng-ipattern-table ipattern-table
462 rng-last-ipattern-index last-ipattern-index
463 nxml-parent-document parent-document)
464 (message "Set parent document to %s" parent-document)
465 (when rng-validate-mode
466 (rng-validate-while-idle (current-buffer)))))
467
468 ;;;###autoload
469 (define-derived-mode nxml-mode text-mode "nXML"
470 ;; We use C-c C-i instead of \\[nxml-balanced-close-start-tag-inline]
471 ;; because Emacs turns C-c C-i into C-c TAB which is hard to type and
472 ;; not mnemonic.
473 "Major mode for editing XML.
474
475 \\[nxml-finish-element] finishes the current element by inserting an end-tag.
476 C-c C-i closes a start-tag with `>' and then inserts a balancing end-tag
477 leaving point between the start-tag and end-tag.
478 \\[nxml-balanced-close-start-tag-block] is similar but for block rather than inline elements:
479 the start-tag, point, and end-tag are all left on separate lines.
480 If `nxml-slash-auto-complete-flag' is non-nil, then inserting a `</'
481 automatically inserts the rest of the end-tag.
482
483 \\[nxml-complete] performs completion on the symbol preceding point.
484
485 \\[nxml-dynamic-markup-word] uses the contents of the current buffer
486 to choose a tag to put around the word preceding point.
487
488 Sections of the document can be displayed in outline form. The
489 variable `nxml-section-element-name-regexp' controls when an element
490 is recognized as a section. The same key sequences that change
491 visibility in outline mode are used except that they start with C-c C-o
492 instead of C-c.
493
494 Validation is provided by the related minor-mode `rng-validate-mode'.
495 This also makes completion schema- and context- sensitive. Element
496 names, attribute names, attribute values and namespace URIs can all be
497 completed. By default, `rng-validate-mode' is automatically enabled.
498 You can toggle it using \\[rng-validate-mode] or change the default by
499 customizing `rng-nxml-auto-validate-flag'.
500
501 \\[indent-for-tab-command] indents the current line appropriately.
502 This can be customized using the variable `nxml-child-indent'
503 and the variable `nxml-attribute-indent'.
504
505 \\[nxml-insert-named-char] inserts a character reference using
506 the character's name (by default, the Unicode name).
507 \\[universal-argument] \\[nxml-insert-named-char] inserts the character directly.
508
509 The Emacs commands that normally operate on balanced expressions will
510 operate on XML markup items. Thus \\[forward-sexp] will move forward
511 across one markup item; \\[backward-sexp] will move backward across
512 one markup item; \\[kill-sexp] will kill the following markup item;
513 \\[mark-sexp] will mark the following markup item. By default, each
514 tag each treated as a single markup item; to make the complete element
515 be treated as a single markup item, set the variable
516 `nxml-sexp-element-flag' to t. For more details, see the function
517 `nxml-forward-balanced-item'.
518
519 \\[nxml-backward-up-element] and \\[nxml-down-element] move up and down the element structure.
520
521 Many aspects this mode can be customized using
522 \\[customize-group] nxml RET."
523 ;; (kill-all-local-variables)
524 (set (make-local-variable 'mode-line-process) '((nxml-degraded "/degraded")))
525 ;; We'll determine the fill prefix ourselves
526 (make-local-variable 'adaptive-fill-mode)
527 (setq adaptive-fill-mode nil)
528 (make-local-variable 'forward-sexp-function)
529 (setq forward-sexp-function 'nxml-forward-balanced-item)
530 (make-local-variable 'indent-line-function)
531 (setq indent-line-function 'nxml-indent-line)
532 (make-local-variable 'fill-paragraph-function)
533 (setq fill-paragraph-function 'nxml-do-fill-paragraph)
534 ;; Comment support
535 ;; This doesn't seem to work too well;
536 ;; I think we should probably roll our own nxml-comment-dwim function.
537 (make-local-variable 'comment-indent-function)
538 (setq comment-indent-function 'nxml-indent-line)
539 (make-local-variable 'comment-start)
540 (setq comment-start "<!--")
541 (make-local-variable 'comment-start-skip)
542 (setq comment-start-skip "<!--[ \t\r\n]*")
543 (make-local-variable 'comment-end)
544 (setq comment-end "-->")
545 (make-local-variable 'comment-end-skip)
546 (setq comment-end-skip "[ \t\r\n]*-->")
547 (make-local-variable 'comment-line-break-function)
548 (setq comment-line-break-function 'nxml-newline-and-indent)
549 (use-local-map nxml-mode-map)
550 (save-excursion
551 (save-restriction
552 (widen)
553 (nxml-clear-dependent-regions (point-min) (point-max))
554 (setq nxml-scan-end (copy-marker (point-min) nil))
555 (nxml-with-unmodifying-text-property-changes
556 (nxml-clear-inside (point-min) (point-max))
557 (nxml-with-invisible-motion
558 (nxml-scan-prolog)))))
559 (add-hook 'after-change-functions 'nxml-after-change nil t)
560 (add-hook 'change-major-mode-hook 'nxml-cleanup nil t)
561
562 ;; Emacs 23 handles the encoding attribute on the xml declaration
563 ;; transparently to nxml-mode, so there is no longer a need for the below
564 ;; hook. The hook also had the drawback of overriding explicit user
565 ;; instruction to save as some encoding other than utf-8.
566 ;;; (add-hook 'write-contents-hooks 'nxml-prepare-to-save)
567 (when (not (and (buffer-file-name) (file-exists-p (buffer-file-name))))
568 (when (and nxml-default-buffer-file-coding-system
569 (not (local-variable-p 'buffer-file-coding-system)))
570 (setq buffer-file-coding-system nxml-default-buffer-file-coding-system))
571 (when nxml-auto-insert-xml-declaration-flag
572 (nxml-insert-xml-declaration)))
573
574 (setq font-lock-defaults
575 '(nxml-font-lock-keywords
576 t ; keywords-only; we highlight comments and strings here
577 nil ; font-lock-keywords-case-fold-search. XML is case sensitive
578 nil ; no special syntax table
579 nil ; no automatic syntactic fontification
580 (font-lock-extend-after-change-region-function
581 . nxml-extend-after-change-region)
582 (font-lock-extend-region-functions . (nxml-extend-region))
583 (jit-lock-contextually . t)
584 (font-lock-unfontify-region-function . nxml-unfontify-region)))
585
586 (rng-nxml-mode-init)
587 (nxml-enable-unicode-char-name-sets))
588
589 (defun nxml-cleanup ()
590 "Clean up after nxml-mode."
591 ;; Disable associated minor modes.
592 (rng-validate-mode -1)
593 ;; Clean up fontification.
594 (save-excursion
595 (widen)
596 (let ((inhibit-read-only t)
597 (buffer-undo-list t)
598 (modified (buffer-modified-p)))
599 (nxml-with-invisible-motion
600 (remove-text-properties (point-min) (point-max) '(face)))
601 (set-buffer-modified-p modified)))
602 (remove-hook 'change-major-mode-hook 'nxml-cleanup t))
603
604 (defun nxml-degrade (context err)
605 (message "Internal nXML mode error in %s (%s), degrading"
606 context
607 (error-message-string err))
608 (ding)
609 (setq nxml-degraded t)
610 (setq nxml-prolog-end 1)
611 (save-excursion
612 (save-restriction
613 (widen)
614 (nxml-with-unmodifying-text-property-changes
615 (nxml-clear-inside (point-min) (point-max))))))
616
617 ;;; Change management
618
619 (defun nxml-debug-region (start end)
620 (interactive "r")
621 (let ((font-lock-beg start)
622 (font-lock-end end))
623 (nxml-extend-region)
624 (goto-char font-lock-beg)
625 (set-mark font-lock-end)))
626
627 (defun nxml-after-change (start end pre-change-length)
628 ; In font-lock mode, nxml-after-change1 is called via
629 ; nxml-extend-after-change-region instead so that the updated
630 ; book-keeping information is available for fontification.
631 (unless (or font-lock-mode nxml-degraded)
632 (nxml-with-degradation-on-error 'nxml-after-change
633 (save-excursion
634 (save-restriction
635 (widen)
636 (save-match-data
637 (nxml-with-invisible-motion
638 (nxml-with-unmodifying-text-property-changes
639 (nxml-after-change1
640 start end pre-change-length)))))))))
641
642 (defun nxml-after-change1 (start end pre-change-length)
643 "After-change bookkeeping.
644 Returns a cons cell containing a possibly-enlarged change region.
645 You must call `nxml-extend-region' on this expanded region to obtain
646 the full extent of the area needing refontification.
647
648 For bookkeeping, call this function even when fontification is
649 disabled."
650 (let ((pre-change-end (+ start pre-change-length)))
651 (setq start
652 (nxml-adjust-start-for-dependent-regions start
653 end
654 pre-change-length))
655 ;; If the prolog might have changed, rescan the prolog
656 (when (<= start
657 ;; Add 2 so as to include the < and following char that
658 ;; start the instance (document element), since changing
659 ;; these can change where the prolog ends.
660 (+ nxml-prolog-end 2))
661 ;; end must be extended to at least the end of the old prolog in
662 ;; case the new prolog is shorter
663 (when (< pre-change-end nxml-prolog-end)
664 (setq end
665 ;; don't let end get out of range even if pre-change-length
666 ;; is bogus
667 (min (point-max)
668 (+ end (- nxml-prolog-end pre-change-end)))))
669 (nxml-scan-prolog)
670 (setq start (point-min))))
671
672 (when (> end nxml-prolog-end)
673 (goto-char start)
674 (nxml-move-tag-backwards (point-min))
675 (setq start (point))
676 (setq end (max (nxml-scan-after-change start end)
677 end)))
678
679 (nxml-debug-change "nxml-after-change1" start end)
680 (cons start end))
681
682 ;;; Encodings
683
684 (defun nxml-insert-xml-declaration ()
685 "Insert an XML declaration at the beginning of buffer.
686 The XML declaration will declare an encoding depending on the buffer's
687 `buffer-file-coding-system'."
688 (interactive "*")
689 (let ((coding-system
690 (if (and buffer-file-coding-system
691 (coding-system-p buffer-file-coding-system)
692 (coding-system-get buffer-file-coding-system
693 'mime-charset))
694 buffer-file-coding-system
695 (nxml-choose-utf-coding-system))))
696 (goto-char (point-min))
697 (insert (format "<?xml version=\"1.0\" encoding=\"%s\"?>\n"
698 (nxml-coding-system-name coding-system)))))
699
700 (defun nxml-prepare-to-save ()
701 (unless (and (not enable-multibyte-characters)
702 (local-variable-p 'buffer-file-coding-system)
703 buffer-file-coding-system
704 (or (eq (coding-system-type buffer-file-coding-system) 5)
705 (eq buffer-file-coding-system 'no-conversion)))
706 (save-excursion
707 (setq buffer-file-coding-system (nxml-select-coding-system))))
708 ;; nil from a function in `write-contents-hooks' means
709 ;; to continue and write the file as normal
710 nil)
711
712 (defun nxml-select-coding-system ()
713 (let* ((suitable-coding-systems
714 (find-coding-systems-region (point-min) (point-max)))
715 (enc-pos (progn
716 (goto-char (point-min))
717 (xmltok-get-declared-encoding-position)))
718 (enc-name
719 (and (consp enc-pos)
720 (buffer-substring-no-properties (car enc-pos)
721 (cdr enc-pos))))
722 (coding-system
723 (cond (enc-name
724 (if (string= (downcase enc-name) "utf-16")
725 (nxml-choose-utf-16-coding-system)
726 (nxml-mime-charset-coding-system enc-name)))
727 (enc-pos (nxml-choose-utf-coding-system)))))
728 ;; Make sure we have a coding-system
729 (unless coding-system
730 (setq coding-system
731 (and (not buffer-read-only)
732 (nxml-choose-suitable-coding-system
733 suitable-coding-systems)))
734 (let ((message
735 (if enc-name
736 (format "Unknown encoding %s" enc-name)
737 "XML declaration is not well-formed")))
738 (cond ((not coding-system)
739 (error "%s" message))
740 ((y-or-n-p
741 (concat message
742 ". "
743 (format (if enc-name
744 "Save with %s"
745 "Modify and save with encoding %s")
746 (nxml-coding-system-name coding-system))
747 " "))
748 (nxml-fix-encoding-declaration enc-pos coding-system))
749 (t (signal 'quit nil)))))
750 ;; Make sure it can encode all the characters in the buffer
751 (unless (or (memq (coding-system-base coding-system)
752 suitable-coding-systems)
753 (equal suitable-coding-systems '(undecided)))
754 (let ((message
755 (nxml-unsuitable-coding-system-message coding-system
756 enc-name)))
757 (setq coding-system
758 (and (not buffer-read-only)
759 (nxml-choose-suitable-coding-system
760 suitable-coding-systems)))
761 (cond ((not coding-system) (error "%s" message))
762 ((y-or-n-p (concat message
763 (format ". Save with %s "
764 (nxml-coding-system-name
765 coding-system))))
766 (nxml-fix-encoding-declaration enc-pos coding-system))
767 (t (signal 'quit nil)))))
768 ;; Merge the newline type of our existing encoding
769 (let ((current-eol-type
770 (coding-system-eol-type buffer-file-coding-system)))
771 (when (and current-eol-type (integerp current-eol-type))
772 (setq coding-system
773 (coding-system-change-eol-conversion coding-system
774 current-eol-type))))
775 coding-system))
776
777 (defun nxml-unsuitable-coding-system-message (coding-system &optional enc-name)
778 (if (nxml-coding-system-unicode-p coding-system)
779 "Cannot translate some characters to Unicode"
780 (format "Cannot encode some characters with %s"
781 (or enc-name
782 (nxml-coding-system-name coding-system)))))
783
784 (defconst nxml-utf-16-coding-systems (and (coding-system-p 'utf-16-be)
785 (coding-system-p 'utf-16-le)
786 '(utf-16-be utf-16-le)))
787
788 (defconst nxml-utf-coding-systems (cons 'utf-8 nxml-utf-16-coding-systems))
789
790 (defun nxml-coding-system-unicode-p (coding-system)
791 (nxml-coding-system-member (coding-system-base coding-system)
792 nxml-utf-coding-systems))
793
794 (defun nxml-coding-system-name (coding-system)
795 (setq coding-system (coding-system-base coding-system))
796 (symbol-name
797 (if (nxml-coding-system-member coding-system nxml-utf-16-coding-systems)
798 'utf-16
799 (or (coding-system-get coding-system 'mime-charset)
800 coding-system))))
801
802 (defun nxml-fix-encoding-declaration (enc-pos coding-system)
803 (let ((charset (nxml-coding-system-name coding-system)))
804 (cond ((consp enc-pos)
805 (delete-region (car enc-pos) (cdr enc-pos))
806 (goto-char (car enc-pos))
807 (insert charset))
808 ((integerp enc-pos)
809 (goto-char enc-pos)
810 (insert " encoding=\"" charset ?\"))
811 (t
812 (goto-char (point-min))
813 (insert "<?xml version=\"1.0\" encoding=\""
814 charset
815 "\"?>\n")
816 (when (and (not enc-pos)
817 (let ((case-fold-search t))
818 (looking-at xmltok-bad-xml-decl-regexp)))
819 (delete-region (point) (match-end 0)))))))
820
821 (defun nxml-choose-suitable-coding-system (suitable-coding-systems)
822 (let (ret coding-system)
823 (if (and buffer-file-coding-system
824 (memq (coding-system-base buffer-file-coding-system)
825 suitable-coding-systems))
826 buffer-file-coding-system
827 (while (and suitable-coding-systems (not ret))
828 (setq coding-system (car suitable-coding-systems))
829 (if (coding-system-get coding-system 'mime-charset)
830 (setq ret coding-system)
831 (setq suitable-coding-systems (cdr suitable-coding-systems))))
832 ret)))
833
834 (defun nxml-choose-utf-coding-system ()
835 (let ((cur (and (local-variable-p 'buffer-file-coding-system)
836 buffer-file-coding-system
837 (coding-system-base buffer-file-coding-system))))
838 (cond ((car (nxml-coding-system-member cur nxml-utf-coding-systems)))
839 ((and nxml-prefer-utf-16-to-utf-8-flag
840 (coding-system-p 'utf-16-le)
841 (coding-system-p 'utf-16-be))
842 (if nxml-prefer-utf-16-little-to-big-endian-flag
843 'utf-16-le
844 'utf-16-be))
845 (t 'utf-8))))
846
847 (defun nxml-choose-utf-16-coding-system ()
848 (let ((cur (and (local-variable-p 'buffer-file-coding-system)
849 buffer-file-coding-system
850 (coding-system-base buffer-file-coding-system))))
851 (cond ((car (nxml-coding-system-member cur nxml-utf-16-coding-systems)))
852 (nxml-prefer-utf-16-little-to-big-endian-flag
853 (and (coding-system-p 'utf-16-le) 'utf-16-le))
854 (t (and (coding-system-p 'utf-16-be) 'utf-16-be)))))
855
856 (defun nxml-coding-system-member (coding-system coding-systems)
857 (let (ret)
858 (while (and coding-systems (not ret))
859 (if (coding-system-equal coding-system
860 (car coding-systems))
861 (setq ret coding-systems)
862 (setq coding-systems (cdr coding-systems))))
863 ret))
864
865 ;;; Fontification
866
867 (defun nxml-unfontify-region (start end)
868 (font-lock-default-unfontify-region start end)
869 (nxml-clear-char-ref-extra-display start end))
870
871 (defvar font-lock-beg) (defvar font-lock-end)
872 (defun nxml-extend-region ()
873 "Extend the region to hold the minimum area we can fontify with nXML.
874 Called with `font-lock-beg' and `font-lock-end' dynamically bound."
875 (let ((start font-lock-beg)
876 (end font-lock-end))
877
878 (nxml-debug-change "nxml-extend-region(input)" start end)
879
880 (when (< start nxml-prolog-end)
881 (setq start (point-min)))
882
883 (cond ((<= end nxml-prolog-end)
884 (setq end nxml-prolog-end))
885
886 (t
887 (goto-char start)
888 ;; some font-lock backends (like Emacs 22 jit-lock) snap
889 ;; the region to the beginning of the line no matter what
890 ;; we say here. To mitigate the resulting excess
891 ;; fontification, ignore leading whitespace.
892 (skip-syntax-forward " ")
893
894 ;; find the beginning of the previous tag
895 (when (not (equal (char-after) ?\<))
896 (search-backward "<" nxml-prolog-end t))
897 (nxml-ensure-scan-up-to-date)
898 (nxml-move-outside-backwards)
899 (setq start (point))
900
901 (while (< (point) end)
902 (nxml-tokenize-forward))
903
904 (setq end (point))))
905
906 (when (or (< start font-lock-beg)
907 (> end font-lock-end))
908 (setq font-lock-beg start
909 font-lock-end end)
910 (nxml-debug-change "nxml-extend-region" start end)
911 t)))
912
913 (defun nxml-extend-after-change-region (start end pre-change-length)
914 (unless nxml-degraded
915 (setq nxml-last-fontify-end nil)
916 (let ((region (nxml-with-degradation-on-error
917 'nxml-extend-after-change-region
918 (save-excursion
919 (save-restriction
920 (widen)
921 (save-match-data
922 (nxml-with-invisible-motion
923 (nxml-with-unmodifying-text-property-changes
924 (nxml-extend-after-change-region1
925 start end pre-change-length)))))))))
926 (if (consp region) region))))
927
928 (defun nxml-extend-after-change-region1 (start end pre-change-length)
929 (let* ((region (nxml-after-change1 start end pre-change-length))
930 (font-lock-beg (car region))
931 (font-lock-end (cdr region)))
932
933 (nxml-extend-region)
934 (cons font-lock-beg font-lock-end)))
935
936 (defun nxml-fontify-matcher (bound)
937 "Called as font-lock keyword matcher."
938
939 (unless nxml-degraded
940 (nxml-debug-change "nxml-fontify-matcher" (point) bound)
941
942 (when (< (point) nxml-prolog-end)
943 ;; prolog needs to be fontified in one go, and
944 ;; nxml-extend-region makes sure we start at BOB.
945 (assert (bobp))
946 (nxml-fontify-prolog)
947 (goto-char nxml-prolog-end))
948
949 (let (xmltok-dependent-regions
950 xmltok-errors)
951 (while (and (nxml-tokenize-forward)
952 (<= (point) bound)) ; intervals are open-ended
953 (nxml-apply-fontify-rule)))
954
955 (setq nxml-last-fontify-end (point)))
956
957 ;; Since we did the fontification internally, tell font-lock to not
958 ;; do anything itself.
959 nil)
960
961 (defun nxml-fontify-prolog ()
962 "Fontify the prolog.
963 The buffer is assumed to be prepared for fontification.
964 This does not set the fontified property, but it does clear
965 faces appropriately."
966 (let ((regions nxml-prolog-regions))
967 (while regions
968 (let ((region (car regions)))
969 (nxml-apply-fontify-rule (aref region 0)
970 (aref region 1)
971 (aref region 2)))
972 (setq regions (cdr regions)))))
973
974 ;; Vectors identify a substring of the token to be highlighted in some face.
975
976 ;; Token types returned by xmltok-forward.
977
978 (put 'start-tag
979 'nxml-fontify-rule
980 '([nil 1 nxml-tag-delimiter]
981 [-1 nil nxml-tag-delimiter]
982 (element-qname . 1)
983 attributes))
984
985 (put 'partial-start-tag
986 'nxml-fontify-rule
987 '([nil 1 nxml-tag-delimiter]
988 (element-qname . 1)
989 attributes))
990
991 (put 'end-tag
992 'nxml-fontify-rule
993 '([nil 1 nxml-tag-delimiter]
994 [1 2 nxml-tag-slash]
995 [-1 nil nxml-tag-delimiter]
996 (element-qname . 2)))
997
998 (put 'partial-end-tag
999 'nxml-fontify-rule
1000 '([nil 1 nxml-tag-delimiter]
1001 [1 2 nxml-tag-slash]
1002 (element-qname . 2)))
1003
1004 (put 'empty-element
1005 'nxml-fontify-rule
1006 '([nil 1 nxml-tag-delimiter]
1007 [-2 -1 nxml-tag-slash]
1008 [-1 nil nxml-tag-delimiter]
1009 (element-qname . 1)
1010 attributes))
1011
1012 (put 'partial-empty-element
1013 'nxml-fontify-rule
1014 '([nil 1 nxml-tag-delimiter]
1015 [-1 nil nxml-tag-slash]
1016 (element-qname . 1)
1017 attributes))
1018
1019 (put 'char-ref
1020 'nxml-fontify-rule
1021 '([nil 2 nxml-char-ref-delimiter]
1022 [2 -1 nxml-char-ref-number]
1023 [-1 nil nxml-char-ref-delimiter]
1024 char-ref))
1025
1026 (put 'entity-ref
1027 'nxml-fontify-rule
1028 '([nil 1 nxml-entity-ref-delimiter]
1029 [1 -1 nxml-entity-ref-name]
1030 [-1 nil nxml-entity-ref-delimiter]))
1031
1032 (put 'comment
1033 'nxml-fontify-rule
1034 '([nil 4 nxml-comment-delimiter]
1035 [4 -3 nxml-comment-content]
1036 [-3 nil nxml-comment-delimiter]))
1037
1038 (put 'processing-instruction
1039 'nxml-fontify-rule
1040 '([nil 2 nxml-processing-instruction-delimiter]
1041 [-2 nil nxml-processing-instruction-delimiter]
1042 processing-instruction-content))
1043
1044 (put 'cdata-section
1045 'nxml-fontify-rule
1046 '([nil 3 nxml-cdata-section-delimiter] ; <![
1047 [3 8 nxml-cdata-section-CDATA] ; CDATA
1048 [8 9 nxml-cdata-section-delimiter] ; [
1049 [9 -3 nxml-cdata-section-content] ; ]]>
1050 [-3 nil nxml-cdata-section-delimiter]))
1051
1052 (put 'data
1053 'nxml-fontify-rule
1054 '([nil nil nxml-text]))
1055
1056 ;; Prolog region types in list returned by xmltok-forward-prolog.
1057
1058 (put 'xml-declaration
1059 'nxml-fontify-rule
1060 '([nil 2 nxml-processing-instruction-delimiter]
1061 [2 5 nxml-processing-instruction-target]
1062 [-2 nil nxml-processing-instruction-delimiter]))
1063
1064 (put 'xml-declaration-attribute-name
1065 'nxml-fontify-rule
1066 '([nil nil nxml-attribute-local-name]))
1067
1068 (put 'xml-declaration-attribute-value
1069 'nxml-fontify-rule
1070 '([nil 1 nxml-attribute-value-delimiter]
1071 [1 -1 nxml-attribute-value]
1072 [-1 nil nxml-attribute-value-delimiter]))
1073
1074 (put 'processing-instruction-left
1075 'nxml-fontify-rule
1076 '([nil 2 nxml-processing-instruction-delimiter]
1077 [2 nil nxml-processing-instruction-target]))
1078
1079 (put 'processing-instruction-right
1080 'nxml-fontify-rule
1081 '([nil -2 nxml-processing-instruction-content]
1082 [-2 nil nxml-processing-instruction-delimiter]))
1083
1084 (put 'literal
1085 'nxml-fontify-rule
1086 '([nil 1 nxml-prolog-literal-delimiter]
1087 [1 -1 nxml-prolog-literal-content]
1088 [-1 nil nxml-prolog-literal-delimiter]))
1089
1090 (put 'keyword
1091 'nxml-fontify-rule
1092 '([nil nil nxml-prolog-keyword]))
1093
1094 (put 'markup-declaration-open
1095 'nxml-fontify-rule
1096 '([0 2 nxml-markup-declaration-delimiter]
1097 [2 nil nxml-prolog-keyword]))
1098
1099 (put 'markup-declaration-close
1100 'nxml-fontify-rule
1101 '([nil nil nxml-markup-declaration-delimiter]))
1102
1103 (put 'internal-subset-open
1104 'nxml-fontify-rule
1105 '([nil nil nxml-markup-declaration-delimiter]))
1106
1107 (put 'internal-subset-close
1108 'nxml-fontify-rule
1109 '([nil 1 nxml-markup-declaration-delimiter]
1110 [-1 nil nxml-markup-declaration-delimiter]))
1111
1112 (put 'hash-name
1113 'nxml-fontify-rule
1114 '([nil 1 nxml-hash]
1115 [1 nil nxml-prolog-keyword]))
1116
1117 (defun nxml-apply-fontify-rule (&optional type start end)
1118 (let ((rule (get (or type xmltok-type) 'nxml-fontify-rule)))
1119 (unless start (setq start xmltok-start))
1120 (unless end (setq end (point)))
1121 (while rule
1122 (let* ((action (car rule)))
1123 (setq rule (cdr rule))
1124 (cond ((vectorp action)
1125 (nxml-set-face (let ((offset (aref action 0)))
1126 (cond ((not offset) start)
1127 ((< offset 0) (+ end offset))
1128 (t (+ start offset))))
1129 (let ((offset (aref action 1)))
1130 (cond ((not offset) end)
1131 ((< offset 0) (+ end offset))
1132 (t (+ start offset))))
1133 (aref action 2)))
1134 ((and (consp action)
1135 (eq (car action) 'element-qname))
1136 (when xmltok-name-end ; maybe nil in partial-end-tag case
1137 (nxml-fontify-qname (+ start (cdr action))
1138 xmltok-name-colon
1139 xmltok-name-end
1140 'nxml-element-prefix
1141 'nxml-element-colon
1142 'nxml-element-local-name)))
1143 ((eq action 'attributes)
1144 (nxml-fontify-attributes))
1145 ((eq action 'processing-instruction-content)
1146 (nxml-set-face (+ start 2)
1147 xmltok-name-end
1148 'nxml-processing-instruction-target)
1149 (nxml-set-face (save-excursion
1150 (goto-char xmltok-name-end)
1151 (skip-chars-forward " \t\r\n")
1152 (point))
1153 (- end 2)
1154 'nxml-processing-instruction-content))
1155 ((eq action 'char-ref)
1156 (nxml-char-ref-display-extra start
1157 end
1158 (xmltok-char-number start end)))
1159 (t (error "Invalid nxml-fontify-rule action %s" action)))))))
1160
1161 (defun nxml-fontify-attributes ()
1162 (while xmltok-namespace-attributes
1163 (nxml-fontify-attribute (car xmltok-namespace-attributes)
1164 'namespace)
1165 (setq xmltok-namespace-attributes
1166 (cdr xmltok-namespace-attributes)))
1167 (while xmltok-attributes
1168 (nxml-fontify-attribute (car xmltok-attributes))
1169 (setq xmltok-attributes
1170 (cdr xmltok-attributes))))
1171
1172 (defun nxml-fontify-attribute (att &optional namespace-declaration)
1173 (if namespace-declaration
1174 (nxml-fontify-qname (xmltok-attribute-name-start att)
1175 (xmltok-attribute-name-colon att)
1176 (xmltok-attribute-name-end att)
1177 'nxml-namespace-attribute-xmlns
1178 'nxml-namespace-attribute-colon
1179 'nxml-namespace-attribute-prefix
1180 'nxml-namespace-attribute-xmlns)
1181 (nxml-fontify-qname (xmltok-attribute-name-start att)
1182 (xmltok-attribute-name-colon att)
1183 (xmltok-attribute-name-end att)
1184 'nxml-attribute-prefix
1185 'nxml-attribute-colon
1186 'nxml-attribute-local-name))
1187 (let ((start (xmltok-attribute-value-start att))
1188 (end (xmltok-attribute-value-end att))
1189 (refs (xmltok-attribute-refs att))
1190 (delimiter-face (if namespace-declaration
1191 'nxml-namespace-attribute-value-delimiter
1192 'nxml-attribute-value-delimiter))
1193 (value-face (if namespace-declaration
1194 'nxml-namespace-attribute-value
1195 'nxml-attribute-value)))
1196 (when start
1197 (nxml-set-face (1- start) start delimiter-face)
1198 (nxml-set-face end (1+ end) delimiter-face)
1199 (while refs
1200 (let* ((ref (car refs))
1201 (ref-type (aref ref 0))
1202 (ref-start (aref ref 1))
1203 (ref-end (aref ref 2)))
1204 (nxml-set-face start ref-start value-face)
1205 (nxml-apply-fontify-rule ref-type ref-start ref-end)
1206 (setq start ref-end))
1207 (setq refs (cdr refs)))
1208 (nxml-set-face start end value-face))))
1209
1210 (defun nxml-fontify-qname (start
1211 colon
1212 end
1213 prefix-face
1214 colon-face
1215 local-name-face
1216 &optional
1217 unprefixed-face)
1218 (cond (colon (nxml-set-face start colon prefix-face)
1219 (nxml-set-face colon (1+ colon) colon-face)
1220 (nxml-set-face (1+ colon) end local-name-face))
1221 (t (nxml-set-face start end (or unprefixed-face
1222 local-name-face)))))
1223
1224 ;;; Editing
1225
1226 (defun nxml-electric-slash (arg)
1227 "Insert a slash.
1228
1229 With a prefix ARG, do nothing other than insert the slash.
1230
1231 Otherwise, if `nxml-slash-auto-complete-flag' is non-nil, insert the
1232 rest of the end-tag or empty-element if the slash is potentially part
1233 of an end-tag or the close of an empty-element.
1234
1235 If the slash is part of an end-tag that is the first non-whitespace
1236 on the line, reindent the line."
1237 (interactive "*P")
1238 (nxml-ensure-scan-up-to-date)
1239 (let* ((slash-pos (point))
1240 (end-tag-p (and (eq (char-before slash-pos) ?<)
1241 (not (nxml-get-inside slash-pos))))
1242 (at-indentation (save-excursion
1243 (back-to-indentation)
1244 (eq (point) (1- slash-pos)))))
1245 (self-insert-command (prefix-numeric-value arg))
1246 (unless arg
1247 (if nxml-slash-auto-complete-flag
1248 (if end-tag-p
1249 (condition-case err
1250 (let ((start-tag-end
1251 (nxml-scan-element-backward (1- slash-pos) t)))
1252 (when start-tag-end
1253 (insert (xmltok-start-tag-qname) ">")
1254 ;; copy the indentation of the start-tag
1255 (when (and at-indentation
1256 (save-excursion
1257 (goto-char xmltok-start)
1258 (back-to-indentation)
1259 (eq (point) xmltok-start)))
1260 (save-excursion
1261 (indent-line-to (save-excursion
1262 (goto-char xmltok-start)
1263 (current-column)))))))
1264 (nxml-scan-error nil))
1265 (when (and (eq (nxml-token-before) (point))
1266 (eq xmltok-type 'partial-empty-element))
1267 (insert ">")))
1268 (when (and end-tag-p at-indentation)
1269 (nxml-indent-line))))))
1270
1271 (defun nxml-balanced-close-start-tag-block ()
1272 "Close the start-tag before point with `>' and insert a balancing end-tag.
1273 Point is left between the start-tag and the end-tag.
1274 If there is nothing but whitespace before the `<' that opens the
1275 start-tag, then put point on a blank line, and put the end-tag on
1276 another line aligned with the start-tag."
1277 (interactive "*")
1278 (nxml-balanced-close-start-tag 'block))
1279
1280 (defun nxml-balanced-close-start-tag-inline ()
1281 "Close the start-tag before point with `>' and insert a balancing end-tag.
1282 Point is left between the start-tag and the end-tag.
1283 No extra whitespace is inserted."
1284 (interactive "*")
1285 (nxml-balanced-close-start-tag 'inline))
1286
1287 (defun nxml-balanced-close-start-tag (block-or-inline)
1288 (let ((token-end (nxml-token-before))
1289 (pos (1+ (point)))
1290 (token-start xmltok-start))
1291 (unless (or (eq xmltok-type 'partial-start-tag)
1292 (and (memq xmltok-type '(start-tag
1293 empty-element
1294 partial-empty-element))
1295 (>= token-end pos)))
1296 (error "Not in a start-tag"))
1297 ;; Note that this insertion changes xmltok-start.
1298 (insert "></"
1299 (buffer-substring-no-properties (+ xmltok-start 1)
1300 (min xmltok-name-end (point)))
1301 ">")
1302 (if (eq block-or-inline 'inline)
1303 (goto-char pos)
1304 (goto-char token-start)
1305 (back-to-indentation)
1306 (if (= (point) token-start)
1307 (let ((indent (current-column)))
1308 (goto-char pos)
1309 (insert "\n")
1310 (indent-line-to indent)
1311 (goto-char pos)
1312 (insert "\n")
1313 (indent-line-to (+ nxml-child-indent indent)))
1314 (goto-char pos)))))
1315
1316 (defun nxml-finish-element ()
1317 "Finish the current element by inserting an end-tag."
1318 (interactive "*")
1319 (nxml-finish-element-1 nil))
1320
1321 (defvar nxml-last-split-position nil
1322 "Position where `nxml-split-element' split the current element.")
1323
1324 (defun nxml-split-element ()
1325 "Split the current element by inserting an end-tag and a start-tag.
1326 Point is left after the newly inserted start-tag. When repeated,
1327 split immediately before the previously inserted start-tag and leave
1328 point unchanged."
1329 (interactive "*")
1330 (setq nxml-last-split-position
1331 (if (and (eq last-command this-command)
1332 nxml-last-split-position)
1333 (save-excursion
1334 (goto-char nxml-last-split-position)
1335 (nxml-finish-element-1 t))
1336 (nxml-finish-element-1 t))))
1337
1338 (defun nxml-finish-element-1 (startp)
1339 "Insert an end-tag for the current element and optionally a start-tag.
1340 The start-tag is inserted if STARTP is non-nil. Return the position
1341 of the inserted start-tag or nil if none was inserted."
1342 (interactive "*")
1343 (let* ((token-end (nxml-token-before))
1344 (start-tag-end
1345 (save-excursion
1346 (when (and (< (point) token-end)
1347 (memq xmltok-type
1348 '(cdata-section
1349 processing-instruction
1350 comment
1351 start-tag
1352 end-tag
1353 empty-element)))
1354 (error "Point is inside a %s"
1355 (nxml-token-type-friendly-name xmltok-type)))
1356 (nxml-scan-element-backward token-end t)))
1357 (starts-line
1358 (save-excursion
1359 (unless (eq xmltok-type 'start-tag)
1360 (error "No matching start-tag"))
1361 (goto-char xmltok-start)
1362 (back-to-indentation)
1363 (eq (point) xmltok-start)))
1364 (ends-line
1365 (save-excursion
1366 (goto-char start-tag-end)
1367 (looking-at "[ \t\r\n]*$")))
1368 (start-tag-indent (save-excursion
1369 (goto-char xmltok-start)
1370 (current-column)))
1371 (qname (xmltok-start-tag-qname))
1372 inserted-start-tag-pos)
1373 (when (and starts-line ends-line)
1374 ;; start-tag is on a line by itself
1375 ;; => put the end-tag on a line by itself
1376 (unless (<= (point)
1377 (save-excursion
1378 (back-to-indentation)
1379 (point)))
1380 (insert "\n"))
1381 (indent-line-to start-tag-indent))
1382 (insert "</" qname ">")
1383 (when startp
1384 (when starts-line
1385 (insert "\n")
1386 (indent-line-to start-tag-indent))
1387 (setq inserted-start-tag-pos (point))
1388 (insert "<" qname ">")
1389 (when (and starts-line ends-line)
1390 (insert "\n")
1391 (indent-line-to (save-excursion
1392 (goto-char xmltok-start)
1393 (forward-line 1)
1394 (back-to-indentation)
1395 (if (= (current-column)
1396 (+ start-tag-indent nxml-child-indent))
1397 (+ start-tag-indent nxml-child-indent)
1398 start-tag-indent)))))
1399 inserted-start-tag-pos))
1400
1401 ;;; Indentation
1402
1403 (defun nxml-indent-line ()
1404 "Indent current line as XML."
1405 (let* ((savep (point))
1406 (indent (condition-case nil
1407 (save-excursion
1408 (forward-line 0)
1409 (skip-chars-forward " \t")
1410 (if (>= (point) savep) (setq savep nil))
1411 (or (nxml-compute-indent) 0))
1412 (error 0))))
1413 (if (not (numberp indent))
1414 ;; If something funny is used (e.g. `noindent'), return it.
1415 indent
1416 (if (< indent 0) (setq indent 0)) ;Just in case.
1417 (if savep
1418 (save-excursion (indent-line-to indent))
1419 (indent-line-to indent)))))
1420
1421 (defun nxml-compute-indent ()
1422 "Return the indent for the line containing point."
1423 (or (nxml-compute-indent-from-matching-start-tag)
1424 (nxml-compute-indent-from-previous-line)))
1425
1426 (defun nxml-compute-indent-from-matching-start-tag ()
1427 "Compute the indent for a line with an end-tag using the matching start-tag.
1428 When the line containing point ends with an end-tag and does not start
1429 in the middle of a token, return the indent of the line containing the
1430 matching start-tag, if there is one and it occurs at the beginning of
1431 its line. Otherwise return nil."
1432 (save-excursion
1433 (back-to-indentation)
1434 (let ((bol (point)))
1435 (let ((inhibit-field-text-motion t))
1436 (end-of-line))
1437 (skip-chars-backward " \t")
1438 (and (= (nxml-token-before) (point))
1439 (memq xmltok-type '(end-tag partial-end-tag))
1440 ;; start of line must not be inside a token
1441 (or (= xmltok-start bol)
1442 (save-excursion
1443 (goto-char bol)
1444 (nxml-token-after)
1445 (= xmltok-start bol))
1446 (eq xmltok-type 'data))
1447 (condition-case err
1448 (nxml-scan-element-backward
1449 (point)
1450 nil
1451 (- (point)
1452 nxml-end-tag-indent-scan-distance))
1453 (nxml-scan-error nil))
1454 (< xmltok-start bol)
1455 (progn
1456 (goto-char xmltok-start)
1457 (skip-chars-backward " \t")
1458 (bolp))
1459 (current-indentation)))))
1460
1461 (defun nxml-compute-indent-from-previous-line ()
1462 "Compute the indent for a line using the indentation of a previous line."
1463 (save-excursion
1464 (end-of-line)
1465 (let ((eol (point))
1466 bol prev-bol ref
1467 before-context after-context)
1468 (back-to-indentation)
1469 (setq bol (point))
1470 (catch 'indent
1471 ;; Move backwards until the start of a non-blank line that is
1472 ;; not inside a token.
1473 (while (progn
1474 (when (= (forward-line -1) -1)
1475 (throw 'indent 0))
1476 (back-to-indentation)
1477 (if (looking-at "[ \t]*$")
1478 t
1479 (or prev-bol
1480 (setq prev-bol (point)))
1481 (nxml-token-after)
1482 (not (or (= xmltok-start (point))
1483 (eq xmltok-type 'data))))))
1484 (setq ref (point))
1485 ;; Now scan over tokens until the end of the line to be indented.
1486 ;; Determine the context before and after the beginning of the
1487 ;; line.
1488 (while (< (point) eol)
1489 (nxml-tokenize-forward)
1490 (cond ((<= bol xmltok-start)
1491 (setq after-context
1492 (nxml-merge-indent-context-type after-context)))
1493 ((and (<= (point) bol)
1494 (not (and (eq xmltok-type 'partial-start-tag)
1495 (= (point) bol))))
1496 (setq before-context
1497 (nxml-merge-indent-context-type before-context)))
1498 ((eq xmltok-type 'data)
1499 (setq before-context
1500 (nxml-merge-indent-context-type before-context))
1501 (setq after-context
1502 (nxml-merge-indent-context-type after-context)))
1503 ;; If in the middle of a token that looks inline,
1504 ;; then indent relative to the previous non-blank line
1505 ((eq (nxml-merge-indent-context-type before-context)
1506 'mixed)
1507 (goto-char prev-bol)
1508 (throw 'indent (current-column)))
1509 (t
1510 (throw 'indent
1511 (nxml-compute-indent-in-token bol))))
1512 (skip-chars-forward " \t\r\n"))
1513 (goto-char ref)
1514 (+ (current-column)
1515 (* nxml-child-indent
1516 (+ (if (eq before-context 'start-tag) 1 0)
1517 (if (eq after-context 'end-tag) -1 0))))))))
1518
1519 (defun nxml-merge-indent-context-type (context)
1520 "Merge the indent context type CONTEXT with the token in `xmltok-type'.
1521 Return the merged indent context type. An indent context type is
1522 either nil or one of the symbols `start-tag', `end-tag', `markup',
1523 `comment', `mixed'."
1524 (cond ((memq xmltok-type '(start-tag partial-start-tag))
1525 (if (memq context '(nil start-tag comment))
1526 'start-tag
1527 'mixed))
1528 ((memq xmltok-type '(end-tag partial-end-tag))
1529 (if (memq context '(nil end-tag comment))
1530 'end-tag
1531 'mixed))
1532 ((eq xmltok-type 'comment)
1533 (cond ((memq context '(start-tag end-tag comment))
1534 context)
1535 (context 'mixed)
1536 (t 'comment)))
1537 (context 'mixed)
1538 (t 'markup)))
1539
1540 (defun nxml-compute-indent-in-token (pos)
1541 "Return the indent for a line that starts inside a token.
1542 POS is the position of the first non-whitespace character of the line.
1543 This expects the xmltok-* variables to be set up as by `xmltok-forward'."
1544 (cond ((memq xmltok-type '(start-tag
1545 partial-start-tag
1546 empty-element
1547 partial-empty-element))
1548 (nxml-compute-indent-in-start-tag pos))
1549 ((eq xmltok-type 'comment)
1550 (nxml-compute-indent-in-delimited-token pos "<!--" "-->"))
1551 ((eq xmltok-type 'cdata-section)
1552 (nxml-compute-indent-in-delimited-token pos "<![CDATA[" "]]>"))
1553 ((eq xmltok-type 'processing-instruction)
1554 (nxml-compute-indent-in-delimited-token pos "<?" "?>"))
1555 (t
1556 (goto-char pos)
1557 (if (and (= (forward-line -1) 0)
1558 (< xmltok-start (point)))
1559 (back-to-indentation)
1560 (goto-char xmltok-start))
1561 (current-column))))
1562
1563 (defun nxml-compute-indent-in-start-tag (pos)
1564 "Return the indent for a line that starts inside a start-tag.
1565 Also for a line that starts inside an empty element.
1566 POS is the position of the first non-whitespace character of the line.
1567 This expects the xmltok-* variables to be set up as by `xmltok-forward'."
1568 (let ((value-boundary (nxml-attribute-value-boundary pos))
1569 (off 0))
1570 (if value-boundary
1571 ;; inside an attribute value
1572 (let ((value-start (car value-boundary))
1573 (value-end (cdr value-boundary)))
1574 (goto-char pos)
1575 (forward-line -1)
1576 (if (< (point) value-start)
1577 (goto-char value-start)
1578 (back-to-indentation)))
1579 ;; outside an attribute value
1580 (goto-char pos)
1581 (while (and (= (forward-line -1) 0)
1582 (nxml-attribute-value-boundary (point))))
1583 (cond ((<= (point) xmltok-start)
1584 (goto-char xmltok-start)
1585 (setq off nxml-attribute-indent)
1586 (let ((atts (xmltok-merge-attributes)))
1587 (when atts
1588 (let* ((att (car atts))
1589 (start (xmltok-attribute-name-start att)))
1590 (when (< start pos)
1591 (goto-char start)
1592 (setq off 0))))))
1593 (t
1594 (back-to-indentation))))
1595 (+ (current-column) off)))
1596
1597 (defun nxml-attribute-value-boundary (pos)
1598 "Return a pair (START . END) if POS is inside an attribute value.
1599 Otherwise return nil. START and END are the positions of the start
1600 and end of the attribute value containing POS. This expects the
1601 xmltok-* variables to be set up as by `xmltok-forward'."
1602 (let ((atts (xmltok-merge-attributes))
1603 att value-start value-end value-boundary)
1604 (while atts
1605 (setq att (car atts))
1606 (setq value-start (xmltok-attribute-value-start att))
1607 (setq value-end (xmltok-attribute-value-end att))
1608 (cond ((and value-start (< pos value-start))
1609 (setq atts nil))
1610 ((and value-start value-end (<= pos value-end))
1611 (setq value-boundary (cons value-start value-end))
1612 (setq atts nil))
1613 (t (setq atts (cdr atts)))))
1614 value-boundary))
1615
1616 (defun nxml-compute-indent-in-delimited-token (pos open-delim close-delim)
1617 "Return the indent for a line that starts inside a token with delimiters.
1618 OPEN-DELIM and CLOSE-DELIM are strings giving the opening and closing
1619 delimiters. POS is the position of the first non-whitespace character
1620 of the line. This expects the xmltok-* variables to be set up as by
1621 `xmltok-forward'."
1622 (cond ((let ((end (+ pos (length close-delim))))
1623 (and (<= end (point-max))
1624 (string= (buffer-substring-no-properties pos end)
1625 close-delim)))
1626 (goto-char xmltok-start))
1627 ((progn
1628 (goto-char pos)
1629 (forward-line -1)
1630 (<= (point) xmltok-start))
1631 (goto-char (+ xmltok-start (length open-delim)))
1632 (when (and (string= open-delim "<!--")
1633 (looking-at " "))
1634 (goto-char (1+ (point)))))
1635 (t (back-to-indentation)))
1636 (current-column))
1637
1638 ;;; Completion
1639
1640 (defun nxml-complete ()
1641 "Perform completion on the symbol preceding point.
1642
1643 Inserts as many characters as can be completed. However, if not even
1644 one character can be completed, then a buffer with the possibilities
1645 is popped up and the symbol is read from the minibuffer with
1646 completion. If the symbol is complete, then any characters that must
1647 follow the symbol are also inserted.
1648
1649 The name space used for completion and what is treated as a symbol
1650 depends on the context. The contexts in which completion is performed
1651 depend on `nxml-completion-hook'."
1652 (interactive)
1653 (unless (run-hook-with-args-until-success 'nxml-completion-hook)
1654 ;; Eventually we will complete on entity names here.
1655 (ding)
1656 (message "Cannot complete in this context")))
1657
1658 ;;; Movement
1659
1660 (defun nxml-forward-balanced-item (&optional arg)
1661 "Move forward across one balanced item.
1662 With ARG, do it that many times. Negative arg -N means
1663 move backward across N balanced expressions.
1664 This is the equivalent of `forward-sexp' for XML.
1665
1666 An element contains as items strings with no markup, tags, processing
1667 instructions, comments, CDATA sections, entity references and
1668 characters references. However, if the variable
1669 `nxml-sexp-element-flag' is non-nil, then an element is treated as a
1670 single markup item. A start-tag contains an element name followed by
1671 one or more attributes. An end-tag contains just an element name.
1672 An attribute value literals contains strings with no markup, entity
1673 references and character references. A processing instruction
1674 consists of a target and a content string. A comment or a CDATA
1675 section contains a single string. An entity reference contains a
1676 single name. A character reference contains a character number."
1677 (interactive "p")
1678 (or arg (setq arg 1))
1679 (cond ((> arg 0)
1680 (while (progn
1681 (nxml-forward-single-balanced-item)
1682 (> (setq arg (1- arg)) 0))))
1683 ((< arg 0)
1684 (while (progn
1685 (nxml-backward-single-balanced-item)
1686 (< (setq arg (1+ arg)) 0))))))
1687
1688 (defun nxml-forward-single-balanced-item ()
1689 (condition-case err
1690 (goto-char (let ((end (nxml-token-after)))
1691 (save-excursion
1692 (while (eq xmltok-type 'space)
1693 (goto-char end)
1694 (setq end (nxml-token-after)))
1695 (cond ((/= (point) xmltok-start)
1696 (nxml-scan-forward-within end))
1697 ((and nxml-sexp-element-flag
1698 (eq xmltok-type 'start-tag))
1699 ;; can't ever return nil here
1700 (nxml-scan-element-forward xmltok-start))
1701 ((and nxml-sexp-element-flag
1702 (memq xmltok-type
1703 '(end-tag partial-end-tag)))
1704 (error "Already at end of element"))
1705 (t end)))))
1706 (nxml-scan-error
1707 (goto-char (cadr err))
1708 (apply 'error (cddr err)))))
1709
1710 (defun nxml-backward-single-balanced-item ()
1711 (condition-case err
1712 (goto-char (let ((end (nxml-token-before)))
1713 (save-excursion
1714 (while (eq xmltok-type 'space)
1715 (goto-char xmltok-start)
1716 (setq end (nxml-token-before)))
1717 (cond ((/= (point) end)
1718 (nxml-scan-backward-within end))
1719 ((and nxml-sexp-element-flag
1720 (eq xmltok-type 'end-tag))
1721 ;; can't ever return nil here
1722 (nxml-scan-element-backward end)
1723 xmltok-start)
1724 ((and nxml-sexp-element-flag
1725 (eq xmltok-type 'start-tag))
1726 (error "Already at start of element"))
1727 (t xmltok-start)))))
1728 (nxml-scan-error
1729 (goto-char (cadr err))
1730 (apply 'error (cddr err)))))
1731
1732 (defun nxml-scan-forward-within (end)
1733 (setq end (- end (nxml-end-delimiter-length xmltok-type)))
1734 (when (<= end (point))
1735 (error "Already at end of %s"
1736 (nxml-token-type-friendly-name xmltok-type)))
1737 (cond ((memq xmltok-type '(start-tag
1738 empty-element
1739 partial-start-tag
1740 partial-empty-element))
1741 (if (< (point) xmltok-name-end)
1742 xmltok-name-end
1743 (let ((att (nxml-find-following-attribute)))
1744 (cond ((not att) end)
1745 ((and (xmltok-attribute-value-start att)
1746 (<= (xmltok-attribute-value-start att)
1747 (point)))
1748 (nxml-scan-forward-in-attribute-value att))
1749 ((xmltok-attribute-value-end att)
1750 (1+ (xmltok-attribute-value-end att)))
1751 ((save-excursion
1752 (goto-char (xmltok-attribute-name-end att))
1753 (looking-at "[ \t\r\n]*="))
1754 (match-end 0))
1755 (t (xmltok-attribute-name-end att))))))
1756 ((and (eq xmltok-type 'processing-instruction)
1757 (< (point) xmltok-name-end))
1758 xmltok-name-end)
1759 (t end)))
1760
1761 (defun nxml-scan-backward-within (end)
1762 (setq xmltok-start
1763 (+ xmltok-start
1764 (nxml-start-delimiter-length xmltok-type)))
1765 (when (<= (point) xmltok-start)
1766 (error "Already at start of %s"
1767 (nxml-token-type-friendly-name xmltok-type)))
1768 (cond ((memq xmltok-type '(start-tag
1769 empty-element
1770 partial-start-tag
1771 partial-empty-element))
1772 (let ((att (nxml-find-preceding-attribute)))
1773 (cond ((not att) xmltok-start)
1774 ((and (xmltok-attribute-value-start att)
1775 (<= (xmltok-attribute-value-start att)
1776 (point))
1777 (<= (point)
1778 (xmltok-attribute-value-end att)))
1779 (nxml-scan-backward-in-attribute-value att))
1780 (t (xmltok-attribute-name-start att)))))
1781 ((and (eq xmltok-type 'processing-instruction)
1782 (let ((content-start (save-excursion
1783 (goto-char xmltok-name-end)
1784 (skip-chars-forward " \r\t\n")
1785 (point))))
1786 (and (< content-start (point))
1787 content-start))))
1788 (t xmltok-start)))
1789
1790 (defun nxml-scan-forward-in-attribute-value (att)
1791 (when (= (point) (xmltok-attribute-value-end att))
1792 (error "Already at end of attribute value"))
1793 (let ((refs (xmltok-attribute-refs att))
1794 ref)
1795 (while refs
1796 (setq ref (car refs))
1797 (if (< (point) (aref ref 2))
1798 (setq refs nil)
1799 (setq ref nil)
1800 (setq refs (cdr refs))))
1801 (cond ((not ref)
1802 (xmltok-attribute-value-end att))
1803 ((< (point) (aref ref 1))
1804 (aref ref 1))
1805 ((= (point) (aref ref 1))
1806 (aref ref 2))
1807 (t
1808 (let ((end (- (aref ref 2)
1809 (nxml-end-delimiter-length (aref ref 0)))))
1810 (if (< (point) end)
1811 end
1812 (error "Already at end of %s"
1813 (nxml-token-type-friendly-name (aref ref 0)))))))))
1814
1815 (defun nxml-scan-backward-in-attribute-value (att)
1816 (when (= (point) (xmltok-attribute-value-start att))
1817 (error "Already at start of attribute value"))
1818 (let ((refs (reverse (xmltok-attribute-refs att)))
1819 ref)
1820 (while refs
1821 (setq ref (car refs))
1822 (if (< (aref ref 1) (point))
1823 (setq refs nil)
1824 (setq ref nil)
1825 (setq refs (cdr refs))))
1826 (cond ((not ref)
1827 (xmltok-attribute-value-start att))
1828 ((< (aref ref 2) (point))
1829 (aref ref 2))
1830 ((= (point) (aref ref 2))
1831 (aref ref 1))
1832 (t
1833 (let ((start (+ (aref ref 1)
1834 (nxml-start-delimiter-length (aref ref 0)))))
1835 (if (< start (point))
1836 start
1837 (error "Already at start of %s"
1838 (nxml-token-type-friendly-name (aref ref 0)))))))))
1839
1840 (defun nxml-find-following-attribute ()
1841 (let ((ret nil)
1842 (atts (or xmltok-attributes xmltok-namespace-attributes))
1843 (more-atts (and xmltok-attributes xmltok-namespace-attributes)))
1844 (while atts
1845 (let* ((att (car atts))
1846 (name-start (xmltok-attribute-name-start att)))
1847 (cond ((and (<= name-start (point))
1848 (xmltok-attribute-value-end att)
1849 ;; <= because end is before quote
1850 (<= (point) (xmltok-attribute-value-end att)))
1851 (setq atts nil)
1852 (setq ret att))
1853 ((and (< (point) name-start)
1854 (or (not ret)
1855 (< name-start
1856 (xmltok-attribute-name-start ret))))
1857 (setq ret att))))
1858 (setq atts (cdr atts))
1859 (unless atts
1860 (setq atts more-atts)
1861 (setq more-atts nil)))
1862 ret))
1863
1864 (defun nxml-find-preceding-attribute ()
1865 (let ((ret nil)
1866 (atts (or xmltok-attributes xmltok-namespace-attributes))
1867 (more-atts (and xmltok-attributes xmltok-namespace-attributes)))
1868 (while atts
1869 (let* ((att (car atts))
1870 (name-start (xmltok-attribute-name-start att)))
1871 (cond ((and (< name-start (point))
1872 (xmltok-attribute-value-end att)
1873 ;; <= because end is before quote
1874 (<= (point) (xmltok-attribute-value-end att)))
1875 (setq atts nil)
1876 (setq ret att))
1877 ((and (< name-start (point))
1878 (or (not ret)
1879 (< (xmltok-attribute-name-start ret)
1880 name-start)))
1881 (setq ret att))))
1882 (setq atts (cdr atts))
1883 (unless atts
1884 (setq atts more-atts)
1885 (setq more-atts nil)))
1886 ret))
1887
1888 (defun nxml-up-element (&optional arg)
1889 (interactive "p")
1890 (or arg (setq arg 1))
1891 (if (< arg 0)
1892 (nxml-backward-up-element (- arg))
1893 (condition-case err
1894 (while (and (> arg 0)
1895 (< (point) (point-max)))
1896 (let ((token-end (nxml-token-after)))
1897 (goto-char (cond ((or (memq xmltok-type '(end-tag
1898 partial-end-tag))
1899 (and (memq xmltok-type
1900 '(empty-element
1901 partial-empty-element))
1902 (< xmltok-start (point))))
1903 token-end)
1904 ((nxml-scan-element-forward
1905 (if (and (eq xmltok-type 'start-tag)
1906 (= (point) xmltok-start))
1907 xmltok-start
1908 token-end)
1909 t))
1910 (t (error "No parent element")))))
1911 (setq arg (1- arg)))
1912 (nxml-scan-error
1913 (goto-char (cadr err))
1914 (apply 'error (cddr err))))))
1915
1916 (defun nxml-backward-up-element (&optional arg)
1917 (interactive "p")
1918 (or arg (setq arg 1))
1919 (if (< arg 0)
1920 (nxml-up-element (- arg))
1921 (condition-case err
1922 (while (and (> arg 0)
1923 (< (point-min) (point)))
1924 (let ((token-end (nxml-token-before)))
1925 (goto-char (cond ((or (memq xmltok-type '(start-tag
1926 partial-start-tag))
1927 (and (memq xmltok-type
1928 '(empty-element
1929 partial-empty-element))
1930 (< (point) token-end)))
1931 xmltok-start)
1932 ((nxml-scan-element-backward
1933 (if (and (eq xmltok-type 'end-tag)
1934 (= (point) token-end))
1935 token-end
1936 xmltok-start)
1937 t)
1938 xmltok-start)
1939 (t (error "No parent element")))))
1940 (setq arg (1- arg)))
1941 (nxml-scan-error
1942 (goto-char (cadr err))
1943 (apply 'error (cddr err))))))
1944
1945 (defun nxml-down-element (&optional arg)
1946 "Move forward down into the content of an element.
1947 With ARG, do this that many times.
1948 Negative ARG means move backward but still down."
1949 (interactive "p")
1950 (or arg (setq arg 1))
1951 (if (< arg 0)
1952 (nxml-backward-down-element (- arg))
1953 (while (> arg 0)
1954 (goto-char
1955 (let ((token-end (nxml-token-after)))
1956 (save-excursion
1957 (goto-char token-end)
1958 (while (progn
1959 (when (memq xmltok-type '(nil end-tag partial-end-tag))
1960 (error "No following start-tags in this element"))
1961 (not (memq xmltok-type '(start-tag partial-start-tag))))
1962 (nxml-tokenize-forward))
1963 (point))))
1964 (setq arg (1- arg)))))
1965
1966 (defun nxml-backward-down-element (&optional arg)
1967 (interactive "p")
1968 (or arg (setq arg 1))
1969 (if (< arg 0)
1970 (nxml-down-element (- arg))
1971 (while (> arg 0)
1972 (goto-char
1973 (save-excursion
1974 (nxml-token-before)
1975 (goto-char xmltok-start)
1976 (while (progn
1977 (when (memq xmltok-type '(start-tag
1978 partial-start-tag
1979 prolog
1980 nil))
1981 (error "No preceding end-tags in this element"))
1982 (not (memq xmltok-type '(end-tag partial-end-tag))))
1983 (if (or (<= (point) nxml-prolog-end)
1984 (not (search-backward "<" nxml-prolog-end t)))
1985 (setq xmltok-type nil)
1986 (nxml-move-outside-backwards)
1987 (xmltok-forward)))
1988 xmltok-start))
1989 (setq arg (1- arg)))))
1990
1991 (defun nxml-forward-element (&optional arg)
1992 "Move forward over one element.
1993 With ARG, do it that many times.
1994 Negative ARG means move backward."
1995 (interactive "p")
1996 (or arg (setq arg 1))
1997 (if (< arg 0)
1998 (nxml-backward-element (- arg))
1999 (condition-case err
2000 (while (and (> arg 0)
2001 (< (point) (point-max)))
2002 (goto-char
2003 (or (nxml-scan-element-forward (nxml-token-before))
2004 (error "No more elements")))
2005 (setq arg (1- arg)))
2006 (nxml-scan-error
2007 (goto-char (cadr err))
2008 (apply 'error (cddr err))))))
2009
2010 (defun nxml-backward-element (&optional arg)
2011 "Move backward over one element.
2012 With ARG, do it that many times.
2013 Negative ARG means move forward."
2014 (interactive "p")
2015 (or arg (setq arg 1))
2016 (if (< arg 0)
2017 (nxml-forward-element (- arg))
2018 (condition-case err
2019 (while (and (> arg 0)
2020 (< (point-min) (point)))
2021 (goto-char
2022 (or (and (nxml-scan-element-backward (progn
2023 (nxml-token-after)
2024 xmltok-start))
2025 xmltok-start)
2026 (error "No preceding elements")))
2027 (setq arg (1- arg)))
2028 (nxml-scan-error
2029 (goto-char (cadr err))
2030 (apply 'error (cddr err))))))
2031
2032 (defun nxml-mark-token-after ()
2033 (interactive)
2034 (push-mark (nxml-token-after) nil t)
2035 (goto-char xmltok-start)
2036 (message "Marked %s" xmltok-type))
2037
2038 ;;; Paragraphs
2039
2040 (defun nxml-mark-paragraph ()
2041 "Put point at beginning of this paragraph, mark at end.
2042 The paragraph marked is the one that contains point or follows point."
2043 (interactive)
2044 (nxml-forward-paragraph)
2045 (push-mark nil t t)
2046 (nxml-backward-paragraph))
2047
2048 (defun nxml-forward-paragraph (&optional arg)
2049 (interactive "p")
2050 (or arg (setq arg 1))
2051 (cond ((< arg 0)
2052 (nxml-backward-paragraph (- arg)))
2053 ((> arg 0)
2054 (forward-line 0)
2055 (while (and (nxml-forward-single-paragraph)
2056 (> (setq arg (1- arg)) 0))))))
2057
2058 (defun nxml-backward-paragraph (&optional arg)
2059 (interactive "p")
2060 (or arg (setq arg 1))
2061 (cond ((< arg 0)
2062 (nxml-forward-paragraph (- arg)))
2063 ((> arg 0)
2064 (unless (bolp)
2065 (let ((inhibit-field-text-motion t))
2066 (end-of-line)))
2067 (while (and (nxml-backward-single-paragraph)
2068 (> (setq arg (1- arg)) 0))))))
2069
2070 (defun nxml-forward-single-paragraph ()
2071 "Move forward over a single paragraph.
2072 Return nil at end of buffer, t otherwise."
2073 (let* ((token-end (nxml-token-after))
2074 (offset (- (point) xmltok-start))
2075 pos had-data)
2076 (goto-char token-end)
2077 (while (and (< (point) (point-max))
2078 (not (setq pos
2079 (nxml-paragraph-end-pos had-data offset))))
2080 (when (nxml-token-contains-data-p offset)
2081 (setq had-data t))
2082 (nxml-tokenize-forward)
2083 (setq offset 0))
2084 (when pos (goto-char pos))))
2085
2086 (defun nxml-backward-single-paragraph ()
2087 "Move backward over a single paragraph.
2088 Return nil at start of buffer, t otherwise."
2089 (let* ((token-end (nxml-token-before))
2090 (offset (- token-end (point)))
2091 (last-tag-pos xmltok-start)
2092 pos had-data last-data-pos)
2093 (goto-char token-end)
2094 (unless (setq pos (nxml-paragraph-start-pos nil offset))
2095 (setq had-data (nxml-token-contains-data-p nil offset))
2096 (goto-char xmltok-start)
2097 (while (and (not pos) (< (point-min) (point)))
2098 (cond ((search-backward "<" nxml-prolog-end t)
2099 (nxml-move-outside-backwards)
2100 (save-excursion
2101 (while (< (point) last-tag-pos)
2102 (xmltok-forward)
2103 (when (and (not had-data) (nxml-token-contains-data-p))
2104 (setq pos nil)
2105 (setq last-data-pos xmltok-start))
2106 (let ((tem (nxml-paragraph-start-pos had-data 0)))
2107 (when tem (setq pos tem)))))
2108 (when (and (not had-data) last-data-pos (not pos))
2109 (setq had-data t)
2110 (save-excursion
2111 (while (< (point) last-data-pos)
2112 (xmltok-forward))
2113 (let ((tem (nxml-paragraph-start-pos had-data 0)))
2114 (when tem (setq pos tem)))))
2115 (setq last-tag-pos (point)))
2116 (t (goto-char (point-min))))))
2117 (when pos (goto-char pos))))
2118
2119 (defun nxml-token-contains-data-p (&optional start end)
2120 (setq start (+ xmltok-start (or start 0)))
2121 (setq end (- (point) (or end 0)))
2122 (when (eq xmltok-type 'cdata-section)
2123 (setq start (max start (+ xmltok-start 9)))
2124 (setq end (min end (- (point) 3))))
2125 (or (and (eq xmltok-type 'data)
2126 (eq start xmltok-start)
2127 (eq end (point)))
2128 (eq xmltok-type 'char-ref)
2129 (and (memq xmltok-type '(data cdata-section))
2130 (< start end)
2131 (save-excursion
2132 (goto-char start)
2133 (re-search-forward "[^ \t\r\n]" end t)))))
2134
2135 (defun nxml-paragraph-end-pos (had-data offset)
2136 "Return the position of the paragraph end if contained in the current token.
2137 Return nil if the current token does not contain the paragraph end.
2138 Only characters after OFFSET from the start of the token are eligible.
2139 HAD-DATA says whether there have been non-whitespace data characters yet."
2140 (cond ((not had-data)
2141 (cond ((memq xmltok-type '(data cdata-section))
2142 (save-excursion
2143 (let ((end (point)))
2144 (goto-char (+ xmltok-start
2145 (max (if (eq xmltok-type 'cdata-section)
2146 9
2147 0)
2148 offset)))
2149 (and (re-search-forward "[^ \t\r\n]" end t)
2150 (re-search-forward "^[ \t]*$" end t)
2151 (match-beginning 0)))))
2152 ((and (eq xmltok-type 'comment)
2153 (nxml-token-begins-line-p)
2154 (nxml-token-ends-line-p))
2155 (save-excursion
2156 (let ((end (point)))
2157 (goto-char (+ xmltok-start (max 4 offset)))
2158 (when (re-search-forward "[^ \t\r\n]" (- end 3) t)
2159 (if (re-search-forward "^[ \t]*$" end t)
2160 (match-beginning 0)
2161 (goto-char (- end 3))
2162 (skip-chars-backward " \t")
2163 (unless (bolp)
2164 (beginning-of-line 2))
2165 (point))))))))
2166 ((memq xmltok-type '(data space cdata-section))
2167 (save-excursion
2168 (let ((end (point)))
2169 (goto-char (+ xmltok-start offset))
2170 (and (re-search-forward "^[ \t]*$" end t)
2171 (match-beginning 0)))))
2172 ((and (memq xmltok-type '(start-tag
2173 end-tag
2174 empty-element
2175 comment
2176 processing-instruction
2177 entity-ref))
2178 (nxml-token-begins-line-p)
2179 (nxml-token-ends-line-p))
2180 (save-excursion
2181 (goto-char xmltok-start)
2182 (skip-chars-backward " \t")
2183 (point)))
2184 ((and (eq xmltok-type 'end-tag)
2185 (looking-at "[ \t]*$")
2186 (not (nxml-in-mixed-content-p t)))
2187 (save-excursion
2188 (or (search-forward "\n" nil t)
2189 (point-max))))))
2190
2191 (defun nxml-paragraph-start-pos (had-data offset)
2192 "Return the position of the paragraph start if contained in the current token.
2193 Return nil if the current token does not contain the paragraph start.
2194 Only characters before OFFSET from the end of the token are eligible.
2195 HAD-DATA says whether there have been non-whitespace data characters yet."
2196 (cond ((not had-data)
2197 (cond ((memq xmltok-type '(data cdata-section))
2198 (save-excursion
2199 (goto-char (- (point)
2200 (max (if (eq xmltok-type 'cdata-section)
2201 3
2202 0)
2203 offset)))
2204 (and (re-search-backward "[^ \t\r\n]" xmltok-start t)
2205 (re-search-backward "^[ \t]*$" xmltok-start t)
2206 (match-beginning 0))))
2207 ((and (eq xmltok-type 'comment)
2208 (nxml-token-ends-line-p)
2209 (nxml-token-begins-line-p))
2210 (save-excursion
2211 (goto-char (- (point) (max 3 offset)))
2212 (when (and (< (+ xmltok-start 4) (point))
2213 (re-search-backward "[^ \t\r\n]"
2214 (+ xmltok-start 4)
2215 t))
2216 (if (re-search-backward "^[ \t]*$" xmltok-start t)
2217 (match-beginning 0)
2218 (goto-char xmltok-start)
2219 (if (looking-at "<!--[ \t]*\n")
2220 (match-end 0)
2221 (skip-chars-backward " \t")
2222 (point))))))))
2223 ((memq xmltok-type '(data space cdata-section))
2224 (save-excursion
2225 (goto-char (- (point) offset))
2226 (and (re-search-backward "^[ \t]*$" xmltok-start t)
2227 (match-beginning 0))))
2228 ((and (memq xmltok-type '(start-tag
2229 end-tag
2230 empty-element
2231 comment
2232 processing-instruction
2233 entity-ref))
2234 (nxml-token-ends-line-p)
2235 (nxml-token-begins-line-p))
2236 (or (search-forward "\n" nil t)
2237 (point-max)))
2238 ((and (eq xmltok-type 'start-tag)
2239 (nxml-token-begins-line-p)
2240 (not (save-excursion
2241 (goto-char xmltok-start)
2242 (nxml-in-mixed-content-p nil))))
2243 (save-excursion
2244 (goto-char xmltok-start)
2245 (skip-chars-backward " \t")
2246 ;; include any blank line before
2247 (or (and (eq (char-before) ?\n)
2248 (save-excursion
2249 (goto-char (1- (point)))
2250 (skip-chars-backward " \t")
2251 (and (bolp) (point))))
2252 (point))))))
2253
2254 (defun nxml-token-ends-line-p () (looking-at "[ \t]*$"))
2255
2256 (defun nxml-token-begins-line-p ()
2257 (save-excursion
2258 (goto-char xmltok-start)
2259 (skip-chars-backward " \t")
2260 (bolp)))
2261
2262 (defun nxml-in-mixed-content-p (endp)
2263 "Return non-nil if point is in mixed content.
2264 Point must be after an end-tag or before a start-tag.
2265 ENDP is t in the former case, nil in the latter."
2266 (let (matching-tag-pos)
2267 (cond ((not (run-hook-with-args-until-failure
2268 'nxml-in-mixed-content-hook))
2269 nil)
2270 ;; See if the matching tag does not start or end a line.
2271 ((condition-case err
2272 (progn
2273 (setq matching-tag-pos
2274 (xmltok-save
2275 (if endp
2276 (and (nxml-scan-element-backward (point))
2277 xmltok-start)
2278 (nxml-scan-element-forward (point)))))
2279 (and matching-tag-pos
2280 (save-excursion
2281 (goto-char matching-tag-pos)
2282 (not (if endp
2283 (progn
2284 (skip-chars-backward " \t")
2285 (bolp))
2286 (looking-at "[ \t]*$"))))))
2287 (nxml-scan-error nil))
2288 t)
2289 ;; See if there's data at the same level.
2290 ((let (start end)
2291 (if endp
2292 (setq start matching-tag-pos
2293 end (point))
2294 (setq start (point)
2295 end matching-tag-pos))
2296 (save-excursion
2297 (or (when start
2298 (goto-char start)
2299 (nxml-preceding-sibling-data-p))
2300 (when end
2301 (goto-char end)
2302 (nxml-following-sibling-data-p)))))
2303 t)
2304 ;; Otherwise, treat as not mixed
2305 (t nil))))
2306
2307 (defun nxml-preceding-sibling-data-p ()
2308 "Return non-nil if there is a previous sibling that is data."
2309 (let ((lim (max (- (point) nxml-mixed-scan-distance)
2310 nxml-prolog-end))
2311 (level 0)
2312 found end)
2313 (xmltok-save
2314 (save-excursion
2315 (while (and (< lim (point))
2316 (>= level 0)
2317 (not found)
2318 (progn
2319 (setq end (point))
2320 (search-backward "<" lim t)))
2321 (nxml-move-outside-backwards)
2322 (save-excursion
2323 (xmltok-forward)
2324 (let ((prev-level level))
2325 (cond ((eq xmltok-type 'end-tag)
2326 (setq level (1+ level)))
2327 ((eq xmltok-type 'start-tag)
2328 (setq level (1- level))))
2329 (when (eq prev-level 0)
2330 (while (and (< (point) end) (not found))
2331 (xmltok-forward)
2332 (when (memq xmltok-type '(data cdata-section char-ref))
2333 (setq found t)))))))))
2334 found))
2335
2336 (defun nxml-following-sibling-data-p ()
2337 (let ((lim (min (+ (point) nxml-mixed-scan-distance)
2338 (point-max)))
2339 (level 0)
2340 found)
2341 (xmltok-save
2342 (save-excursion
2343 (while (and (< (point) lim)
2344 (>= level 0)
2345 (nxml-tokenize-forward)
2346 (not found))
2347 (cond ((eq xmltok-type 'start-tag)
2348 (setq level (1+ level)))
2349 ((eq xmltok-type 'end-tag)
2350 (setq level (1- level)))
2351 ((and (eq level 0)
2352 (memq xmltok-type '(data cdata-section char-ref)))
2353 (setq found t))))))
2354 found))
2355
2356 ;;; Filling
2357
2358 (defun nxml-do-fill-paragraph (arg)
2359 (let (fill-paragraph-function
2360 fill-prefix
2361 start end)
2362 (save-excursion
2363 (nxml-forward-paragraph)
2364 (setq end (point))
2365 (nxml-backward-paragraph)
2366 (skip-chars-forward " \t\r\n")
2367 (setq start (point))
2368 (beginning-of-line)
2369 (setq fill-prefix (buffer-substring-no-properties (point) start))
2370 (when (and (not (nxml-get-inside (point)))
2371 (looking-at "[ \t]*<!--"))
2372 (setq fill-prefix (concat fill-prefix " ")))
2373 (fill-region-as-paragraph start end arg))
2374 (skip-line-prefix fill-prefix)
2375 fill-prefix))
2376
2377 (defun nxml-newline-and-indent (soft)
2378 (delete-horizontal-space)
2379 (if soft (insert-and-inherit ?\n) (newline 1))
2380 (nxml-indent-line))
2381
2382
2383 ;;; Dynamic markup
2384
2385 (defvar nxml-dynamic-markup-prev-pos nil)
2386 (defvar nxml-dynamic-markup-prev-lengths nil)
2387 (defvar nxml-dynamic-markup-prev-found-marker nil)
2388 (defvar nxml-dynamic-markup-prev-start-tags (make-hash-table :test 'equal))
2389
2390 (defun nxml-dynamic-markup-word ()
2391 "Dynamically markup the word before point.
2392 This attempts to find a tag to put around the word before point based
2393 on the contents of the current buffer. The end-tag will be inserted at
2394 point. The start-tag will be inserted at or before the beginning of
2395 the word before point; the contents of the current buffer is used to
2396 decide where.
2397
2398 It works in a similar way to \\[dabbrev-expand]. It searches first
2399 backwards from point, then forwards from point for an element whose
2400 content is a string which matches the contents of the buffer before
2401 point and which includes at least the word before point. It then
2402 copies the start- and end-tags from that element and uses them to
2403 surround the matching string before point.
2404
2405 Repeating \\[nxml-dynamic-markup-word] immediately after successful
2406 \\[nxml-dynamic-markup-word] removes the previously inserted markup
2407 and attempts to find another possible way to do the markup."
2408 (interactive "*")
2409 (let (search-start-pos done)
2410 (if (and (integerp nxml-dynamic-markup-prev-pos)
2411 (= nxml-dynamic-markup-prev-pos (point))
2412 (eq last-command this-command)
2413 nxml-dynamic-markup-prev-lengths)
2414 (let* ((end-tag-open-pos
2415 (- nxml-dynamic-markup-prev-pos
2416 (nth 2 nxml-dynamic-markup-prev-lengths)))
2417 (start-tag-close-pos
2418 (- end-tag-open-pos
2419 (nth 1 nxml-dynamic-markup-prev-lengths)))
2420 (start-tag-open-pos
2421 (- start-tag-close-pos
2422 (nth 0 nxml-dynamic-markup-prev-lengths))))
2423 (delete-region end-tag-open-pos nxml-dynamic-markup-prev-pos)
2424 (delete-region start-tag-open-pos start-tag-close-pos)
2425 (setq search-start-pos
2426 (marker-position nxml-dynamic-markup-prev-found-marker)))
2427 (clrhash nxml-dynamic-markup-prev-start-tags))
2428 (setq nxml-dynamic-markup-prev-pos nil)
2429 (setq nxml-dynamic-markup-prev-lengths nil)
2430 (setq nxml-dynamic-markup-prev-found-marker nil)
2431 (goto-char
2432 (save-excursion
2433 (let* ((pos (point))
2434 (word (progn
2435 (backward-word 1)
2436 (unless (< (point) pos)
2437 (error "No word to markup"))
2438 (buffer-substring-no-properties (point) pos)))
2439 (search (concat word "</"))
2440 done)
2441 (when search-start-pos
2442 (goto-char search-start-pos))
2443 (while (and (not done)
2444 (or (and (< (point) pos)
2445 (or (search-backward search nil t)
2446 (progn (goto-char pos) nil)))
2447 (search-forward search nil t)))
2448 (goto-char (- (match-end 0) 2))
2449 (setq done (nxml-try-copy-markup pos)))
2450 (or done
2451 (error (if (zerop (hash-table-count
2452 nxml-dynamic-markup-prev-start-tags))
2453 "No possible markup found for `%s'"
2454 "No more markup possibilities found for `%s'")
2455 word)))))))
2456
2457 (defun nxml-try-copy-markup (word-end-pos)
2458 (save-excursion
2459 (let ((end-tag-pos (point)))
2460 (when (and (not (nxml-get-inside end-tag-pos))
2461 (search-backward "<" nil t)
2462 (not (nxml-get-inside (point))))
2463 (xmltok-forward)
2464 (when (and (eq xmltok-type 'start-tag)
2465 (< (point) end-tag-pos))
2466 (let* ((start-tag-close-pos (point))
2467 (start-tag
2468 (buffer-substring-no-properties xmltok-start
2469 start-tag-close-pos))
2470 (words
2471 (nreverse
2472 (split-string
2473 (buffer-substring-no-properties start-tag-close-pos
2474 end-tag-pos)
2475 "[ \t\r\n]+"))))
2476 (goto-char word-end-pos)
2477 (while (and words
2478 (re-search-backward (concat
2479 (regexp-quote (car words))
2480 "\\=")
2481 nil
2482 t))
2483 (setq words (cdr words))
2484 (skip-chars-backward " \t\r\n"))
2485 (when (and (not words)
2486 (progn
2487 (skip-chars-forward " \t\r\n")
2488 (not (gethash (cons (point) start-tag)
2489 nxml-dynamic-markup-prev-start-tags)))
2490 (or (< end-tag-pos (point))
2491 (< word-end-pos xmltok-start)))
2492 (setq nxml-dynamic-markup-prev-found-marker
2493 (copy-marker end-tag-pos t))
2494 (puthash (cons (point) start-tag)
2495 t
2496 nxml-dynamic-markup-prev-start-tags)
2497 (setq nxml-dynamic-markup-prev-lengths
2498 (list (- start-tag-close-pos xmltok-start)
2499 (- word-end-pos (point))
2500 (+ (- xmltok-name-end xmltok-start) 2)))
2501 (let ((name (xmltok-start-tag-qname)))
2502 (insert start-tag)
2503 (goto-char (+ word-end-pos
2504 (- start-tag-close-pos xmltok-start)))
2505 (insert "</" name ">")
2506 (setq nxml-dynamic-markup-prev-pos (point))))))))))
2507
2508
2509 ;;; Character names
2510
2511 (defvar nxml-char-name-ignore-case t)
2512
2513 (defvar nxml-char-name-alist nil
2514 "Alist of character names.
2515 Each member of the list has the form (NAME CODE . NAMESET),
2516 where NAME is a string naming a character, NAMESET is a symbol
2517 identifying a set of names and CODE is an integer specifying the
2518 Unicode scalar value of the named character.
2519 The NAME will only be used for completion if NAMESET has
2520 a non-nil `nxml-char-name-set-enabled' property.
2521 If NAMESET does does not have `nxml-char-name-set-defined' property,
2522 then it must have a `nxml-char-name-set-file' property and `load'
2523 will be applied to the value of this property if the nameset
2524 is enabled.")
2525
2526 (defvar nxml-char-name-table (make-hash-table :test 'eq)
2527 "Hash table for mapping char codes to names.
2528 Each key is a Unicode scalar value.
2529 Each value is a list of pairs of the form (NAMESET . NAME),
2530 where NAMESET is a symbol identifying a set of names,
2531 and NAME is a string naming a character.")
2532
2533 (defvar nxml-autoload-char-name-set-list nil
2534 "List of char namesets that can be autoloaded.")
2535
2536 (defun nxml-enable-char-name-set (nameset)
2537 (put nameset 'nxml-char-name-set-enabled t))
2538
2539 (defun nxml-disable-char-name-set (nameset)
2540 (put nameset 'nxml-char-name-set-enabled nil))
2541
2542 (defun nxml-char-name-set-enabled-p (nameset)
2543 (get nameset 'nxml-char-name-set-enabled))
2544
2545 (defun nxml-autoload-char-name-set (nameset file)
2546 (unless (memq nameset nxml-autoload-char-name-set-list)
2547 (setq nxml-autoload-char-name-set-list
2548 (cons nameset nxml-autoload-char-name-set-list)))
2549 (put nameset 'nxml-char-name-set-file file))
2550
2551 (defun nxml-define-char-name-set (nameset alist)
2552 "Define a set of character names.
2553 NAMESET is a symbol identifying the set.
2554 ALIST is a list where each member has the form (NAME CODE),
2555 where NAME is a string naming a character and code is an
2556 integer giving the Unicode scalar value of the character."
2557 (when (get nameset 'nxml-char-name-set-defined)
2558 (error "Nameset `%s' already defined" nameset))
2559 (let ((iter alist))
2560 (while iter
2561 (let* ((name-code (car iter))
2562 (name (car name-code))
2563 (code (cadr name-code)))
2564 (puthash code
2565 (cons (cons nameset name)
2566 (gethash code nxml-char-name-table))
2567 nxml-char-name-table))
2568 (setcdr (cdr (car iter)) nameset)
2569 (setq iter (cdr iter))))
2570 (setq nxml-char-name-alist
2571 (nconc alist nxml-char-name-alist))
2572 (put nameset 'nxml-char-name-set-defined t))
2573
2574 (defun nxml-get-char-name (code)
2575 (mapc 'nxml-maybe-load-char-name-set nxml-autoload-char-name-set-list)
2576 (let ((names (gethash code nxml-char-name-table))
2577 name)
2578 (while (and names (not name))
2579 (if (nxml-char-name-set-enabled-p (caar names))
2580 (setq name (cdar names))
2581 (setq names (cdr names))))
2582 name))
2583
2584 (defvar nxml-named-char-history nil)
2585
2586 (defun nxml-insert-named-char (arg)
2587 "Insert a character using its name.
2588 The name is read from the minibuffer.
2589 Normally, inserts the character as a numeric character reference.
2590 With a prefix argument, inserts the character directly."
2591 (interactive "*P")
2592 (mapc 'nxml-maybe-load-char-name-set nxml-autoload-char-name-set-list)
2593 (let ((name
2594 (let ((completion-ignore-case nxml-char-name-ignore-case))
2595 (completing-read "Character name: "
2596 nxml-char-name-alist
2597 (lambda (member)
2598 (get (cddr member) 'nxml-char-name-set-enabled))
2599 t
2600 nil
2601 'nxml-named-char-history)))
2602 (alist nxml-char-name-alist)
2603 elt code)
2604 (while (and alist (not code))
2605 (setq elt (assoc name alist))
2606 (if (get (cddr elt) 'nxml-char-name-set-enabled)
2607 (setq code (cadr elt))
2608 (setq alist (cdr (member elt alist)))))
2609 (when code
2610 (insert (if arg
2611 (or (decode-char 'ucs code)
2612 (error "Character %x is not supported by Emacs"
2613 code))
2614 (format "&#x%X;" code))))))
2615
2616 (defun nxml-maybe-load-char-name-set (sym)
2617 (when (and (get sym 'nxml-char-name-set-enabled)
2618 (not (get sym 'nxml-char-name-set-defined))
2619 (stringp (get sym 'nxml-char-name-set-file)))
2620 (load (get sym 'nxml-char-name-set-file))))
2621
2622 (defun nxml-toggle-char-ref-extra-display (arg)
2623 "Toggle the display of extra information for character references."
2624 (interactive "P")
2625 (let ((new (if (null arg)
2626 (not nxml-char-ref-extra-display)
2627 (> (prefix-numeric-value arg) 0))))
2628 (when (not (eq new nxml-char-ref-extra-display))
2629 (setq nxml-char-ref-extra-display new)
2630 (font-lock-fontify-buffer))))
2631
2632 (put 'nxml-char-ref 'evaporate t)
2633
2634 (defun nxml-char-ref-display-extra (start end n)
2635 (when nxml-char-ref-extra-display
2636 (let ((name (nxml-get-char-name n))
2637 (glyph-string (and nxml-char-ref-display-glyph-flag
2638 (nxml-glyph-display-string n 'nxml-glyph)))
2639 ov)
2640 (when (or name glyph-string)
2641 (setq ov (make-overlay start end nil t))
2642 (overlay-put ov 'category 'nxml-char-ref)
2643 (when name
2644 (overlay-put ov 'help-echo name))
2645 (when glyph-string
2646 (overlay-put ov
2647 'after-string
2648 (propertize glyph-string 'face 'nxml-glyph)))))))
2649
2650 (defun nxml-clear-char-ref-extra-display (start end)
2651 (let ((ov (overlays-in start end)))
2652 (while ov
2653 (when (eq (overlay-get (car ov) 'category) 'nxml-char-ref)
2654 (delete-overlay (car ov)))
2655 (setq ov (cdr ov)))))
2656
2657
2658 (defun nxml-start-delimiter-length (type)
2659 (or (get type 'nxml-start-delimiter-length)
2660 0))
2661
2662 (put 'cdata-section 'nxml-start-delimiter-length 9)
2663 (put 'comment 'nxml-start-delimiter-length 4)
2664 (put 'processing-instruction 'nxml-start-delimiter-length 2)
2665 (put 'start-tag 'nxml-start-delimiter-length 1)
2666 (put 'empty-element 'nxml-start-delimiter-length 1)
2667 (put 'partial-empty-element 'nxml-start-delimiter-length 1)
2668 (put 'entity-ref 'nxml-start-delimiter-length 1)
2669 (put 'char-ref 'nxml-start-delimiter-length 2)
2670
2671 (defun nxml-end-delimiter-length (type)
2672 (or (get type 'nxml-end-delimiter-length)
2673 0))
2674
2675 (put 'cdata-section 'nxml-end-delimiter-length 3)
2676 (put 'comment 'nxml-end-delimiter-length 3)
2677 (put 'processing-instruction 'nxml-end-delimiter-length 2)
2678 (put 'start-tag 'nxml-end-delimiter-length 1)
2679 (put 'empty-element 'nxml-end-delimiter-length 2)
2680 (put 'partial-empty-element 'nxml-end-delimiter-length 1)
2681 (put 'entity-ref 'nxml-end-delimiter-length 1)
2682 (put 'char-ref 'nxml-end-delimiter-length 1)
2683
2684 (defun nxml-token-type-friendly-name (type)
2685 (or (get type 'nxml-friendly-name)
2686 (symbol-name type)))
2687
2688 (put 'cdata-section 'nxml-friendly-name "CDATA section")
2689 (put 'processing-instruction 'nxml-friendly-name "processing instruction")
2690 (put 'entity-ref 'nxml-friendly-name "entity reference")
2691 (put 'char-ref 'nxml-friendly-name "character reference")
2692
2693 ;;;###autoload
2694 (defalias 'xml-mode 'nxml-mode)
2695
2696 (provide 'nxml-mode)
2697
2698 ;;; nxml-mode.el ends here