Fix an issue with muse-project-file-alist.
[muse-el.git] / lisp / muse-html.el
blobac9f22d7cd1b754ec594b1631d17435c59c33815
1 ;;; muse-html.el --- publish to HTML and XHTML
3 ;; Copyright (C) 2004, 2005 Free Software Foundation, Inc.
5 ;; This file is not part of GNU Emacs.
7 ;; This is free software; you can redistribute it and/or modify it under
8 ;; the terms of the GNU General Public License as published by the Free
9 ;; Software Foundation; either version 2, or (at your option) any later
10 ;; version.
12 ;; This is distributed in the hope that it will be useful, but WITHOUT
13 ;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 ;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 ;; for more details.
17 ;; You should have received a copy of the GNU General Public License
18 ;; along with GNU Emacs; 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>
132 <lisp>muse-html-style-sheet</lisp>
133 </head>
134 <body>
135 <h1><lisp>
136 (concat (muse-publishing-directive \"title\")
137 (let ((author (muse-publishing-directive \"author\")))
138 (if (not (string= author (user-full-name)))
139 (concat \" (by \" author \")\"))))</lisp></h1>
140 <!-- Page published by Emacs Muse begins here -->\n"
141 "Header used for publishing HTML files. This may be text or a filename."
142 :type 'string
143 :group 'muse-html)
145 (defcustom muse-html-footer "
146 <!-- Page published by Emacs Muse ends here -->
147 </body>
148 </html>\n"
149 "Footer used for publishing HTML files. This may be text or a filename."
150 :type 'string
151 :group 'muse-html)
153 (defcustom muse-xhtml-header
154 "<?xml version=\"1.0\" encoding=\"<lisp>
155 (muse-html-encoding)</lisp>\"?>
156 <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"
157 \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">
158 <html xmlns=\"http://www.w3.org/1999/xhtml\">
159 <head>
160 <title><lisp>
161 (concat (muse-publishing-directive \"title\")
162 (let ((author (muse-publishing-directive \"author\")))
163 (if (not (string= author (user-full-name)))
164 (concat \" (by \" author \")\"))))</lisp></title>
165 <meta name=\"generator\" content=\"muse.el\" />
166 <meta http-equiv=\"<lisp>muse-html-meta-http-equiv</lisp>\"
167 content=\"<lisp>muse-html-meta-content-type</lisp>\" />
168 <lisp>
169 (let ((maintainer (muse-style-element :maintainer)))
170 (when maintainer
171 (concat \"<link rev=\\\"made\\\" href=\\\"\" maintainer \"\\\" />\")))
172 </lisp>
173 <lisp>muse-xhtml-style-sheet</lisp>
174 </head>
175 <body>
176 <h1><lisp>
177 (concat (muse-publishing-directive \"title\")
178 (let ((author (muse-publishing-directive \"author\")))
179 (if (not (string= author (user-full-name)))
180 (concat \" (by \" author \")\"))))</lisp></h1>
181 <!-- Page published by Emacs Muse begins here -->\n"
182 "Header used for publishing XHTML files. This may be text or a filename."
183 :type 'string
184 :group 'muse-html)
186 (defcustom muse-xhtml-footer "
187 <!-- Page published by Emacs Muse ends here -->
188 </body>
189 </html>\n"
190 "Footer used for publishing XHTML files. This may be text or a filename."
191 :type 'string
192 :group 'muse-html)
194 (defcustom muse-html-anchor-on-word nil
195 "When true, anchors surround the closest word. This allows you
196 to select them in a browser (i.e. for pasting), but has the
197 side-effect of marking up headers in multiple colors if your
198 header style is different from your link style."
199 :type 'boolean
200 :group 'muse-html)
202 (defcustom muse-html-table-attributes
203 " class=\"muse-table\" border=\"2\" cellpadding=\"5\""
204 "The attribute to be used with HTML <table> tags.
205 Note that since Muse supports insertion of raw HTML tags, as long
206 as you wrap the region in <literal></literal>."
207 :type 'string
208 :group 'muse-html)
210 (defcustom muse-html-markup-regexps
211 `(;; Beginning of doc, end of doc, or plain paragraph separator
212 (10000 ,(concat "\\(\\(\n\\([" muse-regexp-blank "]*\n\\)+\\)"
213 "\\|\\`\\s-*\\|\\s-*\\'\\)")
214 0 muse-html-markup-paragraph))
215 "List of markup rules for publishing a Muse page to HTML.
216 For more on the structure of this list, see `muse-publish-markup-regexps'."
217 :type '(repeat (choice
218 (list :tag "Markup rule"
219 integer
220 (choice regexp symbol)
221 integer
222 (choice string function symbol))
223 function))
224 :group 'muse-html)
226 (defcustom muse-html-markup-functions
227 '((anchor . muse-html-markup-anchor)
228 (table . muse-html-markup-table)
229 (footnote . muse-html-markup-footnote))
230 "An alist of style types to custom functions for that kind of text.
231 For more on the structure of this list, see
232 `muse-publish-markup-functions'."
233 :type '(alist :key-type symbol :value-type function)
234 :group 'muse-html)
236 (defcustom muse-html-markup-strings
237 '((image-with-desc . "<img src=\"%s\" alt=\"%s\">")
238 (image-link . "<img src=\"%s\" alt=\"\">")
239 (url-with-image . "<a class=\"image-link\" href=\"%s\"><img src=\"%s\"></a>")
240 (anchor-ref . "<a href=\"#%s\">%s</a>")
241 (url . "<a href=\"%s\">%s</a>")
242 (link . "<a href=\"%s\">%s</a>")
243 (link-and-anchor . "<a href=\"%s#%s\">%s</a>")
244 (email-addr . "<a href=\"mailto:%s\">%s</a>")
245 (emdash . " &mdash; ")
246 (comment-begin . "<!-- ")
247 (comment-end . " -->")
248 (rule . "<hr>")
249 (fn-sep . "<hr>\n")
250 (no-break-space . "&nbsp;")
251 (enddots . "....")
252 (dots . "...")
253 (section . "<h2>")
254 (section-end . "</h2>")
255 (subsection . "<h3>")
256 (subsection-end . "</h3>")
257 (subsubsection . "<h4>")
258 (subsubsection-end . "</h4>")
259 (section-other . "<h5>")
260 (section-other-end . "</h5>")
261 (begin-underline . "<u>")
262 (end-underline . "</u>")
263 (begin-literal . "<code>")
264 (end-literal . "</code>")
265 (begin-emph . "<em>")
266 (end-emph . "</em>")
267 (begin-more-emph . "<strong>")
268 (end-more-emph . "</strong>")
269 (begin-most-emph . "<strong><em>")
270 (end-most-emph . "</em></strong>")
271 (begin-verse . "<p class=\"verse\">\n")
272 (verse-space . "&nbsp;&nbsp;")
273 (end-verse-line . "<br>")
274 (end-last-stanza-line . "<br>")
275 (empty-verse-line . "<br>")
276 (end-verse . "</p>")
277 (begin-example . "<pre class=\"example\">")
278 (end-example . "</pre>")
279 (begin-center . "<center>\n<p>")
280 (end-center . "</p>\n</center>")
281 (begin-quote . "<blockquote>\n")
282 (end-quote . "\n</blockquote>")
283 (begin-quote-item . "<p class=\"quoted\">")
284 (end-quote-item . "</p>")
285 (begin-uli . "<ul>\n")
286 (end-uli . "\n</ul>")
287 (begin-uli-item . "<li>")
288 (end-uli-item . "</li>")
289 (begin-oli . "<ol>\n")
290 (end-oli . "\n</ol>")
291 (begin-oli-item . "<li>")
292 (end-oli-item . "</li>")
293 (begin-dl . "<dl>\n")
294 (end-dl . "\n</dl>")
295 (begin-ddt . "<dt><strong>")
296 (end-ddt . "</strong></dt>\n")
297 (begin-dde . "<dd>")
298 (end-dde . "</dd>")
299 (begin-table . "<table%s>\n")
300 (end-table . "</table>\n")
301 (begin-table-row . " <tr>\n")
302 (end-table-row . " </tr>\n")
303 (begin-table-entry . " <%s>")
304 (end-table-entry . "</%s>\n"))
305 "Strings used for marking up text as HTML.
306 These cover the most basic kinds of markup, the handling of which
307 differs little between the various styles."
308 :type '(alist :key-type symbol :value-type string)
309 :group 'muse-html)
311 (defcustom muse-xhtml-markup-strings
312 '((image-with-desc . "<img src=\"%s\" alt=\"%s\" />")
313 (image-link . "<img src=\"%s\" alt=\"\" />")
314 (url-with-image . "<a class=\"image-link\" href=\"%s\"><img src=\"%s\" alt=\"\" /></a>")
315 (rule . "<hr />")
316 (fn-sep . "<hr />\n")
317 (begin-underline . "<span style=\"text-decoration: underline;\">")
318 (end-underline . "</span>")
319 (begin-center . "<p style=\"text-align: center;\">\n")
320 (end-center . "\n</p>")
321 (end-verse-line . "<br />")
322 (end-last-stanza-line . "<br />")
323 (empty-verse-line . "<br />"))
324 "Strings used for marking up text as XHTML.
325 These cover the most basic kinds of markup, the handling of which
326 differs little between the various styles.
328 If a markup rule is not found here, `muse-html-markup-strings' is
329 searched."
330 :type '(alist :key-type symbol :value-type string)
331 :group 'muse-html)
333 (defcustom muse-html-markup-tags
334 '(("class" t t muse-html-class-tag))
335 "A list of tag specifications, for specially marking up HTML."
336 :type '(repeat (list (string :tag "Markup tag")
337 (boolean :tag "Expect closing tag" :value t)
338 (boolean :tag "Parse attributes" :value nil)
339 function))
340 :group 'muse-html)
342 (defcustom muse-html-meta-http-equiv "Content-Type"
343 "The http-equiv attribute used for the HTML <meta> tag."
344 :type 'string
345 :group 'muse-html)
347 (defcustom muse-html-meta-content-type "text/html"
348 "The content type used for the HTML <meta> tag.
349 If you are striving for XHTML 1.1 compliance, you may want to
350 change this to \"application/xhtml+xml\"."
351 :type 'string
352 :group 'muse-html)
354 (defcustom muse-html-meta-content-encoding (if (featurep 'mule)
355 'detect
356 "iso-8859-1")
357 "The charset to append to the HTML <meta> tag.
358 If set to the symbol 'detect, use `muse-html-encoding-map' to try
359 and determine the HTML charset from emacs's coding. If set to a
360 string, this string will be used to force a particular charset"
361 :type '(choice string symbol)
362 :group 'muse-html)
364 (defcustom muse-html-encoding-default 'iso-8859-1
365 "The default Emacs buffer encoding to use in published files.
366 This will be used if no special characters are found."
367 :type 'symbol
368 :group 'muse-html)
370 (defcustom muse-html-charset-default "iso-8859-1"
371 "The default HTML meta charset to use if no translation is found in
372 `muse-html-encoding-map'."
373 :type 'string
374 :group 'muse-html)
376 (defun muse-html-insert-anchor (anchor)
377 "Insert an anchor, either around the word at point, or within a tag."
378 (skip-chars-forward (concat muse-regexp-blank "\n"))
379 (if (looking-at (concat "<\\([^" muse-regexp-blank "/>\n]+\\)>"))
380 (let ((tag (match-string 1)))
381 (goto-char (match-end 0))
382 (muse-insert-markup "<a name=\"" anchor "\" id=\"" anchor "\">")
383 (when muse-html-anchor-on-word
384 (or (and (search-forward (format "</%s>" tag)
385 (muse-line-end-position) t)
386 (goto-char (match-beginning 0)))
387 (forward-word 1)))
388 (muse-insert-markup "</a>"))
389 (muse-insert-markup "<a name=\"" anchor "\" id=\"" anchor "\">")
390 (when muse-html-anchor-on-word
391 (forward-word 1))
392 (muse-insert-markup "</a>\n")))
394 (defun muse-html-markup-anchor ()
395 (unless (get-text-property (match-end 1) 'noemphasis)
396 (save-match-data
397 (muse-html-insert-anchor (match-string 2)))
398 (match-string 1)))
400 (defun muse-html-markup-paragraph ()
401 (let ((end (copy-marker (match-end 0) t)))
402 (goto-char (match-beginning 0))
403 (when (save-excursion
404 (save-match-data
405 (and (re-search-backward "<\\(/?\\)p[ >]" nil t)
406 (not (string-equal (match-string 1) "/")))))
407 (muse-insert-markup "</p>"))
408 (goto-char end))
409 (cond
410 ((eobp)
411 (unless (bolp)
412 (insert "\n")))
413 ((eq (char-after) ?\<)
414 (cond
415 ((looking-at "<\\(em\\|strong\\|code\\|span\\)[ >]")
416 (muse-insert-markup "<p>"))
417 ((looking-at "<a ")
418 (if (looking-at "<a[^>\n]+><img")
419 (muse-insert-markup "<p class=\"image-link\">")
420 (muse-insert-markup "<p>")))
421 ((looking-at "<img[ >]")
422 (muse-insert-markup "<p class=\"image-link\">"))))
423 ((muse-looking-back "\\(</h[1-4]>\\|<hr>\\)\n\n")
424 (muse-insert-markup "<p class=\"first\">"))
426 (muse-insert-markup "<p>"))))
428 (defun muse-html-markup-footnote ()
429 (cond
430 ((get-text-property (match-beginning 0) 'noemphasis)
431 nil)
432 ((= (muse-line-beginning-position) (match-beginning 0))
433 (prog1
434 (let ((text (match-string 1)))
435 (muse-insert-markup
436 (concat "<p class=\"footnote\">"
437 "<a name=\"fn." text "\" href=\"#fnr." text "\">"
438 text ".</a>")))
439 (save-excursion
440 (save-match-data
441 (let* ((beg (goto-char (match-end 0)))
442 (end (and (search-forward "\n\n" nil t)
443 (prog1
444 (copy-marker (match-beginning 0))
445 (goto-char beg)))))
446 (while (re-search-forward (concat "^["
447 muse-regexp-blank
448 "]+\\([^\n]\\)")
449 end t)
450 (replace-match "\\1" t)))))
451 (replace-match "")))
452 (t (let ((text (match-string 1)))
453 (muse-insert-markup
454 (concat "<sup><a name=\"fnr." text "\" href=\"#fn." text "\">"
455 text "</a></sup>")))
456 (replace-match ""))))
458 (defun muse-html-markup-table ()
459 (muse-xml-markup-table muse-html-table-attributes))
461 ;; Handling of tags for HTML
463 (defun muse-html-insert-contents (depth)
464 (let ((max-depth (or depth 2))
465 (index 1)
466 base contents l)
467 (save-excursion
468 (goto-char (point-min))
469 (search-forward "Page published by Emacs Muse begins here" nil t)
470 (catch 'done
471 (while (re-search-forward "^<h\\([0-9]+\\)>\\(.+?\\)</h\\1>" nil t)
472 (unless (get-text-property (point) 'read-only)
473 (setq l (1- (string-to-number (match-string 1))))
474 (if (null base)
475 (setq base l)
476 (if (< l base)
477 (throw 'done t)))
478 (when (<= l max-depth)
479 (setq contents (cons (cons l (muse-match-string-no-properties 2))
480 contents))
481 (goto-char (match-beginning 2))
482 (muse-html-insert-anchor (concat "sec" (int-to-string index)))
483 (setq index (1+ index)))))))
484 (setq index 1 contents (reverse contents))
485 (let ((depth 1) (sub-open 0) (p (point)))
486 (muse-insert-markup "<dl class=\"contents\">\n")
487 (while contents
488 (muse-insert-markup "<dt class=\"contents\">\n"
489 "<a href=\"#sec" (int-to-string index) "\">"
490 (muse-publish-strip-tags (cdar contents))
491 "</a>\n"
492 "</dt>\n")
493 (setq index (1+ index)
494 depth (caar contents)
495 contents (cdr contents))
496 (when contents
497 (cond
498 ((< (caar contents) depth)
499 (let ((idx (caar contents)))
500 (while (< idx depth)
501 (muse-insert-markup "</dl>\n</dd>\n")
502 (setq sub-open (1- sub-open)
503 idx (1+ idx)))))
504 ((> (caar contents) depth) ; can't jump more than one ahead
505 (muse-insert-markup "<dd>\n<dl class=\"contents\">\n")
506 (setq sub-open (1+ sub-open))))))
507 (while (> sub-open 0)
508 (muse-insert-markup "</dl>\n</dd>\n")
509 (setq sub-open (1- sub-open)))
510 (muse-insert-markup "</dl>\n")
511 (muse-publish-mark-read-only p (point)))))
513 (defun muse-html-class-tag (beg end attrs)
514 (goto-char beg)
515 (muse-insert-markup "<span class=\"" (cdr (assoc "name" attrs)) "\">")
516 (goto-char end)
517 (muse-insert-markup "</span>"))
519 ;; Register the Muse HTML Publisher
521 (defun muse-html-browse-file (file)
522 (browse-url (concat "file:" file)))
524 (defun muse-html-encoding ()
525 (if (stringp muse-html-meta-content-encoding)
526 muse-html-meta-content-encoding
527 (muse-xml-transform-content-type
528 (or (and (boundp 'buffer-file-coding-system)
529 buffer-file-coding-system)
530 muse-html-encoding-default)
531 muse-html-charset-default)))
533 (defun muse-html-prepare-buffer ()
534 (make-local-variable 'muse-html-meta-http-equiv)
535 (set (make-local-variable 'muse-html-meta-content-type)
536 (if (save-match-data
537 (string-match "charset=" muse-html-meta-content-type))
538 muse-html-meta-content-type
539 (concat muse-html-meta-content-type "; charset="
540 (muse-html-encoding)))))
542 (defun muse-html-finalize-buffer ()
543 (when muse-publish-generate-contents
544 (goto-char (car muse-publish-generate-contents))
545 (muse-html-insert-contents (cdr muse-publish-generate-contents)))
546 (when (and (boundp 'buffer-file-coding-system)
547 (memq buffer-file-coding-system '(no-conversion undecided-unix)))
548 ;; make it agree with the default charset
549 (setq buffer-file-coding-system muse-html-encoding-default)))
551 (unless (assoc "html" muse-publishing-styles)
552 (muse-define-style "html"
553 :suffix 'muse-html-extension
554 :regexps 'muse-html-markup-regexps
555 :functions 'muse-html-markup-functions
556 :strings 'muse-html-markup-strings
557 :tags 'muse-html-markup-tags
558 :specials 'muse-xml-decide-specials
559 :before 'muse-html-prepare-buffer
560 :after 'muse-html-finalize-buffer
561 :header 'muse-html-header
562 :footer 'muse-html-footer
563 :browser 'muse-html-browse-file)
565 (muse-derive-style "xhtml" "html"
566 :suffix 'muse-xhtml-extension
567 :strings 'muse-xhtml-markup-strings
568 :header 'muse-xhtml-header
569 :footer 'muse-xhtml-footer))
571 (provide 'muse-html)
573 ;;; muse-html.el ends here