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