muse-journal: Escape title properly for journal-rss and journal-rdf
[muse-el.git] / lisp / muse-journal.el
blob617dad64f2b3046c29361710296a638d5ad0fc31
1 ;;; muse-journal.el --- keep and publish a journal
3 ;; Copyright (C) 2004, 2005, 2006, 2007 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 ;; The module facilitates the keeping and publication of a journal.
25 ;; When publishing to HTML, it assumes the form of a web log, or blog.
27 ;; The input format for each entry is as follows:
29 ;; * 20040317: Title of entry
31 ;; Text for the entry.
33 ;; <qotd>
34 ;; "You know who you are. It comes down to a simple gut check: You
35 ;; either love what you do or you don't. Period." -- P. Bronson
36 ;; </qotd>
38 ;; The "qotd", or Quote of the Day, is entirely optional. When
39 ;; generated to HTML, this entry is rendered as:
41 ;; <div class="entry">
42 ;; <div class="entry-qotd">
43 ;; <h3>Quote of the Day:</h3>
44 ;; <p>"You know who you are. It comes down to a simple gut
45 ;; check: You either love what you do or you don't. Period."
46 ;; -- P. Bronson</p>
47 ;; </div>
48 ;; <div class="entry-body">
49 ;; <div class="entry-head">
50 ;; <div class="entry-date">
51 ;; <span class="date">March 17, 2004</span>
52 ;; </div>
53 ;; <div class="entry-title">
54 ;; <h2>Title of entry</h2>
55 ;; </div>
56 ;; </div>
57 ;; <div class="entry-text">
58 ;; <p>Text for the entry.</p>
59 ;; </div>
60 ;; </div>
61 ;; </div>
63 ;; The plurality of "div" tags makes it possible to display the
64 ;; entries in any form you wish, using a CSS style.
66 ;; Also, an .RDF file can be generated from your journal by publishing
67 ;; it with the "rdf" style. It uses the first two sentences of the
68 ;; first paragraph of each entry as its "description", and
69 ;; autogenerates tags for linking to the various entries.
71 ;;; Contributors:
73 ;; René Stadler (mail AT renestadler DOT de) provided a patch that
74 ;; causes dates in RSS feeds to be generated in a format that RSS
75 ;; readers can parse.
77 ;;; Code:
79 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
81 ;; Muse Journal Publishing
83 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
85 (require 'muse-publish)
86 (require 'muse-html)
87 (require 'muse-latex)
88 (require 'muse-book)
90 (defgroup muse-journal nil
91 "Rules for transforming a journal into its final form."
92 :group 'muse-publish)
94 (defcustom muse-journal-heading-regexp
95 "\\(?:\\([0-9]+\\)\\(?:: \\)?\\)?\\(.+?\\)?"
96 "A regexp that matches a journal heading.
97 Paren group 1 is the ISO date, group 2 is the optional category,
98 and group 3 is the optional heading for the entry."
99 :type 'regexp
100 :group 'muse-journal)
102 (defcustom muse-journal-date-format "%a, %e %b %Y"
103 "Date format to use for journal entries."
104 :type 'string
105 :group 'muse-journal)
107 (defcustom muse-journal-html-heading-regexp
108 (concat "^<h2[^>\n]*>" muse-journal-heading-regexp "</h2>$")
109 "A regexp that matches a journal heading from an HTML document.
110 Paren group 1 is the ISO date, group 2 is the optional category,
111 and group 3 is the optional heading for the entry."
112 :type 'regexp
113 :group 'muse-journal)
115 (defcustom muse-journal-rss-heading-regexp
116 (concat "^\\* " muse-journal-heading-regexp "$")
117 "A regexp that matches a journal heading from an HTML document.
118 Paren group 1 is the ISO date, group 2 is the optional category,
119 and group 3 is the optional heading for the entry."
120 :type 'regexp
121 :group 'muse-journal)
123 (defcustom muse-journal-html-entry-template
124 "<div class=\"entry\">
125 <a name=\"%anchor%\" style=\"text-decoration: none\">&nbsp;</a>
126 <div class=\"entry-body\">
127 <div class=\"entry-head\">
128 <div class=\"entry-date\">
129 <span class=\"date\">%date%</span>
130 </div>
131 <div class=\"entry-title\">
132 <h2>%title%</h2>
133 </div>
134 </div>
135 <div class=\"entry-text\">
136 <div class=\"entry-qotd\">
137 <p>%qotd%</p>
138 </div>
139 %text%
140 </div>
141 </div>
142 </div>\n\n"
143 "Template used to publish individual journal entries as HTML.
144 This may be text or a filename."
145 :type 'string
146 :group 'muse-journal)
148 (defcustom muse-journal-latex-section
149 "\\section*{%title% \\hfill {\\normalsize %date%}}
150 \\addcontentsline{toc}{chapter}{%title%}"
151 "Template used to publish a LaTeX section."
152 :type 'string
153 :group 'muse-journal)
155 (defcustom muse-journal-latex-subsection
156 "\\subsection*{%title%}
157 \\addcontentsline{toc}{section}{%title%}"
158 "Template used to publish a LaTeX subsection."
159 :type 'string
160 :group 'muse-journal)
162 (defcustom muse-journal-latex-markup-tags
163 '(("qotd" t nil nil muse-journal-latex-qotd-tag))
164 "A list of tag specifications, for specially marking up LaTeX.
165 See `muse-publish-markup-tags' for more info."
166 :type '(repeat (list (string :tag "Markup tag")
167 (boolean :tag "Expect closing tag" :value t)
168 (boolean :tag "Parse attributes" :value nil)
169 (boolean :tag "Nestable" :value nil)
170 function))
171 :group 'muse-journal)
173 ;; FIXME: This doesn't appear to be used.
174 (defun muse-journal-generate-pages ()
175 (let ((output-dir (muse-style-element :path)))
176 (goto-char (point-min))
177 (while (re-search-forward muse-journal-heading-regexp nil t)
178 (let* ((date (match-string 1))
179 (category (match-string 1))
180 (category-file (concat output-dir category "/index.html"))
181 (heading (match-string 1)))
182 t))))
184 (defcustom muse-journal-rdf-extension ".rdf"
185 "Default file extension for publishing RDF (RSS 1.0) files."
186 :type 'string
187 :group 'muse-journal)
189 (defcustom muse-journal-rdf-base-url ""
190 "The base URL of the website referenced by the RDF file."
191 :type 'string
192 :group 'muse-journal)
194 (defcustom muse-journal-rdf-header
195 "<rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"
196 xmlns=\"http://purl.org/rss/1.0/\"
197 xmlns:dc=\"http://purl.org/dc/elements/1.1/\">
198 <channel rdf:about=\"<lisp>(concat (muse-style-element :base-url)
199 (muse-publish-link-name))</lisp>\">
200 <title><lisp>(muse-publishing-directive \"title\")</lisp></title>
201 <link><lisp>(concat (muse-style-element :base-url)
202 (concat (muse-page-name)
203 muse-html-extension))</lisp></link>
204 <description><lisp>(muse-publishing-directive \"desc\")</lisp></description>
205 <items>
206 <rdf:Seq>
207 <rdf:li resource=\"<lisp>
208 (concat (muse-style-element :base-url)
209 (concat (muse-page-name)
210 muse-html-extension))</lisp>\"/>
211 </rdf:Seq>
212 </items>
213 </channel>\n"
214 "Header used for publishing RDF (RSS 1.0) files.
215 This may be text or a filename."
216 :type 'string
217 :group 'muse-journal)
219 (defcustom muse-journal-rdf-footer
220 "</rdf:RDF>\n"
221 "Footer used for publishing RDF (RSS 1.0) files.
222 This may be text or a filename."
223 :type 'string
224 :group 'muse-journal)
226 (defcustom muse-journal-rdf-date-format
227 "%Y-%m-%dT%H:%M:%S"
228 "Date format to use for RDF entries."
229 :type 'string
230 :group 'muse-journal)
232 (defcustom muse-journal-rdf-entry-template
233 "\n <item rdf:about=\"%link%#%anchor%\">
234 <title>%title%</title>
235 <description>
236 %desc%
237 </description>
238 <link>%link%#%anchor%</link>
239 <dc:date>%date%</dc:date>
240 <dc:creator>%maintainer%</dc:creator>
241 </item>\n"
242 "Template used to publish individual journal entries as RDF.
243 This may be text or a filename."
244 :type 'string
245 :group 'muse-journal)
247 (defcustom muse-journal-rdf-summarize-entries t
248 "If non-nil, include only summaries in the RDF file, not the full data."
249 :type 'boolean
250 :group 'muse-journal)
252 (defcustom muse-journal-rss-extension ".xml"
253 "Default file extension for publishing RSS 2.0 files."
254 :type 'string
255 :group 'muse-journal)
257 (defcustom muse-journal-rss-base-url ""
258 "The base URL of the website referenced by the RSS file."
259 :type 'string
260 :group 'muse-journal)
262 (defcustom muse-journal-rss-header
263 "<\?xml version=\"1.0\" encoding=\"<lisp>
264 (muse-html-encoding)</lisp>\"?>
265 <rss version=\"2.0\">
266 <channel>
267 <title><lisp>(muse-publishing-directive \"title\")</lisp></title>
268 <link><lisp>(concat (muse-style-element :base-url)
269 (concat (muse-page-name)
270 muse-html-extension))</lisp></link>
271 <description><lisp>(muse-publishing-directive \"desc\")</lisp></description>
272 <language>en-us</language>
273 <generator>Emacs Muse</generator>\n\n"
274 "Header used for publishing RSS 2.0 files. This may be text or a filename."
275 :type 'string
276 :group 'muse-journal)
278 (defcustom muse-journal-rss-footer
279 "\n\n </channel>
280 </rss>\n"
281 "Footer used for publishing RSS 2.0 files. This may be text or a filename."
282 :type 'string
283 :group 'muse-journal)
285 (defcustom muse-journal-rss-date-format
286 "%a, %d %b %Y %H:%M:%S %Z"
287 "Date format to use for RSS 2.0 entries."
288 :type 'string
289 :group 'muse-journal)
291 (defcustom muse-journal-rss-entry-template
292 "\n <item>
293 <title>%title%</title>
294 <link>%link%#%anchor%</link>
295 <description>%desc%</description>
296 <author><lisp>(muse-publishing-directive \"author\")</lisp></author>
297 <pubDate>%date%</pubDate>
298 <guid>%link%#%anchor%</guid>
299 %enclosure%
300 </item>\n"
301 "Template used to publish individual journal entries as RSS 2.0.
302 This may be text or a filename."
303 :type 'string
304 :group 'muse-journal)
306 (defcustom muse-journal-rss-enclosure-types-alist
307 '(("mp3" . "audio/mpeg"))
308 "File types that are accepted as RSS enclosures.
309 This is an alist that maps file extension to content type.
310 Useful for podcasting."
311 :type '(alist :key-type string :value-type string)
312 :group 'muse-journal)
314 (defcustom muse-journal-rss-summarize-entries nil
315 "If non-nil, include only summaries in the RSS file, not the full data.
316 Many RSS subscribers find this annoying."
317 :type 'boolean
318 :group 'muse-journal)
320 (defcustom muse-journal-rss-markup-regexps
321 '((10000 muse-explicit-link-regexp 0 "\\2"))
322 "List of markup rules for publishing a Muse journal page to RSS 2.0.
323 For more information on the structure of this list, see
324 `muse-publish-markup-regexps'."
325 :type '(repeat (choice
326 (list :tag "Markup rule"
327 integer
328 (choice regexp symbol)
329 integer
330 (choice string function symbol))
331 function))
332 :group 'muse-journal)
334 (defcustom muse-journal-rss-markup-functions
335 '((email . ignore)
336 (link . ignore)
337 (url . ignore))
338 "An alist of style types to custom functions for that kind of text.
339 For more on the structure of this list, see
340 `muse-publish-markup-functions'."
341 :type '(alist :key-type symbol :value-type function)
342 :group 'muse-journal)
344 (defun muse-journal-anchorize-title (title)
345 (save-match-data
346 (if (string-match "(" title)
347 (setq title (substring title 0 (match-beginning 0))))
348 (if (string-match "<[^>]+>" title)
349 (setq title (replace-match "" nil nil title)))
350 (downcase (muse-replace-regexp-in-string "[^a-zA-Z0-9_]" "" title))))
352 (defun muse-journal-sort-entries (&optional direction)
353 (interactive "P")
354 (sort-subr
355 direction
356 (function
357 (lambda ()
358 (if (re-search-forward "^\\* [0-9]+" nil t)
359 (goto-char (match-beginning 0))
360 (goto-char (point-max)))))
361 (function
362 (lambda ()
363 (if (re-search-forward "^\\* [0-9]+" nil t)
364 (goto-char (1- (match-beginning 0)))
365 (goto-char (point-max)))))
366 (function
367 (lambda ()
368 (forward-char 2)))
369 (function
370 (lambda ()
371 (end-of-line)))))
373 (defun muse-journal-html-munge-buffer ()
374 (goto-char (point-min))
375 (let ((heading-regexp muse-journal-html-heading-regexp)
376 (inhibit-read-only t))
377 (while (re-search-forward heading-regexp nil t)
378 (let* ((date (match-string 1))
379 (orig-date date)
380 (title (match-string 2))
381 (clean-title title)
382 datestamp qotd text)
383 (delete-region (match-beginning 0) (match-end 0))
384 (if clean-title
385 (save-match-data
386 (while (string-match "\\(^<[^>]+>\\|<[^>]+>$\\)" clean-title)
387 (setq clean-title (replace-match "" nil nil clean-title)))))
388 (save-match-data
389 (when (and date
390 (string-match
391 (concat "\\`\\([1-9][0-9][0-9][0-9]\\)[./]?"
392 "\\([0-1][0-9]\\)[./]?\\([0-3][0-9]\\)") date))
393 (setq datestamp
394 (encode-time
395 0 0 0
396 (string-to-number (match-string 3 date))
397 (string-to-number (match-string 2 date))
398 (string-to-number (match-string 1 date))
399 (current-time-zone))
400 date (concat (format-time-string
401 muse-journal-date-format datestamp)
402 (substring date (match-end 0))))))
403 (save-restriction
404 (narrow-to-region
405 (point) (if (re-search-forward
406 (concat "\\(^<hr>$\\|"
407 heading-regexp "\\)") nil t)
408 (match-beginning 0)
409 (point-max)))
410 (goto-char (point-max))
411 (while (and (not (bobp))
412 (eq ?\ (char-syntax (char-before))))
413 (delete-char -1))
414 (goto-char (point-min))
415 (while (and (not (eobp))
416 (eq ?\ (char-syntax (char-after))))
417 (delete-char 1))
418 (save-excursion
419 (when (search-forward "<qotd>" nil t)
420 (let ((tag-beg (match-beginning 0))
421 (beg (match-end 0))
422 end)
423 (re-search-forward "</qotd>\n*")
424 (setq end (point-marker))
425 (save-restriction
426 (narrow-to-region beg (match-beginning 0))
427 (muse-publish-escape-specials (point-min) (point-max)
428 nil 'document)
429 (setq qotd (buffer-substring-no-properties
430 (point-min) (point-max))))
431 (delete-region tag-beg end))))
432 (setq text (buffer-string))
433 (delete-region (point-min) (point-max))
434 (let ((entry muse-journal-html-entry-template))
435 (muse-insert-file-or-string entry)
436 (muse-publish-mark-read-only (point-min) (point-max))
437 (goto-char (point-min))
438 (while (search-forward "%date%" nil t)
439 (remove-text-properties (match-beginning 0) (match-end 0)
440 '(read-only nil rear-nonsticky nil))
441 (replace-match (or date "") nil t))
442 (goto-char (point-min))
443 (while (search-forward "%title%" nil t)
444 (remove-text-properties (match-beginning 0) (match-end 0)
445 '(read-only nil rear-nonsticky nil))
446 (replace-match (or title "&nbsp;") nil t))
447 (goto-char (point-min))
448 (while (search-forward "%anchor%" nil t)
449 (replace-match (muse-journal-anchorize-title
450 (or clean-title orig-date))
451 nil t))
452 (goto-char (point-min))
453 (while (search-forward "%qotd%" nil t)
454 (save-restriction
455 (narrow-to-region (match-beginning 0) (match-end 0))
456 (delete-region (point-min) (point-max))
457 (when qotd (muse-insert-markup qotd))))
458 (goto-char (point-min))
459 (while (search-forward "%text%" nil t)
460 (remove-text-properties (match-beginning 0) (match-end 0)
461 '(read-only nil rear-nonsticky nil))
462 (replace-match text nil t))
463 (when (null qotd)
464 (goto-char (point-min))
465 (when (search-forward "<div class=\"entry-qotd\">" nil t)
466 (let ((beg (match-beginning 0)))
467 (re-search-forward "</div>\n*" nil t)
468 (delete-region beg (point))))))))))
469 ;; indicate that we are to continue the :before-end processing
470 nil)
472 (defun muse-journal-latex-munge-buffer ()
473 (goto-char (point-min))
474 (let ((heading-regexp
475 (concat "^" (regexp-quote (muse-markup-text 'section))
476 muse-journal-heading-regexp
477 (regexp-quote (muse-markup-text 'section-end)) "$"))
478 (inhibit-read-only t))
479 (when (re-search-forward heading-regexp nil t)
480 (goto-char (match-beginning 0))
481 (sort-subr nil
482 (function
483 (lambda ()
484 (if (re-search-forward heading-regexp nil t)
485 (goto-char (match-beginning 0))
486 (goto-char (point-max)))))
487 (function
488 (lambda ()
489 (if (re-search-forward heading-regexp nil t)
490 (goto-char (1- (match-beginning 0)))
491 (goto-char (point-max)))))
492 (function
493 (lambda ()
494 (forward-char 2)))
495 (function
496 (lambda ()
497 (end-of-line)))))
498 (while (re-search-forward heading-regexp nil t)
499 (let ((date (match-string 1))
500 (title (match-string 2))
501 ;; FIXME: Nothing is done with qotd
502 qotd section)
503 (save-match-data
504 (when (and date
505 (string-match
506 (concat "\\([1-9][0-9][0-9][0-9]\\)[./]?"
507 "\\([0-1][0-9]\\)[./]?\\([0-3][0-9]\\)") date))
508 (setq date (encode-time
509 0 0 0
510 (string-to-number (match-string 3 date))
511 (string-to-number (match-string 2 date))
512 (string-to-number (match-string 1 date))
513 (current-time-zone))
514 date (format-time-string
515 muse-journal-date-format date))))
516 (save-restriction
517 (narrow-to-region (match-beginning 0) (match-end 0))
518 (delete-region (point-min) (point-max))
519 (muse-insert-markup muse-journal-latex-section)
520 (goto-char (point-min))
521 (while (search-forward "%title%" nil t)
522 (replace-match (or title "Untitled") nil t))
523 (goto-char (point-min))
524 (while (search-forward "%date%" nil t)
525 (replace-match (or date "") nil t))))))
526 (goto-char (point-min))
527 (let ((subheading-regexp
528 (concat "^" (regexp-quote (muse-markup-text 'subsection))
529 "\\([^\n}]+\\)"
530 (regexp-quote (muse-markup-text 'subsection-end)) "$"))
531 (inhibit-read-only t))
532 (while (re-search-forward subheading-regexp nil t)
533 (let ((title (match-string 1)))
534 (save-restriction
535 (narrow-to-region (match-beginning 0) (match-end 0))
536 (delete-region (point-min) (point-max))
537 (muse-insert-markup muse-journal-latex-subsection)
538 (goto-char (point-min))
539 (while (search-forward "%title%" nil t)
540 (replace-match title nil t))))))
541 ;; indicate that we are to continue the :before-end processing
542 nil)
544 (defun muse-journal-latex-qotd-tag (beg end)
545 (muse-publish-ensure-block beg)
546 (muse-insert-markup (muse-markup-text 'begin-quote))
547 (goto-char end)
548 (muse-insert-markup (muse-markup-text 'end-quote)))
550 (defun muse-journal-rss-munge-buffer ()
551 (goto-char (point-min))
552 (let ((heading-regexp muse-journal-rss-heading-regexp)
553 (inhibit-read-only t))
554 (while (re-search-forward heading-regexp nil t)
555 (let* ((date (match-string 1))
556 (orig-date date)
557 (title (match-string 2))
558 ;; FIXME: Nothing is done with qotd
559 enclosure qotd desc)
560 (if title
561 (save-match-data
562 (if (string-match muse-explicit-link-regexp title)
563 (setq enclosure (muse-get-link title)
564 title (muse-get-link-desc title)))))
565 (save-match-data
566 (when (and date
567 (string-match
568 (concat "\\([1-9][0-9][0-9][0-9]\\)[./]?"
569 "\\([0-1][0-9]\\)[./]?\\([0-3][0-9]\\)") date))
570 (setq date (encode-time 0 0 0
571 (string-to-number (match-string 3 date))
572 (string-to-number (match-string 2 date))
573 (string-to-number (match-string 1 date))
574 (current-time-zone))
575 ;; make sure that date is in a format that RSS
576 ;; readers can handle
577 date (let ((system-time-locale "C"))
578 (format-time-string
579 (muse-style-element :date-format) date)))))
580 (save-restriction
581 (narrow-to-region
582 (match-beginning 0)
583 (if (re-search-forward heading-regexp nil t)
584 (match-beginning 0)
585 (if (re-search-forward "^Footnotes:" nil t)
586 (match-beginning 0)
587 (point-max))))
588 (goto-char (point-min))
589 (delete-region (point) (muse-line-end-position))
590 (re-search-forward "</qotd>\n+" nil t)
591 (while (and (char-after)
592 (eq ?\ (char-syntax (char-after))))
593 (delete-char 1))
594 (let ((beg (point)))
595 (if (muse-style-element :summarize)
596 (progn
597 (forward-sentence 2)
598 (setq desc (concat (buffer-substring beg (point)) "...")))
599 (save-restriction
600 (muse-publish-markup-buffer "rss-entry" "html")
601 (goto-char (point-min))
602 (if (re-search-forward "Page published by Emacs Muse" nil t)
603 (goto-char (muse-line-end-position))
604 (muse-display-warning
605 (concat
606 "Cannot find 'Page published by Emacs Muse begins here'.\n"
607 "You will probably need this text in your header."))
608 (goto-char (point-min)))
609 (setq beg (point))
610 (if (re-search-forward "Page published by Emacs Muse" nil t)
611 (goto-char (muse-line-beginning-position))
612 (muse-display-warning
613 (concat
614 "Cannot find 'Page published by Emacs Muse ends here'.\n"
615 "You will probably need this text in your footer."))
616 (goto-char (point-max)))
617 (setq desc (concat "<![CDATA[" (buffer-substring beg (point))
618 "]]>")))))
619 (delete-region (point-min) (point-max))
620 (let ((entry (muse-style-element :entry-template)))
621 (muse-insert-file-or-string entry)
622 (goto-char (point-min))
623 (while (search-forward "%date%" nil t)
624 (replace-match (or date "") nil t))
625 (goto-char (point-min))
626 (while (search-forward "%title%" nil t)
627 (replace-match "")
628 (save-restriction
629 (narrow-to-region (point) (point))
630 (insert (or title "Untitled"))
631 (remove-text-properties (match-beginning 0) (match-end 0)
632 '(read-only nil rear-nonsticky nil))
633 (let ((muse-publishing-current-style (muse-style "xml")))
634 (muse-publish-escape-specials (point-min) (point-max)
635 nil 'document))))
636 (goto-char (point-min))
637 (while (search-forward "%desc%" nil t)
638 (replace-match desc nil t))
639 (goto-char (point-min))
640 (while (search-forward "%enclosure%" nil t)
641 (replace-match
642 (if (null enclosure)
644 (save-match-data
645 (format
646 "<enclosure url=\"%s\" %stype=\"%s\"/>"
647 (if (string-match "//" enclosure)
648 enclosure
649 (concat (muse-style-element :base-url)
650 enclosure))
651 (let ((file
652 (expand-file-name enclosure
653 (muse-style-element :path))))
654 (if (file-readable-p file)
655 (format "length=\"%d\" "
656 (nth 7 (file-attributes file)))
657 ""))
658 (if (string-match "\\.\\([^.]+\\)$" enclosure)
659 (let* ((ext (match-string 1 enclosure))
660 (type
661 (assoc
662 ext muse-journal-rss-enclosure-types-alist)))
663 (if type
664 (cdr type)
665 "application/octet-stream"))))))
666 nil t))
667 (goto-char (point-min))
668 (while (search-forward "%link%" nil t)
669 (replace-match
670 (concat (muse-style-element :base-url)
671 (concat (muse-page-name)
672 muse-html-extension))
673 nil t))
674 (goto-char (point-min))
675 (while (search-forward "%anchor%" nil t)
676 (replace-match
677 (muse-journal-anchorize-title (or title orig-date))
678 nil t))
679 (goto-char (point-min))
680 (while (search-forward "%maintainer%" nil t)
681 (replace-match
682 (or (muse-style-element :maintainer)
683 (concat "webmaster@" (system-name)))
684 nil t)))))))
685 ;; indicate that we are to continue the :before-end processing
686 nil)
689 ;;; Register the Muse Journal Publishers
691 (muse-derive-style "journal-html" "html"
692 :before-end 'muse-journal-html-munge-buffer)
694 (muse-derive-style "journal-xhtml" "xhtml"
695 :before-end 'muse-journal-html-munge-buffer)
697 (muse-derive-style "journal-latex" "latex"
698 :tags 'muse-journal-latex-markup-tags
699 :before-end 'muse-journal-latex-munge-buffer)
701 (muse-derive-style "journal-pdf" "pdf"
702 :tags 'muse-journal-latex-markup-tags
703 :before-end 'muse-journal-latex-munge-buffer)
705 (muse-derive-style "journal-book-latex" "book-latex"
706 ;;:nochapters
707 :tags 'muse-journal-latex-markup-tags
708 :before-end 'muse-journal-latex-munge-buffer)
710 (muse-derive-style "journal-book-pdf" "book-pdf"
711 ;;:nochapters
712 :tags 'muse-journal-latex-markup-tags
713 :before-end 'muse-journal-latex-munge-buffer)
715 (muse-define-style "journal-rdf"
716 :suffix 'muse-journal-rdf-extension
717 :regexps 'muse-journal-rss-markup-regexps
718 :functions 'muse-journal-rss-markup-functions
719 :before 'muse-journal-rss-munge-buffer
720 :header 'muse-journal-rdf-header
721 :footer 'muse-journal-rdf-footer
722 :date-format 'muse-journal-rdf-date-format
723 :entry-template 'muse-journal-rdf-entry-template
724 :base-url 'muse-journal-rdf-base-url
725 :summarize 'muse-journal-rdf-summarize-entries)
727 (muse-define-style "journal-rss"
728 :suffix 'muse-journal-rss-extension
729 :regexps 'muse-journal-rss-markup-regexps
730 :functions 'muse-journal-rss-markup-functions
731 :before 'muse-journal-rss-munge-buffer
732 :header 'muse-journal-rss-header
733 :footer 'muse-journal-rss-footer
734 :date-format 'muse-journal-rss-date-format
735 :entry-template 'muse-journal-rss-entry-template
736 :base-url 'muse-journal-rss-base-url
737 :summarize 'muse-journal-rss-summarize-entries)
739 (provide 'muse-journal)
741 ;;; muse-journal.el ends here