Initial implementation of nested tag support
[muse-el.git] / lisp / muse-html.el
blobf3449c840b45400314c47fbb77f40926e0bdeca6
1 ;;; muse-html.el --- publish to HTML and XHTML
3 ;; Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc.
5 ;; This file is part of Emacs Muse. It is not part of GNU Emacs.
7 ;; Emacs Muse is free software; you can redistribute it and/or modify
8 ;; it under the terms of the GNU General Public License as published
9 ;; by the Free Software Foundation; either version 2, or (at your
10 ;; option) any later version.
12 ;; Emacs Muse is distributed in the hope that it will be useful, but
13 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 ;; General Public License for more details.
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with Emacs Muse; see the file COPYING. If not, write to the
19 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20 ;; Boston, MA 02110-1301, USA.
22 ;;; Commentary:
24 ;;; Contributors:
26 ;; Zhiqiang Ye (yezq AT mail DOT cbi DOT pku DOT edu DOT cn) suggested
27 ;; appending an 'encoding="..."' fragment to the first line of the
28 ;; sample publishing header so that when editing the resulting XHTML
29 ;; file, Emacs would use the proper encoding.
31 ;;; Code:
33 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
35 ;; Muse HTML Publishing
37 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
39 (require 'muse-publish)
40 (require 'muse-regexps)
41 (require 'muse-xml-common)
43 (defgroup muse-html nil
44 "Options controlling the behavior of Muse HTML publishing."
45 :group 'muse-publish)
47 (defcustom muse-html-extension ".html"
48 "Default file extension for publishing HTML files."
49 :type 'string
50 :group 'muse-html)
52 (defcustom muse-xhtml-extension ".html"
53 "Default file extension for publishing XHTML files."
54 :type 'string
55 :group 'muse-html)
57 (defcustom muse-html-style-sheet
58 "<style type=\"text/css\">
59 body {
60 background: white; color: black;
61 margin-left: 3%; margin-right: 7%;
64 p { margin-top: 1% }
65 p.verse { margin-left: 3% }
67 .example { margin-left: 3% }
69 h2 {
70 margin-top: 25px;
71 margin-bottom: 0px;
73 h3 { margin-bottom: 0px; }
74 </style>"
75 "Store your stylesheet definitions here.
76 This is used in `muse-html-header'.
77 You can put raw CSS in here or a <link> tag to an external stylesheet.
78 This text may contain <lisp> markup tags.
80 An example of using <link> is as follows.
82 <link rel=\"stylesheet\" type=\"text/css\" charset=\"utf-8\" media=\"all\" href=\"/default.css\">"
83 :type 'string
84 :group 'muse-html)
86 (defcustom muse-xhtml-style-sheet
87 "<style type=\"text/css\">
88 body {
89 background: white; color: black;
90 margin-left: 3%; margin-right: 7%;
93 p { margin-top: 1% }
94 p.verse { margin-left: 3% }
96 .example { margin-left: 3% }
98 h2 {
99 margin-top: 25px;
100 margin-bottom: 0px;
102 h3 { margin-bottom: 0px; }
103 </style>"
104 "Store your stylesheet definitions here.
105 This is used in `muse-xhtml-header'.
106 You can put raw CSS in here or a <link> tag to an external stylesheet.
107 This text may contain <lisp> markup tags.
109 An example of using <link> is as follows.
111 <link rel=\"stylesheet\" type=\"text/css\" charset=\"utf-8\" media=\"all\" href=\"/default.css\" />"
112 :type 'string
113 :group 'muse-html)
115 (defcustom muse-html-header
116 "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">
117 <html>
118 <head>
119 <title><lisp>
120 (concat (muse-publishing-directive \"title\")
121 (let ((author (muse-publishing-directive \"author\")))
122 (if (not (string= author (user-full-name)))
123 (concat \" (by \" author \")\"))))</lisp></title>
124 <meta name=\"generator\" content=\"muse.el\">
125 <meta http-equiv=\"<lisp>muse-html-meta-http-equiv</lisp>\"
126 content=\"<lisp>muse-html-meta-content-type</lisp>\">
127 <lisp>
128 (let ((maintainer (muse-style-element :maintainer)))
129 (when maintainer
130 (concat \"<link rev=\\\"made\\\" href=\\\"\" maintainer \"\\\">\")))
131 </lisp><lisp>
132 (muse-style-element :style-sheet muse-publishing-current-style)
133 </lisp>
134 </head>
135 <body>
136 <h1><lisp>
137 (concat (muse-publishing-directive \"title\")
138 (let ((author (muse-publishing-directive \"author\")))
139 (if (not (string= author (user-full-name)))
140 (concat \" (by \" author \")\"))))</lisp></h1>
141 <!-- Page published by Emacs Muse begins here -->\n"
142 "Header used for publishing HTML files. This may be text or a filename."
143 :type 'string
144 :group 'muse-html)
146 (defcustom muse-html-footer "
147 <!-- Page published by Emacs Muse ends here -->
148 </body>
149 </html>\n"
150 "Footer used for publishing HTML files. This may be text or a filename."
151 :type 'string
152 :group 'muse-html)
154 (defcustom muse-xhtml-header
155 "<?xml version=\"1.0\" encoding=\"<lisp>
156 (muse-html-encoding)</lisp>\"?>
157 <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"
158 \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">
159 <html xmlns=\"http://www.w3.org/1999/xhtml\">
160 <head>
161 <title><lisp>
162 (concat (muse-publishing-directive \"title\")
163 (let ((author (muse-publishing-directive \"author\")))
164 (if (not (string= author (user-full-name)))
165 (concat \" (by \" author \")\"))))</lisp></title>
166 <meta name=\"generator\" content=\"muse.el\" />
167 <meta http-equiv=\"<lisp>muse-html-meta-http-equiv</lisp>\"
168 content=\"<lisp>muse-html-meta-content-type</lisp>\" />
169 <lisp>
170 (let ((maintainer (muse-style-element :maintainer)))
171 (when maintainer
172 (concat \"<link rev=\\\"made\\\" href=\\\"\" maintainer \"\\\" />\")))
173 </lisp><lisp>
174 (muse-style-element :style-sheet muse-publishing-current-style)
175 </lisp>
176 </head>
177 <body>
178 <h1><lisp>
179 (concat (muse-publishing-directive \"title\")
180 (let ((author (muse-publishing-directive \"author\")))
181 (if (not (string= author (user-full-name)))
182 (concat \" (by \" author \")\"))))</lisp></h1>
183 <!-- Page published by Emacs Muse begins here -->\n"
184 "Header used for publishing XHTML files. This may be text or a filename."
185 :type 'string
186 :group 'muse-html)
188 (defcustom muse-xhtml-footer "
189 <!-- Page published by Emacs Muse ends here -->
190 </body>
191 </html>\n"
192 "Footer used for publishing XHTML files. This may be text or a filename."
193 :type 'string
194 :group 'muse-html)
196 (defcustom muse-html-anchor-on-word nil
197 "When true, anchors surround the closest word. This allows you
198 to select them in a browser (i.e. for pasting), but has the
199 side-effect of marking up headers in multiple colors if your
200 header style is different from your link style."
201 :type 'boolean
202 :group 'muse-html)
204 (defcustom muse-html-table-attributes
205 " class=\"muse-table\" border=\"2\" cellpadding=\"5\""
206 "The attribute to be used with HTML <table> tags.
207 Note that since Muse supports insertion of raw HTML tags, as long
208 as you wrap the region in <literal></literal>."
209 :type 'string
210 :group 'muse-html)
212 (defcustom muse-html-markup-regexps
213 `(;; Beginning of doc, end of doc, or plain paragraph separator
214 (10000 ,(concat "\\(\\(\n\\(?:[" muse-regexp-blank "]*\n\\)*"
215 "\\([" muse-regexp-blank "]*\n\\)\\)"
216 "\\|\\`\\s-*\\|\\s-*\\'\\)")
217 ;; this is somewhat repetitive because we only require the
218 ;; line just before the paragraph beginning to be not
219 ;; read-only
220 3 muse-html-markup-paragraph))
221 "List of markup rules for publishing a Muse page to HTML.
222 For more on the structure of this list, see `muse-publish-markup-regexps'."
223 :type '(repeat (choice
224 (list :tag "Markup rule"
225 integer
226 (choice regexp symbol)
227 integer
228 (choice string function symbol))
229 function))
230 :group 'muse-html)
232 (defcustom muse-html-markup-functions
233 '((anchor . muse-html-markup-anchor)
234 (table . muse-html-markup-table)
235 (footnote . muse-html-markup-footnote))
236 "An alist of style types to custom functions for that kind of text.
237 For more on the structure of this list, see
238 `muse-publish-markup-functions'."
239 :type '(alist :key-type symbol :value-type function)
240 :group 'muse-html)
242 (defcustom muse-html-markup-strings
243 '((image-with-desc . "<table class=\"image\" width=\"100%%\">
244 <tr><td align=\"center\"><img src=\"%1%.%2%\" alt=\"%3%\"></td></tr>
245 <tr><td align=\"center\" class=\"image-caption\">%3%</td></tr>
246 </table>")
247 (image . "<img src=\"%s.%s\" alt=\"\">")
248 (image-link . "<a class=\"image-link\" href=\"%s\">
249 <img src=\"%s.%s\"></a>")
250 (anchor-ref . "<a href=\"#%s\">%s</a>")
251 (url . "<a href=\"%s\">%s</a>")
252 (link . "<a href=\"%s\">%s</a>")
253 (link-and-anchor . "<a href=\"%s#%s\">%s</a>")
254 (email-addr . "<a href=\"mailto:%s\">%s</a>")
255 (emdash . "%s&mdash;%s")
256 (comment-begin . "<!-- ")
257 (comment-end . " -->")
258 (rule . "<hr>")
259 (fn-sep . "<hr>\n")
260 (no-break-space . "&nbsp;")
261 (enddots . "....")
262 (dots . "...")
263 (section . "<h2>")
264 (section-end . "</h2>")
265 (subsection . "<h3>")
266 (subsection-end . "</h3>")
267 (subsubsection . "<h4>")
268 (subsubsection-end . "</h4>")
269 (section-other . "<h5>")
270 (section-other-end . "</h5>")
271 (begin-underline . "<u>")
272 (end-underline . "</u>")
273 (begin-literal . "<code>")
274 (end-literal . "</code>")
275 (begin-emph . "<em>")
276 (end-emph . "</em>")
277 (begin-more-emph . "<strong>")
278 (end-more-emph . "</strong>")
279 (begin-most-emph . "<strong><em>")
280 (end-most-emph . "</em></strong>")
281 (begin-verse . "<p class=\"verse\">\n")
282 (verse-space . "&nbsp;&nbsp;")
283 (end-verse-line . "<br>")
284 (end-last-stanza-line . "<br>")
285 (empty-verse-line . "<br>")
286 (end-verse . "</p>")
287 (begin-example . "<pre class=\"example\">")
288 (end-example . "</pre>")
289 (begin-center . "<center>\n<p>")
290 (end-center . "</p>\n</center>")
291 (begin-quote . "<blockquote>\n")
292 (end-quote . "\n</blockquote>")
293 (begin-quote-item . "<p class=\"quoted\">")
294 (end-quote-item . "</p>")
295 (begin-uli . "<ul>\n")
296 (end-uli . "\n</ul>")
297 (begin-uli-item . "<li>")
298 (end-uli-item . "</li>")
299 (begin-oli . "<ol>\n")
300 (end-oli . "\n</ol>")
301 (begin-oli-item . "<li>")
302 (end-oli-item . "</li>")
303 (begin-dl . "<dl>\n")
304 (end-dl . "\n</dl>")
305 (begin-ddt . "<dt><strong>")
306 (end-ddt . "</strong></dt>")
307 (begin-dde . "<dd>")
308 (end-dde . "</dd>")
309 (begin-table . "<table%s>\n")
310 (end-table . "</table>")
311 (begin-table-row . " <tr>\n")
312 (end-table-row . " </tr>\n")
313 (begin-table-entry . " <%s>")
314 (end-table-entry . "</%s>\n"))
315 "Strings used for marking up text as HTML.
316 These cover the most basic kinds of markup, the handling of which
317 differs little between the various styles."
318 :type '(alist :key-type symbol :value-type string)
319 :group 'muse-html)
321 (defcustom muse-xhtml-markup-strings
322 '((image-with-desc . "<table class=\"image\" width=\"100%%\">
323 <tr><td align=\"center\"><img src=\"%1%.%2%\" alt=\"%3%\" /></td></tr>
324 <tr><td align=\"center\" class=\"image-caption\">%3%</td></tr>
325 </table>")
326 (image . "<img src=\"%s.%s\" alt=\"\" />")
327 (image-link . "<a class=\"image-link\" href=\"%s\">
328 <img src=\"%s.%s\" alt=\"\" /></a>")
329 (rule . "<hr />")
330 (fn-sep . "<hr />\n")
331 (begin-underline . "<span style=\"text-decoration: underline;\">")
332 (end-underline . "</span>")
333 (begin-center . "<p style=\"text-align: center;\">\n")
334 (end-center . "\n</p>")
335 (end-verse-line . "<br />")
336 (end-last-stanza-line . "<br />")
337 (empty-verse-line . "<br />"))
338 "Strings used for marking up text as XHTML.
339 These cover the most basic kinds of markup, the handling of which
340 differs little between the various styles.
342 If a markup rule is not found here, `muse-html-markup-strings' is
343 searched."
344 :type '(alist :key-type symbol :value-type string)
345 :group 'muse-html)
347 (defcustom muse-html-markup-tags
348 '(("class" t t nil muse-html-class-tag))
349 "A list of tag specifications, for specially marking up HTML."
350 :type '(repeat (list (string :tag "Markup tag")
351 (boolean :tag "Expect closing tag" :value t)
352 (boolean :tag "Parse attributes" :value nil)
353 (boolean :tag "Nestable" :value nil)
354 function))
355 :group 'muse-html)
357 (defcustom muse-html-meta-http-equiv "Content-Type"
358 "The http-equiv attribute used for the HTML <meta> tag."
359 :type 'string
360 :group 'muse-html)
362 (defcustom muse-html-meta-content-type "text/html"
363 "The content type used for the HTML <meta> tag.
364 If you are striving for XHTML 1.1 compliance, you may want to
365 change this to \"application/xhtml+xml\"."
366 :type 'string
367 :group 'muse-html)
369 (defcustom muse-html-meta-content-encoding (if (featurep 'mule)
370 'detect
371 "iso-8859-1")
372 "The charset to append to the HTML <meta> tag.
373 If set to the symbol 'detect, use `muse-html-encoding-map' to try
374 and determine the HTML charset from emacs's coding. If set to a
375 string, this string will be used to force a particular charset"
376 :type '(choice string symbol)
377 :group 'muse-html)
379 (defcustom muse-html-encoding-default 'iso-8859-1
380 "The default Emacs buffer encoding to use in published files.
381 This will be used if no special characters are found."
382 :type 'symbol
383 :group 'muse-html)
385 (defcustom muse-html-charset-default "iso-8859-1"
386 "The default HTML meta charset to use if no translation is found in
387 `muse-html-encoding-map'."
388 :type 'string
389 :group 'muse-html)
391 (defun muse-html-insert-anchor (anchor)
392 "Insert an anchor, either around the word at point, or within a tag."
393 (skip-chars-forward (concat muse-regexp-blank "\n"))
394 (if (looking-at (concat "<\\([^" muse-regexp-blank "/>\n]+\\)>"))
395 (let ((tag (match-string 1)))
396 (goto-char (match-end 0))
397 (muse-insert-markup "<a name=\"" anchor "\" id=\"" anchor "\">")
398 (when muse-html-anchor-on-word
399 (or (and (search-forward (format "</%s>" tag)
400 (muse-line-end-position) t)
401 (goto-char (match-beginning 0)))
402 (forward-word 1)))
403 (muse-insert-markup "</a>"))
404 (muse-insert-markup "<a name=\"" anchor "\" id=\"" anchor "\">")
405 (when muse-html-anchor-on-word
406 (forward-word 1))
407 (muse-insert-markup "</a>\n")))
409 (defun muse-html-markup-anchor ()
410 (unless (get-text-property (match-end 1) 'muse-link)
411 (save-match-data
412 (muse-html-insert-anchor (match-string 2)))
413 (match-string 1)))
415 (defun muse-html-markup-paragraph ()
416 (let ((end (copy-marker (match-end 0) t)))
417 (goto-char (match-beginning 0))
418 (when (save-excursion
419 (save-match-data
420 (and (re-search-backward "<\\(/?\\)p[ >]" nil t)
421 (not (string-equal (match-string 1) "/")))))
422 (when (get-text-property (1- (point)) 'end-list)
423 (goto-char (previous-single-property-change (1- (point)) 'end-list)))
424 (muse-insert-markup "</p>"))
425 (goto-char end))
426 (cond
427 ((eobp)
428 (unless (bolp)
429 (insert "\n")))
430 ((eq (char-after) ?\<)
431 (cond
432 ((looking-at "<\\(em\\|strong\\|code\\|span\\)[ >]")
433 (muse-insert-markup "<p>"))
434 ((looking-at "<a ")
435 (if (looking-at "<a[^>\n]+><img")
436 (muse-insert-markup "<p class=\"image-link\">")
437 (muse-insert-markup "<p>")))
438 ((looking-at "<img[ >]")
439 (muse-insert-markup "<p class=\"image\">"))
441 (forward-char 1)
442 nil)))
443 ((muse-looking-back "\\(</h[1-4]>\\|<hr>\\)\n\n")
444 (muse-insert-markup "<p class=\"first\">"))
446 (muse-insert-markup "<p>"))))
448 (defun muse-html-markup-footnote ()
449 (cond
450 ((get-text-property (match-beginning 0) 'muse-link)
451 nil)
452 ((= (muse-line-beginning-position) (match-beginning 0))
453 (prog1
454 (let ((text (match-string 1)))
455 (muse-insert-markup
456 (concat "<p class=\"footnote\">"
457 "<a name=\"fn." text "\" href=\"#fnr." text "\">"
458 text ".</a>")))
459 (save-excursion
460 (save-match-data
461 (let* ((beg (goto-char (match-end 0)))
462 (end (and (search-forward "\n\n" nil t)
463 (prog1
464 (copy-marker (match-beginning 0))
465 (goto-char beg)))))
466 (while (re-search-forward (concat "^["
467 muse-regexp-blank
468 "]+\\([^\n]\\)")
469 end t)
470 (replace-match "\\1" t)))))
471 (replace-match "")))
472 (t (let ((text (match-string 1)))
473 (muse-insert-markup
474 (concat "<sup><a name=\"fnr." text "\" href=\"#fn." text "\">"
475 text "</a></sup>")))
476 (replace-match ""))))
478 (defun muse-html-markup-table ()
479 (muse-xml-markup-table muse-html-table-attributes))
481 ;; Handling of tags for HTML
483 (defun muse-html-insert-contents (depth)
484 (let ((max-depth (or depth 2))
485 (index 1)
486 base contents l)
487 (save-excursion
488 (goto-char (point-min))
489 (search-forward "Page published by Emacs Muse begins here" nil t)
490 (catch 'done
491 (while (re-search-forward "^<h\\([0-9]+\\)>\\(.+?\\)</h\\1>" nil t)
492 (unless (get-text-property (point) 'read-only)
493 (setq l (1- (string-to-number (match-string 1))))
494 (if (null base)
495 (setq base l)
496 (if (< l base)
497 (throw 'done t)))
498 (when (<= l max-depth)
499 (setq contents (cons (cons l (muse-match-string-no-properties 2))
500 contents))
501 (goto-char (match-beginning 2))
502 (muse-html-insert-anchor (concat "sec" (int-to-string index)))
503 (setq index (1+ index)))))))
504 (setq index 1 contents (nreverse contents))
505 (let ((depth 1) (sub-open 0) (p (point)))
506 (muse-insert-markup "<div class=\"contents\">\n<dl>\n")
507 (while contents
508 (muse-insert-markup "<dt>\n"
509 "<a href=\"#sec" (int-to-string index) "\">"
510 (muse-publish-strip-tags (cdar contents))
511 "</a>\n"
512 "</dt>\n")
513 (setq index (1+ index)
514 depth (caar contents)
515 contents (cdr contents))
516 (when contents
517 (cond
518 ((< (caar contents) depth)
519 (let ((idx (caar contents)))
520 (while (< idx depth)
521 (muse-insert-markup "</dl>\n</dd>\n")
522 (setq sub-open (1- sub-open)
523 idx (1+ idx)))))
524 ((> (caar contents) depth) ; can't jump more than one ahead
525 (muse-insert-markup "<dd>\n<dl>\n")
526 (setq sub-open (1+ sub-open))))))
527 (while (> sub-open 0)
528 (muse-insert-markup "</dl>\n</dd>\n")
529 (setq sub-open (1- sub-open)))
530 (muse-insert-markup "</dl>\n</div>\n")
531 (muse-publish-mark-read-only p (point)))))
533 (defun muse-html-class-tag (beg end attrs)
534 (goto-char beg)
535 (muse-insert-markup "<span class=\"" (cdr (assoc "name" attrs)) "\">")
536 (goto-char end)
537 (muse-insert-markup "</span>"))
539 ;; Register the Muse HTML Publisher
541 (defun muse-html-browse-file (file)
542 (browse-url (concat "file:" file)))
544 (defun muse-html-encoding ()
545 (if (stringp muse-html-meta-content-encoding)
546 muse-html-meta-content-encoding
547 (muse-xml-transform-content-type
548 (or (and (boundp 'buffer-file-coding-system)
549 buffer-file-coding-system)
550 muse-html-encoding-default)
551 muse-html-charset-default)))
553 (defun muse-html-prepare-buffer ()
554 (make-local-variable 'muse-html-meta-http-equiv)
555 (set (make-local-variable 'muse-html-meta-content-type)
556 (if (save-match-data
557 (string-match "charset=" muse-html-meta-content-type))
558 muse-html-meta-content-type
559 (concat muse-html-meta-content-type "; charset="
560 (muse-html-encoding)))))
562 (defun muse-html-finalize-buffer ()
563 (when muse-publish-generate-contents
564 (goto-char (car muse-publish-generate-contents))
565 (muse-html-insert-contents (cdr muse-publish-generate-contents)))
566 (when (and (boundp 'buffer-file-coding-system)
567 (memq buffer-file-coding-system '(no-conversion undecided-unix)))
568 ;; make it agree with the default charset
569 (setq buffer-file-coding-system muse-html-encoding-default))
570 ;; stop processing the :after functions
573 (unless (assoc "html" muse-publishing-styles)
574 (muse-define-style "html"
575 :suffix 'muse-html-extension
576 :regexps 'muse-html-markup-regexps
577 :functions 'muse-html-markup-functions
578 :strings 'muse-html-markup-strings
579 :tags 'muse-html-markup-tags
580 :specials 'muse-xml-decide-specials
581 :before 'muse-html-prepare-buffer
582 :after 'muse-html-finalize-buffer
583 :header 'muse-html-header
584 :footer 'muse-html-footer
585 :style-sheet 'muse-html-style-sheet
586 :browser 'muse-html-browse-file)
588 (muse-derive-style "xhtml" "html"
589 :suffix 'muse-xhtml-extension
590 :strings 'muse-xhtml-markup-strings
591 :header 'muse-xhtml-header
592 :footer 'muse-xhtml-footer
593 :style-sheet 'muse-xhtml-style-sheet))
595 (provide 'muse-html)
597 ;;; muse-html.el ends here