Replace "Maintainer: FSF" with the emacs-devel mailing address
[bpt/emacs.git] / lisp / textmodes / sgml-mode.el
CommitLineData
e4abaae3 1;;; sgml-mode.el --- SGML- and HTML-editing modes -*- coding: utf-8 -*-
72c0ae01 2
ba318903 3;; Copyright (C) 1992, 1995-1996, 1998, 2001-2014 Free Software
ab422c4d 4;; Foundation, Inc.
6d74b528 5
64ae0c23 6;; Author: James Clark <jjc@jclark.com>
34dc21db 7;; Maintainer: emacs-devel@gnu.org
3e910376 8;; Adapted-By: ESR, Daniel Pfeiffer <occitan@esperanto.org>,
a391b179 9;; F.Potorti@cnuce.cnr.it
1caf38eb 10;; Keywords: wp, hypermedia, comm, languages
72c0ae01 11
72c0ae01
ER
12;; This file is part of GNU Emacs.
13
1fecc8fe 14;; GNU Emacs is free software: you can redistribute it and/or modify
72c0ae01 15;; it under the terms of the GNU General Public License as published by
1fecc8fe
GM
16;; the Free Software Foundation, either version 3 of the License, or
17;; (at your option) any later version.
72c0ae01
ER
18
19;; GNU Emacs is distributed in the hope that it will be useful,
20;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22;; GNU General Public License for more details.
23
24;; You should have received a copy of the GNU General Public License
1fecc8fe 25;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
72c0ae01
ER
26
27;;; Commentary:
28
1caf38eb
RS
29;; Configurable major mode for editing document in the SGML standard general
30;; markup language. As an example contains a mode for editing the derived
31;; HTML hypertext markup language.
72c0ae01
ER
32
33;;; Code:
34
d4c89075
DL
35(eval-when-compile
36 (require 'skeleton)
a464a6c7 37 (require 'cl-lib))
b0a377e6 38
64ae0c23 39(defgroup sgml nil
fae1a906 40 "SGML editing mode."
8ec3bce0 41 :link '(custom-group-link :tag "Font Lock Faces group" font-lock-faces)
64ae0c23
RS
42 :group 'languages)
43
5f3d924d 44(defcustom sgml-basic-offset 2
00affaf1 45 "Specifies the basic indentation level for `sgml-indent-line'."
5f3d924d
SM
46 :type 'integer
47 :group 'sgml)
48
5c11fc55
GM
49(defcustom sgml-xml-mode nil
50 "When non-nil, tag insertion functions will be XML-compliant.
51It is set to be buffer-local when the file has
52a DOCTYPE or an XML declaration."
53 :type 'boolean
54 :version "22.1"
55 :group 'sgml)
56
aa18ec07 57(defcustom sgml-transformation-function 'identity
00affaf1 58 "Default value for `skeleton-transformation-function' in SGML mode."
a391b179 59 :type 'function
5c11fc55
GM
60 :initialize 'custom-initialize-default
61 :set (lambda (sym val)
62 (set-default sym val)
63 (mapc (lambda (buff)
64 (with-current-buffer buff
2c7d2007 65 (and (derived-mode-p 'sgml-mode)
5c11fc55
GM
66 (not sgml-xml-mode)
67 (setq skeleton-transformation-function val))))
68 (buffer-list)))
c60e7b0d 69 :group 'sgml)
a391b179 70
aa18ec07 71(put 'sgml-transformation-function 'variable-interactive
a391b179 72 "aTransformation function: ")
aa18ec07 73(defvaralias 'sgml-transformation 'sgml-transformation-function)
a391b179 74
d4c89075
DL
75(defcustom sgml-mode-hook nil
76 "Hook run by command `sgml-mode'.
77`text-mode-hook' is run first."
78 :group 'sgml
79 :type 'hook)
80
44e97401 81;; As long as Emacs's syntax can't be complemented with predicates to context
1caf38eb
RS
82;; sensitively confirm the syntax of characters, we have to live with this
83;; kludgy kind of tradeoff.
21a6f23c 84(defvar sgml-specials '(?\")
f788776c 85 "List of characters that have a special meaning for SGML mode.
140d71ba 86This list is used when first loading the `sgml-mode' library.
1caf38eb
RS
87The supported characters and potential disadvantages are:
88
89 ?\\\" Makes \" in text start a string.
90 ?' Makes ' in text start a string.
91 ?- Makes -- in text start a comment.
92
4fa91cfe 93When only one of ?\\\" or ?' are included, \"'\" or '\"', as can be found in
1caf38eb 94DTDs, start a string. To partially avoid this problem this also makes these
21a6f23c
RS
95self insert as named entities depending on `sgml-quick-keys'.
96
97Including ?- has the problem of affecting dashes that have nothing to do
98with comments, so we normally turn it off.")
fcc3195e
RS
99
100(defvar sgml-quick-keys nil
2394187c 101 "Use <, >, &, /, SPC and `sgml-specials' keys \"electrically\" when non-nil.
140d71ba 102This takes effect when first loading the `sgml-mode' library.")
1caf38eb 103
1caf38eb 104(defvar sgml-mode-map
e1940c83 105 (let ((map (make-keymap)) ;`sparse' doesn't allow binding to charsets.
1caf38eb 106 (menu-map (make-sparse-keymap "SGML")))
1caf38eb
RS
107 (define-key map "\C-c\C-i" 'sgml-tags-invisible)
108 (define-key map "/" 'sgml-slash)
fcc3195e
RS
109 (define-key map "\C-c\C-n" 'sgml-name-char)
110 (define-key map "\C-c\C-t" 'sgml-tag)
1caf38eb
RS
111 (define-key map "\C-c\C-a" 'sgml-attributes)
112 (define-key map "\C-c\C-b" 'sgml-skip-tag-backward)
113 (define-key map [?\C-c left] 'sgml-skip-tag-backward)
114 (define-key map "\C-c\C-f" 'sgml-skip-tag-forward)
115 (define-key map [?\C-c right] 'sgml-skip-tag-forward)
116 (define-key map "\C-c\C-d" 'sgml-delete-tag)
117 (define-key map "\C-c\^?" 'sgml-delete-tag)
118 (define-key map "\C-c?" 'sgml-tag-help)
8a79905d 119 (define-key map "\C-c]" 'sgml-close-tag)
f6ab0573 120 (define-key map "\C-c/" 'sgml-close-tag)
2ef3c144
CY
121
122 ;; Redundant keybindings, for consistency with TeX mode.
123 (define-key map "\C-c\C-o" 'sgml-tag)
124 (define-key map "\C-c\C-e" 'sgml-close-tag)
125
1caf38eb
RS
126 (define-key map "\C-c8" 'sgml-name-8bit-mode)
127 (define-key map "\C-c\C-v" 'sgml-validate)
b4f05c38
SS
128 (when sgml-quick-keys
129 (define-key map "&" 'sgml-name-char)
130 (define-key map "<" 'sgml-tag)
131 (define-key map " " 'sgml-auto-attributes)
132 (define-key map ">" 'sgml-maybe-end-tag)
133 (when (memq ?\" sgml-specials)
134 (define-key map "\"" 'sgml-name-self))
135 (when (memq ?' sgml-specials)
136 (define-key map "'" 'sgml-name-self)))
2840d653
EZ
137 (let ((c 127)
138 (map (nth 1 map)))
139 (while (< (setq c (1+ c)) 256)
140 (aset map c 'sgml-maybe-name-self)))
1caf38eb
RS
141 (define-key map [menu-bar sgml] (cons "SGML" menu-map))
142 (define-key menu-map [sgml-validate] '("Validate" . sgml-validate))
143 (define-key menu-map [sgml-name-8bit-mode]
144 '("Toggle 8 Bit Insertion" . sgml-name-8bit-mode))
145 (define-key menu-map [sgml-tags-invisible]
146 '("Toggle Tag Visibility" . sgml-tags-invisible))
147 (define-key menu-map [sgml-tag-help]
148 '("Describe Tag" . sgml-tag-help))
149 (define-key menu-map [sgml-delete-tag]
150 '("Delete Tag" . sgml-delete-tag))
151 (define-key menu-map [sgml-skip-tag-forward]
152 '("Forward Tag" . sgml-skip-tag-forward))
153 (define-key menu-map [sgml-skip-tag-backward]
154 '("Backward Tag" . sgml-skip-tag-backward))
155 (define-key menu-map [sgml-attributes]
156 '("Insert Attributes" . sgml-attributes))
157 (define-key menu-map [sgml-tag] '("Insert Tag" . sgml-tag))
158 map)
159 "Keymap for SGML mode. See also `sgml-specials'.")
160
1c1d2eb6
SM
161(defun sgml-make-syntax-table (specials)
162 (let ((table (make-syntax-table text-mode-syntax-table)))
1caf38eb
RS
163 (modify-syntax-entry ?< "(>" table)
164 (modify-syntax-entry ?> ")<" table)
1c1d2eb6
SM
165 (modify-syntax-entry ?: "_" table)
166 (modify-syntax-entry ?_ "_" table)
167 (modify-syntax-entry ?. "_" table)
168 (if (memq ?- specials)
1caf38eb 169 (modify-syntax-entry ?- "_ 1234" table))
1c1d2eb6 170 (if (memq ?\" specials)
1caf38eb 171 (modify-syntax-entry ?\" "\"\"" table))
1c1d2eb6 172 (if (memq ?' specials)
1caf38eb 173 (modify-syntax-entry ?\' "\"'" table))
1c1d2eb6
SM
174 table))
175
176(defvar sgml-mode-syntax-table (sgml-make-syntax-table sgml-specials)
1caf38eb
RS
177 "Syntax table used in SGML mode. See also `sgml-specials'.")
178
1c1d2eb6 179(defconst sgml-tag-syntax-table
6e69aa4d 180 (let ((table (sgml-make-syntax-table sgml-specials)))
1c1d2eb6
SM
181 (dolist (char '(?\( ?\) ?\{ ?\} ?\[ ?\] ?$ ?% ?& ?* ?+ ?/))
182 (modify-syntax-entry char "." table))
c673dabe
MR
183 (unless (memq ?' sgml-specials)
184 ;; Avoid that skipping a tag backwards skips any "'" prefixing it.
185 (modify-syntax-entry ?' "w" table))
1c1d2eb6
SM
186 table)
187 "Syntax table used to parse SGML tags.")
188
64ae0c23 189(defcustom sgml-name-8bit-mode nil
00affaf1 190 "When non-nil, insert non-ASCII characters as named entities."
64ae0c23
RS
191 :type 'boolean
192 :group 'sgml)
72c0ae01 193
1caf38eb
RS
194(defvar sgml-char-names
195 [nil nil nil nil nil nil nil nil
196 nil nil nil nil nil nil nil nil
197 nil nil nil nil nil nil nil nil
198 nil nil nil nil nil nil nil nil
a391b179 199 "nbsp" "excl" "quot" "num" "dollar" "percnt" "amp" "apos"
1caf38eb
RS
200 "lpar" "rpar" "ast" "plus" "comma" "hyphen" "period" "sol"
201 nil nil nil nil nil nil nil nil
202 nil nil "colon" "semi" "lt" "eq" "gt" "quest"
203 "commat" nil nil nil nil nil nil nil
204 nil nil nil nil nil nil nil nil
205 nil nil nil nil nil nil nil nil
206 nil nil nil "lsqb" nil "rsqb" "uarr" "lowbar"
207 "lsquo" nil nil nil nil nil nil nil
208 nil nil nil nil nil nil nil nil
209 nil nil nil nil nil nil nil nil
210 nil nil nil "lcub" "verbar" "rcub" "tilde" nil
211 nil nil nil nil nil nil nil nil
212 nil nil nil nil nil nil nil nil
213 nil nil nil nil nil nil nil nil
214 nil nil nil nil nil nil nil nil
215 "nbsp" "iexcl" "cent" "pound" "curren" "yen" "brvbar" "sect"
216 "uml" "copy" "ordf" "laquo" "not" "shy" "reg" "macr"
217 "ring" "plusmn" "sup2" "sup3" "acute" "micro" "para" "middot"
e79ad8a1 218 "cedil" "sup1" "ordm" "raquo" "frac14" "frac12" "frac34" "iquest"
1caf38eb
RS
219 "Agrave" "Aacute" "Acirc" "Atilde" "Auml" "Aring" "AElig" "Ccedil"
220 "Egrave" "Eacute" "Ecirc" "Euml" "Igrave" "Iacute" "Icirc" "Iuml"
221 "ETH" "Ntilde" "Ograve" "Oacute" "Ocirc" "Otilde" "Ouml" nil
222 "Oslash" "Ugrave" "Uacute" "Ucirc" "Uuml" "Yacute" "THORN" "szlig"
223 "agrave" "aacute" "acirc" "atilde" "auml" "aring" "aelig" "ccedil"
224 "egrave" "eacute" "ecirc" "euml" "igrave" "iacute" "icirc" "iuml"
225 "eth" "ntilde" "ograve" "oacute" "ocirc" "otilde" "ouml" "divide"
226 "oslash" "ugrave" "uacute" "ucirc" "uuml" "yacute" "thorn" "yuml"]
227 "Vector of symbolic character names without `&' and `;'.")
228
2840d653
EZ
229(put 'sgml-table 'char-table-extra-slots 0)
230
231(defvar sgml-char-names-table
232 (let ((table (make-char-table 'sgml-table))
233 (i 32)
234 elt)
f5027deb 235 (while (< i 128)
2840d653
EZ
236 (setq elt (aref sgml-char-names i))
237 (if elt (aset table (make-char 'latin-iso8859-1 i) elt))
238 (setq i (1+ i)))
239 table)
240 "A table for mapping non-ASCII characters into SGML entity names.
241Currently, only Latin-1 characters are supported.")
242
5121371d
DL
243;; nsgmls is a free SGML parser in the SP suite available from
244;; ftp.jclark.com and otherwise packaged for GNU systems.
1caf38eb
RS
245;; Its error messages can be parsed by next-error.
246;; The -s option suppresses output.
247
5121371d 248(defcustom sgml-validate-command "nsgmls -s" ; replaced old `sgmls'
00affaf1 249 "The command to validate an SGML document.
72c0ae01 250The file name of current buffer file name will be appended to this,
64ae0c23
RS
251separated by a space."
252 :type 'string
d4c89075 253 :version "21.1"
64ae0c23 254 :group 'sgml)
72c0ae01
ER
255
256(defvar sgml-saved-validate-command nil
257 "The command last used to validate in this buffer.")
258
e1940c83
SM
259;; I doubt that null end tags are used much for large elements,
260;; so use a small distance here.
64ae0c23 261(defcustom sgml-slash-distance 1000
00affaf1 262 "If non-nil, is the maximum distance to search for matching `/'."
64ae0c23
RS
263 :type '(choice (const nil) integer)
264 :group 'sgml)
72c0ae01 265
b0045305 266(defconst sgml-namespace-re "[_[:alpha:]][-_.[:alnum:]]*")
5f3d924d
SM
267(defconst sgml-name-re "[_:[:alpha:]][-_.:[:alnum:]]*")
268(defconst sgml-tag-name-re (concat "<\\([!/?]?" sgml-name-re "\\)"))
269(defconst sgml-attrs-re "\\(?:[^\"'/><]\\|\"[^\"]*\"\\|'[^']*'\\)*")
270(defconst sgml-start-tag-regex (concat "<" sgml-name-re sgml-attrs-re)
1caf38eb 271 "Regular expression that matches a non-empty start tag.
f788776c 272Any terminating `>' or `/' is not matched.")
1caf38eb 273
b390eb09 274(defface sgml-namespace
b0045305 275 '((t (:inherit font-lock-builtin-face)))
dc11bf76
LK
276 "`sgml-mode' face used to highlight the namespace part of identifiers."
277 :group 'sgml)
b390eb09 278(defvar sgml-namespace-face 'sgml-namespace)
1caf38eb 279
c6a63534
RS
280;; internal
281(defconst sgml-font-lock-keywords-1
5f3d924d 282 `((,(concat "<\\([!?]" sgml-name-re "\\)") 1 font-lock-keyword-face)
b0045305
SM
283 ;; We could use the simpler "\\(" sgml-namespace-re ":\\)?" instead,
284 ;; but it would cause a bit more backtracking in the re-matcher.
285 (,(concat "</?\\(" sgml-namespace-re "\\)\\(?::\\(" sgml-name-re "\\)\\)?")
286 (1 (if (match-end 2) sgml-namespace-face font-lock-function-name-face))
287 (2 font-lock-function-name-face nil t))
1c1d2eb6 288 ;; FIXME: this doesn't cover the variables using a default value.
9bbceb74
SM
289 ;; The first shy-group is an important anchor: it prevents an O(n^2)
290 ;; pathological case where we otherwise keep retrying a failing match
291 ;; against a very long word at every possible position within the word.
292 (,(concat "\\(?:^\\|[ \t]\\)\\(" sgml-namespace-re "\\)\\(?::\\("
b0045305
SM
293 sgml-name-re "\\)\\)?=[\"']")
294 (1 (if (match-end 2) sgml-namespace-face font-lock-variable-name-face))
295 (2 font-lock-variable-name-face nil t))
5f3d924d 296 (,(concat "[&%]" sgml-name-re ";?") . font-lock-variable-name-face)))
64367655
SM
297
298(defconst sgml-font-lock-keywords-2
299 (append
300 sgml-font-lock-keywords-1
301 '((eval
302 . (cons (concat "<"
303 (regexp-opt (mapcar 'car sgml-tag-face-alist) t)
304 "\\([ \t][^>]*\\)?>\\([^<]+\\)</\\1>")
5d503af9
SM
305 '(3 (cdr (assoc-string (match-string 1) sgml-tag-face-alist t))
306 prepend))))))
c6a63534
RS
307
308;; for font-lock, but must be defvar'ed after
309;; sgml-font-lock-keywords-1 and sgml-font-lock-keywords-2 above
310(defvar sgml-font-lock-keywords sgml-font-lock-keywords-1
fb7ada5f 311 "Rules for highlighting SGML code. See also `sgml-tag-face-alist'.")
1caf38eb 312
cf38dd42
SM
313(defconst sgml-syntax-propertize-function
314 (syntax-propertize-rules
4a83d19e
DS
315 ;; Use the `b' style of comments to avoid interference with the -- ... --
316 ;; comments recognized when `sgml-specials' includes ?-.
64367655 317 ;; FIXME: beware of <!--> blabla <!--> !!
cf38dd42 318 ("\\(<\\)!--" (1 "< b"))
ede8c7f0
SM
319 ("--[ \t\n]*\\(>\\)" (1 "> b"))
320 ;; Double quotes outside of tags should not introduce strings.
41b90495
SM
321 ;; Be careful to call `syntax-ppss' on a position before the one we're
322 ;; going to change, so as not to need to flush the data we just computed.
323 ("\"" (0 (if (prog1 (zerop (car (syntax-ppss (match-beginning 0))))
324 (goto-char (match-end 0)))
4a83d19e 325 (string-to-syntax ".")))))
64367655
SM
326 "Syntactic keywords for `sgml-mode'.")
327
1caf38eb 328;; internal
1caf38eb
RS
329(defvar sgml-face-tag-alist ()
330 "Alist of face and tag name for facemenu.")
331
332(defvar sgml-tag-face-alist ()
333 "Tag names and face or list of faces to fontify with when invisible.
334When `font-lock-maximum-decoration' is 1 this is always used for fontifying.
335When more these are fontified together with `sgml-font-lock-keywords'.")
336
1caf38eb
RS
337(defvar sgml-display-text ()
338 "Tag names as lowercase symbols, and display string when invisible.")
339
340;; internal
341(defvar sgml-tags-invisible nil)
342
64ae0c23 343(defcustom sgml-tag-alist
fcc3195e
RS
344 '(("![" ("ignore" t) ("include" t))
345 ("!attlist")
1caf38eb
RS
346 ("!doctype")
347 ("!element")
348 ("!entity"))
00affaf1 349 "Alist of tag names for completing read and insertion rules.
1caf38eb
RS
350This alist is made up as
351
352 ((\"tag\" . TAGRULE)
353 ...)
354
9d4ce428
MW
355TAGRULE is a list of optionally t (no endtag) or `\\n' (separate endtag by
356newlines) or a skeleton with nil, t or `\\n' in place of the interactor
1caf38eb
RS
357followed by an ATTRIBUTERULE (for an always present attribute) or an
358attribute alist.
359
360The attribute alist is made up as
361
362 ((\"attribute\" . ATTRIBUTERULE)
363 ...)
364
9d4ce428 365ATTRIBUTERULE is a list of optionally t (no value when no input) followed by
64ae0c23
RS
366an optional alist of possible values."
367 :type '(repeat (cons (string :tag "Tag Name")
368 (repeat :tag "Tag Rule" sexp)))
369 :group 'sgml)
aa18ec07 370(put 'sgml-tag-alist 'risky-local-variable t)
1caf38eb 371
64ae0c23 372(defcustom sgml-tag-help
1caf38eb
RS
373 '(("!" . "Empty declaration for comment")
374 ("![" . "Embed declarations with parser directive")
375 ("!attlist" . "Tag attributes declaration")
376 ("!doctype" . "Document type (DTD) declaration")
377 ("!element" . "Tag declaration")
378 ("!entity" . "Entity (macro) declaration"))
00affaf1 379 "Alist of tag name and short description."
64ae0c23
RS
380 :type '(repeat (cons (string :tag "Tag Name")
381 (string :tag "Description")))
382 :group 'sgml)
1caf38eb 383
73d25e52
SM
384(defvar sgml-empty-tags nil
385 "List of tags whose !ELEMENT definition says EMPTY.")
386
5f3d924d
SM
387(defvar sgml-unclosed-tags nil
388 "List of tags whose !ELEMENT definition says the end-tag is optional.")
389
c77c3a73 390(defun sgml-xml-guess ()
5d503af9 391 "Guess whether the current buffer is XML. Return non-nil if so."
c77c3a73
SS
392 (save-excursion
393 (goto-char (point-min))
5d503af9 394 (or (string= "xml" (file-name-extension (or buffer-file-name "")))
6961d41c
GM
395 ;; Maybe the buffer-size check isn't needed, I don't know.
396 (and (zerop (buffer-size))
397 (string= "xhtml" (file-name-extension (or buffer-file-name ""))))
5d503af9
SM
398 (looking-at "\\s-*<\\?xml")
399 (when (re-search-forward
400 (eval-when-compile
59444a9c
SM
401 (mapconcat 'identity
402 '("<!DOCTYPE" "\\(\\w+\\)" "\\(\\w+\\)"
5d503af9
SM
403 "\"\\([^\"]+\\)\"" "\"\\([^\"]+\\)\"")
404 "\\s-+"))
405 nil t)
406 (string-match "X\\(HT\\)?ML" (match-string 3))))))
c77c3a73 407
b0a377e6
DL
408(defvar v2) ; free for skeleton
409
60128096
SM
410(defun sgml-comment-indent-new-line (&optional soft)
411 (let ((comment-start "-- ")
412 (comment-start-skip "\\(<!\\)?--[ \t]*")
413 (comment-end " --")
414 (comment-style 'plain))
415 (comment-indent-new-line soft)))
416
a3ec4ba0 417(defun sgml-mode-facemenu-add-face-function (face end)
673c1168
CY
418 (let ((tag-face (cdr (assq face sgml-face-tag-alist))))
419 (cond (tag-face
420 (setq tag-face (funcall skeleton-transformation-function tag-face))
421 (setq facemenu-end-add-face (concat "</" tag-face ">"))
422 (concat "<" tag-face ">"))
423 ((and (consp face)
424 (consp (car face))
425 (null (cdr face))
426 (memq (caar face) '(:foreground :background)))
427 (setq facemenu-end-add-face "</span>")
428 (format "<span style=\"%s:%s\">"
429 (if (eq (caar face) :foreground)
430 "color"
431 "background-color")
432 (cadr (car face))))
433 (t
434 (error "Face not configured for %s mode"
435 (format-mode-line mode-name))))))
a3ec4ba0 436
ed8031f2 437(defun sgml-fill-nobreak ()
14369ab3
KR
438 "Don't break between a tag name and its first argument.
439This function is designed for use in `fill-nobreak-predicate'.
440
441 <a href=\"some://where\" type=\"text/plain\">
442 ^ ^
443 | no break here | but still allowed here"
ed8031f2
SM
444 (save-excursion
445 (skip-chars-backward " \t")
446 (and (not (zerop (skip-syntax-backward "w_")))
447 (skip-chars-backward "/?!")
448 (eq (char-before) ?<))))
449
a3ec4ba0 450;;;###autoload
5d503af9 451(define-derived-mode sgml-mode text-mode '(sgml-xml-mode "XML" "SGML")
a3ec4ba0
SM
452 "Major mode for editing SGML documents.
453Makes > match <.
2394187c 454Keys <, &, SPC within <>, \", / and ' can be electric depending on
a3ec4ba0
SM
455`sgml-quick-keys'.
456
457An argument of N to a tag-inserting command means to wrap it around
458the next N words. In Transient Mark mode, when the mark is active,
459N defaults to -1, which means to wrap it around the current region.
460
aa18ec07 461If you like upcased tags, put (setq sgml-transformation-function 'upcase)
865fe16f 462in your init file.
a3ec4ba0
SM
463
464Use \\[sgml-validate] to validate your document with an SGML parser.
465
466Do \\[describe-variable] sgml- SPC to see available variables.
467Do \\[describe-key] on the following bindings to discover what they do.
468\\{sgml-mode-map}"
72c0ae01 469 (make-local-variable 'sgml-saved-validate-command)
1caf38eb
RS
470 (make-local-variable 'facemenu-end-add-face)
471 ;;(make-local-variable 'facemenu-remove-face-function)
c77c3a73
SS
472 ;; A start or end tag by itself on a line separates a paragraph.
473 ;; This is desirable because SGML discards a newline that appears
474 ;; immediately after a start tag or immediately before an end tag.
92eadba5 475 (setq-local paragraph-start (concat "[ \t]*$\\|\
5f3d924d 476\[ \t]*</?\\(" sgml-name-re sgml-attrs-re "\\)?>"))
92eadba5
CY
477 (setq-local paragraph-separate (concat paragraph-start "$"))
478 (setq-local adaptive-fill-regexp "[ \t]*")
ed8031f2 479 (add-hook 'fill-nobreak-predicate 'sgml-fill-nobreak nil t)
92eadba5
CY
480 (setq-local indent-line-function 'sgml-indent-line)
481 (setq-local comment-start "<!-- ")
482 (setq-local comment-end " -->")
483 (setq-local comment-indent-function 'sgml-comment-indent)
484 (setq-local comment-line-break-function 'sgml-comment-indent-new-line)
485 (setq-local skeleton-further-elements '((completion-ignore-case t)))
486 (setq-local skeleton-end-hook
487 (lambda ()
488 (or (eolp)
489 (not (or (eq v2 '\n) (eq (car-safe v2) '\n)))
490 (newline-and-indent))))
491 (setq font-lock-defaults '((sgml-font-lock-keywords
492 sgml-font-lock-keywords-1
493 sgml-font-lock-keywords-2)
494 nil t))
495 (setq-local syntax-propertize-function sgml-syntax-propertize-function)
496 (setq-local facemenu-add-face-function 'sgml-mode-facemenu-add-face-function)
497 (setq-local sgml-xml-mode (sgml-xml-guess))
498 (unless sgml-xml-mode
499 (setq-local skeleton-transformation-function sgml-transformation-function))
4afa094d
SM
500 ;; This will allow existing comments within declarations to be
501 ;; recognized.
c2734293
SM
502 ;; I can't find a clear description of SGML/XML comments, but it seems that
503 ;; the only reliable ones are <!-- ... --> although it's not clear what
504 ;; "..." can contain. It used to accept -- ... -- as well, but that was
505 ;; apparently a mistake.
92eadba5
CY
506 (setq-local comment-start-skip "<!--[ \t]*")
507 (setq-local comment-end-skip "[ \t]*--[ \t\n]*>")
4456f964
DP
508 ;; This definition has an HTML leaning but probably fits well for other modes.
509 (setq imenu-generic-expression
510 `((nil
511 ,(concat "<!\\(element\\|entity\\)[ \t\n]+%?[ \t\n]*\\("
512 sgml-name-re "\\)")
513 2)
514 ("Id"
515 ,(concat "<[^>]+[ \t\n]+[Ii][Dd]=\\(['\"]"
516 (if sgml-xml-mode "" "?")
517 "\\)\\(" sgml-name-re "\\)\\1")
518 2)
519 ("Name"
520 ,(concat "<[^>]+[ \t\n]+[Nn][Aa][Mm][Ee]=\\(['\"]"
521 (if sgml-xml-mode "" "?")
522 "\\)\\(" sgml-name-re "\\)\\1")
523 2))))
1caf38eb 524
72c0ae01 525(defun sgml-comment-indent ()
4afa094d 526 (if (looking-at "--") comment-column 0))
72c0ae01 527
72c0ae01 528(defun sgml-slash (arg)
2394187c
SM
529 "Insert ARG slash characters.
530Behaves electrically if `sgml-quick-keys' is non-nil."
531 (interactive "p")
532 (cond
533 ((not (and (eq (char-before) ?<) (= arg 1)))
534 (sgml-slash-matching arg))
535 ((eq sgml-quick-keys 'indent)
536 (insert-char ?/ 1)
537 (indent-according-to-mode))
538 ((eq sgml-quick-keys 'close)
d355a0b7 539 (delete-char -1)
f6ab0573 540 (sgml-close-tag))
2394187c
SM
541 (t
542 (sgml-slash-matching arg))))
543
544(defun sgml-slash-matching (arg)
f788776c
RS
545 "Insert `/' and display any previous matching `/'.
546Two `/'s are treated as matching if the first `/' ends a net-enabling
547start tag, and the second `/' is the corresponding null end tag."
72c0ae01
ER
548 (interactive "p")
549 (insert-char ?/ arg)
550 (if (> arg 0)
551 (let ((oldpos (point))
552 (blinkpos)
553 (level 0))
554 (save-excursion
555 (save-restriction
556 (if sgml-slash-distance
557 (narrow-to-region (max (point-min)
558 (- (point) sgml-slash-distance))
559 oldpos))
560 (if (and (re-search-backward sgml-start-tag-regex (point-min) t)
561 (eq (match-end 0) (1- oldpos)))
562 ()
563 (goto-char (1- oldpos))
564 (while (and (not blinkpos)
565 (search-backward "/" (point-min) t))
566 (let ((tagend (save-excursion
567 (if (re-search-backward sgml-start-tag-regex
568 (point-min) t)
569 (match-end 0)
570 nil))))
571 (if (eq tagend (point))
572 (if (eq level 0)
573 (setq blinkpos (point))
574 (setq level (1- level)))
575 (setq level (1+ level)))))))
5950e029
SS
576 (when blinkpos
577 (goto-char blinkpos)
578 (if (pos-visible-in-window-p)
579 (sit-for 1)
580 (message "Matches %s"
581 (buffer-substring (line-beginning-position)
582 (1+ blinkpos)))))))))
72c0ae01 583
0fda8eff
SM
584;; Why doesn't this use the iso-cvt table or, preferably, generate the
585;; inverse of the extensive table in the SGML Quail input method? -- fx
586;; I guess that's moot since it only works with Latin-1 anyhow.
1caf38eb
RS
587(defun sgml-name-char (&optional char)
588 "Insert a symbolic character name according to `sgml-char-names'.
2840d653
EZ
589Non-ASCII chars may be inserted either with the meta key, as in M-SPC for
590no-break space or M-- for a soft hyphen; or via an input method or
591encoded keyboard operation."
1caf38eb
RS
592 (interactive "*")
593 (insert ?&)
594 (or char
9b0ffdac 595 (setq char (read-quoted-char "Enter char or octal number")))
d355a0b7 596 (delete-char -1)
1caf38eb
RS
597 (insert char)
598 (undo-boundary)
4e7a42d2
SM
599 (sgml-namify-char))
600
601(defun sgml-namify-char ()
602 "Change the char before point into its `&name;' equivalent.
603Uses `sgml-char-names'."
604 (interactive)
605 (let* ((char (char-before))
606 (name
607 (cond
608 ((null char) (error "No char before point"))
609 ((< char 256) (or (aref sgml-char-names char) char))
610 ((aref sgml-char-names-table char))
611 ((encode-char char 'ucs)))))
612 (if (not name)
613 (error "Don't know the name of `%c'" char)
d355a0b7 614 (delete-char -1)
4e7a42d2 615 (insert (format (if (numberp name) "&#%d;" "&%s;") name)))))
1caf38eb
RS
616
617(defun sgml-name-self ()
618 "Insert a symbolic character name according to `sgml-char-names'."
619 (interactive "*")
1ba983e8 620 (sgml-name-char last-command-event))
1caf38eb 621
1caf38eb
RS
622(defun sgml-maybe-name-self ()
623 "Insert a symbolic character name according to `sgml-char-names'."
624 (interactive "*")
625 (if sgml-name-8bit-mode
f0bd0ad5 626 (sgml-name-char last-command-event)
1caf38eb
RS
627 (self-insert-command 1)))
628
1caf38eb 629(defun sgml-name-8bit-mode ()
0fda8eff
SM
630 "Toggle whether to insert named entities instead of non-ASCII characters.
631This only works for Latin-1 input."
1caf38eb 632 (interactive)
d10447ba 633 (setq sgml-name-8bit-mode (not sgml-name-8bit-mode))
2840d653 634 (message "sgml name entity mode is now %s"
d10447ba 635 (if sgml-name-8bit-mode "ON" "OFF")))
1caf38eb 636
f788776c 637;; When an element of a skeleton is a string "str", it is passed
aa18ec07
RW
638;; through `skeleton-transformation-function' and inserted.
639;; If "str" is to be inserted literally, one should obtain it as
640;; the return value of a function, e.g. (identity "str").
1caf38eb 641
4e7a42d2
SM
642(defvar sgml-tag-last nil)
643(defvar sgml-tag-history nil)
1caf38eb 644(define-skeleton sgml-tag
f788776c
RS
645 "Prompt for a tag and insert it, optionally with attributes.
646Completion and configuration are done according to `sgml-tag-alist'.
5c11fc55
GM
647If you like tags and attributes in uppercase, customize
648`sgml-transformation-function' to 'upcase."
aa18ec07 649 (funcall (or skeleton-transformation-function 'identity)
4e7a42d2
SM
650 (setq sgml-tag-last
651 (completing-read
652 (if (> (length sgml-tag-last) 0)
653 (format "Tag (default %s): " sgml-tag-last)
654 "Tag: ")
655 sgml-tag-alist nil nil nil 'sgml-tag-history sgml-tag-last)))
4afa094d 656 ?< str |
d10447ba 657 (("") -1 '(undo-boundary) (identity "&lt;")) | ; see comment above
73d25e52
SM
658 `(("") '(setq v2 (sgml-attributes ,str t)) ?>
659 (cond
660 ((string= "![" ,str)
661 (backward-char)
662 '(("") " [ " _ " ]]"))
a3ec4ba0 663 ((and (eq v2 t) sgml-xml-mode (member ,str sgml-empty-tags))
af3abed1 664 '(("") -1 " />"))
a3ec4ba0 665 ((or (and (eq v2 t) (not sgml-xml-mode)) (string-match "^[/!?]" ,str))
73d25e52
SM
666 nil)
667 ((symbolp v2)
668 ;; Make sure we don't fall into an infinite loop.
669 ;; For xhtml's `tr' tag, we should maybe use \n instead.
670 (if (eq v2 t) (setq v2 nil))
671 ;; We use `identity' to prevent skeleton from passing
aa18ec07 672 ;; `str' through `skeleton-transformation-function' a second time.
9ad79506 673 '(("") v2 _ v2 "</" (identity ',str) ?> >))
73d25e52
SM
674 ((eq (car v2) t)
675 (cons '("") (cdr v2)))
676 (t
677 (append '(("") (car v2))
678 (cdr v2)
9ad79506 679 '(resume: (car v2) _ "</" (identity ',str) ?> >))))))
1caf38eb
RS
680
681(autoload 'skeleton-read "skeleton")
682
d10447ba 683(defun sgml-attributes (tag &optional quiet)
f788776c 684 "When at top level of a tag, interactively insert attributes.
d10447ba 685
f788776c
RS
686Completion and configuration of TAG are done according to `sgml-tag-alist'.
687If QUIET, do not print a message when there are no attributes for TAG."
1caf38eb 688 (interactive (list (save-excursion (sgml-beginning-of-tag t))))
d10447ba
RS
689 (or (stringp tag) (error "Wrong context for adding attribute"))
690 (if tag
1caf38eb 691 (let ((completion-ignore-case t)
d10447ba 692 (alist (cdr (assoc (downcase tag) sgml-tag-alist)))
1caf38eb 693 car attribute i)
1caf38eb
RS
694 (if (or (symbolp (car alist))
695 (symbolp (car (car alist))))
696 (setq car (car alist)
697 alist (cdr alist)))
698 (or quiet
699 (message "No attributes configured."))
700 (if (stringp (car alist))
701 (progn
fae1a906 702 (insert (if (eq (preceding-char) ?\s) "" ?\s)
aa18ec07 703 (funcall skeleton-transformation-function (car alist)))
1caf38eb
RS
704 (sgml-value alist))
705 (setq i (length alist))
706 (while (> i 0)
fae1a906 707 (insert ?\s)
aa18ec07 708 (insert (funcall skeleton-transformation-function
1caf38eb
RS
709 (setq attribute
710 (skeleton-read '(completing-read
d10447ba 711 "Attribute: "
1caf38eb
RS
712 alist)))))
713 (if (string= "" attribute)
714 (setq i 0)
aa7a8f0e 715 (sgml-value (assoc (downcase attribute) alist))
1caf38eb 716 (setq i (1- i))))
fae1a906 717 (if (eq (preceding-char) ?\s)
d355a0b7 718 (delete-char -1)))
1caf38eb
RS
719 car)))
720
721(defun sgml-auto-attributes (arg)
f788776c
RS
722 "Self insert the character typed; at top level of tag, prompt for attributes.
723With prefix argument, only self insert."
1caf38eb
RS
724 (interactive "*P")
725 (let ((point (point))
726 tag)
727 (if (or arg
1caf38eb
RS
728 (not sgml-tag-alist) ; no message when nothing configured
729 (symbolp (setq tag (save-excursion (sgml-beginning-of-tag t))))
730 (eq (aref tag 0) ?/))
731 (self-insert-command (prefix-numeric-value arg))
732 (sgml-attributes tag)
1ba983e8 733 (setq last-command-event ?\s)
1caf38eb
RS
734 (or (> (point) point)
735 (self-insert-command 1)))))
736
1caf38eb 737(defun sgml-tag-help (&optional tag)
f788776c 738 "Display description of tag TAG. If TAG is omitted, use the tag at point."
1da94541
CY
739 (interactive
740 (list (let ((def (save-excursion
741 (if (eq (following-char) ?<) (forward-char))
742 (sgml-beginning-of-tag))))
743 (completing-read (if def
744 (format "Tag (default %s): " def)
745 "Tag: ")
746 sgml-tag-alist nil nil nil
747 'sgml-tag-history def))))
748 (or (and tag (> (length tag) 0))
1caf38eb
RS
749 (save-excursion
750 (if (eq (following-char) ?<)
751 (forward-char))
752 (setq tag (sgml-beginning-of-tag))))
753 (or (stringp tag)
754 (error "No tag selected"))
755 (setq tag (downcase tag))
f68f40e0 756 (message "%s"
aa7a8f0e 757 (or (cdr (assoc (downcase tag) sgml-tag-help))
1caf38eb 758 (and (eq (aref tag 0) ?/)
aa7a8f0e 759 (cdr (assoc (downcase (substring tag 1)) sgml-tag-help)))
1caf38eb
RS
760 "No description available")))
761
1c1d2eb6
SM
762(defun sgml-maybe-end-tag (&optional arg)
763 "Name self unless in position to end a tag or a prefix ARG is given."
764 (interactive "P")
765 (if (or arg (eq (car (sgml-lexical-context)) 'tag))
766 (self-insert-command (prefix-numeric-value arg))
767 (sgml-name-self)))
1caf38eb
RS
768
769(defun sgml-skip-tag-backward (arg)
770 "Skip to beginning of tag or matching opening tag if present.
5d503af9
SM
771With prefix argument ARG, repeat this ARG times.
772Return non-nil if we skipped over matched tags."
1caf38eb 773 (interactive "p")
4e7a42d2 774 ;; FIXME: use sgml-get-context or something similar.
5d503af9
SM
775 (let ((return t))
776 (while (>= arg 1)
777 (search-backward "<" nil t)
778 (if (looking-at "</\\([^ \n\t>]+\\)")
779 ;; end tag, skip any nested pairs
780 (let ((case-fold-search t)
781 (re (concat "</?" (regexp-quote (match-string 1))
782 ;; Ignore empty tags like <foo/>.
783 "\\([^>]*[^/>]\\)?>")))
784 (while (and (re-search-backward re nil t)
785 (eq (char-after (1+ (point))) ?/))
786 (forward-char 1)
787 (sgml-skip-tag-backward 1)))
788 (setq return nil))
789 (setq arg (1- arg)))
790 return))
791
792(defvar sgml-electric-tag-pair-overlays nil)
793(defvar sgml-electric-tag-pair-timer nil)
794
795(defun sgml-electric-tag-pair-before-change-function (beg end)
796 (condition-case err
797 (save-excursion
798 (goto-char end)
799 (skip-chars-backward "[:alnum:]-_.:")
800 (if (and ;; (<= (point) beg) ; This poses problems for downcase-word.
801 (or (eq (char-before) ?<)
802 (and (eq (char-before) ?/)
803 (eq (char-before (1- (point))) ?<)))
804 (null (get-char-property (point) 'text-clones)))
805 (let* ((endp (eq (char-before) ?/))
806 (cl-start (point))
807 (cl-end (progn (skip-chars-forward "[:alnum:]-_.:") (point)))
808 (match
809 (if endp
810 (when (sgml-skip-tag-backward 1) (forward-char 1) t)
811 (with-syntax-table sgml-tag-syntax-table
812 (up-list -1)
813 (when (sgml-skip-tag-forward 1)
814 (backward-sexp 1)
815 (forward-char 2)
816 t))))
817 (clones (get-char-property (point) 'text-clones)))
818 (when (and match
819 (/= cl-end cl-start)
820 (equal (buffer-substring cl-start cl-end)
821 (buffer-substring (point)
822 (save-excursion
823 (skip-chars-forward "[:alnum:]-_.:")
824 (point))))
825 (or (not endp) (eq (char-after cl-end) ?>)))
826 (when clones
827 (message "sgml-electric-tag-pair-before-change-function: deleting old OLs")
828 (mapc 'delete-overlay clones))
829 (message "sgml-electric-tag-pair-before-change-function: new clone")
830 (text-clone-create cl-start cl-end 'spread "[[:alnum:]-_.:]+")
831 (setq sgml-electric-tag-pair-overlays
832 (append (get-char-property (point) 'text-clones)
833 sgml-electric-tag-pair-overlays))))))
834 (scan-error nil)
835 (error (message "Error in sgml-electric-pair-mode: %s" err))))
836
837(defun sgml-electric-tag-pair-flush-overlays ()
838 (while sgml-electric-tag-pair-overlays
839 (delete-overlay (pop sgml-electric-tag-pair-overlays))))
840
841(define-minor-mode sgml-electric-tag-pair-mode
ac6c8639
CY
842 "Toggle SGML Electric Tag Pair mode.
843With a prefix argument ARG, enable the mode if ARG is positive,
844and disable it otherwise. If called from Lisp, enable the mode
845if ARG is omitted or nil.
846
847SGML Electric Tag Pair mode is a buffer-local minor mode for use
53964682 848with `sgml-mode' and related major modes. When enabled, editing
ac6c8639 849an opening markup tag automatically updates the closing tag."
5d503af9
SM
850 :lighter "/e"
851 (if sgml-electric-tag-pair-mode
852 (progn
853 (add-hook 'before-change-functions
854 'sgml-electric-tag-pair-before-change-function
855 nil t)
856 (unless sgml-electric-tag-pair-timer
857 (setq sgml-electric-tag-pair-timer
858 (run-with-idle-timer 5 'repeat 'sgml-electric-tag-pair-flush-overlays))))
859 (remove-hook 'before-change-functions
860 'sgml-electric-tag-pair-before-change-function
861 t)
862 ;; We leave the timer running for other buffers.
863 ))
864
1caf38eb 865
65b34485 866(defun sgml-skip-tag-forward (arg)
1caf38eb 867 "Skip to end of tag or matching closing tag if present.
f788776c 868With prefix argument ARG, repeat this ARG times.
3ecd3a56 869Return t if after a closing tag."
1caf38eb 870 (interactive "p")
4e7a42d2
SM
871 ;; FIXME: Use sgml-get-context or something similar.
872 ;; It currently might jump to an unrelated </P> if the <P>
873 ;; we're skipping has no matching </P>.
65b34485 874 (let ((return t))
4e7a42d2
SM
875 (with-syntax-table sgml-tag-syntax-table
876 (while (>= arg 1)
877 (skip-chars-forward "^<>")
878 (if (eq (following-char) ?>)
879 (up-list -1))
880 (if (looking-at "<\\([^/ \n\t>]+\\)\\([^>]*[^/>]\\)?>")
881 ;; start tag, skip any nested same pairs _and_ closing tag
882 (let ((case-fold-search t)
883 (re (concat "</?" (regexp-quote (match-string 1))
884 ;; Ignore empty tags like <foo/>.
885 "\\([^>]*[^/>]\\)?>"))
886 point close)
887 (forward-list 1)
888 (setq point (point))
889 ;; FIXME: This re-search-forward will mistakenly match
890 ;; tag-like text inside attributes.
891 (while (and (re-search-forward re nil t)
892 (not (setq close
893 (eq (char-after (1+ (match-beginning 0))) ?/)))
894 (goto-char (match-beginning 0))
895 (sgml-skip-tag-forward 1))
896 (setq close nil))
897 (unless close
898 (goto-char point)
899 (setq return nil)))
900 (forward-list 1))
901 (setq arg (1- arg)))
902 return)))
1caf38eb 903
9847dc91
GM
904(defsubst sgml-looking-back-at (str)
905 "Return t if the test before point matches STR."
906 (let ((start (- (point) (length str))))
907 (and (>= start (point-min))
908 (equal str (buffer-substring-no-properties start (point))))))
909
1caf38eb 910(defun sgml-delete-tag (arg)
4e7a42d2 911 ;; FIXME: Should be called sgml-kill-tag or should not touch the kill-ring.
1caf38eb 912 "Delete tag on or after cursor, and matching closing or opening tag.
f788776c 913With prefix argument ARG, repeat this ARG times."
1caf38eb
RS
914 (interactive "p")
915 (while (>= arg 1)
916 (save-excursion
917 (let* (close open)
fcc3195e 918 (if (looking-at "[ \t\n]*<")
1caf38eb
RS
919 ;; just before tag
920 (if (eq (char-after (match-end 0)) ?/)
921 ;; closing tag
922 (progn
923 (setq close (point))
924 (goto-char (match-end 0))))
925 ;; on tag?
926 (or (save-excursion (setq close (sgml-beginning-of-tag)
927 close (and (stringp close)
928 (eq (aref close 0) ?/)
929 (point))))
930 ;; not on closing tag
931 (let ((point (point)))
932 (sgml-skip-tag-backward 1)
933 (if (or (not (eq (following-char) ?<))
934 (save-excursion
935 (forward-list 1)
936 (<= (point) point)))
937 (error "Not on or before tag")))))
938 (if close
939 (progn
940 (sgml-skip-tag-backward 1)
941 (setq open (point))
942 (goto-char close)
943 (kill-sexp 1))
944 (setq open (point))
af3abed1 945 (when (and (sgml-skip-tag-forward 1)
7346a407 946 (not (sgml-looking-back-at "/>")))
4e7a42d2
SM
947 (kill-sexp -1)))
948 ;; Delete any resulting empty line. If we didn't kill-sexp,
949 ;; this *should* do nothing, because we're right after the tag.
950 (if (progn (forward-line 0) (looking-at "\\(?:[ \t]*$\\)\n?"))
951 (delete-region (match-beginning 0) (match-end 0)))
1caf38eb 952 (goto-char open)
4e7a42d2
SM
953 (kill-sexp 1)
954 (if (progn (forward-line 0) (looking-at "\\(?:[ \t]*$\\)\n?"))
955 (delete-region (match-beginning 0) (match-end 0)))))
1caf38eb 956 (setq arg (1- arg))))
9d4ce428 957
a391b179
RS
958\f
959;; Put read-only last to enable setting this even when read-only enabled.
960(or (get 'sgml-tag 'invisible)
961 (setplist 'sgml-tag
962 (append '(invisible t
a391b179
RS
963 point-entered sgml-point-entered
964 rear-nonsticky t
965 read-only t)
966 (symbol-plist 'sgml-tag))))
1caf38eb
RS
967
968(defun sgml-tags-invisible (arg)
969 "Toggle visibility of existing tags."
970 (interactive "P")
971 (let ((modified (buffer-modified-p))
972 (inhibit-read-only t)
e1940c83
SM
973 (inhibit-modification-hooks t)
974 ;; Avoid spurious the `file-locked' checks.
975 (buffer-file-name nil)
a391b179
RS
976 ;; This is needed in case font lock gets called,
977 ;; since it moves point and might call sgml-point-entered.
64367655 978 ;; How could it get called? -stef
a391b179 979 (inhibit-point-motion-hooks t)
64367655 980 string)
e1940c83
SM
981 (unwind-protect
982 (save-excursion
983 (goto-char (point-min))
92eadba5
CY
984 (if (setq-local sgml-tags-invisible
985 (if arg
986 (>= (prefix-numeric-value arg) 0)
987 (not sgml-tags-invisible)))
1c1d2eb6 988 (while (re-search-forward sgml-tag-name-re nil t)
64367655
SM
989 (setq string
990 (cdr (assq (intern-soft (downcase (match-string 1)))
991 sgml-display-text)))
e1940c83 992 (goto-char (match-beginning 0))
64367655 993 (and (stringp string)
e1940c83 994 (not (overlays-at (point)))
73d25e52
SM
995 (let ((ol (make-overlay (point) (match-beginning 1))))
996 (overlay-put ol 'before-string string)
997 (overlay-put ol 'sgml-tag t)))
e1940c83
SM
998 (put-text-property (point)
999 (progn (forward-list) (point))
1000 'category 'sgml-tag))
64367655 1001 (let ((pos (point-min)))
e1940c83 1002 (while (< (setq pos (next-overlay-change pos)) (point-max))
73d25e52 1003 (dolist (ol (overlays-at pos))
b2e8c203 1004 (if (overlay-get ol 'sgml-tag)
73d25e52 1005 (delete-overlay ol)))))
64367655 1006 (remove-text-properties (point-min) (point-max) '(category nil))))
e1940c83 1007 (restore-buffer-modified-p modified))
1caf38eb
RS
1008 (run-hooks 'sgml-tags-invisible-hook)
1009 (message "")))
1010
1011(defun sgml-point-entered (x y)
1012 ;; Show preceding or following hidden tag, depending of cursor direction.
1013 (let ((inhibit-point-motion-hooks t))
1014 (save-excursion
03b1a68c
CY
1015 (condition-case nil
1016 (message "Invisible tag: %s"
1017 ;; Strip properties, otherwise, the text is invisible.
1018 (buffer-substring-no-properties
1019 (point)
1020 (if (or (and (> x y)
1021 (not (eq (following-char) ?<)))
1022 (and (< x y)
1023 (eq (preceding-char) ?>)))
1024 (backward-list)
1025 (forward-list))))
1026 (error nil)))))
1027
9d4ce428 1028
a391b179 1029\f
72c0ae01
ER
1030(defun sgml-validate (command)
1031 "Validate an SGML document.
1032Runs COMMAND, a shell command, in a separate process asynchronously
f788776c 1033with output going to the buffer `*compilation*'.
72c0ae01
ER
1034You can then use the command \\[next-error] to find the next error message
1035and move to the line in the SGML document that caused it."
1036 (interactive
1037 (list (read-string "Validate command: "
1038 (or sgml-saved-validate-command
1039 (concat sgml-validate-command
1040 " "
da8092ef
EZ
1041 (shell-quote-argument
1042 (let ((name (buffer-file-name)))
1043 (and name
1044 (file-name-nondirectory name)))))))))
72c0ae01 1045 (setq sgml-saved-validate-command command)
b7cd1746 1046 (save-some-buffers (not compilation-ask-about-save) nil)
27024d2f 1047 (compilation-start command))
72c0ae01 1048
662deeab
MW
1049(defsubst sgml-at-indentation-p ()
1050 "Return true if point is at the first non-whitespace character on the line."
1051 (save-excursion
1052 (skip-chars-backward " \t")
1053 (bolp)))
1054
1c1d2eb6
SM
1055(defun sgml-lexical-context (&optional limit)
1056 "Return the lexical context at point as (TYPE . START).
1057START is the location of the start of the lexical element.
121f1921 1058TYPE is one of `string', `comment', `tag', `cdata', `pi', or `text'.
1c1d2eb6 1059
41bfcbee
MW
1060Optional argument LIMIT is the position to start parsing from.
1061If nil, start from a preceding tag at indentation."
1c1d2eb6
SM
1062 (save-excursion
1063 (let ((pos (point))
14614b6d 1064 text-start state)
41bfcbee
MW
1065 (if limit
1066 (goto-char limit)
1067 ;; Skip tags backwards until we find one at indentation
1068 (while (and (ignore-errors (sgml-parse-tag-backward))
1069 (not (sgml-at-indentation-p)))))
5f3d924d
SM
1070 (with-syntax-table sgml-tag-syntax-table
1071 (while (< (point) pos)
1072 ;; When entering this loop we're inside text.
80fc318e 1073 (setq text-start (point))
5f3d924d 1074 (skip-chars-forward "^<" pos)
14614b6d
MW
1075 (setq state
1076 (cond
60128096 1077 ((= (point) pos)
14614b6d
MW
1078 ;; We got to the end without seeing a tag.
1079 nil)
1080 ((looking-at "<!\\[[A-Z]+\\[")
1081 ;; We've found a CDATA section or similar.
1082 (let ((cdata-start (point)))
1083 (unless (search-forward "]]>" pos 'move)
1084 (list 0 nil nil 'cdata nil nil nil nil cdata-start))))
5d1fd962
CY
1085 ((looking-at comment-start-skip)
1086 ;; parse-partial-sexp doesn't handle <!-- comments -->,
1087 ;; or only if ?- is in sgml-specials, so match explicitly
1088 (let ((start (point)))
1089 (unless (re-search-forward comment-end-skip pos 'move)
1090 (list 0 nil nil nil t nil nil nil start))))
121f1921
SM
1091 ((and sgml-xml-mode (looking-at "<\\?"))
1092 ;; Processing Instructions.
1093 ;; In SGML, it's basically a normal tag of the form
1094 ;; <?NAME ...> but in XML, it takes the form <? ... ?>.
1095 (let ((pi-start (point)))
1096 (unless (search-forward "?>" pos 'move)
1097 (list 0 nil nil 'pi nil nil nil nil pi-start))))
14614b6d 1098 (t
2871b07a 1099 ;; We've reached a tag. Parse it.
14614b6d
MW
1100 ;; FIXME: Handle net-enabling start-tags
1101 (parse-partial-sexp (point) pos 0))))))
1102 (cond
121f1921 1103 ((memq (nth 3 state) '(cdata pi)) (cons (nth 3 state) (nth 8 state)))
14614b6d
MW
1104 ((nth 3 state) (cons 'string (nth 8 state)))
1105 ((nth 4 state) (cons 'comment (nth 8 state)))
1106 ((and state (> (nth 0 state) 0)) (cons 'tag (nth 1 state)))
1107 (t (cons 'text text-start))))))
1c1d2eb6 1108
1caf38eb
RS
1109(defun sgml-beginning-of-tag (&optional top-level)
1110 "Skip to beginning of tag and return its name.
1c1d2eb6
SM
1111If this can't be done, return nil."
1112 (let ((context (sgml-lexical-context)))
1113 (if (eq (car context) 'tag)
1114 (progn
1115 (goto-char (cdr context))
1116 (when (looking-at sgml-tag-name-re)
1117 (match-string-no-properties 1)))
1118 (if top-level nil
3fb819e5 1119 (when (not (eq (car context) 'text))
1c1d2eb6
SM
1120 (goto-char (cdr context))
1121 (sgml-beginning-of-tag t))))))
1caf38eb
RS
1122
1123(defun sgml-value (alist)
347ea557 1124 "Interactively insert value taken from attribute-rule ALIST.
5950e029 1125See `sgml-tag-alist' for info about attribute rules."
1caf38eb
RS
1126 (setq alist (cdr alist))
1127 (if (stringp (car alist))
1128 (insert "=\"" (car alist) ?\")
a3ec4ba0 1129 (if (and (eq (car alist) t) (not sgml-xml-mode))
5950e029 1130 (when (cdr alist)
73d25e52
SM
1131 (insert "=\"")
1132 (setq alist (skeleton-read '(completing-read "Value: " (cdr alist))))
1133 (if (string< "" alist)
1134 (insert alist ?\")
d355a0b7 1135 (delete-char -2)))
1caf38eb 1136 (insert "=\"")
00affaf1
SM
1137 (if (cdr alist)
1138 (insert (skeleton-read '(completing-read "Value: " alist)))
1139 (when (null alist)
1140 (insert (skeleton-read '(read-string "Value: ")))))
1caf38eb 1141 (insert ?\"))))
64367655
SM
1142
1143(defun sgml-quote (start end &optional unquotep)
7492ed8e
SM
1144 "Quote SGML text in region START ... END.
1145Only &, < and > are quoted, the rest is left untouched.
1146With prefix argument UNQUOTEP, unquote the region."
1147 (interactive "r\nP")
1148 (save-restriction
1149 (narrow-to-region start end)
1150 (goto-char (point-min))
1151 (if unquotep
1152 ;; FIXME: We should unquote other named character references as well.
1153 (while (re-search-forward
1154 "\\(&\\(amp\\|\\(l\\|\\(g\\)\\)t\\)\\)[][<>&;\n\t \"%!'(),/=?]"
1155 nil t)
1156 (replace-match (if (match-end 4) ">" (if (match-end 3) "<" "&")) t t
1157 nil (if (eq (char-before (match-end 0)) ?\;) 0 1)))
1158 (while (re-search-forward "[&<>]" nil t)
1159 (replace-match (cdr (assq (char-before) '((?& . "&amp;")
1160 (?< . "&lt;")
1161 (?> . "&gt;"))))
1162 t t)))))
1163
1164(defun sgml-pretty-print (beg end)
1165 "Simple-minded pretty printer for SGML.
1166Re-indents the code and inserts newlines between BEG and END.
1167You might want to turn on `auto-fill-mode' to get better results."
1168 ;; TODO:
1169 ;; - insert newline between some start-tag and text.
1170 ;; - don't insert newline in front of some end-tags.
1171 (interactive "r")
1172 (save-excursion
1173 (if (< beg end)
1174 (goto-char beg)
1175 (goto-char end)
1176 (setq end beg)
1177 (setq beg (point)))
1178 ;; Don't use narrowing because it screws up auto-indent.
1179 (setq end (copy-marker end t))
1180 (with-syntax-table sgml-tag-syntax-table
1181 (while (re-search-forward "<" end t)
1182 (goto-char (match-beginning 0))
1183 (unless (or ;;(looking-at "</")
1184 (progn (skip-chars-backward " \t") (bolp)))
1185 (reindent-then-newline-and-indent))
1186 (forward-sexp 1)))
1187 ;; (indent-region beg end)
1188 ))
e1940c83 1189
2394187c
SM
1190\f
1191;; Parsing
1192
a464a6c7 1193(cl-defstruct (sgml-tag
2394187c
SM
1194 (:constructor sgml-make-tag (type start end name)))
1195 type start end name)
1196
1197(defsubst sgml-parse-tag-name ()
1198 "Skip past a tag-name, and return the name."
1199 (buffer-substring-no-properties
1200 (point) (progn (skip-syntax-forward "w_") (point))))
1201
e9146d5a
SM
1202(defun sgml-tag-text-p (start end)
1203 "Return non-nil if text between START and END is a tag.
1204Checks among other things that the tag does not contain spurious
1205unquoted < or > chars inside, which would indicate that it
1206really isn't a tag after all."
1207 (save-excursion
1208 (with-syntax-table sgml-tag-syntax-table
1209 (let ((pps (parse-partial-sexp start end 2)))
1210 (and (= (nth 0 pps) 0))))))
1211
4e7a42d2 1212(defun sgml-parse-tag-backward (&optional limit)
2394187c
SM
1213 "Parse an SGML tag backward, and return information about the tag.
1214Assume that parsing starts from within a textual context.
1215Leave point at the beginning of the tag."
e9146d5a
SM
1216 (catch 'found
1217 (let (tag-type tag-start tag-end name)
1218 (or (re-search-backward "[<>]" limit 'move)
1219 (error "No tag found"))
1220 (when (eq (char-after) ?<)
1221 ;; Oops!! Looks like we were not in a textual context after all!.
1222 ;; Let's try to recover.
121f1921
SM
1223 ;; Remember the tag-start so we don't need to look for it later.
1224 ;; This is not just an optimization but also makes sure we don't get
1225 ;; stuck in infloops in cases where "looking back for <" would not go
1226 ;; back far enough.
1227 (setq tag-start (point))
e9146d5a
SM
1228 (with-syntax-table sgml-tag-syntax-table
1229 (let ((pos (point)))
1230 (condition-case nil
121f1921 1231 ;; FIXME: This does not correctly skip over PI an CDATA tags.
e9146d5a
SM
1232 (forward-sexp)
1233 (scan-error
1234 ;; This < seems to be just a spurious one, let's ignore it.
1235 (goto-char pos)
1236 (throw 'found (sgml-parse-tag-backward limit))))
1237 ;; Check it is really a tag, without any extra < or > inside.
1238 (unless (sgml-tag-text-p pos (point))
1239 (goto-char pos)
1240 (throw 'found (sgml-parse-tag-backward limit)))
1241 (forward-char -1))))
1242 (setq tag-end (1+ (point)))
1243 (cond
1244 ((sgml-looking-back-at "--") ; comment
1245 (setq tag-type 'comment
121f1921 1246 tag-start (or tag-start (search-backward "<!--" nil t))))
e9146d5a
SM
1247 ((sgml-looking-back-at "]]") ; cdata
1248 (setq tag-type 'cdata
121f1921
SM
1249 tag-start (or tag-start
1250 (re-search-backward "<!\\[[A-Z]+\\[" nil t))))
1251 ((sgml-looking-back-at "?") ; XML processing-instruction
1252 (setq tag-type 'pi
1253 ;; IIUC: SGML processing instructions take the form <?foo ...>
1254 ;; i.e. a "normal" tag, handled below. In XML this is changed
1255 ;; to <?foo ... ?> where "..." can contain < and > and even <?
1256 ;; but not ?>. This means that when parsing backward, there's
1257 ;; no easy way to make sure that we find the real beginning of
1258 ;; the PI.
1259 tag-start (or tag-start (search-backward "<?" nil t))))
e9146d5a 1260 (t
121f1921
SM
1261 (unless tag-start
1262 (setq tag-start
1263 (with-syntax-table sgml-tag-syntax-table
1264 (goto-char tag-end)
1265 (condition-case nil
1266 (backward-sexp)
1267 (scan-error
1268 ;; This > isn't really the end of a tag. Skip it.
1269 (goto-char (1- tag-end))
1270 (throw 'found (sgml-parse-tag-backward limit))))
1271 (point))))
e9146d5a 1272 (goto-char (1+ tag-start))
a464a6c7 1273 (pcase (char-after)
121f1921
SM
1274 (?! (setq tag-type 'decl)) ; declaration
1275 (?? (setq tag-type 'pi)) ; processing-instruction
1276 (?% (setq tag-type 'jsp)) ; JSP tags
e9146d5a
SM
1277 (?/ ; close-tag
1278 (forward-char 1)
1279 (setq tag-type 'close
1280 name (sgml-parse-tag-name)))
a464a6c7 1281 (_ ; open or empty tag
e9146d5a
SM
1282 (setq tag-type 'open
1283 name (sgml-parse-tag-name))
1284 (if (or (eq ?/ (char-before (- tag-end 1)))
1285 (sgml-empty-tag-p name))
1286 (setq tag-type 'empty))))))
1287 (goto-char tag-start)
1288 (sgml-make-tag tag-type tag-start tag-end name))))
2394187c 1289
59444a9c 1290(defun sgml-get-context (&optional until)
2394187c 1291 "Determine the context of the current position.
59444a9c
SM
1292By default, parse until we find a start-tag as the first thing on a line.
1293If UNTIL is `empty', return even if the context is empty (i.e.
2394187c 1294we just skipped over some element and got to a beginning of line).
2394187c
SM
1295
1296The context is a list of tag-info structures. The last one is the tag
59444a9c
SM
1297immediately enclosing the current position.
1298
1299Point is assumed to be outside of any tag. If we discover that it's
1300not the case, the first tag returned is the one inside which we are."
2394187c 1301 (let ((here (point))
ed8031f2 1302 (stack nil)
2394187c
SM
1303 (ignore nil)
1304 (context nil)
1305 tag-info)
1306 ;; CONTEXT keeps track of the tag-stack
ed8031f2
SM
1307 ;; STACK keeps track of the end tags we've seen (and thus the start-tags
1308 ;; we'll have to ignore) when skipping over matching open..close pairs.
1309 ;; IGNORE is a list of tags that can be ignored because they have been
1310 ;; closed implicitly.
2394187c
SM
1311 (skip-chars-backward " \t\n") ; Make sure we're not at indentation.
1312 (while
59444a9c 1313 (and (not (eq until 'now))
ed8031f2 1314 (or stack
59444a9c 1315 (not (if until (eq until 'empty) context))
2394187c
SM
1316 (not (sgml-at-indentation-p))
1317 (and context
1318 (/= (point) (sgml-tag-start (car context)))
59444a9c 1319 (sgml-unclosed-tag-p (sgml-tag-name (car context)))))
2394187c 1320 (setq tag-info (ignore-errors (sgml-parse-tag-backward))))
a9d4efa2 1321
2394187c
SM
1322 ;; This tag may enclose things we thought were tags. If so,
1323 ;; discard them.
1324 (while (and context
1325 (> (sgml-tag-end tag-info)
1326 (sgml-tag-end (car context))))
1327 (setq context (cdr context)))
a9d4efa2 1328
2394187c 1329 (cond
59444a9c
SM
1330 ((> (sgml-tag-end tag-info) here)
1331 ;; Oops!! Looks like we were not outside of any tag, after all.
1332 (push tag-info context)
1333 (setq until 'now))
2394187c 1334
2394187c
SM
1335 ;; start-tag
1336 ((eq (sgml-tag-type tag-info) 'open)
1337 (cond
ed8031f2 1338 ((null stack)
5d503af9 1339 (if (assoc-string (sgml-tag-name tag-info) ignore t)
2394187c
SM
1340 ;; There was an implicit end-tag.
1341 nil
ed8031f2
SM
1342 (push tag-info context)
1343 ;; We're changing context so the tags implicitly closed inside
1344 ;; the previous context aren't implicitly closed here any more.
1345 ;; [ Well, actually it depends, but we don't have the info about
1346 ;; when it doesn't and when it does. --Stef ]
1347 (setq ignore nil)))
2394187c 1348 ((eq t (compare-strings (sgml-tag-name tag-info) nil nil
ed8031f2
SM
1349 (car stack) nil nil t))
1350 (setq stack (cdr stack)))
2394187c
SM
1351 (t
1352 ;; The open and close tags don't match.
1353 (if (not sgml-xml-mode)
2394187c 1354 (unless (sgml-unclosed-tag-p (sgml-tag-name tag-info))
7492ed8e 1355 (message "Unclosed tag <%s>" (sgml-tag-name tag-info))
ed8031f2 1356 (let ((tmp stack))
7492ed8e
SM
1357 ;; We could just assume that the tag is simply not closed
1358 ;; but it's a bad assumption when tags *are* closed but
1359 ;; not properly nested.
1360 (while (and (cdr tmp)
1361 (not (eq t (compare-strings
1362 (sgml-tag-name tag-info) nil nil
1363 (cadr tmp) nil nil t))))
1364 (setq tmp (cdr tmp)))
1365 (if (cdr tmp) (setcdr tmp (cddr tmp)))))
2394187c 1366 (message "Unmatched tags <%s> and </%s>"
ed8031f2 1367 (sgml-tag-name tag-info) (pop stack)))))
bf247b6e 1368
ed8031f2
SM
1369 (if (and (null stack) (sgml-unclosed-tag-p (sgml-tag-name tag-info)))
1370 ;; This is a top-level open of an implicitly closed tag, so any
1371 ;; occurrence of such an open tag at the same level can be ignored
1372 ;; because it's been implicitly closed.
1373 (push (sgml-tag-name tag-info) ignore)))
2394187c
SM
1374
1375 ;; end-tag
1376 ((eq (sgml-tag-type tag-info) 'close)
1377 (if (sgml-empty-tag-p (sgml-tag-name tag-info))
1378 (message "Spurious </%s>: empty tag" (sgml-tag-name tag-info))
ed8031f2 1379 (push (sgml-tag-name tag-info) stack)))
2394187c
SM
1380 ))
1381
1382 ;; return context
1383 context))
1384
1385(defun sgml-show-context (&optional full)
1386 "Display the current context.
1387If FULL is non-nil, parse back to the beginning of the buffer."
1388 (interactive "P")
1389 (with-output-to-temp-buffer "*XML Context*"
7492ed8e
SM
1390 (save-excursion
1391 (let ((context (sgml-get-context)))
1392 (when full
1393 (let ((more nil))
1394 (while (setq more (sgml-get-context))
1395 (setq context (nconc more context)))))
1396 (pp context)))))
2394187c
SM
1397
1398\f
1399;; Editing shortcuts
1400
f6ab0573 1401(defun sgml-close-tag ()
4e7a42d2
SM
1402 "Close current element.
1403Depending on context, inserts a matching close-tag, or closes
1404the current start-tag or the current comment or the current cdata, ..."
2394187c 1405 (interactive)
a464a6c7
SM
1406 (pcase (car (sgml-lexical-context))
1407 (`comment (insert " -->"))
1408 (`cdata (insert "]]>"))
1409 (`pi (insert " ?>"))
1410 (`jsp (insert " %>"))
1411 (`tag (insert " />"))
1412 (`text
f6ab0573
MW
1413 (let ((context (save-excursion (sgml-get-context))))
1414 (if context
2871b07a 1415 (progn
f6ab0573
MW
1416 (insert "</" (sgml-tag-name (car (last context))) ">")
1417 (indent-according-to-mode)))))
a464a6c7 1418 (_
f6ab0573 1419 (error "Nothing to close"))))
2394187c 1420
347ea557
MW
1421(defun sgml-empty-tag-p (tag-name)
1422 "Return non-nil if TAG-NAME is an implicitly empty tag."
1423 (and (not sgml-xml-mode)
5d503af9 1424 (assoc-string tag-name sgml-empty-tags 'ignore-case)))
347ea557
MW
1425
1426(defun sgml-unclosed-tag-p (tag-name)
1427 "Return non-nil if TAG-NAME is a tag for which an end-tag is optional."
1428 (and (not sgml-xml-mode)
5d503af9
SM
1429 (assoc-string tag-name sgml-unclosed-tags 'ignore-case)))
1430
347ea557 1431
59444a9c
SM
1432(defun sgml-calculate-indent (&optional lcon)
1433 "Calculate the column to which this line should be indented.
1434LCON is the lexical context, if any."
1435 (unless lcon (setq lcon (sgml-lexical-context)))
1436
1437 ;; Indent comment-start markers inside <!-- just like comment-end markers.
1438 (if (and (eq (car lcon) 'tag)
1439 (looking-at "--")
1440 (save-excursion (goto-char (cdr lcon)) (looking-at "<!--")))
1441 (setq lcon (cons 'comment (+ (cdr lcon) 2))))
1442
a464a6c7 1443 (pcase (car lcon)
59444a9c 1444
a464a6c7 1445 (`string
59444a9c
SM
1446 ;; Go back to previous non-empty line.
1447 (while (and (> (point) (cdr lcon))
1448 (zerop (forward-line -1))
1449 (looking-at "[ \t]*$")))
1450 (if (> (point) (cdr lcon))
1451 ;; Previous line is inside the string.
1452 (current-indentation)
1453 (goto-char (cdr lcon))
1454 (1+ (current-column))))
1455
a464a6c7 1456 (`comment
59444a9c 1457 (let ((mark (looking-at "--")))
1c1d2eb6
SM
1458 ;; Go back to previous non-empty line.
1459 (while (and (> (point) (cdr lcon))
1460 (zerop (forward-line -1))
59444a9c
SM
1461 (or (looking-at "[ \t]*$")
1462 (if mark (not (looking-at "[ \t]*--"))))))
1c1d2eb6 1463 (if (> (point) (cdr lcon))
59444a9c
SM
1464 ;; Previous line is inside the comment.
1465 (skip-chars-forward " \t")
1c1d2eb6 1466 (goto-char (cdr lcon))
59444a9c
SM
1467 ;; Skip `<!' to get to the `--' with which we want to align.
1468 (search-forward "--")
1469 (goto-char (match-beginning 0)))
1470 (when (and (not mark) (looking-at "--"))
1471 (forward-char 2) (skip-chars-forward " \t"))
1472 (current-column)))
1473
1474 ;; We don't know how to indent it. Let's be honest about it.
a464a6c7 1475 (`cdata nil)
121f1921 1476 ;; We don't know how to indent it. Let's be honest about it.
a464a6c7 1477 (`pi nil)
59444a9c 1478
a464a6c7 1479 (`tag
59444a9c
SM
1480 (goto-char (1+ (cdr lcon)))
1481 (skip-chars-forward "^ \t\n") ;Skip tag name.
1482 (skip-chars-forward " \t")
1483 (if (not (eolp))
1484 (current-column)
1485 ;; This is the first attribute: indent.
1c1d2eb6 1486 (goto-char (1+ (cdr lcon)))
59444a9c
SM
1487 (+ (current-column) sgml-basic-offset)))
1488
a464a6c7 1489 (`text
59444a9c
SM
1490 (while (looking-at "</")
1491 (forward-sexp 1)
1492 (skip-chars-forward " \t"))
1493 (let* ((here (point))
1494 (unclosed (and ;; (not sgml-xml-mode)
1495 (looking-at sgml-tag-name-re)
5d503af9
SM
1496 (assoc-string (match-string 1)
1497 sgml-unclosed-tags 'ignore-case)
59444a9c
SM
1498 (match-string 1)))
1499 (context
1500 ;; If possible, align on the previous non-empty text line.
1501 ;; Otherwise, do a more serious parsing to find the
1502 ;; tag(s) relative to which we should be indenting.
1503 (if (and (not unclosed) (skip-chars-backward " \t")
1504 (< (skip-chars-backward " \t\n") 0)
1505 (back-to-indentation)
1506 (> (point) (cdr lcon)))
1507 nil
1508 (goto-char here)
1509 (nreverse (sgml-get-context (if unclosed nil 'empty)))))
1510 (there (point)))
1511 ;; Ignore previous unclosed start-tag in context.
1512 (while (and context unclosed
1513 (eq t (compare-strings
1514 (sgml-tag-name (car context)) nil nil
1515 unclosed nil nil t)))
1516 (setq context (cdr context)))
1517 ;; Indent to reflect nesting.
1518 (cond
1519 ;; If we were not in a text context after all, let's try again.
1520 ((and context (> (sgml-tag-end (car context)) here))
1521 (goto-char here)
1522 (sgml-calculate-indent
1523 (cons (if (memq (sgml-tag-type (car context)) '(comment cdata))
1524 (sgml-tag-type (car context)) 'tag)
1525 (sgml-tag-start (car context)))))
1526 ;; Align on the first element after the nearest open-tag, if any.
1527 ((and context
1528 (goto-char (sgml-tag-end (car context)))
1529 (skip-chars-forward " \t\n")
1530 (< (point) here) (sgml-at-indentation-p))
1531 (current-column))
1532 (t
1533 (goto-char there)
1534 (+ (current-column)
1535 (* sgml-basic-offset (length context)))))))
1536
a464a6c7 1537 (_
98d90904 1538 (error "Unrecognized context %s" (car lcon)))
59444a9c
SM
1539
1540 ))
1c1d2eb6
SM
1541
1542(defun sgml-indent-line ()
1543 "Indent the current line as SGML."
1544 (interactive)
1545 (let* ((savep (point))
1546 (indent-col
1547 (save-excursion
5f3d924d 1548 (back-to-indentation)
1c1d2eb6 1549 (if (>= (point) savep) (setq savep nil))
1c1d2eb6 1550 (sgml-calculate-indent))))
59444a9c
SM
1551 (if (null indent-col)
1552 'noindent
1553 (if savep
1554 (save-excursion (indent-line-to indent-col))
1555 (indent-line-to indent-col)))))
1c1d2eb6 1556
2871b07a
MW
1557(defun sgml-guess-indent ()
1558 "Guess an appropriate value for `sgml-basic-offset'.
0b381c7e 1559Base the guessed indentation level on the first indented tag in the buffer.
2871b07a
MW
1560Add this to `sgml-mode-hook' for convenience."
1561 (interactive)
1562 (save-excursion
1563 (goto-char (point-min))
232dbe4f 1564 (if (re-search-forward "^\\([ \t]+\\)<" 500 'noerror)
2871b07a 1565 (progn
92eadba5 1566 (setq-local sgml-basic-offset (1- (current-column)))
2871b07a
MW
1567 (message "Guessed sgml-basic-offset = %d"
1568 sgml-basic-offset)
1569 ))))
1570
5f3d924d
SM
1571(defun sgml-parse-dtd ()
1572 "Simplistic parse of the current buffer as a DTD.
1573Currently just returns (EMPTY-TAGS UNCLOSED-TAGS)."
1574 (goto-char (point-min))
1575 (let ((empty nil)
1576 (unclosed nil))
1577 (while (re-search-forward "<!ELEMENT[ \t\n]+\\([^ \t\n]+\\)[ \t\n]+[-O][ \t\n]+\\([-O]\\)[ \t\n]+\\([^ \t\n]+\\)" nil t)
1578 (cond
1579 ((string= (match-string 3) "EMPTY")
1580 (push (match-string-no-properties 1) empty))
1581 ((string= (match-string 2) "O")
1582 (push (match-string-no-properties 1) unclosed))))
1583 (setq empty (sort (mapcar 'downcase empty) 'string<))
1584 (setq unclosed (sort (mapcar 'downcase unclosed) 'string<))
1585 (list empty unclosed)))
1586
e1940c83
SM
1587;;; HTML mode
1588
d4c89075
DL
1589(defcustom html-mode-hook nil
1590 "Hook run by command `html-mode'.
1591`text-mode-hook' and `sgml-mode-hook' are run first."
1592 :group 'sgml
1593 :type 'hook
1594 :options '(html-autoview-mode))
1595
fcc3195e 1596(defvar html-quick-keys sgml-quick-keys
b1e7bb48 1597 "Use C-c X combinations for quick insertion of frequent tags when non-nil.
fcc3195e 1598This defaults to `sgml-quick-keys'.
1caf38eb
RS
1599This takes effect when first loading the library.")
1600
1601(defvar html-mode-map
5f5c9e79 1602 (let ((map (make-sparse-keymap))
1caf38eb 1603 (menu-map (make-sparse-keymap "HTML")))
5f5c9e79 1604 (set-keymap-parent map sgml-mode-map)
7e49eef2
RS
1605 (define-key map "\C-c6" 'html-headline-6)
1606 (define-key map "\C-c5" 'html-headline-5)
1607 (define-key map "\C-c4" 'html-headline-4)
1608 (define-key map "\C-c3" 'html-headline-3)
1609 (define-key map "\C-c2" 'html-headline-2)
1610 (define-key map "\C-c1" 'html-headline-1)
fcc3195e
RS
1611 (define-key map "\C-c\r" 'html-paragraph)
1612 (define-key map "\C-c\n" 'html-line)
1613 (define-key map "\C-c\C-c-" 'html-horizontal-rule)
7e49eef2
RS
1614 (define-key map "\C-c\C-co" 'html-ordered-list)
1615 (define-key map "\C-c\C-cu" 'html-unordered-list)
fcc3195e
RS
1616 (define-key map "\C-c\C-cr" 'html-radio-buttons)
1617 (define-key map "\C-c\C-cc" 'html-checkboxes)
1618 (define-key map "\C-c\C-cl" 'html-list-item)
1619 (define-key map "\C-c\C-ch" 'html-href-anchor)
1620 (define-key map "\C-c\C-cn" 'html-name-anchor)
1621 (define-key map "\C-c\C-ci" 'html-image)
5950e029
SS
1622 (when html-quick-keys
1623 (define-key map "\C-c-" 'html-horizontal-rule)
1624 (define-key map "\C-co" 'html-ordered-list)
1625 (define-key map "\C-cu" 'html-unordered-list)
1626 (define-key map "\C-cr" 'html-radio-buttons)
1627 (define-key map "\C-cc" 'html-checkboxes)
1628 (define-key map "\C-cl" 'html-list-item)
1629 (define-key map "\C-ch" 'html-href-anchor)
1630 (define-key map "\C-cn" 'html-name-anchor)
1631 (define-key map "\C-ci" 'html-image))
1caf38eb
RS
1632 (define-key map "\C-c\C-s" 'html-autoview-mode)
1633 (define-key map "\C-c\C-v" 'browse-url-of-buffer)
1634 (define-key map [menu-bar html] (cons "HTML" menu-map))
1635 (define-key menu-map [html-autoview-mode]
1636 '("Toggle Autoviewing" . html-autoview-mode))
1637 (define-key menu-map [browse-url-of-buffer]
1638 '("View Buffer Contents" . browse-url-of-buffer))
1639 (define-key menu-map [nil] '("--"))
7e49eef2
RS
1640 ;;(define-key menu-map "6" '("Heading 6" . html-headline-6))
1641 ;;(define-key menu-map "5" '("Heading 5" . html-headline-5))
1642 ;;(define-key menu-map "4" '("Heading 4" . html-headline-4))
1643 (define-key menu-map "3" '("Heading 3" . html-headline-3))
1644 (define-key menu-map "2" '("Heading 2" . html-headline-2))
1645 (define-key menu-map "1" '("Heading 1" . html-headline-1))
1caf38eb 1646 (define-key menu-map "l" '("Radio Buttons" . html-radio-buttons))
fcc3195e 1647 (define-key menu-map "c" '("Checkboxes" . html-checkboxes))
1caf38eb 1648 (define-key menu-map "l" '("List Item" . html-list-item))
7e49eef2
RS
1649 (define-key menu-map "u" '("Unordered List" . html-unordered-list))
1650 (define-key menu-map "o" '("Ordered List" . html-ordered-list))
fcc3195e 1651 (define-key menu-map "-" '("Horizontal Rule" . html-horizontal-rule))
1caf38eb
RS
1652 (define-key menu-map "\n" '("Line Break" . html-line))
1653 (define-key menu-map "\r" '("Paragraph" . html-paragraph))
1654 (define-key menu-map "i" '("Image" . html-image))
1655 (define-key menu-map "h" '("Href Anchor" . html-href-anchor))
1656 (define-key menu-map "n" '("Name Anchor" . html-name-anchor))
1657 map)
1658 "Keymap for commands for use in HTML mode.")
1659
1caf38eb
RS
1660(defvar html-face-tag-alist
1661 '((bold . "b")
1662 (italic . "i")
1663 (underline . "u")
37269466 1664 (mode-line . "rev"))
1caf38eb
RS
1665 "Value of `sgml-face-tag-alist' for HTML mode.")
1666
1667(defvar html-tag-face-alist
1668 '(("b" . bold)
1669 ("big" . bold)
1670 ("blink" . highlight)
1671 ("cite" . italic)
1672 ("em" . italic)
1673 ("h1" bold underline)
1674 ("h2" bold-italic underline)
1675 ("h3" italic underline)
1676 ("h4" . underline)
1677 ("h5" . underline)
1678 ("h6" . underline)
1679 ("i" . italic)
37269466 1680 ("rev" . mode-line)
1caf38eb
RS
1681 ("s" . underline)
1682 ("small" . default)
1683 ("strong" . bold)
1684 ("title" bold underline)
1685 ("tt" . default)
1686 ("u" . underline)
1687 ("var" . italic))
1688 "Value of `sgml-tag-face-alist' for HTML mode.")
1689
1caf38eb
RS
1690(defvar html-display-text
1691 '((img . "[/]")
1692 (hr . "----------")
1693 (li . "o "))
1694 "Value of `sgml-display-text' for HTML mode.")
b4f05c38 1695
9d4ce428 1696\f
3bf0b727 1697;; should code exactly HTML 3 here when that is finished
1caf38eb 1698(defvar html-tag-alist
d10447ba 1699 (let* ((1-7 '(("1") ("2") ("3") ("4") ("5") ("6") ("7")))
e1940c83 1700 (1-9 `(,@1-7 ("8") ("9")))
1caf38eb
RS
1701 (align '(("align" ("left") ("center") ("right"))))
1702 (valign '(("top") ("middle") ("bottom") ("baseline")))
1703 (rel '(("next") ("previous") ("parent") ("subdocument") ("made")))
1704 (href '("href" ("ftp:") ("file:") ("finger:") ("gopher:") ("http:")
1705 ("mailto:") ("news:") ("rlogin:") ("telnet:") ("tn3270:")
fcc3195e 1706 ("wais:") ("/cgi-bin/")))
1caf38eb
RS
1707 (name '("name"))
1708 (link `(,href
1709 ("rel" ,@rel)
1710 ("rev" ,@rel)
1711 ("title")))
b4f05c38 1712 (list '((nil \n ("List item: " "<li>" str
a3ec4ba0 1713 (if sgml-xml-mode "</li>") \n))))
1caf38eb 1714 (cell `(t
e1940c83 1715 ,@align
1caf38eb
RS
1716 ("valign" ,@valign)
1717 ("colspan" ,@1-9)
1718 ("rowspan" ,@1-9)
1719 ("nowrap" t))))
1720 ;; put ,-expressions first, else byte-compile chokes (as of V19.29)
1721 ;; and like this it's more efficient anyway
1722 `(("a" ,name ,@link)
1723 ("base" t ,@href)
1724 ("dir" ,@list)
d10447ba 1725 ("font" nil "size" ("-1") ("+1") ("-2") ("+2") ,@1-7)
73d25e52 1726 ("form" (\n _ \n "<input type=\"submit\" value=\"\""
af3abed1 1727 (if sgml-xml-mode " />" ">"))
fcc3195e 1728 ("action" ,@(cdr href)) ("method" ("get") ("post")))
1caf38eb
RS
1729 ("h1" ,@align)
1730 ("h2" ,@align)
1731 ("h3" ,@align)
1732 ("h4" ,@align)
1733 ("h5" ,@align)
1734 ("h6" ,@align)
1735 ("hr" t ("size" ,@1-9) ("width") ("noshade" t) ,@align)
1736 ("img" t ("align" ,@valign ("texttop") ("absmiddle") ("absbottom"))
1737 ("src") ("alt") ("width" "1") ("height" "1")
1738 ("border" "1") ("vspace" "1") ("hspace" "1") ("ismap" t))
1739 ("input" t ("size" ,@1-9) ("maxlength" ,@1-9) ("checked" t) ,name
fcc3195e
RS
1740 ("type" ("text") ("password") ("checkbox") ("radio")
1741 ("submit") ("reset"))
1caf38eb
RS
1742 ("value"))
1743 ("link" t ,@link)
1744 ("menu" ,@list)
d10447ba 1745 ("ol" ,@list ("type" ("A") ("a") ("I") ("i") ("1")))
1caf38eb
RS
1746 ("p" t ,@align)
1747 ("select" (nil \n
1748 ("Text: "
a3ec4ba0 1749 "<option>" str (if sgml-xml-mode "</option>") \n))
1caf38eb
RS
1750 ,name ("size" ,@1-9) ("multiple" t))
1751 ("table" (nil \n
1752 ((completing-read "Cell kind: " '(("td") ("th"))
1753 nil t "t")
73d25e52 1754 "<tr><" str ?> _
a3ec4ba0 1755 (if sgml-xml-mode (concat "<" str "></tr>")) \n))
1caf38eb
RS
1756 ("border" t ,@1-9) ("width" "10") ("cellpadding"))
1757 ("td" ,@cell)
1758 ("textarea" ,name ("rows" ,@1-9) ("cols" ,@1-9))
1759 ("th" ,@cell)
d10447ba 1760 ("ul" ,@list ("type" ("disc") ("circle") ("square")))
1caf38eb
RS
1761
1762 ,@sgml-tag-alist
1763
1764 ("abbrev")
1765 ("acronym")
1766 ("address")
1767 ("array" (nil \n
a3ec4ba0 1768 ("Item: " "<item>" str (if sgml-xml-mode "</item>") \n))
1caf38eb
RS
1769 "align")
1770 ("au")
1771 ("b")
1772 ("big")
1773 ("blink")
1774 ("blockquote" \n)
1775 ("body" \n ("background" ".gif") ("bgcolor" "#") ("text" "#")
1776 ("link" "#") ("alink" "#") ("vlink" "#"))
a3ec4ba0 1777 ("box" (nil _ "<over>" _ (if sgml-xml-mode "</over>")))
1caf38eb
RS
1778 ("br" t ("clear" ("left") ("right")))
1779 ("caption" ("valign" ("top") ("bottom")))
1780 ("center" \n)
1781 ("cite")
1782 ("code" \n)
a3ec4ba0 1783 ("dd" ,(not sgml-xml-mode))
1caf38eb
RS
1784 ("del")
1785 ("dfn")
e1940c83 1786 ("div")
1caf38eb
RS
1787 ("dl" (nil \n
1788 ( "Term: "
a3ec4ba0
SM
1789 "<dt>" str (if sgml-xml-mode "</dt>")
1790 "<dd>" _ (if sgml-xml-mode "</dd>") \n)))
1791 ("dt" (t _ (if sgml-xml-mode "</dt>")
1792 "<dd>" (if sgml-xml-mode "</dd>") \n))
1caf38eb 1793 ("em")
1da94541 1794 ("fn" "id" "fn") ;; Footnotes were deprecated in HTML 3.2
1caf38eb
RS
1795 ("head" \n)
1796 ("html" (\n
1797 "<head>\n"
1798 "<title>" (setq str (read-input "Title: ")) "</title>\n"
5e532c5c 1799 "</head>\n"
1caf38eb
RS
1800 "<body>\n<h1>" str "</h1>\n" _
1801 "\n<address>\n<a href=\"mailto:"
be047262 1802 user-mail-address
5e532c5c
RS
1803 "\">" (user-full-name) "</a>\n</address>\n"
1804 "</body>"
1805 ))
1caf38eb
RS
1806 ("i")
1807 ("ins")
1808 ("isindex" t ("action") ("prompt"))
1809 ("kbd")
1810 ("lang")
a3ec4ba0 1811 ("li" ,(not sgml-xml-mode))
1caf38eb
RS
1812 ("math" \n)
1813 ("nobr")
1814 ("option" t ("value") ("label") ("selected" t))
1815 ("over" t)
1da94541 1816 ("person") ;; Tag for person's name tag deprecated in HTML 3.2
1caf38eb
RS
1817 ("pre" \n)
1818 ("q")
1819 ("rev")
1820 ("s")
1821 ("samp")
1822 ("small")
64367655
SM
1823 ("span" nil
1824 ("class"
1825 ("builtin")
1826 ("comment")
1827 ("constant")
1828 ("function-name")
1829 ("keyword")
1830 ("string")
1831 ("type")
1832 ("variable-name")
1833 ("warning")))
1caf38eb
RS
1834 ("strong")
1835 ("sub")
1836 ("sup")
1837 ("title")
1838 ("tr" t)
1839 ("tt")
1840 ("u")
1841 ("var")
1842 ("wbr" t)))
fb7ada5f 1843 "Value of `sgml-tag-alist' for HTML mode.")
1caf38eb
RS
1844
1845(defvar html-tag-help
1846 `(,@sgml-tag-help
1847 ("a" . "Anchor of point or link elsewhere")
1da94541
CY
1848 ("abbrev" . "Abbreviation")
1849 ("acronym" . "Acronym")
1caf38eb
RS
1850 ("address" . "Formatted mail address")
1851 ("array" . "Math array")
1da94541 1852 ("au" . "Author")
1caf38eb
RS
1853 ("b" . "Bold face")
1854 ("base" . "Base address for URLs")
1855 ("big" . "Font size")
1856 ("blink" . "Blinking text")
1857 ("blockquote" . "Indented quotation")
1858 ("body" . "Document body")
1859 ("box" . "Math fraction")
1860 ("br" . "Line break")
1861 ("caption" . "Table caption")
1862 ("center" . "Centered text")
1863 ("changed" . "Change bars")
1864 ("cite" . "Citation of a document")
1865 ("code" . "Formatted source code")
1866 ("dd" . "Definition of term")
1da94541
CY
1867 ("del" . "Deleted text")
1868 ("dfn" . "Defining instance of a term")
1caf38eb 1869 ("dir" . "Directory list (obsolete)")
1da94541 1870 ("div" . "Generic block-level container")
1caf38eb 1871 ("dl" . "Definition list")
40a8bdf6 1872 ("dt" . "Term to be defined")
98d90904 1873 ("em" . "Emphasized")
1caf38eb
RS
1874 ("embed" . "Embedded data in foreign format")
1875 ("fig" . "Figure")
1876 ("figa" . "Figure anchor")
1877 ("figd" . "Figure description")
1878 ("figt" . "Figure text")
1da94541 1879 ("fn" . "Footnote") ;; No one supports special footnote rendering.
1caf38eb
RS
1880 ("font" . "Font size")
1881 ("form" . "Form with input fields")
1882 ("group" . "Document grouping")
1883 ("h1" . "Most important section headline")
1884 ("h2" . "Important section headline")
1885 ("h3" . "Section headline")
1886 ("h4" . "Minor section headline")
1887 ("h5" . "Unimportant section headline")
1888 ("h6" . "Least important section headline")
1889 ("head" . "Document header")
1890 ("hr" . "Horizontal rule")
1891 ("html" . "HTML Document")
1892 ("i" . "Italic face")
1893 ("img" . "Graphic image")
1894 ("input" . "Form input field")
1da94541 1895 ("ins" . "Inserted text")
1caf38eb 1896 ("isindex" . "Input field for index search")
f6b1b0a8 1897 ("kbd" . "Keyboard example face")
1caf38eb
RS
1898 ("lang" . "Natural language")
1899 ("li" . "List item")
1900 ("link" . "Link relationship")
1901 ("math" . "Math formula")
1902 ("menu" . "Menu list (obsolete)")
1903 ("mh" . "Form mail header")
1904 ("nextid" . "Allocate new id")
1905 ("nobr" . "Text without line break")
1906 ("ol" . "Ordered list")
1907 ("option" . "Selection list item")
1908 ("over" . "Math fraction rule")
1909 ("p" . "Paragraph start")
1910 ("panel" . "Floating panel")
1da94541 1911 ("person" . "Person's name")
1caf38eb 1912 ("pre" . "Preformatted fixed width text")
1da94541 1913 ("q" . "Quotation")
1caf38eb 1914 ("rev" . "Reverse video")
1da94541 1915 ("s" . "Strikeout")
1caf38eb
RS
1916 ("samp" . "Sample text")
1917 ("select" . "Selection list")
1918 ("small" . "Font size")
1919 ("sp" . "Nobreak space")
1da94541 1920 ("span" . "Generic inline container")
1caf38eb
RS
1921 ("strong" . "Standout text")
1922 ("sub" . "Subscript")
1923 ("sup" . "Superscript")
1924 ("table" . "Table with rows and columns")
1925 ("tb" . "Table vertical break")
1926 ("td" . "Table data cell")
1927 ("textarea" . "Form multiline edit area")
1928 ("th" . "Table header cell")
1929 ("title" . "Document title")
1930 ("tr" . "Table row separator")
1931 ("tt" . "Typewriter face")
1932 ("u" . "Underlined text")
1933 ("ul" . "Unordered list")
1934 ("var" . "Math variable face")
1935 ("wbr" . "Enable <br> within <nobr>"))
9fc9a531 1936 "Value of variable `sgml-tag-help' for HTML mode.")
9d4ce428 1937
1dddcf4c
GM
1938(defvar outline-regexp)
1939(defvar outline-heading-end-regexp)
1940(defvar outline-level)
1941
f38cd76e
JA
1942(defun html-current-defun-name ()
1943 "Return the name of the last HTML title or heading, or nil."
1944 (save-excursion
1945 (if (re-search-backward
1946 (concat
1947 "<[ \t\r\n]*"
1948 "\\(?:[hH][0-6]\\|title\\|TITLE\\|Title\\)"
1949 "[^>]*>"
1950 "[ \t\r\n]*"
1951 "\\([^<\r\n]*[^ <\t\r\n]+\\)")
1952 nil t)
1953 (match-string-no-properties 1))))
1954
3bf0b727 1955\f
1caf38eb 1956;;;###autoload
5d503af9 1957(define-derived-mode html-mode sgml-mode '(sgml-xml-mode "XHTML" "HTML")
1caf38eb 1958 "Major mode based on SGML mode for editing HTML documents.
7be38f7d 1959This allows inserting skeleton constructs used in hypertext documents with
fcc3195e
RS
1960completion. See below for an introduction to HTML. Use
1961\\[browse-url-of-buffer] to see how this comes out. See also `sgml-mode' on
1962which this is based.
1caf38eb 1963
fcc3195e 1964Do \\[describe-variable] html- SPC and \\[describe-variable] sgml- SPC to see available variables.
1caf38eb
RS
1965
1966To write fairly well formatted pages you only need to know few things. Most
1967browsers have a function to read the source code of the page being seen, so
1968you can imitate various tricks. Here's a very short HTML primer which you
1969can also view with a browser to see what happens:
1970
1971<title>A Title Describing Contents</title> should be on every page. Pages can
1972have <h1>Very Major Headlines</h1> through <h6>Very Minor Headlines</h6>
1973<hr> Parts can be separated with horizontal rules.
1974
1975<p>Paragraphs only need an opening tag. Line breaks and multiple spaces are
1976ignored unless the text is <pre>preformatted.</pre> Text can be marked as
73cba75d 1977<b>bold</b>, <i>italic</i> or <u>underlined</u> using the normal M-o or
1caf38eb
RS
1978Edit/Text Properties/Face commands.
1979
1980Pages can have <a name=\"SOMENAME\">named points</a> and can link other points
1981to them with <a href=\"#SOMENAME\">see also somename</a>. In the same way <a
1982href=\"URL\">see also URL</a> where URL is a filename relative to current
f788776c 1983directory, or absolute as in `http://www.cs.indiana.edu/elisp/w3/docs.html'.
1caf38eb
RS
1984
1985Images in many formats can be inlined with <img src=\"URL\">.
1986
f788776c
RS
1987If you mainly create your own documents, `sgml-specials' might be
1988interesting. But note that some HTML 2 browsers can't handle `&apos;'.
1989To work around that, do:
1990 (eval-after-load \"sgml-mode\" '(aset sgml-char-names ?' nil))
1caf38eb 1991
1caf38eb 1992\\{html-mode-map}"
92eadba5
CY
1993 (setq-local sgml-display-text html-display-text)
1994 (setq-local sgml-tag-face-alist html-tag-face-alist)
1995 (setq-local sgml-tag-alist html-tag-alist)
1996 (setq-local sgml-face-tag-alist html-face-tag-alist)
1997 (setq-local sgml-tag-help html-tag-help)
1998 (setq-local outline-regexp "^.*<[Hh][1-6]\\>")
1999 (setq-local outline-heading-end-regexp "</[Hh][1-6]>")
2000 (setq-local outline-level
2001 (lambda () (char-before (match-end 0))))
f38cd76e 2002 (setq-local add-log-current-defun-function #'html-current-defun-name)
92eadba5
CY
2003 (setq-local sentence-end-base "[.?!][]\"'”)}]*\\(<[^>]*>\\)*")
2004
3bf0b727 2005 (setq imenu-create-index-function 'html-imenu-index)
92eadba5
CY
2006
2007 (setq-local sgml-empty-tags
2008 ;; From HTML-4.01's loose.dtd, parsed with
2009 ;; `sgml-parse-dtd', plus manual addition of "wbr".
2010 '("area" "base" "basefont" "br" "col" "frame" "hr" "img" "input"
2011 "isindex" "link" "meta" "param" "wbr"))
2012 (setq-local sgml-unclosed-tags
2013 ;; From HTML-4.01's loose.dtd, parsed with `sgml-parse-dtd'.
2014 '("body" "colgroup" "dd" "dt" "head" "html" "li" "option"
2015 "p" "tbody" "td" "tfoot" "th" "thead" "tr"))
e1940c83
SM
2016 ;; It's for the user to decide if it defeats it or not -stef
2017 ;; (make-local-variable 'imenu-sort-function)
2018 ;; (setq imenu-sort-function nil) ; sorting the menu defeats the purpose
64367655 2019 )
9d4ce428 2020
3bf0b727
RS
2021(defvar html-imenu-regexp
2022 "\\s-*<h\\([1-9]\\)[^\n<>]*>\\(<[^\n<>]*>\\)*\\s-*\\([^\n<>]*\\)"
fb7ada5f 2023 "A regular expression matching a head line to be added to the menu.
3bf0b727
RS
2024The first `match-string' should be a number from 1-9.
2025The second `match-string' matches extra tags and is ignored.
2026The third `match-string' will be the used in the menu.")
2027
2028(defun html-imenu-index ()
a9d4efa2 2029 "Return a table of contents for an HTML buffer for use with Imenu."
3bf0b727
RS
2030 (let (toc-index)
2031 (save-excursion
2032 (goto-char (point-min))
2033 (while (re-search-forward html-imenu-regexp nil t)
2034 (setq toc-index
2035 (cons (cons (concat (make-string
2036 (* 2 (1- (string-to-number (match-string 1))))
fae1a906 2037 ?\s)
3bf0b727 2038 (match-string 3))
5950e029 2039 (line-beginning-position))
3bf0b727
RS
2040 toc-index))))
2041 (nreverse toc-index)))
1caf38eb 2042
4e7a42d2 2043(define-minor-mode html-autoview-mode
ac6c8639
CY
2044 "Toggle viewing of HTML files on save (HTML Autoview mode).
2045With a prefix argument ARG, enable HTML Autoview mode if ARG is
2046positive, and disable it otherwise. If called from Lisp, enable
2047the mode if ARG is omitted or nil.
2048
2049HTML Autoview mode is a buffer-local minor mode for use with
2050`html-mode'. If enabled, saving the file automatically runs
2051`browse-url-of-buffer' to view it."
4e7a42d2 2052 nil nil nil
966cdb22 2053 :group 'sgml
4e7a42d2
SM
2054 (if html-autoview-mode
2055 (add-hook 'after-save-hook 'browse-url-of-buffer nil t)
2056 (remove-hook 'after-save-hook 'browse-url-of-buffer t)))
9d4ce428 2057
3bf0b727 2058\f
1caf38eb
RS
2059(define-skeleton html-href-anchor
2060 "HTML anchor tag with href attribute."
a391b179 2061 "URL: "
af3abed1 2062 ;; '(setq input "http:")
a391b179 2063 "<a href=\"" str "\">" _ "</a>")
1caf38eb
RS
2064
2065(define-skeleton html-name-anchor
2066 "HTML anchor tag with name attribute."
a391b179 2067 "Name: "
af3abed1
JL
2068 "<a name=\"" str "\""
2069 (if sgml-xml-mode (concat " id=\"" str "\""))
2070 ">" _ "</a>")
1caf38eb 2071
7e49eef2
RS
2072(define-skeleton html-headline-1
2073 "HTML level 1 headline tags."
2074 nil
2075 "<h1>" _ "</h1>")
2076
2077(define-skeleton html-headline-2
2078 "HTML level 2 headline tags."
2079 nil
2080 "<h2>" _ "</h2>")
2081
2082(define-skeleton html-headline-3
2083 "HTML level 3 headline tags."
2084 nil
2085 "<h3>" _ "</h3>")
2086
2087(define-skeleton html-headline-4
2088 "HTML level 4 headline tags."
2089 nil
2090 "<h4>" _ "</h4>")
2091
2092(define-skeleton html-headline-5
2093 "HTML level 5 headline tags."
2094 nil
2095 "<h5>" _ "</h5>")
2096
2097(define-skeleton html-headline-6
2098 "HTML level 6 headline tags."
2099 nil
2100 "<h6>" _ "</h6>")
1caf38eb
RS
2101
2102(define-skeleton html-horizontal-rule
2103 "HTML horizontal rule tag."
2104 nil
af3abed1 2105 (if sgml-xml-mode "<hr />" "<hr>") \n)
1caf38eb
RS
2106
2107(define-skeleton html-image
2108 "HTML image tag."
af3abed1
JL
2109 "Image URL: "
2110 "<img src=\"" str "\" alt=\"" _ "\""
2111 (if sgml-xml-mode " />" ">"))
1caf38eb
RS
2112
2113(define-skeleton html-line
2114 "HTML line break tag."
2115 nil
af3abed1 2116 (if sgml-xml-mode "<br />" "<br>") \n)
1caf38eb 2117
7e49eef2
RS
2118(define-skeleton html-ordered-list
2119 "HTML ordered list tags."
2120 nil
a391b179 2121 "<ol>" \n
a3ec4ba0 2122 "<li>" _ (if sgml-xml-mode "</li>") \n
7e49eef2
RS
2123 "</ol>")
2124
2125(define-skeleton html-unordered-list
2126 "HTML unordered list tags."
2127 nil
a391b179 2128 "<ul>" \n
a3ec4ba0 2129 "<li>" _ (if sgml-xml-mode "</li>") \n
7e49eef2 2130 "</ul>")
1caf38eb
RS
2131
2132(define-skeleton html-list-item
2133 "HTML list item tag."
2134 nil
2135 (if (bolp) nil '\n)
a3ec4ba0 2136 "<li>" _ (if sgml-xml-mode "</li>"))
1caf38eb
RS
2137
2138(define-skeleton html-paragraph
2139 "HTML paragraph tag."
2140 nil
2141 (if (bolp) nil ?\n)
af3abed1 2142 "<p>" _ (if sgml-xml-mode "</p>"))
1caf38eb 2143
fcc3195e
RS
2144(define-skeleton html-checkboxes
2145 "Group of connected checkbox inputs."
2146 nil
a391b179
RS
2147 '(setq v1 nil
2148 v2 nil)
2149 ("Value: "
d10447ba 2150 "<input type=\"" (identity "checkbox") ; see comment above about identity
a391b179 2151 "\" name=\"" (or v1 (setq v1 (skeleton-read "Name: ")))
fcc3195e 2152 "\" value=\"" str ?\"
b4f05c38 2153 (when (y-or-n-p "Set \"checked\" attribute? ")
aa18ec07 2154 (funcall skeleton-transformation-function
af3abed1
JL
2155 (if sgml-xml-mode " checked=\"checked\"" " checked")))
2156 (if sgml-xml-mode " />" ">")
a391b179
RS
2157 (skeleton-read "Text: " (capitalize str))
2158 (or v2 (setq v2 (if (y-or-n-p "Newline after text? ")
aa18ec07 2159 (funcall skeleton-transformation-function
af3abed1 2160 (if sgml-xml-mode "<br />" "<br>"))
a391b179
RS
2161 "")))
2162 \n))
fcc3195e 2163
1caf38eb
RS
2164(define-skeleton html-radio-buttons
2165 "Group of connected radio button inputs."
2166 nil
a391b179
RS
2167 '(setq v1 nil
2168 v2 (cons nil nil))
2169 ("Value: "
d10447ba 2170 "<input type=\"" (identity "radio") ; see comment above about identity
a391b179 2171 "\" name=\"" (or (car v2) (setcar v2 (skeleton-read "Name: ")))
1caf38eb 2172 "\" value=\"" str ?\"
b4f05c38 2173 (when (and (not v1) (setq v1 (y-or-n-p "Set \"checked\" attribute? ")))
aa18ec07 2174 (funcall skeleton-transformation-function
af3abed1
JL
2175 (if sgml-xml-mode " checked=\"checked\"" " checked")))
2176 (if sgml-xml-mode " />" ">")
a391b179
RS
2177 (skeleton-read "Text: " (capitalize str))
2178 (or (cdr v2) (setcdr v2 (if (y-or-n-p "Newline after text? ")
aa18ec07 2179 (funcall skeleton-transformation-function
af3abed1 2180 (if sgml-xml-mode "<br />" "<br>"))
a391b179
RS
2181 "")))
2182 \n))
1caf38eb 2183
e1940c83 2184(provide 'sgml-mode)
6a05d05f 2185
72c0ae01 2186;;; sgml-mode.el ends here