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