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