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