Resurrect a comment lost in the previous commit.
[bpt/emacs.git] / lisp / textmodes / sgml-mode.el
CommitLineData
1caf38eb 1;;; sgml-mode.el --- SGML- and HTML-editing modes
72c0ae01 2
e1940c83 3;; Copyright (C) 1992,95,96,98,2001 Free Software Foundation, Inc.
6d74b528 4
64ae0c23 5;; Author: James Clark <jjc@jclark.com>
3e910376 6;; Adapted-By: ESR, Daniel Pfeiffer <occitan@esperanto.org>,
a391b179 7;; F.Potorti@cnuce.cnr.it
1caf38eb 8;; Keywords: wp, hypermedia, comm, languages
72c0ae01 9
72c0ae01
ER
10;; This file is part of GNU Emacs.
11
12;; GNU Emacs is free software; you can redistribute it and/or modify
13;; it under the terms of the GNU General Public License as published by
7c938215 14;; the Free Software Foundation; either version 2, or (at your option)
72c0ae01
ER
15;; any later version.
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
b578f267
EN
23;; along with GNU Emacs; see the file COPYING. If not, write to the
24;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
25;; Boston, MA 02111-1307, USA.
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)
37 (require 'outline))
b0a377e6 38
64ae0c23
RS
39(defgroup sgml nil
40 "SGML editing mode"
41 :group 'languages)
42
d10447ba 43(defcustom sgml-transformation 'identity
a391b179
RS
44 "*Default value for `skeleton-transformation' (which see) in SGML mode."
45 :type 'function
c60e7b0d 46 :group 'sgml)
a391b179
RS
47
48(put 'sgml-transformation 'variable-interactive
49 "aTransformation function: ")
50
d4c89075
DL
51(defcustom sgml-mode-hook nil
52 "Hook run by command `sgml-mode'.
53`text-mode-hook' is run first."
54 :group 'sgml
55 :type 'hook)
56
1caf38eb
RS
57;; As long as Emacs' syntax can't be complemented with predicates to context
58;; sensitively confirm the syntax of characters, we have to live with this
59;; kludgy kind of tradeoff.
21a6f23c 60(defvar sgml-specials '(?\")
f788776c 61 "List of characters that have a special meaning for SGML mode.
1caf38eb
RS
62This list is used when first loading the sgml-mode library.
63The supported characters and potential disadvantages are:
64
65 ?\\\" Makes \" in text start a string.
66 ?' Makes ' in text start a string.
67 ?- Makes -- in text start a comment.
68
4fa91cfe 69When only one of ?\\\" or ?' are included, \"'\" or '\"', as can be found in
1caf38eb 70DTDs, start a string. To partially avoid this problem this also makes these
21a6f23c
RS
71self insert as named entities depending on `sgml-quick-keys'.
72
73Including ?- has the problem of affecting dashes that have nothing to do
74with comments, so we normally turn it off.")
fcc3195e
RS
75
76(defvar sgml-quick-keys nil
f788776c
RS
77 "Use <, >, &, SPC and `sgml-specials' keys \"electrically\" when non-nil.
78This takes effect when first loading the sgml-mode library.")
1caf38eb
RS
79
80
81(defvar sgml-mode-map
e1940c83 82 (let ((map (make-keymap)) ;`sparse' doesn't allow binding to charsets.
1caf38eb 83 (menu-map (make-sparse-keymap "SGML")))
1caf38eb
RS
84 (define-key map "\C-c\C-i" 'sgml-tags-invisible)
85 (define-key map "/" 'sgml-slash)
fcc3195e
RS
86 (define-key map "\C-c\C-n" 'sgml-name-char)
87 (define-key map "\C-c\C-t" 'sgml-tag)
1caf38eb
RS
88 (define-key map "\C-c\C-a" 'sgml-attributes)
89 (define-key map "\C-c\C-b" 'sgml-skip-tag-backward)
90 (define-key map [?\C-c left] 'sgml-skip-tag-backward)
91 (define-key map "\C-c\C-f" 'sgml-skip-tag-forward)
92 (define-key map [?\C-c right] 'sgml-skip-tag-forward)
93 (define-key map "\C-c\C-d" 'sgml-delete-tag)
94 (define-key map "\C-c\^?" 'sgml-delete-tag)
95 (define-key map "\C-c?" 'sgml-tag-help)
1caf38eb
RS
96 (define-key map "\C-c8" 'sgml-name-8bit-mode)
97 (define-key map "\C-c\C-v" 'sgml-validate)
b4f05c38
SS
98 (when sgml-quick-keys
99 (define-key map "&" 'sgml-name-char)
100 (define-key map "<" 'sgml-tag)
101 (define-key map " " 'sgml-auto-attributes)
102 (define-key map ">" 'sgml-maybe-end-tag)
103 (when (memq ?\" sgml-specials)
104 (define-key map "\"" 'sgml-name-self))
105 (when (memq ?' sgml-specials)
106 (define-key map "'" 'sgml-name-self)))
f7ac3e28
SM
107 (define-key map (vector (make-char 'latin-iso8859-1))
108 'sgml-maybe-name-self)
2840d653
EZ
109 (let ((c 127)
110 (map (nth 1 map)))
111 (while (< (setq c (1+ c)) 256)
112 (aset map c 'sgml-maybe-name-self)))
1caf38eb
RS
113 (define-key map [menu-bar sgml] (cons "SGML" menu-map))
114 (define-key menu-map [sgml-validate] '("Validate" . sgml-validate))
115 (define-key menu-map [sgml-name-8bit-mode]
116 '("Toggle 8 Bit Insertion" . sgml-name-8bit-mode))
117 (define-key menu-map [sgml-tags-invisible]
118 '("Toggle Tag Visibility" . sgml-tags-invisible))
119 (define-key menu-map [sgml-tag-help]
120 '("Describe Tag" . sgml-tag-help))
121 (define-key menu-map [sgml-delete-tag]
122 '("Delete Tag" . sgml-delete-tag))
123 (define-key menu-map [sgml-skip-tag-forward]
124 '("Forward Tag" . sgml-skip-tag-forward))
125 (define-key menu-map [sgml-skip-tag-backward]
126 '("Backward Tag" . sgml-skip-tag-backward))
127 (define-key menu-map [sgml-attributes]
128 '("Insert Attributes" . sgml-attributes))
129 (define-key menu-map [sgml-tag] '("Insert Tag" . sgml-tag))
130 map)
131 "Keymap for SGML mode. See also `sgml-specials'.")
132
133
134(defvar sgml-mode-syntax-table
135 (let ((table (copy-syntax-table text-mode-syntax-table)))
136 (modify-syntax-entry ?< "(>" table)
137 (modify-syntax-entry ?> ")<" table)
138 (if (memq ?- sgml-specials)
139 (modify-syntax-entry ?- "_ 1234" table))
140 (if (memq ?\" sgml-specials)
141 (modify-syntax-entry ?\" "\"\"" table))
142 (if (memq ?' sgml-specials)
143 (modify-syntax-entry ?\' "\"'" table))
144 table)
145 "Syntax table used in SGML mode. See also `sgml-specials'.")
146
72c0ae01 147
64ae0c23 148(defcustom sgml-name-8bit-mode nil
2840d653 149 "*When non-nil, insert non-ASCII characters as named entities."
64ae0c23
RS
150 :type 'boolean
151 :group 'sgml)
72c0ae01 152
1caf38eb
RS
153(defvar sgml-char-names
154 [nil nil nil nil nil nil nil nil
155 nil nil nil nil nil nil nil nil
156 nil nil nil nil nil nil nil nil
157 nil nil nil nil nil nil nil nil
a391b179 158 "nbsp" "excl" "quot" "num" "dollar" "percnt" "amp" "apos"
1caf38eb
RS
159 "lpar" "rpar" "ast" "plus" "comma" "hyphen" "period" "sol"
160 nil nil nil nil nil nil nil nil
161 nil nil "colon" "semi" "lt" "eq" "gt" "quest"
162 "commat" nil nil nil nil nil nil nil
163 nil nil nil nil nil nil nil nil
164 nil nil nil nil nil nil nil nil
165 nil nil nil "lsqb" nil "rsqb" "uarr" "lowbar"
166 "lsquo" nil nil nil nil nil nil nil
167 nil nil nil nil nil nil nil nil
168 nil nil nil nil nil nil nil nil
169 nil nil nil "lcub" "verbar" "rcub" "tilde" nil
170 nil nil nil nil nil nil nil nil
171 nil nil nil nil nil nil nil nil
172 nil nil nil nil nil nil nil nil
173 nil nil nil nil nil nil nil nil
174 "nbsp" "iexcl" "cent" "pound" "curren" "yen" "brvbar" "sect"
175 "uml" "copy" "ordf" "laquo" "not" "shy" "reg" "macr"
176 "ring" "plusmn" "sup2" "sup3" "acute" "micro" "para" "middot"
e79ad8a1 177 "cedil" "sup1" "ordm" "raquo" "frac14" "frac12" "frac34" "iquest"
1caf38eb
RS
178 "Agrave" "Aacute" "Acirc" "Atilde" "Auml" "Aring" "AElig" "Ccedil"
179 "Egrave" "Eacute" "Ecirc" "Euml" "Igrave" "Iacute" "Icirc" "Iuml"
180 "ETH" "Ntilde" "Ograve" "Oacute" "Ocirc" "Otilde" "Ouml" nil
181 "Oslash" "Ugrave" "Uacute" "Ucirc" "Uuml" "Yacute" "THORN" "szlig"
182 "agrave" "aacute" "acirc" "atilde" "auml" "aring" "aelig" "ccedil"
183 "egrave" "eacute" "ecirc" "euml" "igrave" "iacute" "icirc" "iuml"
184 "eth" "ntilde" "ograve" "oacute" "ocirc" "otilde" "ouml" "divide"
185 "oslash" "ugrave" "uacute" "ucirc" "uuml" "yacute" "thorn" "yuml"]
186 "Vector of symbolic character names without `&' and `;'.")
187
2840d653
EZ
188(put 'sgml-table 'char-table-extra-slots 0)
189
190(defvar sgml-char-names-table
191 (let ((table (make-char-table 'sgml-table))
192 (i 32)
193 elt)
194 (while (< i 256)
195 (setq elt (aref sgml-char-names i))
196 (if elt (aset table (make-char 'latin-iso8859-1 i) elt))
197 (setq i (1+ i)))
198 table)
199 "A table for mapping non-ASCII characters into SGML entity names.
200Currently, only Latin-1 characters are supported.")
201
1caf38eb 202
5121371d
DL
203;; nsgmls is a free SGML parser in the SP suite available from
204;; ftp.jclark.com and otherwise packaged for GNU systems.
1caf38eb
RS
205;; Its error messages can be parsed by next-error.
206;; The -s option suppresses output.
207
5121371d 208(defcustom sgml-validate-command "nsgmls -s" ; replaced old `sgmls'
72c0ae01
ER
209 "*The command to validate an SGML document.
210The file name of current buffer file name will be appended to this,
64ae0c23
RS
211separated by a space."
212 :type 'string
d4c89075 213 :version "21.1"
64ae0c23 214 :group 'sgml)
72c0ae01
ER
215
216(defvar sgml-saved-validate-command nil
217 "The command last used to validate in this buffer.")
218
72c0ae01 219
e1940c83
SM
220;; I doubt that null end tags are used much for large elements,
221;; so use a small distance here.
64ae0c23 222(defcustom sgml-slash-distance 1000
f788776c 223 "*If non-nil, is the maximum distance to search for matching `/'."
64ae0c23
RS
224 :type '(choice (const nil) integer)
225 :group 'sgml)
72c0ae01 226
1caf38eb
RS
227(defconst sgml-start-tag-regex
228 "<[A-Za-z]\\([-.A-Za-z0-9= \n\t]\\|\"[^\"]*\"\\|'[^']*'\\)*"
229 "Regular expression that matches a non-empty start tag.
f788776c 230Any terminating `>' or `/' is not matched.")
1caf38eb
RS
231
232
c6a63534
RS
233;; internal
234(defconst sgml-font-lock-keywords-1
b0a377e6
DL
235 '(("<\\([!?][a-z][-.a-z0-9]*\\)" 1 font-lock-keyword-face)
236 ("<\\(/?[a-z][-.a-z0-9]*\\)" 1 font-lock-function-name-face)
237 ("[&%][a-z][-.a-z0-9]*;?" . font-lock-variable-name-face)
4fa91cfe 238 ("<! *--.*-- *>" . font-lock-comment-face)))
c6a63534
RS
239
240(defconst sgml-font-lock-keywords-2 ())
241
242;; for font-lock, but must be defvar'ed after
243;; sgml-font-lock-keywords-1 and sgml-font-lock-keywords-2 above
244(defvar sgml-font-lock-keywords sgml-font-lock-keywords-1
1caf38eb
RS
245 "*Rules for highlighting SGML code. See also `sgml-tag-face-alist'.")
246
247;; internal
1caf38eb
RS
248(defvar sgml-face-tag-alist ()
249 "Alist of face and tag name for facemenu.")
250
251(defvar sgml-tag-face-alist ()
252 "Tag names and face or list of faces to fontify with when invisible.
253When `font-lock-maximum-decoration' is 1 this is always used for fontifying.
254When more these are fontified together with `sgml-font-lock-keywords'.")
255
256
257(defvar sgml-display-text ()
258 "Tag names as lowercase symbols, and display string when invisible.")
259
260;; internal
261(defvar sgml-tags-invisible nil)
262
263
64ae0c23 264(defcustom sgml-tag-alist
fcc3195e
RS
265 '(("![" ("ignore" t) ("include" t))
266 ("!attlist")
1caf38eb
RS
267 ("!doctype")
268 ("!element")
269 ("!entity"))
270 "*Alist of tag names for completing read and insertion rules.
271This alist is made up as
272
273 ((\"tag\" . TAGRULE)
274 ...)
275
276TAGRULE is a list of optionally `t' (no endtag) or `\\n' (separate endtag by
277newlines) or a skeleton with `nil', `t' or `\\n' in place of the interactor
278followed by an ATTRIBUTERULE (for an always present attribute) or an
279attribute alist.
280
281The attribute alist is made up as
282
283 ((\"attribute\" . ATTRIBUTERULE)
284 ...)
285
286ATTRIBUTERULE is a list of optionally `t' (no value when no input) followed by
64ae0c23
RS
287an optional alist of possible values."
288 :type '(repeat (cons (string :tag "Tag Name")
289 (repeat :tag "Tag Rule" sexp)))
290 :group 'sgml)
1caf38eb 291
64ae0c23 292(defcustom sgml-tag-help
1caf38eb
RS
293 '(("!" . "Empty declaration for comment")
294 ("![" . "Embed declarations with parser directive")
295 ("!attlist" . "Tag attributes declaration")
296 ("!doctype" . "Document type (DTD) declaration")
297 ("!element" . "Tag declaration")
298 ("!entity" . "Entity (macro) declaration"))
64ae0c23
RS
299 "*Alist of tag name and short description."
300 :type '(repeat (cons (string :tag "Tag Name")
301 (string :tag "Description")))
302 :group 'sgml)
1caf38eb 303
b0a377e6
DL
304(defvar v2) ; free for skeleton
305
1caf38eb
RS
306(defun sgml-mode-common (sgml-tag-face-alist sgml-display-text)
307 "Common code for setting up `sgml-mode' and derived modes.
c6a63534 308SGML-TAG-FACE-ALIST is used for calculating `sgml-font-lock-keywords-2'.
1caf38eb
RS
309SGML-DISPLAY-TEXT sets up alternate text for when tags are invisible (see
310varables of same name)."
72c0ae01 311 (setq local-abbrev-table text-mode-abbrev-table)
1caf38eb
RS
312 (set-syntax-table sgml-mode-syntax-table)
313 (make-local-variable 'indent-line-function)
72c0ae01 314 (make-local-variable 'paragraph-start)
72c0ae01 315 (make-local-variable 'paragraph-separate)
9e832d81 316 (make-local-variable 'adaptive-fill-regexp)
72c0ae01 317 (make-local-variable 'sgml-saved-validate-command)
72c0ae01 318 (make-local-variable 'comment-start)
72c0ae01 319 (make-local-variable 'comment-end)
e41b2db1 320 (make-local-variable 'comment-indent-function)
1caf38eb
RS
321 (make-local-variable 'comment-indent-function)
322 (make-local-variable 'sgml-tags-invisible)
323 (make-local-variable 'skeleton-transformation)
324 (make-local-variable 'skeleton-further-elements)
325 (make-local-variable 'skeleton-end-hook)
326 (make-local-variable 'font-lock-defaults)
327 (make-local-variable 'sgml-font-lock-keywords-1)
c6a63534 328 (make-local-variable 'sgml-font-lock-keywords-2)
1caf38eb
RS
329 (make-local-variable 'facemenu-add-face-function)
330 (make-local-variable 'facemenu-end-add-face)
331 ;;(make-local-variable 'facemenu-remove-face-function)
332 (and sgml-tag-face-alist
333 (not (assq 1 sgml-tag-face-alist))
334 (nconc sgml-tag-face-alist
335 `((1 (,(concat "<\\("
336 (mapconcat 'car sgml-tag-face-alist "\\|")
337 "\\)\\([ \t].+\\)?>\\(.+\\)</\\1>")
aa7a8f0e
DL
338 3 (cdr (assoc (downcase (match-string 1))
339 ',sgml-tag-face-alist)))))))
1caf38eb
RS
340 (setq indent-line-function 'indent-relative-maybe
341 ;; A start or end tag by itself on a line separates a paragraph.
342 ;; This is desirable because SGML discards a newline that appears
343 ;; immediately after a start tag or immediately before an end tag.
6c455d5c
RS
344 paragraph-separate "[ \t]*$\\|\
345\[ \t]*</?\\([A-Za-z]\\([-.A-Za-z0-9= \t\n]\\|\"[^\"]*\"\\|'[^']*'\\)*\\)?>$"
346 paragraph-start "[ \t]*$\\|\
fba5d1de 347\[ \t]*</?\\([A-Za-z]\\([-.A-Za-z0-9= \t\n]\\|\"[^\"]*\"\\|'[^']*'\\)*\\)?>"
6c455d5c 348 adaptive-fill-regexp "[ \t]*"
1caf38eb
RS
349 comment-start "<!-- "
350 comment-end " -->"
351 comment-indent-function 'sgml-comment-indent
a391b179 352 skeleton-transformation sgml-transformation
1caf38eb
RS
353 skeleton-further-elements '((completion-ignore-case t))
354 skeleton-end-hook (lambda ()
355 (or (eolp)
356 (not (or (eq v2 '\n)
357 (eq (car-safe v2) '\n)))
358 (newline-and-indent)))
c6a63534
RS
359 sgml-font-lock-keywords-2 (append
360 sgml-font-lock-keywords-1
361 (cdr (assq 1 sgml-tag-face-alist)))
1caf38eb 362 font-lock-defaults '((sgml-font-lock-keywords
c6a63534
RS
363 sgml-font-lock-keywords-1
364 sgml-font-lock-keywords-2)
1caf38eb
RS
365 nil
366 t)
6811a757 367 facemenu-add-face-function 'sgml-mode-facemenu-add-face-function)
4afa094d
SM
368 ;; This will allow existing comments within declarations to be
369 ;; recognized.
370 (set (make-local-variable 'comment-start-skip) "\\(?:<!\\)?--[ \t]*")
371 (set (make-local-variable 'comment-end-skip) "[ \t]*--\\([ \t\n]*>\\)?")
372 (dolist (pair sgml-display-text)
373 (put (car pair) 'before-string (cdr pair))))
72c0ae01 374
1caf38eb 375
6811a757
RS
376(defun sgml-mode-facemenu-add-face-function (face end)
377 (if (setq face (cdr (assq face sgml-face-tag-alist)))
378 (progn
379 (setq face (funcall skeleton-transformation face))
380 (setq facemenu-end-add-face (concat "</" face ">"))
381 (concat "<" face ">"))
6a05d05f 382 (error "Face not configured for %s mode" mode-name)))
6811a757
RS
383
384
1caf38eb 385;;;###autoload
4afa094d 386(defun sgml-mode ()
1caf38eb
RS
387 "Major mode for editing SGML documents.
388Makes > match <. Makes / blink matching /.
fcc3195e
RS
389Keys <, &, SPC within <>, \" and ' can be electric depending on
390`sgml-quick-keys'.
1caf38eb 391
f788776c
RS
392An argument of N to a tag-inserting command means to wrap it around
393the next N words. In Transient Mark mode, when the mark is active,
394N defaults to -1, which means to wrap it around the current region.
a391b179 395
d10447ba 396If you like upcased tags, put (setq sgml-transformation 'upcase) in
f788776c 397your `.emacs' file.
1caf38eb
RS
398
399Use \\[sgml-validate] to validate your document with an SGML parser.
a391b179
RS
400
401Do \\[describe-variable] sgml- SPC to see available variables.
402Do \\[describe-key] on the following bindings to discover what they do.
1caf38eb
RS
403\\{sgml-mode-map}"
404 (interactive)
a01588fc
RS
405 (kill-all-local-variables)
406 (setq mode-name "SGML"
407 major-mode 'sgml-mode)
1caf38eb 408 (sgml-mode-common sgml-tag-face-alist sgml-display-text)
ec79b93a
KH
409 ;; Set imenu-generic-expression here, rather than in sgml-mode-common,
410 ;; because this definition probably is not useful in HTML mode.
411 (make-local-variable 'imenu-generic-expression)
412 (setq imenu-generic-expression
413 "<!\\(element\\|entity\\)[ \t\n]+%?[ \t\n]*\\([A-Za-z][-A-Za-z.0-9]*\\)")
1caf38eb 414 (use-local-map sgml-mode-map)
a01588fc 415 (run-hooks 'text-mode-hook 'sgml-mode-hook))
1caf38eb
RS
416
417
72c0ae01 418(defun sgml-comment-indent ()
4afa094d 419 (if (looking-at "--") comment-column 0))
72c0ae01 420
72c0ae01 421
72c0ae01
ER
422
423(defun sgml-slash (arg)
f788776c
RS
424 "Insert `/' and display any previous matching `/'.
425Two `/'s are treated as matching if the first `/' ends a net-enabling
426start tag, and the second `/' is the corresponding null end tag."
72c0ae01
ER
427 (interactive "p")
428 (insert-char ?/ arg)
429 (if (> arg 0)
430 (let ((oldpos (point))
431 (blinkpos)
432 (level 0))
433 (save-excursion
434 (save-restriction
435 (if sgml-slash-distance
436 (narrow-to-region (max (point-min)
437 (- (point) sgml-slash-distance))
438 oldpos))
439 (if (and (re-search-backward sgml-start-tag-regex (point-min) t)
440 (eq (match-end 0) (1- oldpos)))
441 ()
442 (goto-char (1- oldpos))
443 (while (and (not blinkpos)
444 (search-backward "/" (point-min) t))
445 (let ((tagend (save-excursion
446 (if (re-search-backward sgml-start-tag-regex
447 (point-min) t)
448 (match-end 0)
449 nil))))
450 (if (eq tagend (point))
451 (if (eq level 0)
452 (setq blinkpos (point))
453 (setq level (1- level)))
454 (setq level (1+ level)))))))
455 (if blinkpos
456 (progn
457 (goto-char blinkpos)
458 (if (pos-visible-in-window-p)
459 (sit-for 1)
460 (message "Matches %s"
461 (buffer-substring (progn
462 (beginning-of-line)
463 (point))
464 (1+ blinkpos))))))))))
465
1caf38eb
RS
466
467(defun sgml-name-char (&optional char)
468 "Insert a symbolic character name according to `sgml-char-names'.
2840d653
EZ
469Non-ASCII chars may be inserted either with the meta key, as in M-SPC for
470no-break space or M-- for a soft hyphen; or via an input method or
471encoded keyboard operation."
1caf38eb
RS
472 (interactive "*")
473 (insert ?&)
474 (or char
9b0ffdac 475 (setq char (read-quoted-char "Enter char or octal number")))
1caf38eb
RS
476 (delete-backward-char 1)
477 (insert char)
478 (undo-boundary)
479 (delete-backward-char 1)
2840d653
EZ
480 (cond
481 ((< char 256)
482 (insert ?&
483 (or (aref sgml-char-names char)
484 (format "#%d" char))
485 ?\;))
486 ((aref sgml-char-names-table char)
487 (insert ?& (aref sgml-char-names-table char) ?\;))
488 ((memq (char-charset char) '(mule-unicode-0100-24ff
489 mule-unicode-2500-33ff
490 mule-unicode-e000-ffff))
491 (insert (format "&#%d;" (encode-char char 'ucs))))
492 (t
493 (insert char))))
1caf38eb
RS
494
495(defun sgml-name-self ()
496 "Insert a symbolic character name according to `sgml-char-names'."
497 (interactive "*")
498 (sgml-name-char last-command-char))
499
1caf38eb
RS
500(defun sgml-maybe-name-self ()
501 "Insert a symbolic character name according to `sgml-char-names'."
502 (interactive "*")
503 (if sgml-name-8bit-mode
2840d653
EZ
504 (let ((mc last-command-char))
505 (if (< mc 256)
506 (setq mc (unibyte-char-to-multibyte mc)))
507 (or mc (setq mc last-command-char))
508 (sgml-name-char mc))
1caf38eb
RS
509 (self-insert-command 1)))
510
1caf38eb 511(defun sgml-name-8bit-mode ()
2840d653 512 "Toggle whether to insert named entities instead of non-ASCII characters."
1caf38eb 513 (interactive)
d10447ba 514 (setq sgml-name-8bit-mode (not sgml-name-8bit-mode))
2840d653 515 (message "sgml name entity mode is now %s"
d10447ba 516 (if sgml-name-8bit-mode "ON" "OFF")))
1caf38eb
RS
517
518
f788776c
RS
519;; When an element of a skeleton is a string "str", it is passed
520;; through skeleton-transformation and inserted. If "str" is to be
521;; inserted literally, one should obtain it as the return value of a
522;; function, e.g. (identity "str").
1caf38eb
RS
523
524(define-skeleton sgml-tag
f788776c
RS
525 "Prompt for a tag and insert it, optionally with attributes.
526Completion and configuration are done according to `sgml-tag-alist'.
d10447ba 527If you like tags and attributes in uppercase do \\[set-variable]
f788776c
RS
528skeleton-transformation RET upcase RET, or put this in your `.emacs':
529 (setq sgml-transformation 'upcase)"
5f5c9e79
SM
530 (funcall skeleton-transformation
531 (completing-read "Tag: " sgml-tag-alist))
4afa094d 532 ?< str |
d10447ba 533 (("") -1 '(undo-boundary) (identity "&lt;")) | ; see comment above
4afa094d
SM
534 `(("") '(setq v2 (sgml-attributes ,str t)) ?>
535 (if (string= "![" ,str)
536 (prog1 '(("") " [ " _ " ]]")
537 (backward-char))
b4f05c38
SS
538 (unless (or (sgml-skip-close-p v2) ; (eq v2 t)
539 (string-match "^[/!?]" ,str))
4afa094d 540 (if (symbolp v2)
5f5c9e79
SM
541 ;; We go use `identity' to prevent skeleton from passing
542 ;; `str' through skeleton-transformation a second time.
543 '(("") v2 _ v2 "</" (identity ',str) ?>)
4afa094d
SM
544 (if (eq (car v2) t)
545 (cons '("") (cdr v2))
546 (append '(("") (car v2))
547 (cdr v2)
5f5c9e79 548 '(resume: (car v2) _ "</" (identity ',str) ?>))))))))
1caf38eb
RS
549
550(autoload 'skeleton-read "skeleton")
551
d10447ba 552(defun sgml-attributes (tag &optional quiet)
f788776c 553 "When at top level of a tag, interactively insert attributes.
d10447ba 554
f788776c
RS
555Completion and configuration of TAG are done according to `sgml-tag-alist'.
556If QUIET, do not print a message when there are no attributes for TAG."
1caf38eb 557 (interactive (list (save-excursion (sgml-beginning-of-tag t))))
d10447ba
RS
558 (or (stringp tag) (error "Wrong context for adding attribute"))
559 (if tag
1caf38eb 560 (let ((completion-ignore-case t)
d10447ba 561 (alist (cdr (assoc (downcase tag) sgml-tag-alist)))
1caf38eb 562 car attribute i)
1caf38eb
RS
563 (if (or (symbolp (car alist))
564 (symbolp (car (car alist))))
565 (setq car (car alist)
566 alist (cdr alist)))
567 (or quiet
568 (message "No attributes configured."))
569 (if (stringp (car alist))
570 (progn
d10447ba
RS
571 (insert (if (eq (preceding-char) ? ) "" ? )
572 (funcall skeleton-transformation (car alist)))
1caf38eb
RS
573 (sgml-value alist))
574 (setq i (length alist))
575 (while (> i 0)
576 (insert ? )
577 (insert (funcall skeleton-transformation
578 (setq attribute
579 (skeleton-read '(completing-read
d10447ba 580 "Attribute: "
1caf38eb
RS
581 alist)))))
582 (if (string= "" attribute)
583 (setq i 0)
aa7a8f0e 584 (sgml-value (assoc (downcase attribute) alist))
1caf38eb
RS
585 (setq i (1- i))))
586 (if (eq (preceding-char) ? )
587 (delete-backward-char 1)))
588 car)))
589
590(defun sgml-auto-attributes (arg)
f788776c
RS
591 "Self insert the character typed; at top level of tag, prompt for attributes.
592With prefix argument, only self insert."
1caf38eb
RS
593 (interactive "*P")
594 (let ((point (point))
595 tag)
596 (if (or arg
1caf38eb
RS
597 (not sgml-tag-alist) ; no message when nothing configured
598 (symbolp (setq tag (save-excursion (sgml-beginning-of-tag t))))
599 (eq (aref tag 0) ?/))
600 (self-insert-command (prefix-numeric-value arg))
601 (sgml-attributes tag)
602 (setq last-command-char ? )
603 (or (> (point) point)
604 (self-insert-command 1)))))
605
606
607(defun sgml-tag-help (&optional tag)
f788776c 608 "Display description of tag TAG. If TAG is omitted, use the tag at point."
1caf38eb
RS
609 (interactive)
610 (or tag
611 (save-excursion
612 (if (eq (following-char) ?<)
613 (forward-char))
614 (setq tag (sgml-beginning-of-tag))))
615 (or (stringp tag)
616 (error "No tag selected"))
617 (setq tag (downcase tag))
f68f40e0 618 (message "%s"
aa7a8f0e 619 (or (cdr (assoc (downcase tag) sgml-tag-help))
1caf38eb 620 (and (eq (aref tag 0) ?/)
aa7a8f0e 621 (cdr (assoc (downcase (substring tag 1)) sgml-tag-help)))
1caf38eb
RS
622 "No description available")))
623
624
625(defun sgml-maybe-end-tag ()
626 "Name self unless in position to end a tag."
627 (interactive)
628 (or (condition-case nil
629 (save-excursion (up-list -1))
630 (error
631 (sgml-name-self)
632 t))
633 (condition-case nil
634 (progn
635 (save-excursion (up-list 1))
636 (sgml-name-self))
637 (error (self-insert-command 1)))))
638
639
640(defun sgml-skip-tag-backward (arg)
641 "Skip to beginning of tag or matching opening tag if present.
f788776c 642With prefix argument ARG, repeat this ARG times."
1caf38eb
RS
643 (interactive "p")
644 (while (>= arg 1)
645 (search-backward "<" nil t)
646 (if (looking-at "</\\([^ \n\t>]+\\)")
647 ;; end tag, skip any nested pairs
648 (let ((case-fold-search t)
649 (re (concat "</?" (regexp-quote (match-string 1)))))
650 (while (and (re-search-backward re nil t)
651 (eq (char-after (1+ (point))) ?/))
652 (forward-char 1)
653 (sgml-skip-tag-backward 1))))
654 (setq arg (1- arg))))
655
656(defun sgml-skip-tag-forward (arg &optional return)
657 "Skip to end of tag or matching closing tag if present.
f788776c 658With prefix argument ARG, repeat this ARG times.
1caf38eb
RS
659Return t iff after a closing tag."
660 (interactive "p")
661 (setq return t)
662 (while (>= arg 1)
663 (skip-chars-forward "^<>")
664 (if (eq (following-char) ?>)
665 (up-list -1))
666 (if (looking-at "<\\([^/ \n\t>]+\\)")
667 ;; start tag, skip any nested same pairs _and_ closing tag
668 (let ((case-fold-search t)
669 (re (concat "</?" (regexp-quote (match-string 1))))
670 point close)
671 (forward-list 1)
672 (setq point (point))
673 (while (and (re-search-forward re nil t)
674 (not (setq close
675 (eq (char-after (1+ (match-beginning 0))) ?/)))
676 (not (up-list -1))
677 (sgml-skip-tag-forward 1))
678 (setq close nil))
679 (if close
680 (up-list 1)
681 (goto-char point)
682 (setq return)))
683 (forward-list 1))
684 (setq arg (1- arg)))
685 return)
686
687(defun sgml-delete-tag (arg)
688 "Delete tag on or after cursor, and matching closing or opening tag.
f788776c 689With prefix argument ARG, repeat this ARG times."
1caf38eb
RS
690 (interactive "p")
691 (while (>= arg 1)
692 (save-excursion
693 (let* (close open)
fcc3195e 694 (if (looking-at "[ \t\n]*<")
1caf38eb
RS
695 ;; just before tag
696 (if (eq (char-after (match-end 0)) ?/)
697 ;; closing tag
698 (progn
699 (setq close (point))
700 (goto-char (match-end 0))))
701 ;; on tag?
702 (or (save-excursion (setq close (sgml-beginning-of-tag)
703 close (and (stringp close)
704 (eq (aref close 0) ?/)
705 (point))))
706 ;; not on closing tag
707 (let ((point (point)))
708 (sgml-skip-tag-backward 1)
709 (if (or (not (eq (following-char) ?<))
710 (save-excursion
711 (forward-list 1)
712 (<= (point) point)))
713 (error "Not on or before tag")))))
714 (if close
715 (progn
716 (sgml-skip-tag-backward 1)
717 (setq open (point))
718 (goto-char close)
719 (kill-sexp 1))
720 (setq open (point))
721 (sgml-skip-tag-forward 1)
722 (backward-list)
723 (forward-char)
724 (if (eq (aref (sgml-beginning-of-tag) 0) ?/)
725 (kill-sexp 1)))
726 (goto-char open)
727 (kill-sexp 1)))
728 (setq arg (1- arg))))
a391b179
RS
729\f
730;; Put read-only last to enable setting this even when read-only enabled.
731(or (get 'sgml-tag 'invisible)
732 (setplist 'sgml-tag
733 (append '(invisible t
734 intangible t
735 point-entered sgml-point-entered
736 rear-nonsticky t
737 read-only t)
738 (symbol-plist 'sgml-tag))))
1caf38eb
RS
739
740(defun sgml-tags-invisible (arg)
741 "Toggle visibility of existing tags."
742 (interactive "P")
743 (let ((modified (buffer-modified-p))
744 (inhibit-read-only t)
e1940c83
SM
745 (inhibit-modification-hooks t)
746 ;; Avoid spurious the `file-locked' checks.
747 (buffer-file-name nil)
a391b179
RS
748 ;; This is needed in case font lock gets called,
749 ;; since it moves point and might call sgml-point-entered.
750 (inhibit-point-motion-hooks t)
1caf38eb 751 symbol)
e1940c83
SM
752 (unwind-protect
753 (save-excursion
754 (goto-char (point-min))
755 (if (setq sgml-tags-invisible
756 (if arg
757 (>= (prefix-numeric-value arg) 0)
758 (not sgml-tags-invisible)))
759 (while (re-search-forward "<\\([!/?A-Za-z][-A-Za-z0-9]*\\)"
760 nil t)
761 (setq symbol (intern-soft (downcase (match-string 1))))
762 (goto-char (match-beginning 0))
763 (and (get symbol 'before-string)
764 (not (overlays-at (point)))
765 (overlay-put (make-overlay (point)
766 (match-beginning 1))
767 'category symbol))
768 (put-text-property (point)
769 (progn (forward-list) (point))
770 'category 'sgml-tag))
771 (let ((pos (point)))
772 (while (< (setq pos (next-overlay-change pos)) (point-max))
773 (delete-overlay (car (overlays-at pos)))))
774 (remove-text-properties (point-min) (point-max)
775 '(category sgml-tag intangible t))))
776 (restore-buffer-modified-p modified))
1caf38eb
RS
777 (run-hooks 'sgml-tags-invisible-hook)
778 (message "")))
779
780(defun sgml-point-entered (x y)
781 ;; Show preceding or following hidden tag, depending of cursor direction.
782 (let ((inhibit-point-motion-hooks t))
783 (save-excursion
784 (message "Invisible tag: %s"
e1940c83
SM
785 ;; Strip properties, otherwise, the text is invisible.
786 (buffer-substring-no-properties
1caf38eb
RS
787 (point)
788 (if (or (and (> x y)
789 (not (eq (following-char) ?<)))
790 (and (< x y)
791 (eq (preceding-char) ?>)))
792 (backward-list)
793 (forward-list)))))))
a391b179 794\f
1caf38eb
RS
795(autoload 'compile-internal "compile")
796
72c0ae01
ER
797(defun sgml-validate (command)
798 "Validate an SGML document.
799Runs COMMAND, a shell command, in a separate process asynchronously
f788776c 800with output going to the buffer `*compilation*'.
72c0ae01
ER
801You can then use the command \\[next-error] to find the next error message
802and move to the line in the SGML document that caused it."
803 (interactive
804 (list (read-string "Validate command: "
805 (or sgml-saved-validate-command
806 (concat sgml-validate-command
807 " "
808 (let ((name (buffer-file-name)))
809 (and name
810 (file-name-nondirectory name))))))))
811 (setq sgml-saved-validate-command command)
b7cd1746 812 (save-some-buffers (not compilation-ask-about-save) nil)
c7aa4667 813 (compile-internal command "No more errors"))
72c0ae01 814
1caf38eb
RS
815
816(defun sgml-beginning-of-tag (&optional top-level)
817 "Skip to beginning of tag and return its name.
f788776c 818If this can't be done, return t."
1caf38eb
RS
819 (or (if top-level
820 (condition-case nil
821 (up-list -1)
822 (error t))
823 (>= (point)
824 (if (search-backward "<" nil t)
825 (save-excursion
826 (forward-list)
827 (point))
828 0)))
829 (if (looking-at "<[!?/]?[[A-Za-z][A-Za-z0-9]*")
830 (buffer-substring-no-properties
831 (1+ (point))
832 (match-end 0))
833 t)))
834
b4f05c38
SS
835(defun sgml-skip-close-p (obj)
836 (and (eq obj t) (not html-xhtml)))
837
1caf38eb 838(defun sgml-value (alist)
f788776c
RS
839 "Interactively insert value taken from attributerule ALIST.
840See `sgml-tag-alist' for info about attributerules.."
1caf38eb
RS
841 (setq alist (cdr alist))
842 (if (stringp (car alist))
843 (insert "=\"" (car alist) ?\")
b4f05c38 844 (if (sgml-skip-close-p (car alist)) ; (eq (car alist) t)
1caf38eb
RS
845 (if (cdr alist)
846 (progn
847 (insert "=\"")
848 (setq alist (skeleton-read '(completing-read
d10447ba 849 "Value: " (cdr alist))))
1caf38eb 850 (if (string< "" alist)
a391b179 851 (insert alist ?\")
1caf38eb
RS
852 (delete-backward-char 2))))
853 (insert "=\"")
854 (if alist
a391b179 855 (insert (skeleton-read '(completing-read "Value: " alist))))
1caf38eb 856 (insert ?\"))))
1caf38eb 857\f
e1940c83
SM
858
859;;; HTML mode
860
d4c89075
DL
861(defcustom html-mode-hook nil
862 "Hook run by command `html-mode'.
863`text-mode-hook' and `sgml-mode-hook' are run first."
864 :group 'sgml
865 :type 'hook
866 :options '(html-autoview-mode))
867
fcc3195e 868(defvar html-quick-keys sgml-quick-keys
b1e7bb48 869 "Use C-c X combinations for quick insertion of frequent tags when non-nil.
fcc3195e 870This defaults to `sgml-quick-keys'.
1caf38eb
RS
871This takes effect when first loading the library.")
872
873(defvar html-mode-map
5f5c9e79 874 (let ((map (make-sparse-keymap))
1caf38eb 875 (menu-map (make-sparse-keymap "HTML")))
5f5c9e79 876 (set-keymap-parent map sgml-mode-map)
7e49eef2
RS
877 (define-key map "\C-c6" 'html-headline-6)
878 (define-key map "\C-c5" 'html-headline-5)
879 (define-key map "\C-c4" 'html-headline-4)
880 (define-key map "\C-c3" 'html-headline-3)
881 (define-key map "\C-c2" 'html-headline-2)
882 (define-key map "\C-c1" 'html-headline-1)
fcc3195e
RS
883 (define-key map "\C-c\r" 'html-paragraph)
884 (define-key map "\C-c\n" 'html-line)
885 (define-key map "\C-c\C-c-" 'html-horizontal-rule)
7e49eef2
RS
886 (define-key map "\C-c\C-co" 'html-ordered-list)
887 (define-key map "\C-c\C-cu" 'html-unordered-list)
fcc3195e
RS
888 (define-key map "\C-c\C-cr" 'html-radio-buttons)
889 (define-key map "\C-c\C-cc" 'html-checkboxes)
890 (define-key map "\C-c\C-cl" 'html-list-item)
891 (define-key map "\C-c\C-ch" 'html-href-anchor)
892 (define-key map "\C-c\C-cn" 'html-name-anchor)
893 (define-key map "\C-c\C-ci" 'html-image)
1caf38eb
RS
894 (if html-quick-keys
895 (progn
1caf38eb 896 (define-key map "\C-c-" 'html-horizontal-rule)
7e49eef2
RS
897 (define-key map "\C-co" 'html-ordered-list)
898 (define-key map "\C-cu" 'html-unordered-list)
1caf38eb 899 (define-key map "\C-cr" 'html-radio-buttons)
fcc3195e 900 (define-key map "\C-cc" 'html-checkboxes)
1caf38eb
RS
901 (define-key map "\C-cl" 'html-list-item)
902 (define-key map "\C-ch" 'html-href-anchor)
903 (define-key map "\C-cn" 'html-name-anchor)
904 (define-key map "\C-ci" 'html-image)))
905 (define-key map "\C-c\C-s" 'html-autoview-mode)
906 (define-key map "\C-c\C-v" 'browse-url-of-buffer)
907 (define-key map [menu-bar html] (cons "HTML" menu-map))
908 (define-key menu-map [html-autoview-mode]
909 '("Toggle Autoviewing" . html-autoview-mode))
910 (define-key menu-map [browse-url-of-buffer]
911 '("View Buffer Contents" . browse-url-of-buffer))
912 (define-key menu-map [nil] '("--"))
7e49eef2
RS
913 ;;(define-key menu-map "6" '("Heading 6" . html-headline-6))
914 ;;(define-key menu-map "5" '("Heading 5" . html-headline-5))
915 ;;(define-key menu-map "4" '("Heading 4" . html-headline-4))
916 (define-key menu-map "3" '("Heading 3" . html-headline-3))
917 (define-key menu-map "2" '("Heading 2" . html-headline-2))
918 (define-key menu-map "1" '("Heading 1" . html-headline-1))
1caf38eb 919 (define-key menu-map "l" '("Radio Buttons" . html-radio-buttons))
fcc3195e 920 (define-key menu-map "c" '("Checkboxes" . html-checkboxes))
1caf38eb 921 (define-key menu-map "l" '("List Item" . html-list-item))
7e49eef2
RS
922 (define-key menu-map "u" '("Unordered List" . html-unordered-list))
923 (define-key menu-map "o" '("Ordered List" . html-ordered-list))
fcc3195e 924 (define-key menu-map "-" '("Horizontal Rule" . html-horizontal-rule))
1caf38eb
RS
925 (define-key menu-map "\n" '("Line Break" . html-line))
926 (define-key menu-map "\r" '("Paragraph" . html-paragraph))
927 (define-key menu-map "i" '("Image" . html-image))
928 (define-key menu-map "h" '("Href Anchor" . html-href-anchor))
929 (define-key menu-map "n" '("Name Anchor" . html-name-anchor))
930 map)
931 "Keymap for commands for use in HTML mode.")
932
933
934(defvar html-face-tag-alist
935 '((bold . "b")
936 (italic . "i")
937 (underline . "u")
938 (modeline . "rev"))
939 "Value of `sgml-face-tag-alist' for HTML mode.")
940
941(defvar html-tag-face-alist
942 '(("b" . bold)
943 ("big" . bold)
944 ("blink" . highlight)
945 ("cite" . italic)
946 ("em" . italic)
947 ("h1" bold underline)
948 ("h2" bold-italic underline)
949 ("h3" italic underline)
950 ("h4" . underline)
951 ("h5" . underline)
952 ("h6" . underline)
953 ("i" . italic)
954 ("rev" . modeline)
955 ("s" . underline)
956 ("small" . default)
957 ("strong" . bold)
958 ("title" bold underline)
959 ("tt" . default)
960 ("u" . underline)
961 ("var" . italic))
962 "Value of `sgml-tag-face-alist' for HTML mode.")
963
964
965(defvar html-display-text
966 '((img . "[/]")
967 (hr . "----------")
968 (li . "o "))
969 "Value of `sgml-display-text' for HTML mode.")
3bf0b727 970\f
b4f05c38
SS
971
972(defcustom html-xhtml nil
973 "*When non-nil, tag insertion functions will be XHTML-compliant."
974 :type 'boolean
975 :version "21.2"
976 :group 'sgml)
977
3bf0b727 978;; should code exactly HTML 3 here when that is finished
1caf38eb 979(defvar html-tag-alist
d10447ba 980 (let* ((1-7 '(("1") ("2") ("3") ("4") ("5") ("6") ("7")))
e1940c83 981 (1-9 `(,@1-7 ("8") ("9")))
1caf38eb
RS
982 (align '(("align" ("left") ("center") ("right"))))
983 (valign '(("top") ("middle") ("bottom") ("baseline")))
984 (rel '(("next") ("previous") ("parent") ("subdocument") ("made")))
985 (href '("href" ("ftp:") ("file:") ("finger:") ("gopher:") ("http:")
986 ("mailto:") ("news:") ("rlogin:") ("telnet:") ("tn3270:")
fcc3195e 987 ("wais:") ("/cgi-bin/")))
1caf38eb
RS
988 (name '("name"))
989 (link `(,href
990 ("rel" ,@rel)
991 ("rev" ,@rel)
992 ("title")))
b4f05c38
SS
993 (list '((nil \n ("List item: " "<li>" str
994 (if html-xhtml "</li>") \n))))
1caf38eb 995 (cell `(t
e1940c83 996 ,@align
1caf38eb
RS
997 ("valign" ,@valign)
998 ("colspan" ,@1-9)
999 ("rowspan" ,@1-9)
1000 ("nowrap" t))))
1001 ;; put ,-expressions first, else byte-compile chokes (as of V19.29)
1002 ;; and like this it's more efficient anyway
1003 `(("a" ,name ,@link)
1004 ("base" t ,@href)
1005 ("dir" ,@list)
d10447ba 1006 ("font" nil "size" ("-1") ("+1") ("-2") ("+2") ,@1-7)
fcc3195e
RS
1007 ("form" (\n _ \n "<input type=\"submit\" value=\"\">")
1008 ("action" ,@(cdr href)) ("method" ("get") ("post")))
1caf38eb
RS
1009 ("h1" ,@align)
1010 ("h2" ,@align)
1011 ("h3" ,@align)
1012 ("h4" ,@align)
1013 ("h5" ,@align)
1014 ("h6" ,@align)
1015 ("hr" t ("size" ,@1-9) ("width") ("noshade" t) ,@align)
1016 ("img" t ("align" ,@valign ("texttop") ("absmiddle") ("absbottom"))
1017 ("src") ("alt") ("width" "1") ("height" "1")
1018 ("border" "1") ("vspace" "1") ("hspace" "1") ("ismap" t))
1019 ("input" t ("size" ,@1-9) ("maxlength" ,@1-9) ("checked" t) ,name
fcc3195e
RS
1020 ("type" ("text") ("password") ("checkbox") ("radio")
1021 ("submit") ("reset"))
1caf38eb
RS
1022 ("value"))
1023 ("link" t ,@link)
1024 ("menu" ,@list)
d10447ba 1025 ("ol" ,@list ("type" ("A") ("a") ("I") ("i") ("1")))
1caf38eb
RS
1026 ("p" t ,@align)
1027 ("select" (nil \n
1028 ("Text: "
1029 "<option>" str \n))
1030 ,name ("size" ,@1-9) ("multiple" t))
1031 ("table" (nil \n
1032 ((completing-read "Cell kind: " '(("td") ("th"))
1033 nil t "t")
1034 "<tr><" str ?> _ \n))
1035 ("border" t ,@1-9) ("width" "10") ("cellpadding"))
1036 ("td" ,@cell)
1037 ("textarea" ,name ("rows" ,@1-9) ("cols" ,@1-9))
1038 ("th" ,@cell)
d10447ba 1039 ("ul" ,@list ("type" ("disc") ("circle") ("square")))
1caf38eb
RS
1040
1041 ,@sgml-tag-alist
1042
1043 ("abbrev")
1044 ("acronym")
1045 ("address")
1046 ("array" (nil \n
1047 ("Item: " "<item>" str \n))
1048 "align")
1049 ("au")
1050 ("b")
1051 ("big")
1052 ("blink")
1053 ("blockquote" \n)
1054 ("body" \n ("background" ".gif") ("bgcolor" "#") ("text" "#")
1055 ("link" "#") ("alink" "#") ("vlink" "#"))
1056 ("box" (nil _ "<over>" _))
1057 ("br" t ("clear" ("left") ("right")))
1058 ("caption" ("valign" ("top") ("bottom")))
1059 ("center" \n)
1060 ("cite")
1061 ("code" \n)
1062 ("dd" t)
1063 ("del")
1064 ("dfn")
e1940c83 1065 ("div")
1caf38eb
RS
1066 ("dl" (nil \n
1067 ( "Term: "
b4f05c38
SS
1068 "<dt>" str (if html-xhtml "</dt>")
1069 "<dd>" _ (if html-xhtml "</dd>") \n)))
1caf38eb
RS
1070 ("dt" (t _ "<dd>"))
1071 ("em")
d10447ba 1072 ;("fn" "id" "fn") ; ???
1caf38eb
RS
1073 ("head" \n)
1074 ("html" (\n
1075 "<head>\n"
1076 "<title>" (setq str (read-input "Title: ")) "</title>\n"
5e532c5c 1077 "</head>\n"
1caf38eb
RS
1078 "<body>\n<h1>" str "</h1>\n" _
1079 "\n<address>\n<a href=\"mailto:"
be047262 1080 user-mail-address
5e532c5c
RS
1081 "\">" (user-full-name) "</a>\n</address>\n"
1082 "</body>"
1083 ))
1caf38eb
RS
1084 ("i")
1085 ("ins")
1086 ("isindex" t ("action") ("prompt"))
1087 ("kbd")
1088 ("lang")
1089 ("li" t)
1090 ("math" \n)
1091 ("nobr")
1092 ("option" t ("value") ("label") ("selected" t))
1093 ("over" t)
1094 ("person")
1095 ("pre" \n)
1096 ("q")
1097 ("rev")
1098 ("s")
1099 ("samp")
1100 ("small")
e1940c83 1101 ("span")
1caf38eb
RS
1102 ("strong")
1103 ("sub")
1104 ("sup")
1105 ("title")
1106 ("tr" t)
1107 ("tt")
1108 ("u")
1109 ("var")
1110 ("wbr" t)))
1111 "*Value of `sgml-tag-alist' for HTML mode.")
1112
1113(defvar html-tag-help
1114 `(,@sgml-tag-help
1115 ("a" . "Anchor of point or link elsewhere")
1116 ("abbrev" . "?")
1117 ("acronym" . "?")
1118 ("address" . "Formatted mail address")
1119 ("array" . "Math array")
1120 ("au" . "?")
1121 ("b" . "Bold face")
1122 ("base" . "Base address for URLs")
1123 ("big" . "Font size")
1124 ("blink" . "Blinking text")
1125 ("blockquote" . "Indented quotation")
1126 ("body" . "Document body")
1127 ("box" . "Math fraction")
1128 ("br" . "Line break")
1129 ("caption" . "Table caption")
1130 ("center" . "Centered text")
1131 ("changed" . "Change bars")
1132 ("cite" . "Citation of a document")
1133 ("code" . "Formatted source code")
1134 ("dd" . "Definition of term")
1135 ("del" . "?")
1136 ("dfn" . "?")
1137 ("dir" . "Directory list (obsolete)")
1138 ("dl" . "Definition list")
1139 ("dt" . "Term to be definined")
b4f05c38 1140 ("em" . "Emphasised")
1caf38eb
RS
1141 ("embed" . "Embedded data in foreign format")
1142 ("fig" . "Figure")
1143 ("figa" . "Figure anchor")
1144 ("figd" . "Figure description")
1145 ("figt" . "Figure text")
d10447ba 1146 ;("fn" . "?") ; ???
1caf38eb
RS
1147 ("font" . "Font size")
1148 ("form" . "Form with input fields")
1149 ("group" . "Document grouping")
1150 ("h1" . "Most important section headline")
1151 ("h2" . "Important section headline")
1152 ("h3" . "Section headline")
1153 ("h4" . "Minor section headline")
1154 ("h5" . "Unimportant section headline")
1155 ("h6" . "Least important section headline")
1156 ("head" . "Document header")
1157 ("hr" . "Horizontal rule")
1158 ("html" . "HTML Document")
1159 ("i" . "Italic face")
1160 ("img" . "Graphic image")
1161 ("input" . "Form input field")
1162 ("ins" . "?")
1163 ("isindex" . "Input field for index search")
1164 ("kbd" . "Keybard example face")
1165 ("lang" . "Natural language")
1166 ("li" . "List item")
1167 ("link" . "Link relationship")
1168 ("math" . "Math formula")
1169 ("menu" . "Menu list (obsolete)")
1170 ("mh" . "Form mail header")
1171 ("nextid" . "Allocate new id")
1172 ("nobr" . "Text without line break")
1173 ("ol" . "Ordered list")
1174 ("option" . "Selection list item")
1175 ("over" . "Math fraction rule")
1176 ("p" . "Paragraph start")
1177 ("panel" . "Floating panel")
1178 ("person" . "?")
1179 ("pre" . "Preformatted fixed width text")
1180 ("q" . "?")
1181 ("rev" . "Reverse video")
1182 ("s" . "?")
1183 ("samp" . "Sample text")
1184 ("select" . "Selection list")
1185 ("small" . "Font size")
1186 ("sp" . "Nobreak space")
1187 ("strong" . "Standout text")
1188 ("sub" . "Subscript")
1189 ("sup" . "Superscript")
1190 ("table" . "Table with rows and columns")
1191 ("tb" . "Table vertical break")
1192 ("td" . "Table data cell")
1193 ("textarea" . "Form multiline edit area")
1194 ("th" . "Table header cell")
1195 ("title" . "Document title")
1196 ("tr" . "Table row separator")
1197 ("tt" . "Typewriter face")
1198 ("u" . "Underlined text")
1199 ("ul" . "Unordered list")
1200 ("var" . "Math variable face")
1201 ("wbr" . "Enable <br> within <nobr>"))
1202"*Value of `sgml-tag-help' for HTML mode.")
3bf0b727 1203\f
1caf38eb
RS
1204;;;###autoload
1205(defun html-mode ()
1206 "Major mode based on SGML mode for editing HTML documents.
7be38f7d 1207This allows inserting skeleton constructs used in hypertext documents with
fcc3195e
RS
1208completion. See below for an introduction to HTML. Use
1209\\[browse-url-of-buffer] to see how this comes out. See also `sgml-mode' on
1210which this is based.
1caf38eb 1211
fcc3195e 1212Do \\[describe-variable] html- SPC and \\[describe-variable] sgml- SPC to see available variables.
1caf38eb
RS
1213
1214To write fairly well formatted pages you only need to know few things. Most
1215browsers have a function to read the source code of the page being seen, so
1216you can imitate various tricks. Here's a very short HTML primer which you
1217can also view with a browser to see what happens:
1218
1219<title>A Title Describing Contents</title> should be on every page. Pages can
1220have <h1>Very Major Headlines</h1> through <h6>Very Minor Headlines</h6>
1221<hr> Parts can be separated with horizontal rules.
1222
1223<p>Paragraphs only need an opening tag. Line breaks and multiple spaces are
1224ignored unless the text is <pre>preformatted.</pre> Text can be marked as
1225<b>bold</b>, <i>italic</i> or <u>underlined</u> using the normal M-g or
1226Edit/Text Properties/Face commands.
1227
1228Pages can have <a name=\"SOMENAME\">named points</a> and can link other points
1229to them with <a href=\"#SOMENAME\">see also somename</a>. In the same way <a
1230href=\"URL\">see also URL</a> where URL is a filename relative to current
f788776c 1231directory, or absolute as in `http://www.cs.indiana.edu/elisp/w3/docs.html'.
1caf38eb
RS
1232
1233Images in many formats can be inlined with <img src=\"URL\">.
1234
f788776c
RS
1235If you mainly create your own documents, `sgml-specials' might be
1236interesting. But note that some HTML 2 browsers can't handle `&apos;'.
1237To work around that, do:
1238 (eval-after-load \"sgml-mode\" '(aset sgml-char-names ?' nil))
1caf38eb 1239
1caf38eb
RS
1240\\{html-mode-map}"
1241 (interactive)
a01588fc
RS
1242 (kill-all-local-variables)
1243 (setq mode-name "HTML"
1244 major-mode 'html-mode)
1caf38eb
RS
1245 (sgml-mode-common html-tag-face-alist html-display-text)
1246 (use-local-map html-mode-map)
1247 (make-local-variable 'sgml-tag-alist)
1248 (make-local-variable 'sgml-face-tag-alist)
1249 (make-local-variable 'sgml-tag-help)
1250 (make-local-variable 'outline-regexp)
1251 (make-local-variable 'outline-heading-end-regexp)
1252 (make-local-variable 'outline-level)
da84bdc4
RS
1253 (make-local-variable 'sentence-end)
1254 (setq sentence-end
b8b14971
DL
1255 (if sentence-end-double-space
1256 "[.?!][]\"')}]*\\(<[^>]*>\\)*\\($\\| $\\|\t\\| \\)[ \t\n]*"
b4f05c38 1257
b8b14971 1258 "[.?!][]\"')}]*\\(<[^>]*>\\)*\\($\\| \\|\t\\)[ \t\n]*"))
a01588fc 1259 (setq sgml-tag-alist html-tag-alist
1caf38eb
RS
1260 sgml-face-tag-alist html-face-tag-alist
1261 sgml-tag-help html-tag-help
1262 outline-regexp "^.*<[Hh][1-6]\\>"
1263 outline-heading-end-regexp "</[Hh][1-6]>"
1264 outline-level (lambda ()
1265 (char-after (1- (match-end 0)))))
3bf0b727 1266 (setq imenu-create-index-function 'html-imenu-index)
e1940c83
SM
1267 ;; It's for the user to decide if it defeats it or not -stef
1268 ;; (make-local-variable 'imenu-sort-function)
1269 ;; (setq imenu-sort-function nil) ; sorting the menu defeats the purpose
a01588fc 1270 (run-hooks 'text-mode-hook 'sgml-mode-hook 'html-mode-hook))
3bf0b727
RS
1271\f
1272(defvar html-imenu-regexp
1273 "\\s-*<h\\([1-9]\\)[^\n<>]*>\\(<[^\n<>]*>\\)*\\s-*\\([^\n<>]*\\)"
1274 "*A regular expression matching a head line to be added to the menu.
1275The first `match-string' should be a number from 1-9.
1276The second `match-string' matches extra tags and is ignored.
1277The third `match-string' will be the used in the menu.")
1278
1279(defun html-imenu-index ()
1280 "Return an table of contents for an HTML buffer for use with Imenu."
1281 (let (toc-index)
1282 (save-excursion
1283 (goto-char (point-min))
1284 (while (re-search-forward html-imenu-regexp nil t)
1285 (setq toc-index
1286 (cons (cons (concat (make-string
1287 (* 2 (1- (string-to-number (match-string 1))))
1288 ?\ )
1289 (match-string 3))
1290 (save-excursion (beginning-of-line) (point)))
1291 toc-index))))
1292 (nreverse toc-index)))
1caf38eb 1293
3bf0b727 1294(defun html-autoview-mode (&optional arg)
d4c89075 1295 "Toggle automatic viewing via `browse-url-of-buffer' upon saving buffer.
3bf0b727
RS
1296With positive prefix ARG always turns viewing on, with negative ARG always off.
1297Can be used as a value for `html-mode-hook'."
1298 (interactive "P")
1299 (if (setq arg (if arg
1300 (< (prefix-numeric-value arg) 0)
1301 (and (boundp 'after-save-hook)
1302 (memq 'browse-url-of-buffer after-save-hook))))
1303 (setq after-save-hook (delq 'browse-url-of-buffer after-save-hook))
3bf0b727
RS
1304 (add-hook 'after-save-hook 'browse-url-of-buffer nil t))
1305 (message "Autoviewing turned %s."
1306 (if arg "off" "on")))
1307\f
1caf38eb
RS
1308(define-skeleton html-href-anchor
1309 "HTML anchor tag with href attribute."
a391b179
RS
1310 "URL: "
1311 '(setq input "http:")
1312 "<a href=\"" str "\">" _ "</a>")
1caf38eb
RS
1313
1314(define-skeleton html-name-anchor
1315 "HTML anchor tag with name attribute."
a391b179
RS
1316 "Name: "
1317 "<a name=\"" str "\">" _ "</a>")
1caf38eb 1318
7e49eef2
RS
1319(define-skeleton html-headline-1
1320 "HTML level 1 headline tags."
1321 nil
1322 "<h1>" _ "</h1>")
1323
1324(define-skeleton html-headline-2
1325 "HTML level 2 headline tags."
1326 nil
1327 "<h2>" _ "</h2>")
1328
1329(define-skeleton html-headline-3
1330 "HTML level 3 headline tags."
1331 nil
1332 "<h3>" _ "</h3>")
1333
1334(define-skeleton html-headline-4
1335 "HTML level 4 headline tags."
1336 nil
1337 "<h4>" _ "</h4>")
1338
1339(define-skeleton html-headline-5
1340 "HTML level 5 headline tags."
1341 nil
1342 "<h5>" _ "</h5>")
1343
1344(define-skeleton html-headline-6
1345 "HTML level 6 headline tags."
1346 nil
1347 "<h6>" _ "</h6>")
1caf38eb
RS
1348
1349(define-skeleton html-horizontal-rule
1350 "HTML horizontal rule tag."
1351 nil
b4f05c38 1352 (if html-xhtml "<hr/>" "<hr>") \n)
1caf38eb
RS
1353
1354(define-skeleton html-image
1355 "HTML image tag."
1356 nil
b4f05c38
SS
1357 "<img src=\"" _ "\""
1358 (if html-xhtml "/>" ">"))
1caf38eb
RS
1359
1360(define-skeleton html-line
1361 "HTML line break tag."
1362 nil
b4f05c38 1363 (if html-xhtml "<br/>" "<br>") \n)
1caf38eb 1364
7e49eef2
RS
1365(define-skeleton html-ordered-list
1366 "HTML ordered list tags."
1367 nil
a391b179 1368 "<ol>" \n
b4f05c38 1369 "<li>" _ (if html-xhtml "</li>") \n
7e49eef2
RS
1370 "</ol>")
1371
1372(define-skeleton html-unordered-list
1373 "HTML unordered list tags."
1374 nil
a391b179 1375 "<ul>" \n
b4f05c38 1376 "<li>" _ (if html-xhtml "</li>") \n
7e49eef2 1377 "</ul>")
1caf38eb
RS
1378
1379(define-skeleton html-list-item
1380 "HTML list item tag."
1381 nil
1382 (if (bolp) nil '\n)
b4f05c38 1383 "<li>" _ (if html-xhtml "</li>"))
1caf38eb
RS
1384
1385(define-skeleton html-paragraph
1386 "HTML paragraph tag."
1387 nil
1388 (if (bolp) nil ?\n)
b4f05c38 1389 \n "<p>" _ (if html-xhtml "</p>"))
1caf38eb 1390
fcc3195e
RS
1391(define-skeleton html-checkboxes
1392 "Group of connected checkbox inputs."
1393 nil
a391b179
RS
1394 '(setq v1 nil
1395 v2 nil)
1396 ("Value: "
d10447ba 1397 "<input type=\"" (identity "checkbox") ; see comment above about identity
a391b179 1398 "\" name=\"" (or v1 (setq v1 (skeleton-read "Name: ")))
fcc3195e 1399 "\" value=\"" str ?\"
b4f05c38
SS
1400 (when (y-or-n-p "Set \"checked\" attribute? ")
1401 (funcall skeleton-transformation " checked"))
1402 (if html-xhtml "/>" ">")
a391b179
RS
1403 (skeleton-read "Text: " (capitalize str))
1404 (or v2 (setq v2 (if (y-or-n-p "Newline after text? ")
b4f05c38
SS
1405 (funcall skeleton-transformation
1406 (if html-xhtml "<br/>" "<br>"))
a391b179
RS
1407 "")))
1408 \n))
fcc3195e 1409
1caf38eb
RS
1410(define-skeleton html-radio-buttons
1411 "Group of connected radio button inputs."
1412 nil
a391b179
RS
1413 '(setq v1 nil
1414 v2 (cons nil nil))
1415 ("Value: "
d10447ba 1416 "<input type=\"" (identity "radio") ; see comment above about identity
a391b179 1417 "\" name=\"" (or (car v2) (setcar v2 (skeleton-read "Name: ")))
1caf38eb 1418 "\" value=\"" str ?\"
b4f05c38
SS
1419 (when (and (not v1) (setq v1 (y-or-n-p "Set \"checked\" attribute? ")))
1420 (funcall skeleton-transformation " checked"))
1421 (if html-xhtml "/>" ">")
a391b179
RS
1422 (skeleton-read "Text: " (capitalize str))
1423 (or (cdr v2) (setcdr v2 (if (y-or-n-p "Newline after text? ")
b4f05c38
SS
1424 (funcall skeleton-transformation
1425 (if html-xhtml "<br/>" "<br>"))
a391b179
RS
1426 "")))
1427 \n))
1caf38eb 1428
e1940c83 1429(provide 'sgml-mode)
6a05d05f 1430
72c0ae01 1431;;; sgml-mode.el ends here