Convert consecutive FSF copyright years to ranges.
[bpt/emacs.git] / lisp / nxml / nxml-mode.el
CommitLineData
8cd39fb3
MH
1;;; nxml-mode.el --- a new XML mode
2
73b0cd50 3;; Copyright (C) 2003-2004, 2007-2011
e9bffc61 4;; Free Software Foundation, Inc.
8cd39fb3
MH
5
6;; Author: James Clark
7;; Keywords: XML
8
e290ff07 9;; This file is part of GNU Emacs.
8cd39fb3 10
4936186e 11;; GNU Emacs is free software: you can redistribute it and/or modify
e290ff07 12;; it under the terms of the GNU General Public License as published by
4936186e
GM
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
8cd39fb3 15
e290ff07
GM
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
4936186e 22;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
8cd39fb3
MH
23
24;;; Commentary:
25
8cd39fb3
MH
26;; See nxml-rap.el for description of parsing strategy.
27
8cd39fb3
MH
28;;; Code:
29
30(when (featurep 'mucs)
31 (error "nxml-mode is not compatible with Mule-UCS"))
32
9c8dd25e
GM
33(eval-when-compile (require 'cl)) ; for assert
34
8cd39fb3
MH
35(require 'xmltok)
36(require 'nxml-enc)
37(require 'nxml-glyph)
38(require 'nxml-util)
39(require 'nxml-rap)
40(require 'nxml-outln)
afe27238
GM
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)
839087fe 45
8cd39fb3
MH
46;;; Customization
47
48(defgroup nxml nil
10545bd8 49 "New XML editing mode."
eba5b4dd 50 :group 'languages)
8cd39fb3 51
92bbfd0d 52(defgroup nxml-faces nil
8cd39fb3 53 "Faces for XML syntax highlighting."
ff90f4b0 54 :group 'nxml)
8cd39fb3 55
8cd39fb3 56(defcustom nxml-char-ref-display-glyph-flag t
65beee52 57 "Non-nil means display glyph following character reference.
92bbfd0d 58The glyph is displayed in face `nxml-glyph'. The hook
8cd39fb3
MH
59`nxml-glyph-set-hook' can be used to customize for which characters
60glyphs are displayed."
61 :group 'nxml
62 :type 'boolean)
63
8cd39fb3 64(defcustom nxml-sexp-element-flag nil
65beee52 65 "Non-nil means sexp commands treat an element as a single expression."
8cd39fb3
MH
66 :group 'nxml
67 :type 'boolean)
68
69(defcustom nxml-slash-auto-complete-flag nil
65beee52 70 "Non-nil means typing a slash automatically completes the end-tag.
8cd39fb3
MH
71This is used by `nxml-electric-slash'."
72 :group 'nxml
73 :type 'boolean)
74
75(defcustom nxml-child-indent 2
65beee52 76 "Indentation for the children of an element relative to the start-tag.
8cd39fb3
MH
77This only applies when the line or lines containing the start-tag contains
78nothing else other than that start-tag."
79 :group 'nxml
80 :type 'integer)
81
82(defcustom nxml-attribute-indent 4
65beee52 83 "Indentation for the attributes of an element relative to the start-tag.
10545bd8
JB
84This only applies when the first attribute of a tag starts a line.
85In other cases, the first attribute on one line is indented the same
86as the first attribute on the previous line."
8cd39fb3
MH
87 :group 'nxml
88 :type 'integer)
89
8cd39fb3 90(defcustom nxml-bind-meta-tab-to-complete-flag (not window-system)
65beee52 91 "Non-nil means bind M-TAB in `nxml-mode-map' to `nxml-complete'.
8cd39fb3
MH
92C-return will be bound to `nxml-complete' in any case.
93M-TAB gets swallowed by many window systems/managers, and
94`documentation' will show M-TAB rather than C-return as the
10545bd8 95binding for `nxml-complete' when both are bound. So it's better
8cd39fb3
MH
96to 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
65beee52 105 "Non-nil means prefer UTF-16 to UTF-8 when saving a buffer.
8cd39fb3
MH
106This is used only when a buffer does not contain an encoding declaration
107and when its current `buffer-file-coding-system' specifies neither UTF-16
108nor 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)
65beee52 114 "Non-nil means prefer little-endian to big-endian byte-order for UTF-16.
8cd39fb3
MH
115This is used only for saving a buffer; when reading the byte-order is
116auto-detected. It may be relevant both when there is no encoding declaration
117and when the encoding declaration specifies `UTF-16'."
118 :group 'nxml
119 :type 'boolean)
120
121(defcustom nxml-default-buffer-file-coding-system nil
65beee52 122 "Default value for `buffer-file-coding-system' for a buffer for a new file.
10545bd8 123A value of nil means use the default value of `buffer-file-coding-system' as normal.
8cd39fb3
MH
124A 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
65beee52 129 "Non-nil means automatically insert an XML declaration in a new file.
8cd39fb3
MH
130The XML declaration is inserted using `nxml-insert-xml-declaration'."
131 :group 'nxml
132 :type 'boolean)
133
92bbfd0d
JR
134(defface nxml-delimited-data
135 '((t (:inherit font-lock-doc-face)))
8cd39fb3 136 "Face used to highlight data enclosed between delimiters.
92bbfd0d
JR
137This is not used directly, but only via inheritance by other faces."
138 :group 'nxml-faces)
8cd39fb3 139
92bbfd0d
JR
140(defface nxml-name
141 '((t (:inherit font-lock-builtin-face)))
8cd39fb3
MH
142 "Face used to highlight various names.
143This includes element and attribute names, processing
144instruction targets and the CDATA keyword in a CDATA section.
145This is not used directly, but only via inheritance by other faces."
92bbfd0d 146 :group 'nxml-faces)
8cd39fb3 147
92bbfd0d
JR
148(defface nxml-ref
149 '((t (:inherit font-lock-constant-face)))
8cd39fb3
MH
150 "Face used to highlight character and entity references.
151This is not used directly, but only via inheritance by other faces."
92bbfd0d 152 :group 'nxml-faces)
8cd39fb3 153
92bbfd0d
JR
154(defface nxml-delimiter
155 nil
8cd39fb3
MH
156 "Face used to highlight delimiters.
157This is not used directly, but only via inheritance by other faces."
92bbfd0d 158 :group 'nxml-faces)
8cd39fb3 159
92bbfd0d 160(defface nxml-text
8cd39fb3
MH
161 nil
162 "Face used to highlight text."
92bbfd0d 163 :group 'nxml-faces)
8cd39fb3 164
92bbfd0d
JR
165(defface nxml-comment-content
166 '((t (:inherit font-lock-comment-face)))
8cd39fb3 167 "Face used to highlight the content of comments."
92bbfd0d 168 :group 'nxml-faces)
8cd39fb3 169
92bbfd0d
JR
170(defface nxml-comment-delimiter
171 '((t (:inherit font-lock-comment-delimiter-face)))
8cd39fb3 172 "Face used for the delimiters of comments, i.e <!-- and -->."
92bbfd0d 173 :group 'nxml-faces)
8cd39fb3 174
92bbfd0d
JR
175(defface nxml-processing-instruction-delimiter
176 '((t (:inherit nxml-delimiter)))
8cd39fb3 177 "Face used for the delimiters of processing instructions, i.e <? and ?>."
92bbfd0d 178 :group 'nxml-faces)
8cd39fb3 179
92bbfd0d
JR
180(defface nxml-processing-instruction-target
181 '((t (:inherit font-lock-keyword-face)))
8cd39fb3 182 "Face used for the target of processing instructions."
92bbfd0d 183 :group 'nxml-faces)
8cd39fb3 184
92bbfd0d
JR
185(defface nxml-processing-instruction-content
186 '((t (:inherit nxml-delimited-data)))
8cd39fb3 187 "Face used for the content of processing instructions."
92bbfd0d 188 :group 'nxml-faces)
8cd39fb3 189
92bbfd0d
JR
190(defface nxml-cdata-section-delimiter
191 '((t (:inherit nxml-delimiter)))
8cd39fb3 192 "Face used for the delimiters of CDATA sections, i.e <![, [, and ]]>."
92bbfd0d 193 :group 'nxml-faces)
8cd39fb3 194
92bbfd0d
JR
195(defface nxml-cdata-section-CDATA
196 '((t (:inherit nxml-name)))
8cd39fb3 197 "Face used for the CDATA keyword in CDATA sections."
92bbfd0d 198 :group 'nxml-faces)
8cd39fb3 199
92bbfd0d
JR
200(defface nxml-cdata-section-content
201 '((t (:inherit nxml-text)))
8cd39fb3 202 "Face used for the content of CDATA sections."
92bbfd0d 203 :group 'nxml-faces)
8cd39fb3 204
92bbfd0d
JR
205(defface nxml-char-ref-number
206 '((t (:inherit nxml-ref)))
8cd39fb3
MH
207 "Face used for the number in character references.
208This includes ths `x' in hex references."
92bbfd0d 209 :group 'nxml-faces)
8cd39fb3 210
92bbfd0d
JR
211(defface nxml-char-ref-delimiter
212 '((t (:inherit nxml-ref)))
8cd39fb3 213 "Face used for the delimiters of character references, i.e &# and ;."
92bbfd0d 214 :group 'nxml-faces)
8cd39fb3 215
92bbfd0d
JR
216(defface nxml-entity-ref-name
217 '((t (:inherit nxml-ref)))
8cd39fb3 218 "Face used for the entity name in general entity references."
92bbfd0d 219 :group 'nxml-faces)
8cd39fb3 220
92bbfd0d
JR
221(defface nxml-entity-ref-delimiter
222 '((t (:inherit nxml-ref)))
8cd39fb3 223 "Face used for the delimiters of entity references, i.e & and ;."
92bbfd0d 224 :group 'nxml-faces)
8cd39fb3 225
92bbfd0d
JR
226(defface nxml-tag-delimiter
227 '((t (:inherit nxml-delimiter)))
8cd39fb3 228 "Face used for the angle brackets delimiting tags.
92bbfd0d
JR
229`nxml-tag-slash' is used for slashes."
230 :group 'nxml-faces)
8cd39fb3 231
92bbfd0d
JR
232(defface nxml-tag-slash
233 '((t (:inherit nxml-tag-delimiter)))
8cd39fb3 234 "Face used for slashes in tags, both in end-tags and empty-elements."
92bbfd0d 235 :group 'nxml-faces)
8cd39fb3 236
92bbfd0d
JR
237(defface nxml-element-prefix
238 '((t (:inherit nxml-name)))
8cd39fb3 239 "Face used for the prefix of elements."
92bbfd0d 240 :group 'nxml-faces)
8cd39fb3 241
92bbfd0d
JR
242(defface nxml-element-colon
243 nil
8cd39fb3 244 "Face used for the colon in element names."
92bbfd0d 245 :group 'nxml-faces)
8cd39fb3 246
92bbfd0d
JR
247(defface nxml-element-local-name
248 '((t (:inherit font-lock-function-name-face)))
8cd39fb3 249 "Face used for the local name of elements."
92bbfd0d 250 :group 'nxml-faces)
8cd39fb3 251
92bbfd0d
JR
252(defface nxml-attribute-prefix
253 '((t (:inherit nxml-name)))
8cd39fb3 254 "Face used for the prefix of attributes."
92bbfd0d 255 :group 'nxml-faces)
8cd39fb3 256
92bbfd0d
JR
257(defface nxml-attribute-colon
258 '((t (:inherit nxml-delimiter)))
8cd39fb3 259 "Face used for the colon in attribute names."
92bbfd0d 260 :group 'nxml-faces)
10545bd8 261
92bbfd0d
JR
262(defface nxml-attribute-local-name
263 '((t (:inherit font-lock-variable-name-face)))
8cd39fb3 264 "Face used for the local name of attributes."
92bbfd0d 265 :group 'nxml-faces)
8cd39fb3 266
92bbfd0d
JR
267(defface nxml-namespace-attribute-xmlns
268 '((t (:inherit nxml-attribute-prefix)))
8cd39fb3 269 "Face used for `xmlns' in namespace attributes."
92bbfd0d 270 :group 'nxml-faces)
8cd39fb3 271
92bbfd0d
JR
272(defface nxml-namespace-attribute-colon
273 '((t (:inherit nxml-attribute-colon)))
8cd39fb3 274 "Face used for the colon in namespace attributes."
92bbfd0d 275 :group 'nxml-faces)
8cd39fb3 276
92bbfd0d
JR
277(defface nxml-namespace-attribute-prefix
278 '((t (:inherit nxml-attribute-local-name)))
8cd39fb3 279 "Face used for the prefix declared in namespace attributes."
92bbfd0d 280 :group 'nxml-faces)
8cd39fb3 281
92bbfd0d
JR
282(defface nxml-attribute-value
283 '((t (:inherit font-lock-string-face)))
8cd39fb3 284 "Face used for the value of attributes."
92bbfd0d 285 :group 'nxml-faces)
8cd39fb3 286
92bbfd0d
JR
287(defface nxml-attribute-value-delimiter
288 '((t (:inherit nxml-attribute-value)))
8cd39fb3 289 "Face used for the delimiters of attribute values."
92bbfd0d 290 :group 'nxml-faces)
8cd39fb3 291
92bbfd0d
JR
292(defface nxml-namespace-attribute-value
293 '((t (:inherit nxml-attribute-value)))
8cd39fb3 294 "Face used for the value of namespace attributes."
92bbfd0d 295 :group 'nxml-faces)
8cd39fb3 296
92bbfd0d
JR
297(defface nxml-namespace-attribute-value-delimiter
298 '((t (:inherit nxml-attribute-value-delimiter)))
8cd39fb3 299 "Face used for the delimiters of namespace attribute values."
92bbfd0d 300 :group 'nxml-faces)
8cd39fb3 301
92bbfd0d
JR
302(defface nxml-prolog-literal-delimiter
303 '((t (:inherit nxml-delimited-data)))
8cd39fb3 304 "Face used for the delimiters of literals in the prolog."
92bbfd0d 305 :group 'nxml-faces)
8cd39fb3 306
92bbfd0d
JR
307(defface nxml-prolog-literal-content
308 '((t (:inherit nxml-delimited-data)))
8cd39fb3 309 "Face used for the content of literals in the prolog."
92bbfd0d 310 :group 'nxml-faces)
8cd39fb3 311
92bbfd0d
JR
312(defface nxml-prolog-keyword
313 '((t (:inherit font-lock-keyword-face)))
8cd39fb3 314 "Face used for keywords in the prolog."
92bbfd0d 315 :group 'nxml-faces)
8cd39fb3 316
92bbfd0d
JR
317(defface nxml-markup-declaration-delimiter
318 '((t (:inherit nxml-delimiter)))
8cd39fb3
MH
319 "Face used for the delimiters of markup declarations in the prolog.
320The delimiters are <! and >."
92bbfd0d 321 :group 'nxml-faces)
8cd39fb3 322
92bbfd0d
JR
323(defface nxml-hash
324 '((t (:inherit nxml-name)))
8cd39fb3 325 "Face used for # before a name in the prolog."
92bbfd0d 326 :group 'nxml-faces)
8cd39fb3 327
92bbfd0d 328(defface nxml-glyph
8cd39fb3
MH
329 '((((type x))
330 (:family
331 "misc-fixed"
332 :background
333 "light grey"
334 :foreground
335 "black"
336 :weight
10545bd8 337 normal
8cd39fb3
MH
338 :slant
339 normal))
340 (t
341 (:background
342 "light grey"
343 :foreground
344 "black"
345 :weight
10545bd8 346 normal
8cd39fb3
MH
347 :slant
348 normal)))
349 "Face used for glyph for char references."
92bbfd0d 350 :group 'nxml-faces)
8cd39fb3
MH
351
352;;; Global variables
353
f7ca27a1
SS
354(defvar nxml-parent-document nil
355 "The parent document for a part of a modular document.
356Use `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
8cd39fb3
MH
360(defvar nxml-prolog-regions nil
361 "List of regions in the prolog to be fontified.
362See 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.
10545bd8 367It is nil if the buffer changed since the last fontification.")
8cd39fb3
MH
368(make-variable-buffer-local 'nxml-last-fontify-end)
369
370(defvar nxml-degraded nil
371 "Non-nil if currently operating in degraded mode.
372Degraded mode is enabled when an internal error is encountered in the
373fontification or after-change functions.")
374(make-variable-buffer-local 'nxml-degraded)
375
376(defvar nxml-completion-hook nil
377 "Hook run by `nxml-complete'.
378This hook is run until success.")
379
380(defvar nxml-in-mixed-content-hook nil
381 "Hook to determine whether point is in mixed content.
382The hook is called without arguments. It should return nil if it is
383definitely not mixed; non-nil otherwise. The hook will be run until
384one 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.
394The extra information consists of a tooltip with the character name
395and, if `nxml-char-ref-display-glyph-flag' is non-nil, a glyph
396corresponding to the referenced character following the character
397reference.")
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)
8a79905d 410 (define-key map "\C-c]" 'nxml-finish-element)
f8e63691 411 (define-key map "\C-c/" 'nxml-finish-element)
8cd39fb3
MH
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)
10545bd8 422 (define-key map [C-return] 'nxml-complete)
8cd39fb3
MH
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
e8ec402f
MO
428(defvar nxml-font-lock-keywords
429 '(nxml-fontify-matcher)
430 "Default font lock keywords for nxml-mode.")
431
8cd39fb3
MH
432(defsubst nxml-set-face (start end face)
433 (when (and face (< start end))
e8ec402f 434 (font-lock-append-text-property start end 'face face)))
8cd39fb3 435
f7ca27a1
SS
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
8cd39fb3 468;;;###autoload
f7ca27a1 469(define-derived-mode nxml-mode text-mode "nXML"
8cd39fb3
MH
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
8cd39fb3
MH
475\\[nxml-finish-element] finishes the current element by inserting an end-tag.
476C-c C-i closes a start-tag with `>' and then inserts a balancing end-tag
10545bd8 477leaving point between the start-tag and end-tag.
8cd39fb3
MH
478\\[nxml-balanced-close-start-tag-block] is similar but for block rather than inline elements:
479the start-tag, point, and end-tag are all left on separate lines.
480If `nxml-slash-auto-complete-flag' is non-nil, then inserting a `</'
481automatically 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
486to choose a tag to put around the word preceding point.
487
488Sections of the document can be displayed in outline form. The
489variable `nxml-section-element-name-regexp' controls when an element
490is recognized as a section. The same key sequences that change
491visibility in outline mode are used except that they start with C-c C-o
492instead of C-c.
493
494Validation is provided by the related minor-mode `rng-validate-mode'.
495This also makes completion schema- and context- sensitive. Element
496names, attribute names, attribute values and namespace URIs can all be
10545bd8
JB
497completed. By default, `rng-validate-mode' is automatically enabled.
498You can toggle it using \\[rng-validate-mode] or change the default by
6bd1e223 499customizing `rng-nxml-auto-validate-flag'.
8cd39fb3
MH
500
501\\[indent-for-tab-command] indents the current line appropriately.
502This can be customized using the variable `nxml-child-indent'
503and the variable `nxml-attribute-indent'.
504
505\\[nxml-insert-named-char] inserts a character reference using
10545bd8
JB
506the character's name (by default, the Unicode name).
507\\[universal-argument] \\[nxml-insert-named-char] inserts the character directly.
8cd39fb3
MH
508
509The Emacs commands that normally operate on balanced expressions will
510operate on XML markup items. Thus \\[forward-sexp] will move forward
511across one markup item; \\[backward-sexp] will move backward across
512one markup item; \\[kill-sexp] will kill the following markup item;
513\\[mark-sexp] will mark the following markup item. By default, each
514tag each treated as a single markup item; to make the complete element
515be 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
521Many aspects this mode can be customized using
522\\[customize-group] nxml RET."
f7ca27a1 523 ;; (kill-all-local-variables)
7d7d1bb6 524 (set (make-local-variable 'mode-line-process) '((nxml-degraded "/degraded")))
8cd39fb3
MH
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
e8ec402f 556 (nxml-clear-inside (point-min) (point-max))
8cd39fb3
MH
557 (nxml-with-invisible-motion
558 (nxml-scan-prolog)))))
8cd39fb3 559 (add-hook 'after-change-functions 'nxml-after-change nil t)
44e8d8d3
JR
560 (add-hook 'change-major-mode-hook 'nxml-cleanup nil t)
561
4d42bcc9
JR
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)
8cd39fb3
MH
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)))
e8ec402f
MO
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
6bd1e223 586 (rng-nxml-mode-init)
f7ca27a1 587 (nxml-enable-unicode-char-name-sets))
8cd39fb3 588
44e8d8d3
JR
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
8cd39fb3
MH
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
7d7d1bb6 615 (nxml-clear-inside (point-min) (point-max))))))
8cd39fb3
MH
616
617;;; Change management
618
e8ec402f
MO
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
8cd39fb3 627(defun nxml-after-change (start end pre-change-length)
e8ec402f
MO
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)))))))))
8cd39fb3
MH
641
642(defun nxml-after-change1 (start end pre-change-length)
10545bd8
JB
643 "After-change bookkeeping.
644Returns a cons cell containing a possibly-enlarged change region.
645You must call `nxml-extend-region' on this expanded region to obtain
646the full extent of the area needing refontification.
e8ec402f
MO
647
648For bookkeeping, call this function even when fontification is
649disabled."
8cd39fb3
MH
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))
e8ec402f 655 ;; If the prolog might have changed, rescan the prolog
8cd39fb3 656 (when (<= start
e8ec402f
MO
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.
8cd39fb3 660 (+ nxml-prolog-end 2))
e8ec402f
MO
661 ;; end must be extended to at least the end of the old prolog in
662 ;; case the new prolog is shorter
8cd39fb3
MH
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)))))
e8ec402f
MO
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
8cd39fb3
MH
682;;; Encodings
683
684(defun nxml-insert-xml-declaration ()
685 "Insert an XML declaration at the beginning of buffer.
686The 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
10545bd8 834(defun nxml-choose-utf-coding-system ()
8cd39fb3
MH
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
e8ec402f
MO
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.
10545bd8 874Called with `font-lock-beg' and `font-lock-end' dynamically bound."
e8ec402f
MO
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)
116bd1ee
CY
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))))
e8ec402f
MO
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)
8cd39fb3
MH
960
961(defun nxml-fontify-prolog ()
962 "Fontify the prolog.
963The buffer is assumed to be prepared for fontification.
964This does not set the fontified property, but it does clear
965faces appropriately."
966 (let ((regions nxml-prolog-regions))
8cd39fb3
MH
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
8cd39fb3
MH
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
92bbfd0d
JR
980 '([nil 1 nxml-tag-delimiter]
981 [-1 nil nxml-tag-delimiter]
8cd39fb3
MH
982 (element-qname . 1)
983 attributes))
984
985(put 'partial-start-tag
986 'nxml-fontify-rule
92bbfd0d 987 '([nil 1 nxml-tag-delimiter]
8cd39fb3
MH
988 (element-qname . 1)
989 attributes))
990
991(put 'end-tag
992 'nxml-fontify-rule
92bbfd0d
JR
993 '([nil 1 nxml-tag-delimiter]
994 [1 2 nxml-tag-slash]
995 [-1 nil nxml-tag-delimiter]
8cd39fb3
MH
996 (element-qname . 2)))
997
998(put 'partial-end-tag
999 'nxml-fontify-rule
92bbfd0d
JR
1000 '([nil 1 nxml-tag-delimiter]
1001 [1 2 nxml-tag-slash]
8cd39fb3
MH
1002 (element-qname . 2)))
1003
1004(put 'empty-element
1005 'nxml-fontify-rule
92bbfd0d
JR
1006 '([nil 1 nxml-tag-delimiter]
1007 [-2 -1 nxml-tag-slash]
1008 [-1 nil nxml-tag-delimiter]
8cd39fb3
MH
1009 (element-qname . 1)
1010 attributes))
1011
1012(put 'partial-empty-element
1013 'nxml-fontify-rule
92bbfd0d
JR
1014 '([nil 1 nxml-tag-delimiter]
1015 [-1 nil nxml-tag-slash]
8cd39fb3
MH
1016 (element-qname . 1)
1017 attributes))
1018
1019(put 'char-ref
1020 'nxml-fontify-rule
92bbfd0d
JR
1021 '([nil 2 nxml-char-ref-delimiter]
1022 [2 -1 nxml-char-ref-number]
1023 [-1 nil nxml-char-ref-delimiter]
8cd39fb3
MH
1024 char-ref))
1025
1026(put 'entity-ref
1027 'nxml-fontify-rule
92bbfd0d
JR
1028 '([nil 1 nxml-entity-ref-delimiter]
1029 [1 -1 nxml-entity-ref-name]
1030 [-1 nil nxml-entity-ref-delimiter]))
8cd39fb3
MH
1031
1032(put 'comment
1033 'nxml-fontify-rule
92bbfd0d
JR
1034 '([nil 4 nxml-comment-delimiter]
1035 [4 -3 nxml-comment-content]
1036 [-3 nil nxml-comment-delimiter]))
8cd39fb3
MH
1037
1038(put 'processing-instruction
1039 'nxml-fontify-rule
92bbfd0d
JR
1040 '([nil 2 nxml-processing-instruction-delimiter]
1041 [-2 nil nxml-processing-instruction-delimiter]
8cd39fb3
MH
1042 processing-instruction-content))
1043
1044(put 'cdata-section
1045 'nxml-fontify-rule
92bbfd0d
JR
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]))
8cd39fb3
MH
1051
1052(put 'data
1053 'nxml-fontify-rule
92bbfd0d 1054 '([nil nil nxml-text]))
8cd39fb3
MH
1055
1056;; Prolog region types in list returned by xmltok-forward-prolog.
1057
1058(put 'xml-declaration
1059 'nxml-fontify-rule
92bbfd0d
JR
1060 '([nil 2 nxml-processing-instruction-delimiter]
1061 [2 5 nxml-processing-instruction-target]
1062 [-2 nil nxml-processing-instruction-delimiter]))
8cd39fb3
MH
1063
1064(put 'xml-declaration-attribute-name
1065 'nxml-fontify-rule
92bbfd0d 1066 '([nil nil nxml-attribute-local-name]))
8cd39fb3
MH
1067
1068(put 'xml-declaration-attribute-value
1069 'nxml-fontify-rule
92bbfd0d
JR
1070 '([nil 1 nxml-attribute-value-delimiter]
1071 [1 -1 nxml-attribute-value]
1072 [-1 nil nxml-attribute-value-delimiter]))
8cd39fb3
MH
1073
1074(put 'processing-instruction-left
1075 'nxml-fontify-rule
92bbfd0d
JR
1076 '([nil 2 nxml-processing-instruction-delimiter]
1077 [2 nil nxml-processing-instruction-target]))
8cd39fb3
MH
1078
1079(put 'processing-instruction-right
1080 'nxml-fontify-rule
92bbfd0d
JR
1081 '([nil -2 nxml-processing-instruction-content]
1082 [-2 nil nxml-processing-instruction-delimiter]))
8cd39fb3
MH
1083
1084(put 'literal
1085 'nxml-fontify-rule
92bbfd0d
JR
1086 '([nil 1 nxml-prolog-literal-delimiter]
1087 [1 -1 nxml-prolog-literal-content]
1088 [-1 nil nxml-prolog-literal-delimiter]))
8cd39fb3
MH
1089
1090(put 'keyword
1091 'nxml-fontify-rule
92bbfd0d 1092 '([nil nil nxml-prolog-keyword]))
8cd39fb3
MH
1093
1094(put 'markup-declaration-open
1095 'nxml-fontify-rule
92bbfd0d
JR
1096 '([0 2 nxml-markup-declaration-delimiter]
1097 [2 nil nxml-prolog-keyword]))
8cd39fb3
MH
1098
1099(put 'markup-declaration-close
1100 'nxml-fontify-rule
92bbfd0d 1101 '([nil nil nxml-markup-declaration-delimiter]))
8cd39fb3
MH
1102
1103(put 'internal-subset-open
1104 'nxml-fontify-rule
92bbfd0d 1105 '([nil nil nxml-markup-declaration-delimiter]))
8cd39fb3
MH
1106
1107(put 'internal-subset-close
1108 'nxml-fontify-rule
92bbfd0d
JR
1109 '([nil 1 nxml-markup-declaration-delimiter]
1110 [-1 nil nxml-markup-declaration-delimiter]))
8cd39fb3
MH
1111
1112(put 'hash-name
1113 'nxml-fontify-rule
92bbfd0d
JR
1114 '([nil 1 nxml-hash]
1115 [1 nil nxml-prolog-keyword]))
8cd39fb3
MH
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
92bbfd0d
JR
1140 'nxml-element-prefix
1141 'nxml-element-colon
1142 'nxml-element-local-name)))
8cd39fb3
MH
1143 ((eq action 'attributes)
1144 (nxml-fontify-attributes))
1145 ((eq action 'processing-instruction-content)
1146 (nxml-set-face (+ start 2)
1147 xmltok-name-end
92bbfd0d 1148 'nxml-processing-instruction-target)
8cd39fb3
MH
1149 (nxml-set-face (save-excursion
1150 (goto-char xmltok-name-end)
1151 (skip-chars-forward " \t\r\n")
1152 (point))
1153 (- end 2)
92bbfd0d 1154 'nxml-processing-instruction-content))
8cd39fb3
MH
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)
92bbfd0d
JR
1177 'nxml-namespace-attribute-xmlns
1178 'nxml-namespace-attribute-colon
1179 'nxml-namespace-attribute-prefix
1180 'nxml-namespace-attribute-xmlns)
8cd39fb3
MH
1181 (nxml-fontify-qname (xmltok-attribute-name-start att)
1182 (xmltok-attribute-name-colon att)
1183 (xmltok-attribute-name-end att)
92bbfd0d
JR
1184 'nxml-attribute-prefix
1185 'nxml-attribute-colon
1186 'nxml-attribute-local-name))
8cd39fb3
MH
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
92bbfd0d
JR
1191 'nxml-namespace-attribute-value-delimiter
1192 'nxml-attribute-value-delimiter))
8cd39fb3 1193 (value-face (if namespace-declaration
92bbfd0d
JR
1194 'nxml-namespace-attribute-value
1195 'nxml-attribute-value)))
8cd39fb3
MH
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
1229With a prefix ARG, do nothing other than insert the slash.
1230
1231Otherwise, if `nxml-slash-auto-complete-flag' is non-nil, insert the
1232rest of the end-tag or empty-element if the slash is potentially part
1233of an end-tag or the close of an empty-element.
1234
1235If the slash is part of an end-tag that is the first non-whitespace
1236on 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.
1273Point is left between the start-tag and the end-tag.
1274If there is nothing but whitespace before the `<' that opens the
1275start-tag, then put point on a blank line, and put the end-tag on
1276another 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.
1282Point is left between the start-tag and the end-tag.
1283No 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))
ba07c9ad
CY
1289 (pos (1+ (point)))
1290 (token-start xmltok-start))
8cd39fb3
MH
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"))
ba07c9ad 1297 ;; Note that this insertion changes xmltok-start.
8cd39fb3
MH
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)
ba07c9ad 1304 (goto-char token-start)
8cd39fb3 1305 (back-to-indentation)
ba07c9ad 1306 (if (= (point) token-start)
8cd39fb3 1307 (let ((indent (current-column)))
ba07c9ad
CY
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)))
8cd39fb3 1314 (goto-char pos)))))
10545bd8 1315
8cd39fb3
MH
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.
1326Point is left after the newly inserted start-tag. When repeated,
1327split immediately before the previously inserted start-tag and leave
1328point 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.
1340The start-tag is inserted if STARTP is non-nil. Return the position
1341of 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."
b263a4c4
SM
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)))))
8cd39fb3
MH
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.
1428When the line containing point ends with an end-tag and does not start
1429in the middle of a token, return the indent of the line containing the
1430matching start-tag, if there is one and it occurs at the beginning of
1431its 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'.
1521Return the merged indent context type. An indent context type is
10545bd8
JB
1522either nil or one of the symbols `start-tag', `end-tag', `markup',
1523`comment', `mixed'."
8cd39fb3
MH
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.
1542POS is the position of the first non-whitespace character of the line.
1543This 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.
1565Also for a line that starts inside an empty element.
1566POS is the position of the first non-whitespace character of the line.
1567This 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.
1599Otherwise return nil. START and END are the positions of the start
1600and end of the attribute value containing POS. This expects the
1601xmltok-* 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))
10545bd8 1615
8cd39fb3
MH
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.
1618OPEN-DELIM and CLOSE-DELIM are strings giving the opening and closing
1619delimiters. POS is the position of the first non-whitespace character
1620of 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
1643Inserts as many characters as can be completed. However, if not even
1644one character can be completed, then a buffer with the possibilities
1645is popped up and the symbol is read from the minibuffer with
10545bd8 1646completion. If the symbol is complete, then any characters that must
8cd39fb3
MH
1647follow the symbol are also inserted.
1648
1649The name space used for completion and what is treated as a symbol
1650depends on the context. The contexts in which completion is performed
1651depend 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.
1662With ARG, do it that many times. Negative arg -N means
1663move backward across N balanced expressions.
1664This is the equivalent of `forward-sexp' for XML.
1665
1666An element contains as items strings with no markup, tags, processing
1667instructions, comments, CDATA sections, entity references and
10545bd8 1668characters references. However, if the variable
8cd39fb3
MH
1669`nxml-sexp-element-flag' is non-nil, then an element is treated as a
1670single markup item. A start-tag contains an element name followed by
10545bd8
JB
1671one or more attributes. An end-tag contains just an element name.
1672An attribute value literals contains strings with no markup, entity
8cd39fb3
MH
1673references and character references. A processing instruction
1674consists of a target and a content string. A comment or a CDATA
1675section contains a single string. An entity reference contains a
1676single 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.
1947With ARG, do this that many times.
1948Negative 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.
1993With ARG, do it that many times.
1994Negative 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.
2012With ARG, do it that many times.
2013Negative 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.
2042The 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.
2072Return 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.
2088Return 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.
2137Return nil if the current token does not contain the paragraph end.
2138Only characters after OFFSET from the start of the token are eligible.
2139HAD-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)))))
10545bd8 2172 ((and (memq xmltok-type '(start-tag
8cd39fb3
MH
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
10545bd8 2188 (or (search-forward "\n" nil t)
8cd39fb3
MH
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.
2193Return nil if the current token does not contain the paragraph start.
2194Only characters before OFFSET from the end of the token are eligible.
2195HAD-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))))
10545bd8 2228 ((and (memq xmltok-type '(start-tag
8cd39fb3
MH
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))
10545bd8 2236 (or (search-forward "\n" nil t)
8cd39fb3
MH
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.
2264Point must be after an end-tag or before a start-tag.
2265ENDP 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))
10545bd8 2376
8cd39fb3
MH
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.
2392This attempts to find a tag to put around the word before point based
2393on the contents of the current buffer. The end-tag will be inserted at
2394point. The start-tag will be inserted at or before the beginning of
2395the word before point; the contents of the current buffer is used to
2396decide where.
2397
10545bd8 2398It works in a similar way to \\[dabbrev-expand]. It searches first
8cd39fb3
MH
2399backwards from point, then forwards from point for an element whose
2400content is a string which matches the contents of the buffer before
10545bd8 2401point and which includes at least the word before point. It then
8cd39fb3
MH
2402copies the start- and end-tags from that element and uses them to
2403surround the matching string before point.
2404
2405Repeating \\[nxml-dynamic-markup-word] immediately after successful
2406\\[nxml-dynamic-markup-word] removes the previously inserted markup
2407and 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))))))))))
10545bd8 2507
8cd39fb3
MH
2508
2509;;; Character names
2510
b85b6604 2511(defvar nxml-char-name-ignore-case t)
8cd39fb3
MH
2512
2513(defvar nxml-char-name-alist nil
2514 "Alist of character names.
2515Each member of the list has the form (NAME CODE . NAMESET),
2516where NAME is a string naming a character, NAMESET is a symbol
2517identifying a set of names and CODE is an integer specifying the
2518Unicode scalar value of the named character.
2519The NAME will only be used for completion if NAMESET has
2520a non-nil `nxml-char-name-set-enabled' property.
2521If NAMESET does does not have `nxml-char-name-set-defined' property,
2522then it must have a `nxml-char-name-set-file' property and `load'
2523will be applied to the value of this property if the nameset
2524is enabled.")
2525
2526(defvar nxml-char-name-table (make-hash-table :test 'eq)
2527 "Hash table for mapping char codes to names.
2528Each key is a Unicode scalar value.
2529Each value is a list of pairs of the form (NAMESET . NAME),
2530where NAMESET is a symbol identifying a set of names,
2531and 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
10545bd8 2536(defun nxml-enable-char-name-set (nameset)
8cd39fb3
MH
2537 (put nameset 'nxml-char-name-set-enabled t))
2538
10545bd8 2539(defun nxml-disable-char-name-set (nameset)
8cd39fb3
MH
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.
2553NAMESET is a symbol identifying the set.
10545bd8
JB
2554ALIST is a list where each member has the form (NAME CODE),
2555where NAME is a string naming a character and code is an
2556integer giving the Unicode scalar value of the character."
8cd39fb3
MH
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)
e290ff07 2575 (mapc 'nxml-maybe-load-char-name-set nxml-autoload-char-name-set-list)
8cd39fb3
MH
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.
2588The name is read from the minibuffer.
2589Normally, inserts the character as a numeric character reference.
2590With a prefix argument, inserts the character directly."
2591 (interactive "*P")
e290ff07 2592 (mapc 'nxml-maybe-load-char-name-set nxml-autoload-char-name-set-list)
8cd39fb3
MH
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))))))
10545bd8 2615
8cd39fb3
MH
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)
10545bd8 2623 "Toggle the display of extra information for character references."
8cd39fb3
MH
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)
e8ec402f 2630 (font-lock-fontify-buffer))))
8cd39fb3
MH
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
92bbfd0d 2638 (nxml-glyph-display-string n 'nxml-glyph)))
8cd39fb3
MH
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
92bbfd0d 2648 (propertize glyph-string 'face 'nxml-glyph)))))))
8cd39fb3
MH
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
8cd39fb3
MH
2657
2658(defun nxml-start-delimiter-length (type)
2659 (or (get type 'nxml-start-delimiter-length)
2660 0))
10545bd8 2661
8cd39fb3
MH
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))
10545bd8 2674
8cd39fb3
MH
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
e477ca84
CY
2693;;;###autoload
2694(defalias 'xml-mode 'nxml-mode)
2695
8cd39fb3
MH
2696(provide 'nxml-mode)
2697
2698;;; nxml-mode.el ends here