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.
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 ;; Sun Jiyang (sunyijiang AT gmail DOT com) came up with the idea for
32 ;; the <src> tag and provided an implementation for emacs-wiki.
34 ;; Charles Wang (wcy123 AT gmail DOT com) provided an initial
35 ;; implementation of the <src> tag for Muse.
37 ;; Clinton Ebadi (clinton AT unknownlamer DOT org) provided further
38 ;; ideas for the implementation of the <src> tag.
42 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
44 ;; Muse HTML Publishing
46 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
48 (require 'muse-publish
)
49 (require 'muse-regexps
)
50 (require 'muse-xml-common
)
52 (defgroup muse-html nil
53 "Options controlling the behavior of Muse HTML publishing."
56 (defcustom muse-html-extension
".html"
57 "Default file extension for publishing HTML files."
61 (defcustom muse-xhtml-extension
".html"
62 "Default file extension for publishing XHTML files."
66 (defcustom muse-html-style-sheet
67 "<style type=\"text/css\">
69 background: white; color: black;
70 margin-left: 3%; margin-right: 7%;
74 p.verse { margin-left: 3% }
76 .example { margin-left: 3% }
82 h3 { margin-bottom: 0px; }
84 "Store your stylesheet definitions here.
85 This is used in `muse-html-header'.
86 You can put raw CSS in here or a <link> tag to an external stylesheet.
87 This text may contain <lisp> markup tags.
89 An example of using <link> is as follows.
91 <link rel=\"stylesheet\" type=\"text/css\" charset=\"utf-8\" media=\"all\" href=\"/default.css\">"
95 (defcustom muse-xhtml-style-sheet
96 "<style type=\"text/css\">
98 background: white; color: black;
99 margin-left: 3%; margin-right: 7%;
103 p.verse { margin-left: 3% }
105 .example { margin-left: 3% }
111 h3 { margin-bottom: 0px; }
113 "Store your stylesheet definitions here.
114 This is used in `muse-xhtml-header'.
115 You can put raw CSS in here or a <link> tag to an external stylesheet.
116 This text may contain <lisp> markup tags.
118 An example of using <link> is as follows.
120 <link rel=\"stylesheet\" type=\"text/css\" charset=\"utf-8\" media=\"all\" href=\"/default.css\" />"
124 (defcustom muse-html-header
125 "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\">
129 (concat (muse-publishing-directive \"title\")
130 (let ((author (muse-publishing-directive \"author\")))
131 (if (not (string= author (user-full-name)))
132 (concat \" (by \" author \")\"))))</lisp></title>
133 <meta name=\"generator\" content=\"muse.el\">
134 <meta http-equiv=\"<lisp>muse-html-meta-http-equiv</lisp>\"
135 content=\"<lisp>muse-html-meta-content-type</lisp>\">
137 (let ((maintainer (muse-style-element :maintainer)))
139 (concat \"<link rev=\\\"made\\\" href=\\\"\" maintainer \"\\\">\")))
141 (muse-style-element :style-sheet muse-publishing-current-style)
146 (concat (muse-publishing-directive \"title\")
147 (let ((author (muse-publishing-directive \"author\")))
148 (if (not (string= author (user-full-name)))
149 (concat \" (by \" author \")\"))))</lisp></h1>
150 <!-- Page published by Emacs Muse begins here -->\n"
151 "Header used for publishing HTML files. This may be text or a filename."
155 (defcustom muse-html-footer
"
156 <!-- Page published by Emacs Muse ends here -->
159 "Footer used for publishing HTML files. This may be text or a filename."
163 (defcustom muse-xhtml-header
164 "<?xml version=\"1.0\" encoding=\"<lisp>
165 (muse-html-encoding)</lisp>\"?>
166 <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"
167 \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">
168 <html xmlns=\"http://www.w3.org/1999/xhtml\">
171 (concat (muse-publishing-directive \"title\")
172 (let ((author (muse-publishing-directive \"author\")))
173 (if (not (string= author (user-full-name)))
174 (concat \" (by \" author \")\"))))</lisp></title>
175 <meta name=\"generator\" content=\"muse.el\" />
176 <meta http-equiv=\"<lisp>muse-html-meta-http-equiv</lisp>\"
177 content=\"<lisp>muse-html-meta-content-type</lisp>\" />
179 (let ((maintainer (muse-style-element :maintainer)))
181 (concat \"<link rev=\\\"made\\\" href=\\\"\" maintainer \"\\\" />\")))
183 (muse-style-element :style-sheet muse-publishing-current-style)
188 (concat (muse-publishing-directive \"title\")
189 (let ((author (muse-publishing-directive \"author\")))
190 (if (not (string= author (user-full-name)))
191 (concat \" (by \" author \")\"))))</lisp></h1>
192 <!-- Page published by Emacs Muse begins here -->\n"
193 "Header used for publishing XHTML files. This may be text or a filename."
197 (defcustom muse-xhtml-footer
"
198 <!-- Page published by Emacs Muse ends here -->
201 "Footer used for publishing XHTML files. This may be text or a filename."
205 (defcustom muse-html-anchor-on-word nil
206 "When true, anchors surround the closest word. This allows you
207 to select them in a browser (i.e. for pasting), but has the
208 side-effect of marking up headers in multiple colors if your
209 header style is different from your link style."
213 (defcustom muse-html-table-attributes
214 " class=\"muse-table\" border=\"2\" cellpadding=\"5\""
215 "The attribute to be used with HTML <table> tags.
216 Note that since Muse supports insertion of raw HTML tags, as long
217 as you wrap the region in <literal></literal>."
221 (defcustom muse-html-markup-regexps
222 `(;; Beginning of doc, end of doc, or plain paragraph separator
223 (10000 ,(concat "\\(\\(\n\\(?:[" muse-regexp-blank
"]*\n\\)*"
224 "\\([" muse-regexp-blank
"]*\n\\)\\)"
225 "\\|\\`\\s-*\\|\\s-*\\'\\)")
226 ;; this is somewhat repetitive because we only require the
227 ;; line just before the paragraph beginning to be not
229 3 muse-html-markup-paragraph
))
230 "List of markup rules for publishing a Muse page to HTML.
231 For more on the structure of this list, see `muse-publish-markup-regexps'."
232 :type
'(repeat (choice
233 (list :tag
"Markup rule"
235 (choice regexp symbol
)
237 (choice string function symbol
))
241 (defcustom muse-html-markup-functions
242 '((anchor . muse-html-markup-anchor
)
243 (table . muse-html-markup-table
)
244 (footnote . muse-html-markup-footnote
))
245 "An alist of style types to custom functions for that kind of text.
246 For more on the structure of this list, see
247 `muse-publish-markup-functions'."
248 :type
'(alist :key-type symbol
:value-type function
)
251 (defcustom muse-html-markup-strings
252 '((image-with-desc .
"<table class=\"image\" width=\"100%%\">
253 <tr><td align=\"center\"><img src=\"%1%.%2%\" alt=\"%3%\"></td></tr>
254 <tr><td align=\"center\" class=\"image-caption\">%3%</td></tr>
256 (image .
"<img src=\"%s.%s\" alt=\"\">")
257 (image-link .
"<a class=\"image-link\" href=\"%s\">
258 <img src=\"%s.%s\"></a>")
259 (anchor-ref .
"<a href=\"#%s\">%s</a>")
260 (url .
"<a href=\"%s\">%s</a>")
261 (link .
"<a href=\"%s\">%s</a>")
262 (link-and-anchor .
"<a href=\"%s#%s\">%s</a>")
263 (email-addr .
"<a href=\"mailto:%s\">%s</a>")
264 (emdash .
"%s—%s")
265 (comment-begin .
"<!-- ")
266 (comment-end .
" -->")
269 (no-break-space .
" ")
273 (section-end .
"</h2>")
274 (subsection .
"<h3>")
275 (subsection-end .
"</h3>")
276 (subsubsection .
"<h4>")
277 (subsubsection-end .
"</h4>")
278 (section-other .
"<h5>")
279 (section-other-end .
"</h5>")
280 (begin-underline .
"<u>")
281 (end-underline .
"</u>")
282 (begin-literal .
"<code>")
283 (end-literal .
"</code>")
284 (begin-emph .
"<em>")
286 (begin-more-emph .
"<strong>")
287 (end-more-emph .
"</strong>")
288 (begin-most-emph .
"<strong><em>")
289 (end-most-emph .
"</em></strong>")
290 (begin-verse .
"<p class=\"verse\">\n")
291 (verse-space .
" ")
292 (end-verse-line .
"<br>")
293 (end-last-stanza-line .
"<br>")
294 (empty-verse-line .
"<br>")
296 (begin-example .
"<pre class=\"example\">")
297 (end-example .
"</pre>")
298 (begin-center .
"<center>\n<p>")
299 (end-center .
"</p>\n</center>")
300 (begin-quote .
"<blockquote>\n")
301 (end-quote .
"\n</blockquote>")
302 (begin-quote-item .
"<p class=\"quoted\">")
303 (end-quote-item .
"</p>")
304 (begin-uli .
"<ul>\n")
305 (end-uli .
"\n</ul>")
306 (begin-uli-item .
"<li>")
307 (end-uli-item .
"</li>")
308 (begin-oli .
"<ol>\n")
309 (end-oli .
"\n</ol>")
310 (begin-oli-item .
"<li>")
311 (end-oli-item .
"</li>")
312 (begin-dl .
"<dl>\n")
314 (begin-ddt .
"<dt><strong>")
315 (end-ddt .
"</strong></dt>")
318 (begin-table .
"<table%s>\n")
319 (end-table .
"</table>")
320 (begin-table-row .
" <tr>\n")
321 (end-table-row .
" </tr>\n")
322 (begin-table-entry .
" <%s>")
323 (end-table-entry .
"</%s>\n"))
324 "Strings used for marking up text as HTML.
325 These cover the most basic kinds of markup, the handling of which
326 differs little between the various styles."
327 :type
'(alist :key-type symbol
:value-type string
)
330 (defcustom muse-xhtml-markup-strings
331 '((image-with-desc .
"<table class=\"image\" width=\"100%%\">
332 <tr><td align=\"center\"><img src=\"%1%.%2%\" alt=\"%3%\" /></td></tr>
333 <tr><td align=\"center\" class=\"image-caption\">%3%</td></tr>
335 (image .
"<img src=\"%s.%s\" alt=\"\" />")
336 (image-link .
"<a class=\"image-link\" href=\"%s\">
337 <img src=\"%s.%s\" alt=\"\" /></a>")
339 (fn-sep .
"<hr />\n")
340 (begin-underline .
"<span style=\"text-decoration: underline;\">")
341 (end-underline .
"</span>")
342 (begin-center .
"<p style=\"text-align: center;\">\n")
343 (end-center .
"\n</p>")
344 (end-verse-line .
"<br />")
345 (end-last-stanza-line .
"<br />")
346 (empty-verse-line .
"<br />"))
347 "Strings used for marking up text as XHTML.
348 These cover the most basic kinds of markup, the handling of which
349 differs little between the various styles.
351 If a markup rule is not found here, `muse-html-markup-strings' is
353 :type
'(alist :key-type symbol
:value-type string
)
356 (defcustom muse-html-markup-tags
357 '(("class" t t t muse-html-class-tag
)
358 ("src" t t nil muse-html-src-tag
))
359 "A list of tag specifications, for specially marking up HTML."
360 :type
'(repeat (list (string :tag
"Markup tag")
361 (boolean :tag
"Expect closing tag" :value t
)
362 (boolean :tag
"Parse attributes" :value nil
)
363 (boolean :tag
"Nestable" :value nil
)
367 (defcustom muse-html-meta-http-equiv
"Content-Type"
368 "The http-equiv attribute used for the HTML <meta> tag."
372 (defcustom muse-html-meta-content-type
"text/html"
373 "The content type used for the HTML <meta> tag.
374 If you are striving for XHTML 1.1 compliance, you may want to
375 change this to \"application/xhtml+xml\"."
379 (defcustom muse-html-meta-content-encoding
(if (featurep 'mule
)
382 "The charset to append to the HTML <meta> tag.
383 If set to the symbol 'detect, use `muse-html-encoding-map' to try
384 and determine the HTML charset from emacs's coding. If set to a
385 string, this string will be used to force a particular charset"
386 :type
'(choice string symbol
)
389 (defcustom muse-html-encoding-default
'iso-8859-1
390 "The default Emacs buffer encoding to use in published files.
391 This will be used if no special characters are found."
395 (defcustom muse-html-charset-default
"iso-8859-1"
396 "The default HTML meta charset to use if no translation is found in
397 `muse-html-encoding-map'."
401 (defun muse-html-insert-anchor (anchor)
402 "Insert an anchor, either around the word at point, or within a tag."
403 (skip-chars-forward (concat muse-regexp-blank
"\n"))
404 (if (looking-at (concat "<\\([^" muse-regexp-blank
"/>\n]+\\)>"))
405 (let ((tag (match-string 1)))
406 (goto-char (match-end 0))
407 (muse-insert-markup "<a name=\"" anchor
"\" id=\"" anchor
"\">")
408 (when muse-html-anchor-on-word
409 (or (and (search-forward (format "</%s>" tag
)
410 (muse-line-end-position) t
)
411 (goto-char (match-beginning 0)))
413 (muse-insert-markup "</a>"))
414 (muse-insert-markup "<a name=\"" anchor
"\" id=\"" anchor
"\">")
415 (when muse-html-anchor-on-word
417 (muse-insert-markup "</a>\n")))
419 (defun muse-html-markup-anchor ()
420 (unless (get-text-property (match-end 1) 'muse-link
)
422 (muse-html-insert-anchor (match-string 2)))
425 (defun muse-html-markup-paragraph ()
426 (let ((end (copy-marker (match-end 0) t
)))
427 (goto-char (match-beginning 0))
428 (when (save-excursion
430 (and (re-search-backward "<\\(/?\\)p[ >]" nil t
)
431 (not (string-equal (match-string 1) "/")))))
432 (when (get-text-property (1- (point)) 'end-list
)
433 (goto-char (previous-single-property-change (1- (point)) 'end-list
)))
434 (muse-insert-markup "</p>"))
440 ((eq (char-after) ?\
<)
442 ((looking-at "<\\(em\\|strong\\|code\\|span\\)[ >]")
443 (muse-insert-markup "<p>"))
445 (if (looking-at "<a[^>\n]+><img")
446 (muse-insert-markup "<p class=\"image-link\">")
447 (muse-insert-markup "<p>")))
448 ((looking-at "<img[ >]")
449 (muse-insert-markup "<p class=\"image\">"))
453 ((muse-looking-back "\\(</h[1-4]>\\|<hr>\\)\n\n")
454 (muse-insert-markup "<p class=\"first\">"))
456 (muse-insert-markup "<p>"))))
458 (defun muse-html-markup-footnote ()
460 ((get-text-property (match-beginning 0) 'muse-link
)
462 ((= (muse-line-beginning-position) (match-beginning 0))
464 (let ((text (match-string 1)))
466 (concat "<p class=\"footnote\">"
467 "<a name=\"fn." text
"\" href=\"#fnr." text
"\">"
471 (let* ((beg (goto-char (match-end 0)))
472 (end (and (search-forward "\n\n" nil t
)
474 (copy-marker (match-beginning 0))
476 (while (re-search-forward (concat "^["
480 (replace-match "\\1" t
)))))
482 (t (let ((text (match-string 1)))
484 (concat "<sup><a name=\"fnr." text
"\" href=\"#fn." text
"\">"
486 (replace-match ""))))
488 (defun muse-html-markup-table ()
489 (muse-xml-markup-table muse-html-table-attributes
))
491 ;; Handling of tags for HTML
493 (defun muse-html-insert-contents (depth)
494 (let ((max-depth (or depth
2))
498 (goto-char (point-min))
499 (search-forward "Page published by Emacs Muse begins here" nil t
)
501 (while (re-search-forward "<h\\([0-9]+\\)>\\(.+?\\)</h\\1>$" nil t
)
502 (unless (get-text-property (point) 'read-only
)
503 (setq l
(1- (string-to-number (match-string 1))))
508 (when (<= l max-depth
)
509 (setq contents
(cons (cons l
(muse-match-string-no-properties 2))
511 (goto-char (match-beginning 2))
512 (muse-html-insert-anchor (concat "sec" (int-to-string index
)))
513 (setq index
(1+ index
)))))))
514 (setq index
1 contents
(nreverse contents
))
515 (let ((depth 1) (sub-open 0) (p (point)))
516 (muse-insert-markup "<div class=\"contents\">\n<dl>\n")
518 (muse-insert-markup "<dt>\n"
519 "<a href=\"#sec" (int-to-string index
) "\">"
520 (muse-publish-strip-tags (cdar contents
))
523 (setq index
(1+ index
)
524 depth
(caar contents
)
525 contents
(cdr contents
))
528 ((< (caar contents
) depth
)
529 (let ((idx (caar contents
)))
531 (muse-insert-markup "</dl>\n</dd>\n")
532 (setq sub-open
(1- sub-open
)
534 ((> (caar contents
) depth
) ; can't jump more than one ahead
535 (muse-insert-markup "<dd>\n<dl>\n")
536 (setq sub-open
(1+ sub-open
))))))
537 (while (> sub-open
0)
538 (muse-insert-markup "</dl>\n</dd>\n")
539 (setq sub-open
(1- sub-open
)))
540 (muse-insert-markup "</dl>\n</div>\n")
541 (muse-publish-mark-read-only p
(point)))))
543 (defun muse-html-class-tag (beg end attrs
)
545 (muse-insert-markup "<span class=\"" (cdr (assoc "name" attrs
)) "\">")
547 (muse-insert-markup "</span>"))
549 (defun muse-html-src-tag (beg end attrs
)
550 "Publish the region using htmlize.
551 The language to use may be specified by the \"lang\" attribute.
553 Muse will look for a function named LANG-mode, where LANG is the
554 value of the \"lang\" attribute.
556 This tag requires htmlize 1.34 or later in order to work."
557 (if (condition-case nil
560 (if (fboundp 'htmlize-region-for-paste
)
562 (muse-display-warning
563 (concat "The `htmlize-region-for-paste' function was not"
564 " found.\nThis is available in htmlize.el 1.34"
568 ;; if htmlize.el was not found, treat this like an example tag
569 (muse-publish-example-tag beg end
)
570 (let* ((mode (and (assoc "lang" attrs
)
571 (intern (concat (cdr (assoc "lang" attrs
))
573 (text (delete-and-extract-region beg end
))
580 (font-lock-fontify-buffer)
581 ;; silence the byte-compiler
582 (when (fboundp 'htmlize-region-for-paste
)
583 ;; transform the region to HTML
584 (htmlize-region-for-paste (point-min) (point-max))))))
586 (narrow-to-region (point) (point))
588 (goto-char (point-min))
589 (re-search-forward "<pre\\([^>]*\\)>\n?" nil t
)
590 (replace-match "<pre class=\"src\">")
591 (goto-char (point-max))
592 (muse-publish-mark-read-only (point-min) (point-max))))))
594 ;; Register the Muse HTML Publisher
596 (defun muse-html-browse-file (file)
597 (browse-url (concat "file:" file
)))
599 (defun muse-html-encoding ()
600 (if (stringp muse-html-meta-content-encoding
)
601 muse-html-meta-content-encoding
602 (muse-xml-transform-content-type
603 (or (and (boundp 'buffer-file-coding-system
)
604 buffer-file-coding-system
)
605 muse-html-encoding-default
)
606 muse-html-charset-default
)))
608 (defun muse-html-prepare-buffer ()
609 (make-local-variable 'muse-html-meta-http-equiv
)
610 (set (make-local-variable 'muse-html-meta-content-type
)
612 (string-match "charset=" muse-html-meta-content-type
))
613 muse-html-meta-content-type
614 (concat muse-html-meta-content-type
"; charset="
615 (muse-html-encoding)))))
617 (defun muse-html-munge-buffer ()
618 (when muse-publish-generate-contents
619 (goto-char (car muse-publish-generate-contents
))
620 (muse-html-insert-contents (cdr muse-publish-generate-contents
))
621 (setq muse-publish-generate-contents nil
)))
623 (defun muse-html-finalize-buffer ()
624 (when (and (boundp 'buffer-file-coding-system
)
625 (memq buffer-file-coding-system
'(no-conversion undecided-unix
)))
626 ;; make it agree with the default charset
627 (setq buffer-file-coding-system muse-html-encoding-default
)))
629 ;;; Register the Muse HTML and XHTML Publishers
631 (muse-define-style "html"
632 :suffix
'muse-html-extension
633 :regexps
'muse-html-markup-regexps
634 :functions
'muse-html-markup-functions
635 :strings
'muse-html-markup-strings
636 :tags
'muse-html-markup-tags
637 :specials
'muse-xml-decide-specials
638 :before
'muse-html-prepare-buffer
639 :before-end
'muse-html-munge-buffer
640 :after
'muse-html-finalize-buffer
641 :header
'muse-html-header
642 :footer
'muse-html-footer
643 :style-sheet
'muse-html-style-sheet
644 :browser
'muse-html-browse-file
)
646 (muse-derive-style "xhtml" "html"
647 :suffix
'muse-xhtml-extension
648 :strings
'muse-xhtml-markup-strings
649 :header
'muse-xhtml-header
650 :footer
'muse-xhtml-footer
651 :style-sheet
'muse-xhtml-style-sheet
)
655 ;;; muse-html.el ends here