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