Standardize source headers, appease elint, don't require planner
[muse-el.git] / muse-html.el
blobca498cdb54b6e3d76f7804d4bfaae65fc859d36b
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., 59 Temple Place - Suite 330, Boston,
20 ;; MA 02111-1307, USA.
22 ;;; Commentary:
24 ;;; Contributors:
26 ;;; Code:
28 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
30 ;; Muse HTML Publishing
32 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
34 (require 'muse-publish)
35 (require 'muse-regexps)
37 (defgroup muse-html nil
38 "Options controlling the behaviour of Muse HTML publishing.
39 See `muse-html' for more information."
40 :group 'muse-publish)
42 (defcustom muse-html-extension ".html"
43 "Default file extension for publishing HTML files."
44 :type 'string
45 :group 'muse-html)
47 (defcustom muse-html-style-sheet
48 "<style type=\"text/css\">
49 body {
50 background: white; color: black;
51 margin-left: 3%; margin-right: 7%;
54 p { margin-top: 1% }
55 p.verse { margin-left: 3% }
57 .example { margin-left: 3% }
59 h2 {
60 margin-top: 25px;
61 margin-bottom: 0px;
63 h3 { margin-bottom: 0px; }
64 </style>"
65 "Store your stylesheet definitions here.
66 This is used in `muse-html-header'.
67 You can put raw CSS in here or a <link> tag to an external stylesheet.
68 This text may contain <lisp> markup tags.
70 An example of using <link> is as follows.
72 <link rel=\"stylesheet\" type=\"text/css\" charset=\"utf-8\" media=\"all\" href=\"/default.css\">
74 If you are using XHTML, make sure to close the tag properly, as
75 shown in the following example.
77 <link rel=\"stylesheet\" type=\"text/css\" charset=\"utf-8\" media=\"all\" href=\"/default.css\" />"
78 :type 'string
79 :group 'muse-html)
81 (defcustom muse-html-header
82 "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0//EN\">
83 <html>
84 <head>
85 <title><lisp>
86 (concat (muse-publishing-directive \"title\")
87 (let ((author (muse-publishing-directive \"author\")))
88 (if (not (string= author (user-full-name)))
89 (concat \" (by \" author \")\"))))</lisp></title>
90 <meta name=\"generator\" content=\"muse.el\">
91 <meta http-equiv=\"<lisp>muse-html-meta-http-equiv</lisp>\"
92 content=\"<lisp>muse-html-meta-content-type</lisp>\">
93 <lisp>
94 (let ((maintainer (muse-style-element :maintainer)))
95 (when maintainer
96 (concat \"<link rev=\\\"made\\\" href=\\\"\" maintainer \"\\\">\")))
97 </lisp>
98 <lisp>muse-html-style-sheet</lisp>
99 </head>
100 <body>
101 <h1><lisp>
102 (concat (muse-publishing-directive \"title\")
103 (let ((author (muse-publishing-directive \"author\")))
104 (if (not (string= author (user-full-name)))
105 (concat \" (by \" author \")\"))))</lisp></h1>
106 <!-- Page published by Emacs Muse begins here -->\n"
107 "Header used for publishing HTML files."
108 :type '(choice string file)
109 :group 'muse-html)
111 (defcustom muse-html-footer "
112 <!-- Page published by Emacs Muse ends here -->
113 </body>
114 </html>\n"
115 "Footer used for publishing HTML files."
116 :type '(choice string file)
117 :group 'muse-html)
119 (defcustom muse-xhtml-header
120 "<?xml version=\"1.0\"?>
121 <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"
122 \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">
123 <html xmlns=\"http://www.w3.org/1999/xhtml\">
124 <head>
125 <title><lisp>
126 (concat (muse-publishing-directive \"title\")
127 (let ((author (muse-publishing-directive \"author\")))
128 (if (not (string= author (user-full-name)))
129 (concat \" (by \" author \")\"))))</lisp></title>
130 <meta name=\"generator\" content=\"muse.el\" />
131 <meta http-equiv=\"<lisp>muse-html-meta-http-equiv</lisp>\"
132 content=\"<lisp>muse-html-meta-content-type</lisp>\" />
133 <lisp>
134 (let ((maintainer (muse-style-element :maintainer)))
135 (when maintainer
136 (concat \"<link rev=\\\"made\\\" href=\\\"\" maintainer \"\\\" />\")))
137 </lisp>
138 <lisp>muse-html-style-sheet</lisp>
139 </head>
140 <body>
141 <h1><lisp>
142 (concat (muse-publishing-directive \"title\")
143 (let ((author (muse-publishing-directive \"author\")))
144 (if (not (string= author (user-full-name)))
145 (concat \" (by \" author \")\"))))</lisp></h1>
146 <!-- Page published by Emacs Muse begins here -->\n"
147 "Header used for publishing XHTML files."
148 :type '(choice string file)
149 :group 'muse-html)
151 (defcustom muse-xhtml-footer "
152 <!-- Page published by Emacs Muse ends here -->
153 </body>
154 </html>\n"
155 "Footer used for publishing XHTML files."
156 :type '(choice string file)
157 :group 'muse-html)
159 (defcustom muse-html-anchor-on-word nil
160 "When true, anchors surround the closest word. This allows you
161 to select them in a browser (ie, for pasting), but has the
162 side-effect of marking up headers in multiple colours if your
163 header style is different from your link style."
164 :type 'boolean
165 :group 'muse-html)
167 (defcustom muse-html-table-attributes
168 "class=\"muse-table\" border=\"2\" cellpadding=\"5\""
169 "The attribute to be used with HTML <table> tags.
170 Note that since Muse supports direct insertion of HTML tags, you
171 can easily create any kind of table you want, as long as each
172 line begins at column 0 (to prevent it from being blockquote'd).
173 To make such a table, use this idiom:
175 <verbatim>
176 <table>
177 [... contents of my table, in raw HTML ...]
178 </verbatim></table>
180 It may look strange to have the tags out of sequence, but this is
181 because the Muse verbatim tag is handled during a different pass
182 than the HTML table tag."
183 :type 'string
184 :group 'muse-html)
186 (defcustom muse-html-markup-regexps
187 `(;; join together the parts of a list or table
188 (10000 "</\\([oud]l\\)>\\s-*<\\1>\\s-*" 0 "")
189 (10100 ,(concat " </t\\(body\\|head\\|foot\\)>\\s-*</table>\\s-*"
190 "<table[^>]*>\\s-*<t\\1>\n") 0 "")
191 (10200 "</table>\\s-*<table[^>]*>\n" 0 "")
193 ;; the beginning of the buffer begins the first paragraph
194 (10300 "\\`\n*\\([^<-]\\|<\\(em\\|strong\\|code\\)>\\|<a \\)" 0
195 "<p class=\"first\">\\1")
196 ;; plain paragraph separator
197 (10400 ,(concat "\\(\n</\\(blockquote\\|center\\)>\\)?\n"
198 "\\(["
199 muse-regexp-blank
200 "]*\n\\)+\\(<\\(blockquote\\|center\\)>\n\\)?")
201 0 muse-html-markup-paragraph)
202 (10500 ,(concat "\\([^>"
203 muse-regexp-space
204 "]\\)\\s-*\\'")
205 0 "\\1</p>\n"))
206 "List of markup rules for publishing a Muse page to HTML.
207 For more on the structure of this list, see `muse-publish-markup-regexps'."
208 :type '(repeat (choice
209 (list :tag "Markup rule"
210 integer
211 (choice regexp symbol)
212 integer
213 (choice string function symbol))
214 function))
215 :group 'muse-html)
217 (defcustom muse-html-markup-functions
218 '((anchor . muse-html-markup-anchor)
219 (table . muse-html-markup-table)
220 (footnote . muse-html-markup-footnote))
221 "An alist of style types to custom functions for that kind of text.
222 For more on the structure of this list, see
223 `muse-publish-markup-functions'."
224 :type '(alist :key-type symbol :value-type function)
225 :group 'muse-html)
227 (defcustom muse-html-markup-strings
228 '((image-with-desc . "<img src=\"%s\" alt=\"%s\">")
229 (image-link . "<img src=\"%s\">")
230 (url-with-image . "<a class=\"image-link\" href=\"%s\"><img src=\"%s\"></a>")
231 (url-link . "<a href=\"%s\">%s</a>")
232 (email-addr . "<a href=\"mailto:%s\">%s</a>")
233 (emdash . " &mdash; ")
234 (rule . "<hr>")
235 (fn-sep . "<hr>\n")
236 (enddots . "....")
237 (dots . "...")
238 (section . "<h2>")
239 (section-end . "</h2>")
240 (subsection . "<h3>")
241 (subsection-end . "</h3>")
242 (subsubsection . "<h4>")
243 (subsubsection-end . "</h4>")
244 (begin-underline . "<u>")
245 (end-underline . "</u>")
246 (begin-literal . "<code>")
247 (end-literal . "</code>")
248 (begin-emph . "<em>")
249 (end-emph . "</em>")
250 (begin-more-emph . "<strong>")
251 (end-more-emph . "</strong>")
252 (begin-most-emph . "<strong><em>")
253 (end-most-emph . "</em></strong>")
254 (begin-verse . "<p class=\"verse\">\n")
255 (verse-space . "&nbsp;&nbsp;")
256 (end-verse-line . "<br>")
257 (last-stanza-end . "<br>")
258 (empty-verse-line . "<br>")
259 (end-verse . "</p>")
260 (begin-example . "<pre class=\"example\">")
261 (end-example . "</pre>")
262 (begin-center . "<center>\n")
263 (end-center . "\n</center>")
264 (begin-quote . "<blockquote>\n")
265 (end-quote . "\n</blockquote>")
266 (begin-uli . "<ul>\n<li>")
267 (end-uli . "</li>\n</ul>")
268 (begin-oli . "<ol>\n<li>")
269 (end-oli . "</li>\n</ol>")
270 (begin-ddt . "<dl>\n<dt><strong>")
271 (start-dde . "</strong></dt>\n<dd>")
272 (end-ddt . "</dd>\n</dl>"))
273 "Strings used for marking up text.
274 These cover the most basic kinds of markup, the handling of which
275 differs little between the various styles."
276 :type '(alist :key-type symbol :value-type string)
277 :group 'muse-html)
279 (defcustom muse-xhtml-markup-strings
280 '((image-with-desc . "<img src=\"%s\" alt=\"%s\" />")
281 (image-link . "<img src=\"%s\" alt=\"\" />")
282 (url-with-image . "<a class=\"image-link\" href=\"%s\"><img src=\"%s\" alt=\"\" /></a>")
283 (begin-underline . "<span style=\"text-decoration: underline;\">\n")
284 (end-underline . "</span>")
285 (begin-center . "<span style=\"text-align: center;\">\n")
286 (end-center . "\n</span>"))
287 "Strings used for marking up text.
288 These cover the most basic kinds of markup, the handling of which
289 differs little between the various styles.
291 If a markup rule is not found here, `muse-html-markup-strings' is
292 searched."
293 :type '(alist :key-type symbol :value-type string)
294 :group 'muse-html)
296 (defcustom muse-html-markup-tags
297 '(("class" t t muse-html-class-tag))
298 "A list of tag specifications, for specially marking up HTML."
299 :type '(repeat (list (string :tag "Markup tag")
300 (boolean :tag "Expect closing tag" :value t)
301 (boolean :tag "Parse attributes" :value nil)
302 function))
303 :group 'muse-html)
305 (defcustom muse-html-markup-specials
306 '((?\" . "&quot;")
307 (?\< . "&lt;")
308 (?\> . "&gt;")
309 (?\& . "&amp;"))
310 "A table of characters which must be represented specially."
311 :type '(alist :key-type character :value-type string)
312 :group 'muse-html)
314 (defcustom muse-html-meta-http-equiv "Content-Type"
315 "The http-equiv attribute used for the HTML <meta> tag."
316 :type 'string
317 :group 'muse-html)
319 (defcustom muse-html-meta-content-type "text/html"
320 "The content type used for the HTML <meta> tag."
321 :type 'string
322 :group 'muse-html)
324 (defcustom muse-html-meta-content-encoding (if (featurep 'mule)
325 'detect
326 "iso-8859-1")
327 "If set to the symbol 'detect, use `muse-coding-map' to try
328 and determine the HTML charset from emacs's coding. If set to a string, this
329 string will be used to force a particular charset"
330 :type '(choice string symbol)
331 :group 'muse-html)
333 (defcustom muse-html-charset-default "iso-8859-1"
334 "The default HTML meta charset to use if no translation is found in
335 `muse-coding-map'"
336 :type 'string
337 :group 'muse-html)
339 (defcustom muse-html-encoding-default 'iso-8859-1
340 "The default emacs coding use if no special characters are found"
341 :type 'symbol
342 :group 'muse-html)
344 (defcustom muse-html-encoding-map
345 '((iso-2022-jp . "iso-2022-jp")
346 (utf-8 . "utf-8")
347 (japanese-iso-8bit . "euc-jp")
348 (chinese-big5 . "big5")
349 (mule-utf-8 . "utf-8")
350 (chinese-iso-8bit . "gb2312")
351 (chinese-gbk . "gbk"))
352 "An alist mapping emacs coding systems to appropriate HTML charsets.
353 Use the base name of the coding system (ie, without the -unix)"
354 :type '(alist :key-type coding-system :value-type string)
355 :group 'muse-html)
357 (defun muse-html-transform-content-type (content-type)
358 "Using `muse-html-encoding-map', try and resolve an emacs coding
359 system to an associated HTML coding system. If no match is found,
360 `muse-html-charset-default' is used instead."
361 (let ((match (assoc (coding-system-base content-type)
362 muse-html-encoding-map)))
363 (if match
364 (cdr match)
365 muse-html-charset-default)))
367 (defun muse-html-insert-anchor (anchor)
368 "Insert an anchor, either around the word at point, or within a tag."
369 (skip-chars-forward muse-regexp-space)
370 (if (looking-at "<\\([^ />]+\\)>")
371 (let ((tag (match-string 1)))
372 (goto-char (match-end 0))
373 (insert "<a name=\"" anchor "\" id=\"" anchor "\">")
374 (when muse-html-anchor-on-word
375 (or (and (search-forward (format "</%s>" tag)
376 (line-end-position) t)
377 (goto-char (match-beginning 0)))
378 (forward-word 1)))
379 (insert "</a>"))
380 (insert "<a name=\"" anchor "\" id=\"" anchor "\">")
381 (when muse-html-anchor-on-word
382 (forward-word 1))
383 (insert "</a>")))
385 (unless (fboundp 'looking-back)
386 (defun looking-back (regexp &optional limit)
387 (save-excursion
388 (re-search-backward (concat "\\(?:" regexp "\\)\\=") limit t))))
390 (defun muse-html-markup-paragraph ()
391 (let ((end (copy-marker (match-end 0) t)))
392 (goto-char (match-beginning 0))
393 (unless (eq (char-before) ?\>) (insert "</p>"))
394 (goto-char end)
395 (unless (and (eq (char-after) ?\<)
396 (not (or (looking-at "<\\(em\\|strong\\|code\\)>")
397 (and (looking-at "<a ")
398 (not (looking-at "<a[^>]+><img"))))))
399 (cond
400 ((looking-back "\\(</h[1-4]>\\|<hr>\\)\n\n")
401 (insert "<p class=\"first\">"))
402 ((looking-back "<\\(blockquote\\|center\\)>\n")
403 (insert "<p class=\"quoted\">"))
405 (insert "<p>"))))))
407 (defun muse-html-markup-anchor ()
408 (save-match-data
409 (muse-html-insert-anchor (match-string 1))) "")
411 (defun muse-html-escape-string (str)
412 "Convert to character entities any non-alphanumeric characters
413 outside a few punctuation symbols, that risk being misinterpreted
414 if not escaped."
415 (when str
416 (let (pos code len)
417 (save-match-data
418 (while (setq pos (string-match (concat "[^-"
419 muse-regexp-alnum
420 "/:._=@\\?~#]")
421 str pos))
422 (setq code (int-to-string (aref str pos))
423 len (length code)
424 str (replace-match (concat "&#" code ";") nil nil str)
425 pos (+ 3 len pos)))
426 str))))
428 (defun muse-html-markup-footnote ()
429 (if (/= (line-beginning-position) (match-beginning 0))
430 "<sup><a name=\"fnr.\\1\" href=\"#fn.\\1\">\\1</a></sup>"
431 (prog1
432 "<p class=\"footnote\"><a name=\"fn.\\1\" href=\"#fnr.\\1\">\\1.</a>"
433 (save-excursion
434 (save-match-data
435 (let* ((beg (goto-char (match-end 0)))
436 (end (and (search-forward "\n\n" nil t)
437 (prog1
438 (copy-marker (match-beginning 0))
439 (goto-char beg)))))
440 (while (re-search-forward (concat "^["
441 muse-regexp-blank
442 "]+\\([^\n]\\)")
443 end t)
444 (replace-match "\\1" t))))))))
446 (defun muse-html-markup-table ()
447 (let* ((str (prog1
448 (match-string 1)
449 (delete-region (match-beginning 0) (match-end 0))))
450 (fields (split-string str "\\s-*|+\\s-*"))
451 (type (and (string-match "\\s-*\\(|+\\)\\s-*" str)
452 (length (match-string 1 str))))
453 (part (cond ((= type 1) "tbody")
454 ((= type 2) "thead")
455 ((= type 3) "tfoot")))
456 (col (cond ((= type 1) "td")
457 ((= type 2) "th")
458 ((= type 3) "td")))
459 field)
460 (insert "<table " muse-html-table-attributes ">\n"
461 " <" part ">\n"
462 " <tr>\n")
463 (dolist (field fields)
464 (insert " <" col ">" field "</" col ">\n"))
465 (insert " </tr>\n"
466 " </" part ">\n"
467 "</table>\n")))
469 ;; Handling of tags for HTML
471 (defun muse-html-insert-contents (depth)
472 (let ((max-depth (or depth 2))
473 (index 1)
474 base contents l)
475 (save-excursion
476 (goto-char (point-min))
477 (search-forward "Page published by Emacs Muse begins here" nil t)
478 (catch 'done
479 (while (re-search-forward "^<h\\([0-9]+\\)>\\(.+?\\)</h\\1>" nil t)
480 (unless (get-text-property (point) 'read-only)
481 (setq l (1- (string-to-int (match-string 1))))
482 (if (null base)
483 (setq base l)
484 (if (< l base)
485 (throw 'done t)))
486 (when (<= l max-depth)
487 (setq contents (cons (cons l (match-string-no-properties 2))
488 contents))
489 (goto-char (match-beginning 2))
490 (muse-html-insert-anchor (concat "sec" (int-to-string index)))
491 (setq index (1+ index)))))))
492 (setq index 1 contents (reverse contents))
493 (let ((depth 1) (sub-open 0) (p (point)))
494 (insert "<dl class=\"contents\">\n")
495 (while contents
496 (insert "<dt class=\"contents\">\n")
497 (insert "<a href=\"#sec" (int-to-string index) "\">"
498 (muse-publish-strip-tags (cdar contents))
499 "</a>\n")
500 (setq index (1+ index))
501 (insert "</dt>\n")
502 (setq depth (caar contents)
503 contents (cdr contents))
504 (if contents
505 (cond
506 ((< (caar contents) depth)
507 (let ((idx (caar contents)))
508 (while (< idx depth)
509 (insert "</dl>\n</dd>\n")
510 (setq sub-open (1- sub-open)
511 idx (1+ idx)))))
512 ((> (caar contents) depth) ; can't jump more than one ahead
513 (insert "<dd>\n<dl class=\"contents\">\n")
514 (setq sub-open (1+ sub-open))))))
515 (while (> sub-open 0)
516 (insert "</dl>\n</dd>\n")
517 (setq sub-open (1- sub-open)))
518 (insert "</dl>\n")
519 (muse-publish-mark-read-only p (point)))))
521 (defun muse-html-class-tag (beg end attrs)
522 (goto-char beg)
523 (insert "<span class=\"" (cdr (assoc "name" attrs)) "\">")
524 (goto-char end)
525 (insert "</span>"))
527 ;; Register the Muse HTML Publisher
529 (defun muse-html-browse-file (file)
530 (browse-url (concat "file:" file)))
532 (defun muse-html-encoding ()
533 (if (stringp muse-html-meta-content-encoding)
534 muse-html-meta-content-encoding
535 (muse-html-transform-content-type
536 (or buffer-file-coding-system
537 muse-html-encoding-default))))
539 (defun muse-html-prepare-buffer ()
540 (set (make-local-variable 'muse-publish-url-transforms)
541 (cons 'muse-html-escape-string muse-publish-url-transforms))
542 (make-local-variable 'muse-html-meta-http-equiv)
543 (set (make-local-variable 'muse-html-meta-content-type)
544 (concat muse-html-meta-content-type "; charset="
545 (muse-html-encoding))))
547 (defun muse-html-finalize-buffer ()
548 (when muse-publish-generate-contents
549 (goto-char (car muse-publish-generate-contents))
550 (muse-html-insert-contents (cdr muse-publish-generate-contents)))
551 (when (memq buffer-file-coding-system '(no-conversion undecided-unix))
552 ;; make it agree with the default charset
553 (setq buffer-file-coding-system muse-html-encoding-default)))
555 (unless (assoc "html" muse-publishing-styles)
556 (muse-define-style "html"
557 :suffix 'muse-html-extension
558 :regexps 'muse-html-markup-regexps
559 :functions 'muse-html-markup-functions
560 :strings 'muse-html-markup-strings
561 :tags 'muse-html-markup-tags
562 :specials 'muse-html-markup-specials
563 :before 'muse-html-prepare-buffer
564 :after 'muse-html-finalize-buffer
565 :header 'muse-html-header
566 :footer 'muse-html-footer
567 :browser 'muse-html-browse-file)
569 (muse-derive-style "xhtml" "html"
570 :strings 'muse-xhtml-markup-strings
571 :header 'muse-xhtml-header
572 :footer 'muse-xhtml-footer))
574 (provide 'muse-html)
576 ;;; muse-html.el ends here