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