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