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