;;; nxml-mode.el --- a new XML mode ;; Copyright (C) 2003, 2004 Free Software Foundation, Inc. ;; Author: James Clark ;; Keywords: XML ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License as ;; published by the Free Software Foundation; either version 2 of ;; the License, or (at your option) any later version. ;; This program is distributed in the hope that it will be ;; useful, but WITHOUT ANY WARRANTY; without even the implied ;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR ;; PURPOSE. See the GNU General Public License for more details. ;; You should have received a copy of the GNU General Public ;; License along with this program; if not, write to the Free ;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, ;; MA 02111-1307 USA ;;; Commentary: ;; To use this include rng-auto.el in your .emacs. ;; See nxml-rap.el for description of parsing strategy. ;; The font locking here is independent of font-lock.el. We want to ;; do more sophisticated handling of changes and we want to use the ;; same xmltok rather than regexps for parsing so that we parse ;; consistently and correctly. ;;; Code: (when (featurep 'mucs) (error "nxml-mode is not compatible with Mule-UCS")) (require 'xmltok) (require 'nxml-enc) (require 'nxml-glyph) (require 'nxml-util) (require 'nxml-rap) (require 'nxml-outln) ;;; Customization (defgroup nxml nil "New XML editing mode" :group 'languages :group 'wp) (defgroup nxml-highlighting-faces nil "Faces for XML syntax highlighting." :group 'nxml :group 'font-lock-highlighting-faces) (defcustom nxml-syntax-highlight-flag t "*Non-nil means nxml-mode should perform syntax highlighting." :group 'nxml :type 'boolean) (defcustom nxml-char-ref-display-glyph-flag t "*Non-nil means display glyph following character reference. The glyph is displayed in `nxml-glyph-face'. The hook `nxml-glyph-set-hook' can be used to customize for which characters glyphs are displayed." :group 'nxml :type 'boolean) (defcustom nxml-mode-hook nil "Hook run by command `nxml-mode'." :group 'nxml :type 'hook) (defcustom nxml-sexp-element-flag nil "*Non-nil means sexp commands treat an element as a single expression." :group 'nxml :type 'boolean) (defcustom nxml-slash-auto-complete-flag nil "*Non-nil means typing a slash automatically completes the end-tag. This is used by `nxml-electric-slash'." :group 'nxml :type 'boolean) (defcustom nxml-child-indent 2 "*Indentation for the children of an element relative to the start-tag. This only applies when the line or lines containing the start-tag contains nothing else other than that start-tag." :group 'nxml :type 'integer) (defcustom nxml-attribute-indent 4 "*Indentation for the attributes of an element relative to the start-tag. This only applies when the first attribute of a tag starts a line. In other cases, the first attribute on one line is indented the same as the first attribute on the previous line." :group 'nxml :type 'integer) (defvar nxml-fontify-chunk-size 500) (defcustom nxml-bind-meta-tab-to-complete-flag (not window-system) "*Non-nil means bind M-TAB in `nxml-mode-map' to `nxml-complete'. C-return will be bound to `nxml-complete' in any case. M-TAB gets swallowed by many window systems/managers, and `documentation' will show M-TAB rather than C-return as the binding `rng-complete' when both are bound. So it's better to bind M-TAB only when it will work." :group 'nxml :set (lambda (sym flag) (set-default sym flag) (when (and (boundp 'nxml-mode-map) nxml-mode-map) (define-key nxml-mode-map "\M-\t" (and flag 'nxml-complete)))) :type 'boolean) (defcustom nxml-prefer-utf-16-to-utf-8-flag nil "*Non-nil means prefer UTF-16 to UTF-8 when saving a buffer. This is used only when a buffer does not contain an encoding declaration and when its current `buffer-file-coding-system' specifies neither UTF-16 nor UTF-8." :group 'nxml :type 'boolean) (defcustom nxml-prefer-utf-16-little-to-big-endian-flag (eq system-type 'windows-nt) "*Non-nil means prefer little-endian to big-endian byte-order for UTF-16. This is used only for saving a buffer; when reading the byte-order is auto-detected. It may be relevant both when there is no encoding declaration and when the encoding declaration specifies `UTF-16'." :group 'nxml :type 'boolean) (defcustom nxml-default-buffer-file-coding-system nil "*Default value for `buffer-file-coding-system' for a buffer for a new file. Nil means use the default value of `buffer-file-coding-system' as normal. A buffer's `buffer-file-coding-system' affects what \\[nxml-insert-xml-declaration] inserts." :group 'nxml :type 'coding-system) (defcustom nxml-auto-insert-xml-declaration-flag nil "*Non-nil means automatically insert an XML declaration in a new file. The XML declaration is inserted using `nxml-insert-xml-declaration'." :group 'nxml :type 'boolean) ;; The following are the colors we use with a light background. ;; The two blues have the same hue but contrasting saturation/value. ;; The hue of the green is 120 degrees different from that of the ;; blue. The red used for highlighting errors is 120 degrees ;; different again. We use the light blue only for refs and ;; delimiters, since these are short (long stretches in a light color ;; would be too hard to read). The dark blue is closest to black ;; (which we use by default for text), so we use it for attribute ;; values, which are similar to text. (defconst nxml-light-blue-color "#9292C9") ; hue 240 (defconst nxml-dark-blue-color "#3A3A7B") ; hue 240 (defconst nxml-green-color "#257A25") ; hue 120 ;; Similar principles apply with a dark background. However, ;; we switch green and blue, because darker blues are very hard to ;; read (for me anyway) on a dark background. (defconst nxml-sky-blue-color "#ACACFC") ; hue 240 (defconst nxml-dark-green-color "#00AD00") ; hue 120 (defconst nxml-light-green-color "#70F170") ; hue 120 (defface nxml-delimited-data-face `((((class color) (background light)) (:foreground ,nxml-dark-blue-color)) (((class color) (background dark)) (:foreground ,nxml-light-green-color))) "Face used to highlight data enclosed between delimiters. By default, this is inherited by `nxml-attribute-value-face' and `nxml-processing-instruction-content-face'." :group 'nxml-highlighting-faces) (defface nxml-name-face `((((class color) (background light)) (:foreground ,nxml-green-color)) (((class color) (background dark)) (:foreground ,nxml-sky-blue-color))) "Face used to highlight various names. This includes element and attribute names, processing instruction targets and the CDATA keyword in a CDATA section. This is not used directly, but only via inheritance by other faces." :group 'nxml-highlighting-faces) (defface nxml-ref-face `((((class color) (background light)) (:foreground ,nxml-light-blue-color)) (((class color) (background dark)) (:foreground ,nxml-dark-green-color))) "Face used to highlight character and entity references. This is not used directly, but only via inheritance by other faces." :group 'nxml-highlighting-faces) (defface nxml-delimiter-face `((((class color) (background light)) (:foreground ,nxml-light-blue-color)) (((class color) (background dark)) (:foreground ,nxml-dark-green-color)) (t (:bold t))) "Face used to highlight delimiters. This is not used directly, but only via inheritance by other faces." :group 'nxml-highlighting-faces) (defface nxml-text-face nil "Face used to highlight text." :group 'nxml-highlighting-faces) (defface nxml-comment-content-face '((t (:italic t))) "Face used to highlight the content of comments." :group 'nxml-highlighting-faces) (defface nxml-comment-delimiter-face '((t (:inherit nxml-delimiter-face))) "Face used for the delimiters of comments, i.e ." :group 'nxml-highlighting-faces) (defface nxml-processing-instruction-delimiter-face '((t (:inherit nxml-delimiter-face))) "Face used for the delimiters of processing instructions, i.e ." :group 'nxml-highlighting-faces) (defface nxml-processing-instruction-target-face '((t (:inherit nxml-name-face))) "Face used for the target of processing instructions." :group 'nxml-highlighting-faces) (defface nxml-processing-instruction-content-face '((t (:inherit nxml-delimited-data-face))) "Face used for the content of processing instructions." :group 'nxml-highlighting-faces) (defface nxml-cdata-section-delimiter-face '((t (:inherit nxml-delimiter-face))) "Face used for the delimiters of CDATA sections, i.e ." :group 'nxml-highlighting-faces) (defface nxml-cdata-section-CDATA-face '((t (:inherit nxml-name-face))) "Face used for the CDATA keyword in CDATA sections." :group 'nxml-highlighting-faces) (defface nxml-cdata-section-content-face '((t (:inherit nxml-text-face))) "Face used for the content of CDATA sections." :group 'nxml-highlighting-faces) (defface nxml-char-ref-number-face '((t (:inherit nxml-ref-face))) "Face used for the number in character references. This includes ths `x' in hex references." :group 'nxml-highlighting-faces) (defface nxml-char-ref-delimiter-face '((t (:inherit nxml-ref-face))) "Face used for the delimiters of character references, i.e &# and ;." :group 'nxml-highlighting-faces) (defface nxml-entity-ref-name-face '((t (:inherit nxml-ref-face))) "Face used for the entity name in general entity references." :group 'nxml-highlighting-faces) (defface nxml-entity-ref-delimiter-face '((t (:inherit nxml-ref-face))) "Face used for the delimiters of entity references, i.e & and ;." :group 'nxml-highlighting-faces) (defface nxml-tag-delimiter-face '((t (:inherit nxml-delimiter-face))) "Face used for the angle brackets delimiting tags. `nxml-tag-slash-face' is used for slashes." :group 'nxml-highlighting-faces) (defface nxml-tag-slash-face '((t (:inherit nxml-name-face))) "Face used for slashes in tags, both in end-tags and empty-elements." :group 'nxml-highlighting-faces) (defface nxml-element-prefix-face '((t (:inherit nxml-name-face))) "Face used for the prefix of elements." :group 'nxml-highlighting-faces) (defface nxml-element-colon-face '((t (:inherit nxml-name-face))) "Face used for the colon in element names." :group 'nxml-highlighting-faces) (defface nxml-element-local-name-face '((t (:inherit nxml-name-face))) "Face used for the local name of elements." :group 'nxml-highlighting-faces) (defface nxml-attribute-prefix-face '((t (:inherit nxml-name-face))) "Face used for the prefix of attributes." :group 'nxml-highlighting-faces) (defface nxml-attribute-colon-face '((t (:inherit nxml-name-face))) "Face used for the colon in attribute names." :group 'nxml-highlighting-faces) (defface nxml-attribute-local-name-face '((t (:inherit nxml-name-face))) "Face used for the local name of attributes." :group 'nxml-highlighting-faces) (defface nxml-namespace-attribute-xmlns-face '((t (:inherit nxml-name-face))) "Face used for `xmlns' in namespace attributes." :group 'nxml-highlighting-faces) (defface nxml-namespace-attribute-colon-face '((t (:inherit nxml-name-face))) "Face used for the colon in namespace attributes." :group 'nxml-highlighting-faces) (defface nxml-namespace-attribute-prefix-face '((t (:inherit nxml-name-face))) "Face used for the prefix declared in namespace attributes." :group 'nxml-highlighting-faces) (defface nxml-attribute-value-face '((t (:inherit nxml-delimited-data-face))) "Face used for the value of attributes." :group 'nxml-highlighting-faces) (defface nxml-attribute-value-delimiter-face '((t (:inherit nxml-delimiter-face))) "Face used for the delimiters of attribute values." :group 'nxml-highlighting-faces) (defface nxml-namespace-attribute-value-face '((t (:inherit nxml-attribute-value-face))) "Face used for the value of namespace attributes." :group 'nxml-highlighting-faces) (defface nxml-namespace-attribute-value-delimiter-face '((t (:inherit nxml-attribute-value-delimiter-face))) "Face used for the delimiters of namespace attribute values." :group 'nxml-highlighting-faces) (defface nxml-prolog-literal-delimiter-face '((t (:inherit nxml-delimiter-face))) "Face used for the delimiters of literals in the prolog." :group 'nxml-highlighting-faces) (defface nxml-prolog-literal-content-face '((t (:inherit nxml-delimited-data-face))) "Face used for the content of literals in the prolog." :group 'nxml-highlighting-faces) (defface nxml-prolog-keyword-face '((t (:inherit nxml-name-face))) "Face used for keywords in the prolog." :group 'nxml-highlighting-faces) (defface nxml-markup-declaration-delimiter-face '((t (:inherit nxml-delimiter-face))) "Face used for the delimiters of markup declarations in the prolog. The delimiters are ." :group 'nxml-highlighting-faces) (defface nxml-hash-face '((t (:inherit nxml-name-face))) "Face used for # before a name in the prolog." :group 'nxml-highlighting-faces) (defface nxml-glyph-face '((((type x)) (:family "misc-fixed" :background "light grey" :foreground "black" :weight normal :slant normal)) (t (:background "light grey" :foreground "black" :weight normal :slant normal))) "Face used for glyph for char references." :group 'nxml-highlighting-faces) ;;; Global variables ;; This is initialized in rng-auto.el. (defvar nxml-version nil "*The version of nxml-mode that is being used.") (defvar nxml-prolog-regions nil "List of regions in the prolog to be fontified. See the function `xmltok-forward-prolog' for more information.") (make-variable-buffer-local 'nxml-prolog-regions) (defvar nxml-last-fontify-end nil "Position where fontification last ended. Nil if the buffer changed since the last fontification.") (make-variable-buffer-local 'nxml-last-fontify-end) (defvar nxml-degraded nil "Non-nil if currently operating in degraded mode. Degraded mode is enabled when an internal error is encountered in the fontification or after-change functions.") (make-variable-buffer-local 'nxml-degraded) (defvar nxml-completion-hook nil "Hook run by `nxml-complete'. This hook is run until success.") (defvar nxml-in-mixed-content-hook nil "Hook to determine whether point is in mixed content. The hook is called without arguments. It should return nil if it is definitely not mixed; non-nil otherwise. The hook will be run until one of the functions returns nil.") (defvar nxml-mixed-scan-distance 4000 "Maximum distance from point to scan when checking for mixed content.") (defvar nxml-end-tag-indent-scan-distance 4000 "Maximum distance from point to scan backwards when indenting end-tag.") (defvar nxml-char-ref-extra-display t "Non-nil means display extra information for character references. The extra information consists of a tooltip with the character name and, if `nxml-char-ref-display-glyph-flag' is non-nil, a glyph corresponding to the referenced character following the character reference.") (make-variable-buffer-local 'nxml-char-ref-extra-display) (defvar nxml-mode-map (let ((map (make-sparse-keymap))) (define-key map "\M-\C-u" 'nxml-backward-up-element) (define-key map "\M-\C-d" 'nxml-down-element) (define-key map "\M-\C-n" 'nxml-forward-element) (define-key map "\M-\C-p" 'nxml-backward-element) (define-key map "\M-{" 'nxml-backward-paragraph) (define-key map "\M-}" 'nxml-forward-paragraph) (define-key map "\M-h" 'nxml-mark-paragraph) (define-key map "\C-c\C-f" 'nxml-finish-element) (define-key map "\C-c\C-m" 'nxml-split-element) (define-key map "\C-c\C-b" 'nxml-balanced-close-start-tag-block) (define-key map "\C-c\C-i" 'nxml-balanced-close-start-tag-inline) (define-key map "\C-c\C-x" 'nxml-insert-xml-declaration) (define-key map "\C-c\C-d" 'nxml-dynamic-markup-word) ;; u is for Unicode (define-key map "\C-c\C-u" 'nxml-insert-named-char) (define-key map "\C-c\C-o" nxml-outline-prefix-map) (define-key map [S-mouse-2] 'nxml-mouse-hide-direct-text-content) (define-key map "/" 'nxml-electric-slash) (define-key map [C-return] 'nxml-complete) (when nxml-bind-meta-tab-to-complete-flag (define-key map "\M-\t" 'nxml-complete)) map) "Keymap for nxml-mode.") (defsubst nxml-set-face (start end face) (when (and face (< start end)) (put-text-property start end 'face face))) (defun nxml-clear-face (start end) (remove-text-properties start end '(face nil)) (nxml-clear-char-ref-extra-display start end)) (defsubst nxml-set-fontified (start end) (put-text-property start end 'fontified t)) (defsubst nxml-clear-fontified (start end) (remove-text-properties start end '(fontified nil))) ;;;###autoload (defun nxml-mode () ;; We use C-c C-i instead of \\[nxml-balanced-close-start-tag-inline] ;; because Emacs turns C-c C-i into C-c TAB which is hard to type and ;; not mnemonic. "Major mode for editing XML. Syntax highlighting is performed unless the variable `nxml-syntax-highlight-flag' is nil. \\[nxml-finish-element] finishes the current element by inserting an end-tag. C-c C-i closes a start-tag with `>' and then inserts a balancing end-tag leaving point between the start-tag and end-tag. \\[nxml-balanced-close-start-tag-block] is similar but for block rather than inline elements: the start-tag, point, and end-tag are all left on separate lines. If `nxml-slash-auto-complete-flag' is non-nil, then inserting a `") (make-local-variable 'comment-end-skip) (setq comment-end-skip "[ \t\r\n]*-->") (make-local-variable 'comment-line-break-function) (setq comment-line-break-function 'nxml-newline-and-indent) (use-local-map nxml-mode-map) (save-excursion (save-restriction (widen) (nxml-clear-dependent-regions (point-min) (point-max)) (setq nxml-scan-end (copy-marker (point-min) nil)) (nxml-with-unmodifying-text-property-changes (when nxml-syntax-highlight-flag (nxml-clear-fontified (point-min) (point-max))) (nxml-clear-inside (point-min) (point-max)) (nxml-with-invisible-motion (nxml-scan-prolog))))) (when nxml-syntax-highlight-flag (add-hook 'fontification-functions 'nxml-fontify nil t)) (add-hook 'after-change-functions 'nxml-after-change nil t) (add-hook 'write-contents-hooks 'nxml-prepare-to-save) (when (not (and (buffer-file-name) (file-exists-p (buffer-file-name)))) (when (and nxml-default-buffer-file-coding-system (not (local-variable-p 'buffer-file-coding-system))) (setq buffer-file-coding-system nxml-default-buffer-file-coding-system)) (when nxml-auto-insert-xml-declaration-flag (nxml-insert-xml-declaration))) (run-hooks 'nxml-mode-hook)) (defun nxml-degrade (context err) (message "Internal nXML mode error in %s (%s), degrading" context (error-message-string err)) (ding) (setq nxml-degraded t) (setq nxml-prolog-end 1) (save-excursion (save-restriction (widen) (nxml-with-unmodifying-text-property-changes (nxml-clear-face (point-min) (point-max)) (nxml-set-fontified (point-min) (point-max)) (nxml-clear-inside (point-min) (point-max))) (setq mode-name "nXML/degraded")))) ;;; Change management (defun nxml-after-change (start end pre-change-length) ;; Work around bug in insert-file-contents. (when (> end (1+ (buffer-size))) (setq start 1) (setq end (1+ (buffer-size)))) (unless nxml-degraded (condition-case err (save-excursion (save-restriction (widen) (save-match-data (nxml-with-invisible-motion (nxml-with-unmodifying-text-property-changes (nxml-after-change1 start end pre-change-length)))))) (error (nxml-degrade 'nxml-after-change err))))) (defun nxml-after-change1 (start end pre-change-length) (setq nxml-last-fontify-end nil) (let ((pre-change-end (+ start pre-change-length))) (setq start (nxml-adjust-start-for-dependent-regions start end pre-change-length)) (when (<= start ;; Add 2 so as to include the < and following char ;; that start the instance, since changing these ;; can change where the prolog ends. (+ nxml-prolog-end 2)) ;; end must be extended to at least the end of the old prolog (when (< pre-change-end nxml-prolog-end) (setq end ;; don't let end get out of range even if pre-change-length ;; is bogus (min (point-max) (+ end (- nxml-prolog-end pre-change-end))))) (nxml-scan-prolog))) (cond ((<= end nxml-prolog-end) (setq end nxml-prolog-end) (goto-char start) ;; This is so that Emacs redisplay works (setq start (line-beginning-position))) ((and (<= start nxml-scan-end) (> start (point-min)) (nxml-get-inside (1- start))) ;; The closing delimiter might have been removed. ;; So we may need to redisplay from the beginning ;; of the token. (goto-char (1- start)) (nxml-move-outside-backwards) ;; This is so that Emacs redisplay works (setq start (line-beginning-position)) (setq end (max (nxml-scan-after-change (point) end) end))) (t (goto-char start) ;; This is both for redisplay and to move back ;; past any incomplete opening delimiters (setq start (line-beginning-position)) (setq end (max (nxml-scan-after-change start end) end)))) (when nxml-syntax-highlight-flag (when (>= start end) ;; Must clear at least one char so as to trigger redisplay. (cond ((< start (point-max)) (setq end (1+ start))) (t (setq end (point-max)) (goto-char end) (setq start (line-beginning-position))))) (nxml-clear-fontified start end))) ;;; Encodings (defun nxml-insert-xml-declaration () "Insert an XML declaration at the beginning of buffer. The XML declaration will declare an encoding depending on the buffer's `buffer-file-coding-system'." (interactive "*") (let ((coding-system (if (and buffer-file-coding-system (coding-system-p buffer-file-coding-system) (coding-system-get buffer-file-coding-system 'mime-charset)) buffer-file-coding-system (nxml-choose-utf-coding-system)))) (goto-char (point-min)) (insert (format "\n" (nxml-coding-system-name coding-system))))) (defun nxml-prepare-to-save () (unless (and (not enable-multibyte-characters) (local-variable-p 'buffer-file-coding-system) buffer-file-coding-system (or (eq (coding-system-type buffer-file-coding-system) 5) (eq buffer-file-coding-system 'no-conversion))) (save-excursion (setq buffer-file-coding-system (nxml-select-coding-system)))) ;; nil from a function in `write-contents-hooks' means ;; to continue and write the file as normal nil) (defun nxml-select-coding-system () (let* ((suitable-coding-systems (find-coding-systems-region (point-min) (point-max))) (enc-pos (progn (goto-char (point-min)) (xmltok-get-declared-encoding-position))) (enc-name (and (consp enc-pos) (buffer-substring-no-properties (car enc-pos) (cdr enc-pos)))) (coding-system (cond (enc-name (if (string= (downcase enc-name) "utf-16") (nxml-choose-utf-16-coding-system) (nxml-mime-charset-coding-system enc-name))) (enc-pos (nxml-choose-utf-coding-system))))) ;; Make sure we have a coding-system (unless coding-system (setq coding-system (and (not buffer-read-only) (nxml-choose-suitable-coding-system suitable-coding-systems))) (let ((message (if enc-name (format "Unknown encoding %s" enc-name) "XML declaration is not well-formed"))) (cond ((not coding-system) (error "%s" message)) ((y-or-n-p (concat message ". " (format (if enc-name "Save with %s" "Modify and save with encoding %s") (nxml-coding-system-name coding-system)) " ")) (nxml-fix-encoding-declaration enc-pos coding-system)) (t (signal 'quit nil))))) ;; Make sure it can encode all the characters in the buffer (unless (or (memq (coding-system-base coding-system) suitable-coding-systems) (equal suitable-coding-systems '(undecided))) (let ((message (nxml-unsuitable-coding-system-message coding-system enc-name))) (setq coding-system (and (not buffer-read-only) (nxml-choose-suitable-coding-system suitable-coding-systems))) (cond ((not coding-system) (error "%s" message)) ((y-or-n-p (concat message (format ". Save with %s " (nxml-coding-system-name coding-system)))) (nxml-fix-encoding-declaration enc-pos coding-system)) (t (signal 'quit nil))))) ;; Merge the newline type of our existing encoding (let ((current-eol-type (coding-system-eol-type buffer-file-coding-system))) (when (and current-eol-type (integerp current-eol-type)) (setq coding-system (coding-system-change-eol-conversion coding-system current-eol-type)))) coding-system)) (defun nxml-unsuitable-coding-system-message (coding-system &optional enc-name) (if (nxml-coding-system-unicode-p coding-system) "Cannot translate some characters to Unicode" (format "Cannot encode some characters with %s" (or enc-name (nxml-coding-system-name coding-system))))) (defconst nxml-utf-16-coding-systems (and (coding-system-p 'utf-16-be) (coding-system-p 'utf-16-le) '(utf-16-be utf-16-le))) (defconst nxml-utf-coding-systems (cons 'utf-8 nxml-utf-16-coding-systems)) (defun nxml-coding-system-unicode-p (coding-system) (nxml-coding-system-member (coding-system-base coding-system) nxml-utf-coding-systems)) (defun nxml-coding-system-name (coding-system) (setq coding-system (coding-system-base coding-system)) (symbol-name (if (nxml-coding-system-member coding-system nxml-utf-16-coding-systems) 'utf-16 (or (coding-system-get coding-system 'mime-charset) coding-system)))) (defun nxml-fix-encoding-declaration (enc-pos coding-system) (let ((charset (nxml-coding-system-name coding-system))) (cond ((consp enc-pos) (delete-region (car enc-pos) (cdr enc-pos)) (goto-char (car enc-pos)) (insert charset)) ((integerp enc-pos) (goto-char enc-pos) (insert " encoding=\"" charset ?\")) (t (goto-char (point-min)) (insert "\n") (when (and (not enc-pos) (let ((case-fold-search t)) (looking-at xmltok-bad-xml-decl-regexp))) (delete-region (point) (match-end 0))))))) (defun nxml-choose-suitable-coding-system (suitable-coding-systems) (let (ret coding-system) (if (and buffer-file-coding-system (memq (coding-system-base buffer-file-coding-system) suitable-coding-systems)) buffer-file-coding-system (while (and suitable-coding-systems (not ret)) (setq coding-system (car suitable-coding-systems)) (if (coding-system-get coding-system 'mime-charset) (setq ret coding-system) (setq suitable-coding-systems (cdr suitable-coding-systems)))) ret))) (defun nxml-choose-utf-coding-system () (let ((cur (and (local-variable-p 'buffer-file-coding-system) buffer-file-coding-system (coding-system-base buffer-file-coding-system)))) (cond ((car (nxml-coding-system-member cur nxml-utf-coding-systems))) ((and nxml-prefer-utf-16-to-utf-8-flag (coding-system-p 'utf-16-le) (coding-system-p 'utf-16-be)) (if nxml-prefer-utf-16-little-to-big-endian-flag 'utf-16-le 'utf-16-be)) (t 'utf-8)))) (defun nxml-choose-utf-16-coding-system () (let ((cur (and (local-variable-p 'buffer-file-coding-system) buffer-file-coding-system (coding-system-base buffer-file-coding-system)))) (cond ((car (nxml-coding-system-member cur nxml-utf-16-coding-systems))) (nxml-prefer-utf-16-little-to-big-endian-flag (and (coding-system-p 'utf-16-le) 'utf-16-le)) (t (and (coding-system-p 'utf-16-be) 'utf-16-be))))) (defun nxml-coding-system-member (coding-system coding-systems) (let (ret) (while (and coding-systems (not ret)) (if (coding-system-equal coding-system (car coding-systems)) (setq ret coding-systems) (setq coding-systems (cdr coding-systems)))) ret)) ;;; Fontification (defun nxml-fontify (start) (condition-case err (save-excursion (save-restriction (widen) (save-match-data (nxml-with-invisible-motion (nxml-with-unmodifying-text-property-changes (if (or nxml-degraded ;; just in case we get called in the wrong buffer (not nxml-prolog-end)) (nxml-set-fontified start (point-max)) (nxml-fontify1 start))))))) (error (nxml-degrade 'nxml-fontify err)))) (defun nxml-fontify1 (start) (cond ((< start nxml-prolog-end) (nxml-fontify-prolog) (nxml-set-fontified (point-min) nxml-prolog-end)) (t (goto-char start) (when (not (eq nxml-last-fontify-end start)) (when (not (equal (char-after) ?\<)) (search-backward "<" nxml-prolog-end t)) (nxml-ensure-scan-up-to-date) (nxml-move-outside-backwards)) (let ((start (point))) (nxml-do-fontify (min (point-max) (+ start nxml-fontify-chunk-size))) (setq nxml-last-fontify-end (point)) (nxml-set-fontified start nxml-last-fontify-end))))) (defun nxml-fontify-buffer () (interactive) (save-excursion (save-restriction (widen) (nxml-with-invisible-motion (goto-char (point-min)) (nxml-with-unmodifying-text-property-changes (nxml-fontify-prolog) (goto-char nxml-prolog-end) (nxml-do-fontify)))))) (defun nxml-fontify-prolog () "Fontify the prolog. The buffer is assumed to be prepared for fontification. This does not set the fontified property, but it does clear faces appropriately." (let ((regions nxml-prolog-regions)) (nxml-clear-face (point-min) nxml-prolog-end) (while regions (let ((region (car regions))) (nxml-apply-fontify-rule (aref region 0) (aref region 1) (aref region 2))) (setq regions (cdr regions))))) (defun nxml-do-fontify (&optional bound) "Fontify at least as far as bound. Leave point after last fontified position." (unless bound (setq bound (point-max))) (let (xmltok-dependent-regions xmltok-errors) (while (and (< (point) bound) (nxml-tokenize-forward)) (nxml-clear-face xmltok-start (point)) (nxml-apply-fontify-rule)))) ;; Vectors identify a substring of the token to be highlighted in some face. ;; Token types returned by xmltok-forward. (put 'start-tag 'nxml-fontify-rule '([nil 1 nxml-tag-delimiter-face] [-1 nil nxml-tag-delimiter-face] (element-qname . 1) attributes)) (put 'partial-start-tag 'nxml-fontify-rule '([nil 1 nxml-tag-delimiter-face] (element-qname . 1) attributes)) (put 'end-tag 'nxml-fontify-rule '([nil 1 nxml-tag-delimiter-face] [1 2 nxml-tag-slash-face] [-1 nil nxml-tag-delimiter-face] (element-qname . 2))) (put 'partial-end-tag 'nxml-fontify-rule '([nil 1 nxml-tag-delimiter-face] [1 2 nxml-tag-slash-face] (element-qname . 2))) (put 'empty-element 'nxml-fontify-rule '([nil 1 nxml-tag-delimiter-face] [-2 -1 nxml-tag-slash-face] [-1 nil nxml-tag-delimiter-face] (element-qname . 1) attributes)) (put 'partial-empty-element 'nxml-fontify-rule '([nil 1 nxml-tag-delimiter-face] [-1 nil nxml-tag-slash-face] (element-qname . 1) attributes)) (put 'char-ref 'nxml-fontify-rule '([nil 2 nxml-char-ref-delimiter-face] [2 -1 nxml-char-ref-number-face] [-1 nil nxml-char-ref-delimiter-face] char-ref)) (put 'entity-ref 'nxml-fontify-rule '([nil 1 nxml-entity-ref-delimiter-face] [1 -1 nxml-entity-ref-name-face] [-1 nil nxml-entity-ref-delimiter-face])) (put 'comment 'nxml-fontify-rule '([nil 4 nxml-comment-delimiter-face] [4 -3 nxml-comment-content-face] [-3 nil nxml-comment-delimiter-face])) (put 'processing-instruction 'nxml-fontify-rule '([nil 2 nxml-processing-instruction-delimiter-face] [-2 nil nxml-processing-instruction-delimiter-face] processing-instruction-content)) (put 'cdata-section 'nxml-fontify-rule '([nil 3 nxml-cdata-section-delimiter-face] ; [-3 nil nxml-cdata-section-delimiter-face])) (put 'data 'nxml-fontify-rule '([nil nil nxml-text-face])) ;; Prolog region types in list returned by xmltok-forward-prolog. (put 'xml-declaration 'nxml-fontify-rule '([nil 2 nxml-processing-instruction-delimiter-face] [2 5 nxml-processing-instruction-target-face] [-2 nil nxml-processing-instruction-delimiter-face])) (put 'xml-declaration-attribute-name 'nxml-fontify-rule '([nil nil nxml-attribute-local-name-face])) (put 'xml-declaration-attribute-value 'nxml-fontify-rule '([nil 1 nxml-attribute-value-delimiter-face] [1 -1 nxml-attribute-value-face] [-1 nil nxml-attribute-value-delimiter-face])) (put 'processing-instruction-left 'nxml-fontify-rule '([nil 2 nxml-processing-instruction-delimiter-face] [2 nil nxml-processing-instruction-target-face])) (put 'processing-instruction-right 'nxml-fontify-rule '([nil -2 nxml-processing-instruction-content-face] [-2 nil nxml-processing-instruction-delimiter-face])) (put 'literal 'nxml-fontify-rule '([nil 1 nxml-prolog-literal-delimiter-face] [1 -1 nxml-prolog-literal-content-face] [-1 nil nxml-prolog-literal-delimiter-face])) (put 'keyword 'nxml-fontify-rule '([nil nil nxml-prolog-keyword-face])) (put 'markup-declaration-open 'nxml-fontify-rule '([0 2 nxml-markup-declaration-delimiter-face] [2 nil nxml-prolog-keyword-face])) (put 'markup-declaration-close 'nxml-fontify-rule '([nil nil nxml-markup-declaration-delimiter-face])) (put 'internal-subset-open 'nxml-fontify-rule '([nil nil nxml-markup-declaration-delimiter-face])) (put 'internal-subset-close 'nxml-fontify-rule '([nil 1 nxml-markup-declaration-delimiter-face] [-1 nil nxml-markup-declaration-delimiter-face])) (put 'hash-name 'nxml-fontify-rule '([nil 1 nxml-hash-face] [1 nil nxml-prolog-keyword-face])) (defun nxml-apply-fontify-rule (&optional type start end) (let ((rule (get (or type xmltok-type) 'nxml-fontify-rule))) (unless start (setq start xmltok-start)) (unless end (setq end (point))) (while rule (let* ((action (car rule))) (setq rule (cdr rule)) (cond ((vectorp action) (nxml-set-face (let ((offset (aref action 0))) (cond ((not offset) start) ((< offset 0) (+ end offset)) (t (+ start offset)))) (let ((offset (aref action 1))) (cond ((not offset) end) ((< offset 0) (+ end offset)) (t (+ start offset)))) (aref action 2))) ((and (consp action) (eq (car action) 'element-qname)) (when xmltok-name-end ; maybe nil in partial-end-tag case (nxml-fontify-qname (+ start (cdr action)) xmltok-name-colon xmltok-name-end 'nxml-element-prefix-face 'nxml-element-colon-face 'nxml-element-local-name-face))) ((eq action 'attributes) (nxml-fontify-attributes)) ((eq action 'processing-instruction-content) (nxml-set-face (+ start 2) xmltok-name-end 'nxml-processing-instruction-target-face) (nxml-set-face (save-excursion (goto-char xmltok-name-end) (skip-chars-forward " \t\r\n") (point)) (- end 2) 'nxml-processing-instruction-content-face)) ((eq action 'char-ref) (nxml-char-ref-display-extra start end (xmltok-char-number start end))) (t (error "Invalid nxml-fontify-rule action %s" action))))))) (defun nxml-fontify-attributes () (while xmltok-namespace-attributes (nxml-fontify-attribute (car xmltok-namespace-attributes) 'namespace) (setq xmltok-namespace-attributes (cdr xmltok-namespace-attributes))) (while xmltok-attributes (nxml-fontify-attribute (car xmltok-attributes)) (setq xmltok-attributes (cdr xmltok-attributes)))) (defun nxml-fontify-attribute (att &optional namespace-declaration) (if namespace-declaration (nxml-fontify-qname (xmltok-attribute-name-start att) (xmltok-attribute-name-colon att) (xmltok-attribute-name-end att) 'nxml-namespace-attribute-xmlns-face 'nxml-namespace-attribute-colon-face 'nxml-namespace-attribute-prefix-face 'nxml-namespace-attribute-xmlns-face) (nxml-fontify-qname (xmltok-attribute-name-start att) (xmltok-attribute-name-colon att) (xmltok-attribute-name-end att) 'nxml-attribute-prefix-face 'nxml-attribute-colon-face 'nxml-attribute-local-name-face)) (let ((start (xmltok-attribute-value-start att)) (end (xmltok-attribute-value-end att)) (refs (xmltok-attribute-refs att)) (delimiter-face (if namespace-declaration 'nxml-namespace-attribute-value-delimiter-face 'nxml-attribute-value-delimiter-face)) (value-face (if namespace-declaration 'nxml-namespace-attribute-value-face 'nxml-attribute-value-face))) (when start (nxml-set-face (1- start) start delimiter-face) (nxml-set-face end (1+ end) delimiter-face) (while refs (let* ((ref (car refs)) (ref-type (aref ref 0)) (ref-start (aref ref 1)) (ref-end (aref ref 2))) (nxml-set-face start ref-start value-face) (nxml-apply-fontify-rule ref-type ref-start ref-end) (setq start ref-end)) (setq refs (cdr refs))) (nxml-set-face start end value-face)))) (defun nxml-fontify-qname (start colon end prefix-face colon-face local-name-face &optional unprefixed-face) (cond (colon (nxml-set-face start colon prefix-face) (nxml-set-face colon (1+ colon) colon-face) (nxml-set-face (1+ colon) end local-name-face)) (t (nxml-set-face start end (or unprefixed-face local-name-face))))) ;;; Editing (defun nxml-electric-slash (arg) "Insert a slash. With a prefix ARG, do nothing other than insert the slash. Otherwise, if `nxml-slash-auto-complete-flag' is non-nil, insert the rest of the end-tag or empty-element if the slash is potentially part of an end-tag or the close of an empty-element. If the slash is part of an end-tag that is the first non-whitespace on the line, reindent the line." (interactive "*P") (nxml-ensure-scan-up-to-date) (let* ((slash-pos (point)) (end-tag-p (and (eq (char-before slash-pos) ?<) (not (nxml-get-inside slash-pos)))) (at-indentation (save-excursion (back-to-indentation) (eq (point) (1- slash-pos))))) (self-insert-command (prefix-numeric-value arg)) (unless arg (if nxml-slash-auto-complete-flag (if end-tag-p (condition-case err (let ((start-tag-end (nxml-scan-element-backward (1- slash-pos) t))) (when start-tag-end (insert (xmltok-start-tag-qname) ">") ;; copy the indentation of the start-tag (when (and at-indentation (save-excursion (goto-char xmltok-start) (back-to-indentation) (eq (point) xmltok-start))) (save-excursion (indent-line-to (save-excursion (goto-char xmltok-start) (current-column))))))) (nxml-scan-error nil)) (when (and (eq (nxml-token-before) (point)) (eq xmltok-type 'partial-empty-element)) (insert ">"))) (when (and end-tag-p at-indentation) (nxml-indent-line)))))) (defun nxml-balanced-close-start-tag-block () "Close the start-tag before point with `>' and insert a balancing end-tag. Point is left between the start-tag and the end-tag. If there is nothing but whitespace before the `<' that opens the start-tag, then put point on a blank line, and put the end-tag on another line aligned with the start-tag." (interactive "*") (nxml-balanced-close-start-tag 'block)) (defun nxml-balanced-close-start-tag-inline () "Close the start-tag before point with `>' and insert a balancing end-tag. Point is left between the start-tag and the end-tag. No extra whitespace is inserted." (interactive "*") (nxml-balanced-close-start-tag 'inline)) (defun nxml-balanced-close-start-tag (block-or-inline) (let ((token-end (nxml-token-before)) (pos (1+ (point)))) (unless (or (eq xmltok-type 'partial-start-tag) (and (memq xmltok-type '(start-tag empty-element partial-empty-element)) (>= token-end pos))) (error "Not in a start-tag")) (insert ">") (if (eq block-or-inline 'inline) (goto-char pos) (goto-char xmltok-start) (back-to-indentation) (if (= (point) xmltok-start) (let ((indent (current-column))) (goto-char pos) (insert "\n") (indent-line-to indent) (goto-char pos) (insert "\n") (indent-line-to (+ nxml-child-indent indent))) (goto-char pos))))) (defun nxml-finish-element () "Finish the current element by inserting an end-tag." (interactive "*") (nxml-finish-element-1 nil)) (defvar nxml-last-split-position nil "Position where `nxml-split-element' split the current element.") (defun nxml-split-element () "Split the current element by inserting an end-tag and a start-tag. Point is left after the newly inserted start-tag. When repeated, split immediately before the previously inserted start-tag and leave point unchanged." (interactive "*") (setq nxml-last-split-position (if (and (eq last-command this-command) nxml-last-split-position) (save-excursion (goto-char nxml-last-split-position) (nxml-finish-element-1 t)) (nxml-finish-element-1 t)))) (defun nxml-finish-element-1 (startp) "Insert an end-tag for the current element and optionally a start-tag. The start-tag is inserted if STARTP is non-nil. Return the position of the inserted start-tag or nil if none was inserted." (interactive "*") (let* ((token-end (nxml-token-before)) (start-tag-end (save-excursion (when (and (< (point) token-end) (memq xmltok-type '(cdata-section processing-instruction comment start-tag end-tag empty-element))) (error "Point is inside a %s" (nxml-token-type-friendly-name xmltok-type))) (nxml-scan-element-backward token-end t))) (starts-line (save-excursion (unless (eq xmltok-type 'start-tag) (error "No matching start-tag")) (goto-char xmltok-start) (back-to-indentation) (eq (point) xmltok-start))) (ends-line (save-excursion (goto-char start-tag-end) (looking-at "[ \t\r\n]*$"))) (start-tag-indent (save-excursion (goto-char xmltok-start) (current-column))) (qname (xmltok-start-tag-qname)) inserted-start-tag-pos) (when (and starts-line ends-line) ;; start-tag is on a line by itself ;; => put the end-tag on a line by itself (unless (<= (point) (save-excursion (back-to-indentation) (point))) (insert "\n")) (indent-line-to start-tag-indent)) (insert "") (when startp (when starts-line (insert "\n") (indent-line-to start-tag-indent)) (setq inserted-start-tag-pos (point)) (insert "<" qname ">") (when (and starts-line ends-line) (insert "\n") (indent-line-to (save-excursion (goto-char xmltok-start) (forward-line 1) (back-to-indentation) (if (= (current-column) (+ start-tag-indent nxml-child-indent)) (+ start-tag-indent nxml-child-indent) start-tag-indent))))) inserted-start-tag-pos)) ;;; Indentation (defun nxml-indent-line () "Indent current line as XML." (let ((indent (nxml-compute-indent)) (from-end (- (point-max) (point)))) (when indent (beginning-of-line) (let ((bol (point))) (skip-chars-forward " \t") (delete-region bol (point))) (indent-to indent) (when (> (- (point-max) from-end) (point)) (goto-char (- (point-max) from-end)))))) (defun nxml-compute-indent () "Return the indent for the line containing point." (or (nxml-compute-indent-from-matching-start-tag) (nxml-compute-indent-from-previous-line))) (defun nxml-compute-indent-from-matching-start-tag () "Compute the indent for a line with an end-tag using the matching start-tag. When the line containing point ends with an end-tag and does not start in the middle of a token, return the indent of the line containing the matching start-tag, if there is one and it occurs at the beginning of its line. Otherwise return nil." (save-excursion (back-to-indentation) (let ((bol (point))) (let ((inhibit-field-text-motion t)) (end-of-line)) (skip-chars-backward " \t") (and (= (nxml-token-before) (point)) (memq xmltok-type '(end-tag partial-end-tag)) ;; start of line must not be inside a token (or (= xmltok-start bol) (save-excursion (goto-char bol) (nxml-token-after) (= xmltok-start bol)) (eq xmltok-type 'data)) (condition-case err (nxml-scan-element-backward (point) nil (- (point) nxml-end-tag-indent-scan-distance)) (nxml-scan-error nil)) (< xmltok-start bol) (progn (goto-char xmltok-start) (skip-chars-backward " \t") (bolp)) (current-indentation))))) (defun nxml-compute-indent-from-previous-line () "Compute the indent for a line using the indentation of a previous line." (save-excursion (end-of-line) (let ((eol (point)) bol prev-bol ref before-context after-context) (back-to-indentation) (setq bol (point)) (catch 'indent ;; Move backwards until the start of a non-blank line that is ;; not inside a token. (while (progn (when (= (forward-line -1) -1) (throw 'indent 0)) (back-to-indentation) (if (looking-at "[ \t]*$") t (or prev-bol (setq prev-bol (point))) (nxml-token-after) (not (or (= xmltok-start (point)) (eq xmltok-type 'data)))))) (setq ref (point)) ;; Now scan over tokens until the end of the line to be indented. ;; Determine the context before and after the beginning of the ;; line. (while (< (point) eol) (nxml-tokenize-forward) (cond ((<= bol xmltok-start) (setq after-context (nxml-merge-indent-context-type after-context))) ((and (<= (point) bol) (not (and (eq xmltok-type 'partial-start-tag) (= (point) bol)))) (setq before-context (nxml-merge-indent-context-type before-context))) ((eq xmltok-type 'data) (setq before-context (nxml-merge-indent-context-type before-context)) (setq after-context (nxml-merge-indent-context-type after-context))) ;; If in the middle of a token that looks inline, ;; then indent relative to the previous non-blank line ((eq (nxml-merge-indent-context-type before-context) 'mixed) (goto-char prev-bol) (throw 'indent (current-column))) (t (throw 'indent (nxml-compute-indent-in-token bol)))) (skip-chars-forward " \t\r\n")) (goto-char ref) (+ (current-column) (* nxml-child-indent (+ (if (eq before-context 'start-tag) 1 0) (if (eq after-context 'end-tag) -1 0)))))))) (defun nxml-merge-indent-context-type (context) "Merge the indent context type CONTEXT with the token in `xmltok-type'. Return the merged indent context type. An indent context type is either nil or one of the symbols start-tag, end-tag, markup, comment, mixed." (cond ((memq xmltok-type '(start-tag partial-start-tag)) (if (memq context '(nil start-tag comment)) 'start-tag 'mixed)) ((memq xmltok-type '(end-tag partial-end-tag)) (if (memq context '(nil end-tag comment)) 'end-tag 'mixed)) ((eq xmltok-type 'comment) (cond ((memq context '(start-tag end-tag comment)) context) (context 'mixed) (t 'comment))) (context 'mixed) (t 'markup))) (defun nxml-compute-indent-in-token (pos) "Return the indent for a line that starts inside a token. POS is the position of the first non-whitespace character of the line. This expects the xmltok-* variables to be set up as by `xmltok-forward'." (cond ((memq xmltok-type '(start-tag partial-start-tag empty-element partial-empty-element)) (nxml-compute-indent-in-start-tag pos)) ((eq xmltok-type 'comment) (nxml-compute-indent-in-delimited-token pos "")) ((eq xmltok-type 'cdata-section) (nxml-compute-indent-in-delimited-token pos "")) ((eq xmltok-type 'processing-instruction) (nxml-compute-indent-in-delimited-token pos "")) (t (goto-char pos) (if (and (= (forward-line -1) 0) (< xmltok-start (point))) (back-to-indentation) (goto-char xmltok-start)) (current-column)))) (defun nxml-compute-indent-in-start-tag (pos) "Return the indent for a line that starts inside a start-tag. Also for a line that starts inside an empty element. POS is the position of the first non-whitespace character of the line. This expects the xmltok-* variables to be set up as by `xmltok-forward'." (let ((value-boundary (nxml-attribute-value-boundary pos)) (off 0)) (if value-boundary ;; inside an attribute value (let ((value-start (car value-boundary)) (value-end (cdr value-boundary))) (goto-char pos) (forward-line -1) (if (< (point) value-start) (goto-char value-start) (back-to-indentation))) ;; outside an attribute value (goto-char pos) (while (and (= (forward-line -1) 0) (nxml-attribute-value-boundary (point)))) (cond ((<= (point) xmltok-start) (goto-char xmltok-start) (setq off nxml-attribute-indent) (let ((atts (xmltok-merge-attributes))) (when atts (let* ((att (car atts)) (start (xmltok-attribute-name-start att))) (when (< start pos) (goto-char start) (setq off 0)))))) (t (back-to-indentation)))) (+ (current-column) off))) (defun nxml-attribute-value-boundary (pos) "Return a pair (START . END) if POS is inside an attribute value. Otherwise return nil. START and END are the positions of the start and end of the attribute value containing POS. This expects the xmltok-* variables to be set up as by `xmltok-forward'." (let ((atts (xmltok-merge-attributes)) att value-start value-end value-boundary) (while atts (setq att (car atts)) (setq value-start (xmltok-attribute-value-start att)) (setq value-end (xmltok-attribute-value-end att)) (cond ((and value-start (< pos value-start)) (setq atts nil)) ((and value-start value-end (<= pos value-end)) (setq value-boundary (cons value-start value-end)) (setq atts nil)) (t (setq atts (cdr atts))))) value-boundary)) (defun nxml-compute-indent-in-delimited-token (pos open-delim close-delim) "Return the indent for a line that starts inside a token with delimiters. OPEN-DELIM and CLOSE-DELIM are strings giving the opening and closing delimiters. POS is the position of the first non-whitespace character of the line. This expects the xmltok-* variables to be set up as by `xmltok-forward'." (cond ((let ((end (+ pos (length close-delim)))) (and (<= end (point-max)) (string= (buffer-substring-no-properties pos end) close-delim))) (goto-char xmltok-start)) ((progn (goto-char pos) (forward-line -1) (<= (point) xmltok-start)) (goto-char (+ xmltok-start (length open-delim))) (when (and (string= open-delim "