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