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