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