Skip to content

Instantly share code, notes, and snippets.

@tjmaynes
Last active January 24, 2020 10:22
Show Gist options
  • Select an option

  • Save tjmaynes/e0b13e010f4c34dd71c41a4f50e0e5cd to your computer and use it in GitHub Desktop.

Select an option

Save tjmaynes/e0b13e010f4c34dd71c41a4f50e0e5cd to your computer and use it in GitHub Desktop.
;;; htmlize.el --- Convert buffer text and decorations to HTML. -*- lexical-binding: t -*-
;; Copyright (C) 1997-2003,2005,2006,2009,2011,2012,2014,2017,2018 Hrvoje Niksic
;; Author: Hrvoje Niksic <hniksic@gmail.com>
;; Homepage: https://github.com/hniksic/emacs-htmlize
;; Keywords: hypermedia, extensions
;; Version: 1.56
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation; either version 2, or (at your option)
;; any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program; see the file COPYING. If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.
;;; Commentary:
;; This package converts the buffer text and the associated
;; decorations to HTML. Mail to <hniksic@gmail.com> to discuss
;; features and additions. All suggestions are more than welcome.
;; To use it, just switch to the buffer you want HTML-ized and type
;; `M-x htmlize-buffer'. You will be switched to a new buffer that
;; contains the resulting HTML code. You can edit and inspect this
;; buffer, or you can just save it with C-x C-w. `M-x htmlize-file'
;; will find a file, fontify it, and save the HTML version in
;; FILE.html, without any additional intervention. `M-x
;; htmlize-many-files' allows you to htmlize any number of files in
;; the same manner. `M-x htmlize-many-files-dired' does the same for
;; files marked in a dired buffer.
;; htmlize supports three types of HTML output, selected by setting
;; `htmlize-output-type': `css', `inline-css', and `font'. In `css'
;; mode, htmlize uses cascading style sheets to specify colors; it
;; generates classes that correspond to Emacs faces and uses <span
;; class=FACE>...</span> to color parts of text. In this mode, the
;; produced HTML is valid under the 4.01 strict DTD, as confirmed by
;; the W3C validator. `inline-css' is like `css', except the CSS is
;; put directly in the STYLE attribute of the SPAN element, making it
;; possible to paste the generated HTML into existing HTML documents.
;; In `font' mode, htmlize uses <font color="...">...</font> to
;; colorize HTML, which is not standard-compliant, but works better in
;; older browsers. `css' mode is the default.
;; You can also use htmlize from your Emacs Lisp code. When called
;; non-interactively, `htmlize-buffer' and `htmlize-region' will
;; return the resulting HTML buffer, but will not change current
;; buffer or move the point. htmlize will do its best to work on
;; non-windowing Emacs sessions but the result will be limited to
;; colors supported by the terminal.
;; htmlize aims for compatibility with older Emacs versions. Please
;; let me know if it doesn't work on the version of GNU Emacs that you
;; are using. The package relies on the presence of CL extensions;
;; please don't try to remove that dependency. I see no practical
;; problems with using the full power of the CL extensions, except
;; that one might learn to like them too much.
;; The latest version is available at:
;;
;; <https://github.com/hniksic/emacs-htmlize>
;; <https://code.orgmode.org/mirrors/emacs-htmlize>
;;
;; Thanks go to the many people who have sent reports and contributed
;; comments, suggestions, and fixes. They include Ron Gut, Bob
;; Weiner, Toni Drabik, Peter Breton, Ville Skytta, Thomas Vogels,
;; Juri Linkov, Maciek Pasternacki, and many others.
;; User quotes: "You sir, are a sick, sick, _sick_ person. :)"
;; -- Bill Perry, author of Emacs/W3
;;; Code:
(require 'cl)
(eval-when-compile
(defvar font-lock-auto-fontify)
(defvar font-lock-support-mode)
(defvar global-font-lock-mode))
(defconst htmlize-version "1.56")
(defgroup htmlize nil
"Convert buffer text and faces to HTML."
:group 'hypermedia)
(defcustom htmlize-head-tags ""
"Additional tags to insert within HEAD of the generated document."
:type 'string
:group 'htmlize)
(defcustom htmlize-output-type 'css
"Output type of generated HTML, one of `css', `inline-css', or `font'.
When set to `css' (the default), htmlize will generate a style sheet
with description of faces, and use it in the HTML document, specifying
the faces in the actual text with <span class=\"FACE\">.
When set to `inline-css', the style will be generated as above, but
placed directly in the STYLE attribute of the span ELEMENT: <span
style=\"STYLE\">. This makes it easier to paste the resulting HTML to
other documents.
When set to `font', the properties will be set using layout tags
<font>, <b>, <i>, <u>, and <strike>.
`css' output is normally preferred, but `font' is still useful for
supporting old, pre-CSS browsers, and both `inline-css' and `font' for
easier embedding of colorized text in foreign HTML documents (no style
sheet to carry around)."
:type '(choice (const css) (const inline-css) (const font))
:group 'htmlize)
(defcustom htmlize-use-images t
"Whether htmlize generates `img' for images attached to buffer contents."
:type 'boolean
:group 'htmlize)
(defcustom htmlize-force-inline-images nil
"Non-nil means generate all images inline using data URLs.
Normally htmlize converts image descriptors with :file properties to
relative URIs, and those with :data properties to data URIs. With this
flag set, the images specified as a file name are loaded into memory and
embedded in the HTML as data URIs."
:type 'boolean
:group 'htmlize)
(defcustom htmlize-max-alt-text 100
"Maximum size of text to use as ALT text in images.
Normally when htmlize encounters text covered by the `display' property
that specifies an image, it generates an `alt' attribute containing the
original text. If the text is larger than `htmlize-max-alt-text' characters,
this will not be done."
:type 'integer
:group 'htmlize)
(defcustom htmlize-transform-image 'htmlize-default-transform-image
"Function called to modify the image descriptor.
The function is called with the image descriptor found in the buffer and
the text the image is supposed to replace. It should return a (possibly
different) image descriptor property list or a replacement string to use
instead of of the original buffer text.
Returning nil is the same as returning the original text."
:type 'boolean
:group 'htmlize)
(defcustom htmlize-generate-hyperlinks t
"Non-nil means auto-generate the links from URLs and mail addresses in buffer.
This is on by default; set it to nil if you don't want htmlize to
autogenerate such links. Note that this option only turns off automatic
search for contents that looks like URLs and converting them to links.
It has no effect on whether htmlize respects the `htmlize-link' property."
:type 'boolean
:group 'htmlize)
(defcustom htmlize-hyperlink-style "
a {
color: inherit;
background-color: inherit;
font: inherit;
text-decoration: inherit;
}
a:hover {
text-decoration: underline;
}
"
"The CSS style used for hyperlinks when in CSS mode."
:type 'string
:group 'htmlize)
(defcustom htmlize-replace-form-feeds t
"Non-nil means replace form feeds in source code with HTML separators.
Form feeds are the ^L characters at line beginnings that are sometimes
used to separate sections of source code. If this variable is set to
`t', form feed characters are replaced with the <hr> separator. If this
is a string, it specifies the replacement to use. Note that <pre> is
temporarily closed before the separator is inserted, so the default
replacement is effectively \"</pre><hr /><pre>\". If you specify
another replacement, don't forget to close and reopen the <pre> if you
want the output to remain valid HTML.
If you need more elaborate processing, set this to nil and use
htmlize-after-hook."
:type 'boolean
:group 'htmlize)
(defcustom htmlize-html-charset nil
"The charset declared by the resulting HTML documents.
When non-nil, causes htmlize to insert the following in the HEAD section
of the generated HTML:
<meta http-equiv=\"Content-Type\" content=\"text/html; charset=CHARSET\">
where CHARSET is the value you've set for htmlize-html-charset. Valid
charsets are defined by MIME and include strings like \"iso-8859-1\",
\"iso-8859-15\", \"utf-8\", etc.
If you are using non-Latin-1 charsets, you might need to set this for
your documents to render correctly. Also, the W3C validator requires
submitted HTML documents to declare a charset. So if you care about
validation, you can use this to prevent the validator from bitching.
Needless to say, if you set this, you should actually make sure that
the buffer is in the encoding you're claiming it is in. (This is
normally achieved by using the correct file coding system for the
buffer.) If you don't understand what that means, you should probably
leave this option in its default setting."
:type '(choice (const :tag "Unset" nil)
string)
:group 'htmlize)
(defcustom htmlize-convert-nonascii-to-entities t
"Whether non-ASCII characters should be converted to HTML entities.
When this is non-nil, characters with codes in the 128-255 range will be
considered Latin 1 and rewritten as \"&#CODE;\". Characters with codes
above 255 will be converted to \"&#UCS;\", where UCS denotes the Unicode
code point of the character. If the code point cannot be determined,
the character will be copied unchanged, as would be the case if the
option were nil.
When the option is nil, the non-ASCII characters are copied to HTML
without modification. In that case, the web server and/or the browser
must be set to understand the encoding that was used when saving the
buffer. (You might also want to specify it by setting
`htmlize-html-charset'.)
Note that in an HTML entity \"&#CODE;\", CODE is always a UCS code point,
which has nothing to do with the charset the page is in. For example,
\"&#169;\" *always* refers to the copyright symbol, regardless of charset
specified by the META tag or the charset sent by the HTTP server. In
other words, \"&#169;\" is exactly equivalent to \"&copy;\".
For most people htmlize will work fine with this option left at the
default setting; don't change it unless you know what you're doing."
:type 'sexp
:group 'htmlize)
(defcustom htmlize-ignore-face-size 'absolute
"Whether face size should be ignored when generating HTML.
If this is nil, face sizes are used. If set to t, sizes are ignored
If set to `absolute', only absolute size specifications are ignored.
Please note that font sizes only work with CSS-based output types."
:type '(choice (const :tag "Don't ignore" nil)
(const :tag "Ignore all" t)
(const :tag "Ignore absolute" absolute))
:group 'htmlize)
(defcustom htmlize-css-name-prefix ""
"The prefix used for CSS names.
The CSS names that htmlize generates from face names are often too
generic for CSS files; for example, `font-lock-type-face' is transformed
to `type'. Use this variable to add a prefix to the generated names.
The string \"htmlize-\" is an example of a reasonable prefix."
:type 'string
:group 'htmlize)
(defcustom htmlize-use-rgb-txt t
"Whether `rgb.txt' should be used to convert color names to RGB.
This conversion means determining, for instance, that the color
\"IndianRed\" corresponds to the (205, 92, 92) RGB triple. `rgb.txt'
is the X color database that maps hundreds of color names to such RGB
triples. When this variable is non-nil, `htmlize' uses `rgb.txt' to
look up color names.
If this variable is nil, htmlize queries Emacs for RGB components of
colors using `color-instance-rgb-components' and `color-values'.
This can yield incorrect results on non-true-color displays.
If the `rgb.txt' file is not found (which will be the case if you're
running Emacs on non-X11 systems), this option is ignored."
:type 'boolean
:group 'htmlize)
(defvar htmlize-face-overrides nil
"Overrides for face definitions.
Normally face definitions are taken from Emacs settings for fonts
in the current frame. For faces present in this plist, the
definitions will be used instead. Keys in the plist are symbols
naming the face and values are the overriding definitions. For
example:
(setq htmlize-face-overrides
'(font-lock-warning-face \"black\"
font-lock-function-name-face \"red\"
font-lock-comment-face \"blue\"
default (:foreground \"dark-green\" :background \"yellow\")))
This variable can be also be `let' bound when running `htmlize-buffer'.")
(defcustom htmlize-untabify t
"Non-nil means untabify buffer contents during htmlization."
:type 'boolean
:group 'htmlize)
(defcustom htmlize-html-major-mode nil
"The mode the newly created HTML buffer will be put in.
Set this to nil if you prefer the default (fundamental) mode."
:type '(radio (const :tag "No mode (fundamental)" nil)
(function-item html-mode)
(function :tag "User-defined major mode"))
:group 'htmlize)
(defcustom htmlize-pre-style nil
"When non-nil, `<pre>' tags will be decorated with style
information in `font' and `inline-css' modes. This allows a
consistent background for captures of regions."
:type 'boolean
:group 'htmlize)
(defvar htmlize-before-hook nil
"Hook run before htmlizing a buffer.
The hook functions are run in the source buffer (not the resulting HTML
buffer).")
(defvar htmlize-after-hook nil
"Hook run after htmlizing a buffer.
Unlike `htmlize-before-hook', these functions are run in the generated
HTML buffer. You may use them to modify the outlook of the final HTML
output.")
(defvar htmlize-file-hook nil
"Hook run by `htmlize-file' after htmlizing a file, but before saving it.")
(defvar htmlize-buffer-places)
;;; Some cross-Emacs compatibility.
;; We need a function that efficiently finds the next change of a
;; property regardless of whether the change occurred because of a
;; text property or an extent/overlay.
(defun htmlize-next-change (pos prop &optional limit)
(if prop
(next-single-char-property-change pos prop nil limit)
(next-char-property-change pos limit)))
(defun htmlize-overlay-faces-at (pos)
(delq nil (mapcar (lambda (o) (overlay-get o 'face)) (overlays-at pos))))
(defun htmlize-next-face-change (pos &optional limit)
;; (htmlize-next-change pos 'face limit) would skip over entire
;; overlays that specify the `face' property, even when they
;; contain smaller text properties that also specify `face'.
;; Emacs display engine merges those faces, and so must we.
(or limit
(setq limit (point-max)))
(let ((next-prop (next-single-property-change pos 'face nil limit))
(overlay-faces (htmlize-overlay-faces-at pos)))
(while (progn
(setq pos (next-overlay-change pos))
(and (< pos next-prop)
(equal overlay-faces (htmlize-overlay-faces-at pos)))))
(setq pos (min pos next-prop))
;; Additionally, we include the entire region that specifies the
;; `display' property.
(when (get-char-property pos 'display)
(setq pos (next-single-char-property-change pos 'display nil limit)))
pos))
(defmacro htmlize-lexlet (&rest letforms)
(declare (indent 1) (debug let))
(if (and (boundp 'lexical-binding)
lexical-binding)
`(let ,@letforms)
;; cl extensions have a macro implementing lexical let
`(lexical-let ,@letforms)))
;;; Transformation of buffer text: HTML escapes, untabification, etc.
(defvar htmlize-basic-character-table
;; Map characters in the 0-127 range to either one-character strings
;; or to numeric entities.
(let ((table (make-vector 128 ?\0)))
;; Map characters in the 32-126 range to themselves, others to
;; &#CODE entities;
(dotimes (i 128)
(setf (aref table i) (if (and (>= i 32) (<= i 126))
(char-to-string i)
(format "&#%d;" i))))
;; Set exceptions manually.
(setf
;; Don't escape newline, carriage return, and TAB.
(aref table ?\n) "\n"
(aref table ?\r) "\r"
(aref table ?\t) "\t"
;; Escape &, <, and >.
(aref table ?&) "&amp;"
(aref table ?<) "&lt;"
(aref table ?>) "&gt;"
;; Not escaping '"' buys us a measurable speedup. It's only
;; necessary to quote it for strings used in attribute values,
;; which htmlize doesn't typically do.
;(aref table ?\") "&quot;"
)
table))
;; A cache of HTML representation of non-ASCII characters. Depending
;; on the setting of `htmlize-convert-nonascii-to-entities', this maps
;; non-ASCII characters to either "&#<code>;" or "<char>" (mapconcat's
;; mapper must always return strings). It's only filled as characters
;; are encountered, so that in a buffer with e.g. French text, it will
;; only ever contain French accented characters as keys. It's cleared
;; on each entry to htmlize-buffer-1 to allow modifications of
;; `htmlize-convert-nonascii-to-entities' to take effect.
(defvar htmlize-extended-character-cache (make-hash-table :test 'eq))
(defun htmlize-protect-string (string)
"HTML-protect string, escaping HTML metacharacters and I18N chars."
;; Only protecting strings that actually contain unsafe or non-ASCII
;; chars removes a lot of unnecessary funcalls and consing.
(if (not (string-match "[^\r\n\t -%'-;=?-~]" string))
string
(mapconcat (lambda (char)
(cond
((< char 128)
;; ASCII: use htmlize-basic-character-table.
(aref htmlize-basic-character-table char))
((gethash char htmlize-extended-character-cache)
;; We've already seen this char; return the cached
;; string.
)
((not htmlize-convert-nonascii-to-entities)
;; If conversion to entities is not desired, always
;; copy the char literally.
(setf (gethash char htmlize-extended-character-cache)
(char-to-string char)))
((< char 256)
;; Latin 1: no need to call encode-char.
(setf (gethash char htmlize-extended-character-cache)
(format "&#%d;" char)))
((encode-char char 'ucs)
;; Must check if encode-char works for CHAR;
;; it fails for Arabic and possibly elsewhere.
(setf (gethash char htmlize-extended-character-cache)
(format "&#%d;" (encode-char char 'ucs))))
(t
;; encode-char doesn't work for this char. Copy it
;; unchanged and hope for the best.
(setf (gethash char htmlize-extended-character-cache)
(char-to-string char)))))
string "")))
(defun htmlize-attr-escape (string)
;; Like htmlize-protect-string, but also escapes double-quoted
;; strings to make it usable in attribute values.
(setq string (htmlize-protect-string string))
(if (not (string-match "\"" string))
string
(mapconcat (lambda (char)
(if (eql char ?\")
"&quot;"
(char-to-string char)))
string "")))
(defsubst htmlize-concat (list)
(if (and (consp list) (null (cdr list)))
;; Don't create a new string in the common case where the list only
;; consists of one element.
(car list)
(apply #'concat list)))
(defun htmlize-format-link (linkprops text)
(let ((uri (if (stringp linkprops)
linkprops
(plist-get linkprops :uri)))
(escaped-text (htmlize-protect-string text)))
(if uri
(format "<a href=\"%s\">%s</a>" (htmlize-attr-escape uri) escaped-text)
escaped-text)))
(defun htmlize-escape-or-link (string)
;; Escape STRING and/or add hyperlinks. STRING comes from a
;; `display' property.
(let ((pos 0) (end (length string)) outlist)
(while (< pos end)
(let* ((link (get-char-property pos 'htmlize-link string))
(next-link-change (next-single-property-change
pos 'htmlize-link string end))
(chunk (substring string pos next-link-change)))
(push
(cond (link
(htmlize-format-link link chunk))
((get-char-property 0 'htmlize-literal chunk)
chunk)
(t
(htmlize-protect-string chunk)))
outlist)
(setq pos next-link-change)))
(htmlize-concat (nreverse outlist))))
(defun htmlize-display-prop-to-html (display text)
(let (desc)
(cond ((stringp display)
;; Emacs ignores recursive display properties.
(htmlize-escape-or-link display))
((not (eq (car-safe display) 'image))
(htmlize-protect-string text))
((null (setq desc (funcall htmlize-transform-image
(cdr display) text)))
(htmlize-escape-or-link text))
((stringp desc)
(htmlize-escape-or-link desc))
(t
(htmlize-generate-image desc text)))))
(defun htmlize-string-to-html (string)
;; Convert the string to HTML, including images attached as
;; `display' property and links as `htmlize-link' property. In a
;; string without images or links, this is equivalent to
;; `htmlize-protect-string'.
(let ((pos 0) (end (length string)) outlist)
(while (< pos end)
(let* ((display (get-char-property pos 'display string))
(next-display-change (next-single-property-change
pos 'display string end))
(chunk (substring string pos next-display-change)))
(push
(if display
(htmlize-display-prop-to-html display chunk)
(htmlize-escape-or-link chunk))
outlist)
(setq pos next-display-change)))
(htmlize-concat (nreverse outlist))))
(defun htmlize-default-transform-image (imgprops _text)
"Default transformation of image descriptor to something usable in HTML.
If `htmlize-use-images' is nil, the function always returns nil, meaning
use original text. Otherwise, it tries to find the image for images that
specify a file name. If `htmlize-force-inline-images' is non-nil, it also
converts the :file attribute to :data and returns the modified property
list."
(when htmlize-use-images
(when (plist-get imgprops :file)
(let ((location (plist-get (cdr (find-image (list imgprops))) :file)))
(when location
(setq imgprops (plist-put (copy-list imgprops) :file location)))))
(if htmlize-force-inline-images
(let ((location (plist-get imgprops :file))
data)
(when location
(with-temp-buffer
(condition-case nil
(progn
(insert-file-contents-literally location)
(setq data (buffer-string)))
(error nil))))
;; if successful, return the new plist, otherwise return
;; nil, which will use the original text
(and data
(plist-put (plist-put imgprops :file nil)
:data data)))
imgprops)))
(defun htmlize-alt-text (_imgprops origtext)
(and (/= (length origtext) 0)
(<= (length origtext) htmlize-max-alt-text)
(not (string-match "[\0-\x1f]" origtext))
origtext))
(defun htmlize-generate-image (imgprops origtext)
(let* ((alt-text (htmlize-alt-text imgprops origtext))
(alt-attr (if alt-text
(format " alt=\"%s\"" (htmlize-attr-escape alt-text))
"")))
(cond ((plist-get imgprops :file)
;; Try to find the image in image-load-path
(let* ((found-props (cdr (find-image (list imgprops))))
(file (or (plist-get found-props :file)
(plist-get imgprops :file))))
(format "<img src=\"%s\"%s />"
(htmlize-attr-escape (file-relative-name file))
alt-attr)))
((plist-get imgprops :data)
(format "<img src=\"data:image/%s;base64,%s\"%s />"
(or (plist-get imgprops :type) "")
(base64-encode-string (plist-get imgprops :data))
alt-attr)))))
(defconst htmlize-ellipsis "...")
(put-text-property 0 (length htmlize-ellipsis) 'htmlize-ellipsis t htmlize-ellipsis)
(defun htmlize-match-inv-spec (inv)
(member* inv buffer-invisibility-spec
:key (lambda (i)
(if (symbolp i) i (car i)))))
(defun htmlize-decode-invisibility-spec (invisible)
;; Return t, nil, or `ellipsis', depending on how invisible text should be inserted.
(if (not (listp buffer-invisibility-spec))
;; If buffer-invisibility-spec is not a list, then all
;; characters with non-nil `invisible' property are visible.
(not invisible)
;; Otherwise, the value of a non-nil `invisible' property can be:
;; 1. a symbol -- make the text invisible if it matches
;; buffer-invisibility-spec.
;; 2. a list of symbols -- make the text invisible if
;; any symbol in the list matches
;; buffer-invisibility-spec.
;; If the match of buffer-invisibility-spec has a non-nil
;; CDR, replace the invisible text with an ellipsis.
(let ((match (if (symbolp invisible)
(htmlize-match-inv-spec invisible)
(some #'htmlize-match-inv-spec invisible))))
(cond ((null match) t)
((cdr-safe (car match)) 'ellipsis)
(t nil)))))
(defun htmlize-add-before-after-strings (beg end text)
;; Find overlays specifying before-string and after-string in [beg,
;; pos). If any are found, splice them into TEXT and return the new
;; text.
(let (additions)
(dolist (overlay (overlays-in beg end))
(let ((before (overlay-get overlay 'before-string))
(after (overlay-get overlay 'after-string)))
(when after
(push (cons (- (overlay-end overlay) beg)
after)
additions))
(when before
(push (cons (- (overlay-start overlay) beg)
before)
additions))))
(if additions
(let ((textlist nil)
(strpos 0))
(dolist (add (stable-sort additions #'< :key #'car))
(let ((addpos (car add))
(addtext (cdr add)))
(push (substring text strpos addpos) textlist)
(push addtext textlist)
(setq strpos addpos)))
(push (substring text strpos) textlist)
(apply #'concat (nreverse textlist)))
text)))
(defun htmlize-copy-prop (prop beg end string)
;; Copy the specified property from the specified region of the
;; buffer to the target string. We cannot rely on Emacs to copy the
;; property because we want to handle properties coming from both
;; text properties and overlays.
(let ((pos beg))
(while (< pos end)
(let ((value (get-char-property pos prop))
(next-change (htmlize-next-change pos prop end)))
(when value
(put-text-property (- pos beg) (- next-change beg)
prop value string))
(setq pos next-change)))))
(defun htmlize-get-text-with-display (beg end)
;; Like buffer-substring-no-properties, except it copies the
;; `display' property from the buffer, if found.
(let ((text (buffer-substring-no-properties beg end)))
(htmlize-copy-prop 'display beg end text)
(htmlize-copy-prop 'htmlize-link beg end text)
(setq text (htmlize-add-before-after-strings beg end text))
text))
(defun htmlize-buffer-substring-no-invisible (beg end)
;; Like buffer-substring-no-properties, but don't copy invisible
;; parts of the region. Where buffer-substring-no-properties
;; mandates an ellipsis to be shown, htmlize-ellipsis is inserted.
(let ((pos beg)
visible-list invisible show last-show next-change)
;; Iterate over the changes in the `invisible' property and filter
;; out the portions where it's non-nil, i.e. where the text is
;; invisible.
(while (< pos end)
(setq invisible (get-char-property pos 'invisible)
next-change (htmlize-next-change pos 'invisible end)
show (htmlize-decode-invisibility-spec invisible))
(cond ((eq show t)
(push (htmlize-get-text-with-display pos next-change)
visible-list))
((and (eq show 'ellipsis)
(not (eq last-show 'ellipsis))
;; Conflate successive ellipses.
(push htmlize-ellipsis visible-list))))
(setq pos next-change last-show show))
(htmlize-concat (nreverse visible-list))))
(defun htmlize-trim-ellipsis (text)
;; Remove htmlize-ellipses ("...") from the beginning of TEXT if it
;; starts with it. It checks for the special property of the
;; ellipsis so it doesn't work on ordinary text that begins with
;; "...".
(if (get-text-property 0 'htmlize-ellipsis text)
(substring text (length htmlize-ellipsis))
text))
(defconst htmlize-tab-spaces
;; A table of strings with spaces. (aref htmlize-tab-spaces 5) is
;; like (make-string 5 ?\ ), except it doesn't cons.
(let ((v (make-vector 32 nil)))
(dotimes (i (length v))
(setf (aref v i) (make-string i ?\ )))
v))
(defun htmlize-untabify-string (text start-column)
"Untabify TEXT, assuming it starts at START-COLUMN."
(let ((column start-column)
(last-match 0)
(chunk-start 0)
chunks match-pos tab-size)
(while (string-match "[\t\n]" text last-match)
(setq match-pos (match-beginning 0))
(cond ((eq (aref text match-pos) ?\t)
;; Encountered a tab: create a chunk of text followed by
;; the expanded tab.
(push (substring text chunk-start match-pos) chunks)
;; Increase COLUMN by the length of the text we've
;; skipped since last tab or newline. (Encountering
;; newline resets it.)
(incf column (- match-pos last-match))
;; Calculate tab size based on tab-width and COLUMN.
(setq tab-size (- tab-width (% column tab-width)))
;; Expand the tab, carefully recreating the `display'
;; property if one was on the TAB.
(let ((display (get-text-property match-pos 'display text))
(expanded-tab (aref htmlize-tab-spaces tab-size)))
(when display
(put-text-property 0 tab-size 'display display expanded-tab))
(push expanded-tab chunks))
(incf column tab-size)
(setq chunk-start (1+ match-pos)))
(t
;; Reset COLUMN at beginning of line.
(setq column 0)))
(setq last-match (1+ match-pos)))
;; If no chunks have been allocated, it means there have been no
;; tabs to expand. Return TEXT unmodified.
(if (null chunks)
text
(when (< chunk-start (length text))
;; Push the remaining chunk.
(push (substring text chunk-start) chunks))
;; Generate the output from the available chunks.
(htmlize-concat (nreverse chunks)))))
(defun htmlize-extract-text (beg end trailing-ellipsis)
;; Extract buffer text, sans the invisible parts. Then
;; untabify it and escape the HTML metacharacters.
(let ((text (htmlize-buffer-substring-no-invisible beg end)))
(when trailing-ellipsis
(setq text (htmlize-trim-ellipsis text)))
;; If TEXT ends up empty, don't change trailing-ellipsis.
(when (> (length text) 0)
(setq trailing-ellipsis
(get-text-property (1- (length text))
'htmlize-ellipsis text)))
(when htmlize-untabify
(setq text (htmlize-untabify-string text (current-column))))
(setq text (htmlize-string-to-html text))
(values text trailing-ellipsis)))
(defun htmlize-despam-address (string)
"Replace every occurrence of '@' in STRING with %40.
This is used to protect mailto links without modifying their meaning."
;; Suggested by Ville Skytta.
(while (string-match "@" string)
(setq string (replace-match "%40" nil t string)))
string)
(defun htmlize-make-tmp-overlay (beg end props)
(let ((overlay (make-overlay beg end)))
(overlay-put overlay 'htmlize-tmp-overlay t)
(while props
(overlay-put overlay (pop props) (pop props)))
overlay))
(defun htmlize-delete-tmp-overlays ()
(dolist (overlay (overlays-in (point-min) (point-max)))
(when (overlay-get overlay 'htmlize-tmp-overlay)
(delete-overlay overlay))))
(defun htmlize-make-link-overlay (beg end uri)
(htmlize-make-tmp-overlay beg end `(htmlize-link (:uri ,uri))))
(defun htmlize-create-auto-links ()
"Add `htmlize-link' property to all mailto links in the buffer."
(save-excursion
(goto-char (point-min))
(while (re-search-forward
"<\\(\\(mailto:\\)?\\([-=+_.a-zA-Z0-9]+@[-_.a-zA-Z0-9]+\\)\\)>"
nil t)
(let* ((address (match-string 3))
(beg (match-beginning 0)) (end (match-end 0))
(uri (concat "mailto:" (htmlize-despam-address address))))
(htmlize-make-link-overlay beg end uri)))
(goto-char (point-min))
(while (re-search-forward "<\\(\\(URL:\\)?\\([a-zA-Z]+://[^;]+\\)\\)>"
nil t)
(htmlize-make-link-overlay
(match-beginning 0) (match-end 0) (match-string 3)))))
;; Tests for htmlize-create-auto-links:
;; <mailto:hniksic@xemacs.org>
;; <http://fly.srk.fer.hr>
;; <URL:http://www.xemacs.org>
;; <http://www.mail-archive.com/bbdb-info@xemacs.org/>
;; <hniksic@xemacs.org>
;; <xalan-dev-sc.10148567319.hacuhiucknfgmpfnjcpg-john=doe.com@xml.apache.org>
(defun htmlize-shadow-form-feeds ()
(let ((s "\n<hr />"))
(put-text-property 0 (length s) 'htmlize-literal t s)
(let ((disp `(display ,s)))
(while (re-search-forward "\n\^L" nil t)
(let* ((beg (match-beginning 0))
(end (match-end 0))
(form-feed-pos (1+ beg))
;; don't process ^L if invisible or covered by `display'
(show (and (htmlize-decode-invisibility-spec
(get-char-property form-feed-pos 'invisible))
(not (get-char-property form-feed-pos 'display)))))
(when show
(htmlize-make-tmp-overlay beg end disp)))))))
(defun htmlize-defang-local-variables ()
;; Juri Linkov reports that an HTML-ized "Local variables" can lead
;; visiting the HTML to fail with "Local variables list is not
;; properly terminated". He suggested changing the phrase to
;; syntactically equivalent HTML that Emacs doesn't recognize.
(goto-char (point-min))
(while (search-forward "Local Variables:" nil t)
(replace-match "Local Variables&#58;" nil t)))
;;; Color handling.
(defvar htmlize-x-library-search-path
`(,data-directory
"/etc/X11/rgb.txt"
"/usr/share/X11/rgb.txt"
;; the remainder of this list really belongs in a museum
"/usr/X11R6/lib/X11/"
"/usr/X11R5/lib/X11/"
"/usr/lib/X11R6/X11/"
"/usr/lib/X11R5/X11/"
"/usr/local/X11R6/lib/X11/"
"/usr/local/X11R5/lib/X11/"
"/usr/local/lib/X11R6/X11/"
"/usr/local/lib/X11R5/X11/"
"/usr/X11/lib/X11/"
"/usr/lib/X11/"
"/usr/local/lib/X11/"
"/usr/X386/lib/X11/"
"/usr/x386/lib/X11/"
"/usr/XFree86/lib/X11/"
"/usr/unsupported/lib/X11/"
"/usr/athena/lib/X11/"
"/usr/local/x11r5/lib/X11/"
"/usr/lpp/Xamples/lib/X11/"
"/usr/openwin/lib/X11/"
"/usr/openwin/share/lib/X11/"))
(defun htmlize-get-color-rgb-hash (&optional rgb-file)
"Return a hash table mapping X color names to RGB values.
The keys in the hash table are X11 color names, and the values are the
#rrggbb RGB specifications, extracted from `rgb.txt'.
If RGB-FILE is nil, the function will try hard to find a suitable file
in the system directories.
If no rgb.txt file is found, return nil."
(let ((rgb-file (or rgb-file (locate-file
"rgb.txt"
htmlize-x-library-search-path)))
(hash nil))
(when rgb-file
(with-temp-buffer
(insert-file-contents rgb-file)
(setq hash (make-hash-table :test 'equal))
(while (not (eobp))
(cond ((looking-at "^\\s-*\\([!#]\\|$\\)")
;; Skip comments and empty lines.
)
((looking-at
"[ \t]*\\([0-9]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\([0-9]+\\)[ \t]+\\(.*\\)")
(setf (gethash (downcase (match-string 4)) hash)
(format "#%02x%02x%02x"
(string-to-number (match-string 1))
(string-to-number (match-string 2))
(string-to-number (match-string 3)))))
(t
(error
"Unrecognized line in %s: %s"
rgb-file
(buffer-substring (point) (progn (end-of-line) (point))))))
(forward-line 1))))
hash))
;; Compile the RGB map when loaded. On systems where rgb.txt is
;; missing, the value of the variable will be nil, and rgb.txt will
;; not be used.
(defvar htmlize-color-rgb-hash (htmlize-get-color-rgb-hash))
;;; Face handling.
(defun htmlize-face-color-internal (face fg)
;; Used only under GNU Emacs. Return the color of FACE, but don't
;; return "unspecified-fg" or "unspecified-bg". If the face is
;; `default' and the color is unspecified, look up the color in
;; frame parameters.
(let* ((function (if fg #'face-foreground #'face-background))
(color (funcall function face nil t)))
(when (and (eq face 'default) (null color))
(setq color (cdr (assq (if fg 'foreground-color 'background-color)
(frame-parameters)))))
(when (or (eq color 'unspecified)
(equal color "unspecified-fg")
(equal color "unspecified-bg"))
(setq color nil))
(when (and (eq face 'default)
(null color))
;; Assuming black on white doesn't seem right, but I can't think
;; of anything better to do.
(setq color (if fg "black" "white")))
color))
(defun htmlize-face-foreground (face)
;; Return the name of the foreground color of FACE. If FACE does
;; not specify a foreground color, return nil.
(htmlize-face-color-internal face t))
(defun htmlize-face-background (face)
;; Return the name of the background color of FACE. If FACE does
;; not specify a background color, return nil.
;; GNU Emacs.
(htmlize-face-color-internal face nil))
;; Convert COLOR to the #RRGGBB string. If COLOR is already in that
;; format, it's left unchanged.
(defun htmlize-color-to-rgb (color)
(let ((rgb-string nil))
(cond ((null color)
;; Ignore nil COLOR because it means that the face is not
;; specifying any color. Hence (htmlize-color-to-rgb nil)
;; returns nil.
)
((string-match "\\`#" color)
;; The color is already in #rrggbb format.
(setq rgb-string color))
((and htmlize-use-rgb-txt
htmlize-color-rgb-hash)
;; Use of rgb.txt is requested, and it's available on the
;; system. Use it.
(setq rgb-string (gethash (downcase color) htmlize-color-rgb-hash)))
(t
;; We're getting the RGB components from Emacs.
(let ((rgb (mapcar (lambda (arg)
(/ arg 256))
(color-values color))))
(when rgb
(setq rgb-string (apply #'format "#%02x%02x%02x" rgb))))))
;; If RGB-STRING is still nil, it means the color cannot be found,
;; for whatever reason. In that case just punt and return COLOR.
;; Most browsers support a decent set of color names anyway.
(or rgb-string color)))
;; We store the face properties we care about into an
;; `htmlize-fstruct' type. That way we only have to analyze face
;; properties, which can be time consuming, once per each face. The
;; mapping between Emacs faces and htmlize-fstructs is established by
;; htmlize-make-face-map. The name "fstruct" refers to variables of
;; type `htmlize-fstruct', while the term "face" is reserved for Emacs
;; faces.
(defstruct htmlize-fstruct
foreground ; foreground color, #rrggbb
background ; background color, #rrggbb
size ; size
boldp ; whether face is bold
italicp ; whether face is italic
underlinep ; whether face is underlined
overlinep ; whether face is overlined
strikep ; whether face is struck through
css-name ; CSS name of face
)
(defun htmlize-face-set-from-keyword-attr (fstruct attr value)
;; For ATTR and VALUE, set the equivalent value in FSTRUCT.
(case attr
(:foreground
(setf (htmlize-fstruct-foreground fstruct) (htmlize-color-to-rgb value)))
(:background
(setf (htmlize-fstruct-background fstruct) (htmlize-color-to-rgb value)))
(:height
(setf (htmlize-fstruct-size fstruct) value))
(:weight
(when (string-match (symbol-name value) "bold")
(setf (htmlize-fstruct-boldp fstruct) t)))
(:slant
(setf (htmlize-fstruct-italicp fstruct) (or (eq value 'italic)
(eq value 'oblique))))
(:bold
(setf (htmlize-fstruct-boldp fstruct) value))
(:italic
(setf (htmlize-fstruct-italicp fstruct) value))
(:underline
(setf (htmlize-fstruct-underlinep fstruct) value))
(:overline
(setf (htmlize-fstruct-overlinep fstruct) value))
(:strike-through
(setf (htmlize-fstruct-strikep fstruct) value))))
(defun htmlize-face-size (face)
;; The size (height) of FACE, taking inheritance into account.
;; Only works in Emacs 21 and later.
(let* ((face-list (list face))
(head face-list)
(tail face-list))
(while head
(let ((inherit (face-attribute (car head) :inherit)))
(cond ((listp inherit)
(setcdr tail (copy-list inherit))
(setq tail (last tail)))
((eq inherit 'unspecified))
(t
(setcdr tail (list inherit))
(setq tail (cdr tail)))))
(pop head))
(let ((size-list
(loop
for f in face-list
for h = (face-attribute f :height)
collect (if (eq h 'unspecified) nil h))))
(reduce 'htmlize-merge-size (cons nil size-list)))))
(defun htmlize-face-css-name (face)
;; Generate the css-name property for the given face. Emacs places
;; no restrictions on the names of symbols that represent faces --
;; any characters may be in the name, even control chars. We try
;; hard to beat the face name into shape, both esthetically and
;; according to CSS1 specs.
(let ((name (downcase (symbol-name face))))
(when (string-match "\\`font-lock-" name)
;; font-lock-FOO-face -> FOO.
(setq name (replace-match "" t t name)))
(when (string-match "-face\\'" name)
;; Drop the redundant "-face" suffix.
(setq name (replace-match "" t t name)))
(while (string-match "[^-a-zA-Z0-9]" name)
;; Drop the non-alphanumerics.
(setq name (replace-match "X" t t name)))
(when (string-match "\\`[-0-9]" name)
;; CSS identifiers may not start with a digit.
(setq name (concat "X" name)))
;; After these transformations, the face could come out empty.
(when (equal name "")
(setq name "face"))
;; Apply the prefix.
(concat htmlize-css-name-prefix name)))
(defun htmlize-face-to-fstruct-1 (face)
"Convert Emacs face FACE to fstruct, internal."
(let ((fstruct (make-htmlize-fstruct
:foreground (htmlize-color-to-rgb
(htmlize-face-foreground face))
:background (htmlize-color-to-rgb
(htmlize-face-background face)))))
;; GNU Emacs
(dolist (attr '(:weight :slant :underline :overline :strike-through))
(let ((value (face-attribute face attr nil t)))
(when (and value (not (eq value 'unspecified)))
(htmlize-face-set-from-keyword-attr fstruct attr value))))
(let ((size (htmlize-face-size face)))
(unless (eql size 1.0) ; ignore non-spec
(setf (htmlize-fstruct-size fstruct) size)))
(setf (htmlize-fstruct-css-name fstruct) (htmlize-face-css-name face))
fstruct))
(defun htmlize-face-to-fstruct (face)
(let* ((face-list (or (and (symbolp face)
(cdr (assq face face-remapping-alist)))
(list face)))
(fstruct (htmlize-merge-faces
(mapcar (lambda (face)
(if (symbolp face)
(or (htmlize-get-override-fstruct face)
(htmlize-face-to-fstruct-1 face))
(htmlize-attrlist-to-fstruct face)))
(nreverse face-list)))))
(when (symbolp face)
(setf (htmlize-fstruct-css-name fstruct) (htmlize-face-css-name face)))
fstruct))
(defmacro htmlize-copy-attr-if-set (attr-list dest source)
;; Generate code with the following pattern:
;; (progn
;; (when (htmlize-fstruct-ATTR source)
;; (setf (htmlize-fstruct-ATTR dest) (htmlize-fstruct-ATTR source)))
;; ...)
;; for the given list of boolean attributes.
(cons 'progn
(loop for attr in attr-list
for attr-sym = (intern (format "htmlize-fstruct-%s" attr))
collect `(when (,attr-sym ,source)
(setf (,attr-sym ,dest) (,attr-sym ,source))))))
(defun htmlize-merge-size (merged next)
;; Calculate the size of the merge of MERGED and NEXT.
(cond ((null merged) next)
((integerp next) next)
((null next) merged)
((floatp merged) (* merged next))
((integerp merged) (round (* merged next)))))
(defun htmlize-merge-two-faces (merged next)
(htmlize-copy-attr-if-set
(foreground background boldp italicp underlinep overlinep strikep)
merged next)
(setf (htmlize-fstruct-size merged)
(htmlize-merge-size (htmlize-fstruct-size merged)
(htmlize-fstruct-size next)))
merged)
(defun htmlize-merge-faces (fstruct-list)
(cond ((null fstruct-list)
;; Nothing to do, return a dummy face.
(make-htmlize-fstruct))
((null (cdr fstruct-list))
;; Optimize for the common case of a single face, simply
;; return it.
(car fstruct-list))
(t
(reduce #'htmlize-merge-two-faces
(cons (make-htmlize-fstruct) fstruct-list)))))
;; GNU Emacs 20+ supports attribute lists in `face' properties. For
;; example, you can use `(:foreground "red" :weight bold)' as an
;; overlay's "face", or you can even use a list of such lists, etc.
;; We call those "attrlists".
;;
;; htmlize supports attrlist by converting them to fstructs, the same
;; as with regular faces.
(defun htmlize-attrlist-to-fstruct (attrlist &optional name)
;; Like htmlize-face-to-fstruct, but accepts an ATTRLIST as input.
(let ((fstruct (make-htmlize-fstruct)))
(cond ((eq (car attrlist) 'foreground-color)
;; ATTRLIST is (foreground-color . COLOR)
(setf (htmlize-fstruct-foreground fstruct)
(htmlize-color-to-rgb (cdr attrlist))))
((eq (car attrlist) 'background-color)
;; ATTRLIST is (background-color . COLOR)
(setf (htmlize-fstruct-background fstruct)
(htmlize-color-to-rgb (cdr attrlist))))
(t
;; ATTRLIST is a plist.
(while attrlist
(let ((attr (pop attrlist))
(value (pop attrlist)))
(when (and value (not (eq value 'unspecified)))
(htmlize-face-set-from-keyword-attr fstruct attr value))))))
(setf (htmlize-fstruct-css-name fstruct) (or name "custom"))
fstruct))
(defun htmlize-decode-face-prop (prop)
"Turn face property PROP into a list of face-like objects."
;; PROP can be a symbol naming a face, a string naming such a
;; symbol, a cons (foreground-color . COLOR) or (background-color
;; COLOR), a property list (:attr1 val1 :attr2 val2 ...), or a list
;; of any of those.
;;
;; (htmlize-decode-face-prop 'face) -> (face)
;; (htmlize-decode-face-prop '(face1 face2)) -> (face1 face2)
;; (htmlize-decode-face-prop '(:attr "val")) -> ((:attr "val"))
;; (htmlize-decode-face-prop '((:attr "val") face (foreground-color "red")))
;; -> ((:attr "val") face (foreground-color "red"))
;;
;; Unrecognized atoms or non-face symbols/strings are silently
;; stripped away.
(cond ((null prop)
nil)
((symbolp prop)
(and (facep prop)
(list prop)))
((stringp prop)
(and (facep (intern-soft prop))
(list prop)))
((atom prop)
nil)
((and (symbolp (car prop))
(eq ?: (aref (symbol-name (car prop)) 0)))
(list prop))
((or (eq (car prop) 'foreground-color)
(eq (car prop) 'background-color))
(list prop))
(t
(apply #'nconc (mapcar #'htmlize-decode-face-prop prop)))))
(defun htmlize-get-override-fstruct (face)
(let* ((raw-def (plist-get htmlize-face-overrides face))
(def (cond ((stringp raw-def) (list :foreground raw-def))
((listp raw-def) raw-def)
(t
(error (format (concat "face override must be an "
"attribute list or string, got %s")
raw-def))))))
(and def
(htmlize-attrlist-to-fstruct def (symbol-name face)))))
(defun htmlize-make-face-map (faces)
;; Return a hash table mapping Emacs faces to htmlize's fstructs.
;; The keys are either face symbols or attrlists, so the test
;; function must be `equal'.
(let ((face-map (make-hash-table :test 'equal))
css-names)
(dolist (face faces)
(unless (gethash face face-map)
;; Haven't seen FACE yet; convert it to an fstruct and cache
;; it.
(let ((fstruct (htmlize-face-to-fstruct face)))
(setf (gethash face face-map) fstruct)
(let* ((css-name (htmlize-fstruct-css-name fstruct))
(new-name css-name)
(i 0))
;; Uniquify the face's css-name by using NAME-1, NAME-2,
;; etc.
(while (member new-name css-names)
(setq new-name (format "%s-%s" css-name (incf i))))
(unless (equal new-name css-name)
(setf (htmlize-fstruct-css-name fstruct) new-name))
(push new-name css-names)))))
face-map))
(defun htmlize-unstringify-face (face)
"If FACE is a string, return it interned, otherwise return it unchanged."
(if (stringp face)
(intern face)
face))
(defun htmlize-faces-in-buffer ()
"Return a list of faces used in the current buffer.
This is the set of faces specified by the `face' text property and by buffer
overlays that specify `face'."
(let (faces)
;; Faces used by text properties.
(let ((pos (point-min)) face-prop next)
(while (< pos (point-max))
(setq face-prop (get-text-property pos 'face)
next (or (next-single-property-change pos 'face) (point-max)))
(setq faces (nunion (htmlize-decode-face-prop face-prop)
faces :test 'equal))
(setq pos next)))
;; Faces used by overlays.
(dolist (overlay (overlays-in (point-min) (point-max)))
(let ((face-prop (overlay-get overlay 'face)))
(setq faces (nunion (htmlize-decode-face-prop face-prop)
faces :test 'equal))))
faces))
(if (>= emacs-major-version 25)
(defun htmlize-sorted-overlays-at (pos)
(overlays-at pos t))
(defun htmlize-sorted-overlays-at (pos)
;; Like OVERLAYS-AT with the SORTED argument, for older Emacsen.
(let ((overlays (overlays-at pos)))
(setq overlays (sort* overlays #'<
:key (lambda (o)
(- (overlay-end o) (overlay-start o)))))
(setq overlays
(stable-sort overlays #'<
:key (lambda (o)
(let ((prio (overlay-get o 'priority)))
(if (numberp prio) prio 0)))))
(nreverse overlays))))
;; htmlize-faces-at-point returns the faces in use at point. The
;; faces are sorted by increasing priority, i.e. the last face takes
;; precedence.
;;
;; This returns all the faces in the `face' property and all the faces
;; in the overlays at point.
(defun htmlize-faces-at-point ()
(let (all-faces)
;; Faces from text properties.
(let ((face-prop (get-text-property (point) 'face)))
;; we need to reverse the `face' prop because we want
;; more specific faces to come later
(setq all-faces (nreverse (htmlize-decode-face-prop face-prop))))
;; Faces from overlays.
(let ((overlays
;; Collect overlays at point that specify `face'.
(delete-if-not (lambda (o)
(overlay-get o 'face))
(nreverse (htmlize-sorted-overlays-at (point)))))
list face-prop)
(dolist (overlay overlays)
(setq face-prop (overlay-get overlay 'face)
list (nconc (htmlize-decode-face-prop face-prop) list)))
;; Under "Merging Faces" the manual explicitly states
;; that faces specified by overlays take precedence over
;; faces specified by text properties.
(setq all-faces (nconc all-faces list)))
all-faces))
;; htmlize supports generating HTML in several flavors, some of which
;; use CSS, and others the <font> element. We take an OO approach and
;; define "methods" that indirect to the functions that depend on
;; `htmlize-output-type'. The currently used methods are `doctype',
;; `insert-head', `body-tag', `pre-tag', and `text-markup'. Not all
;; output types define all methods.
;;
;; Methods are called either with (htmlize-method METHOD ARGS...)
;; special form, or by accessing the function with
;; (htmlize-method-function 'METHOD) and calling (funcall FUNCTION).
;; The latter form is useful in tight loops because `htmlize-method'
;; conses.
(defmacro htmlize-method (method &rest args)
;; Expand to (htmlize-TYPE-METHOD ...ARGS...). TYPE is the value of
;; `htmlize-output-type' at run time.
`(funcall (htmlize-method-function ',method) ,@args))
(defun htmlize-method-function (method)
;; Return METHOD's function definition for the current output type.
;; The returned object can be safely funcalled.
(let ((sym (intern (format "htmlize-%s-%s" htmlize-output-type method))))
(indirect-function (if (fboundp sym)
sym
(let ((default (intern (concat "htmlize-default-"
(symbol-name method)))))
(if (fboundp default)
default
'ignore))))))
(defvar htmlize-memoization-table (make-hash-table :test 'equal))
(defmacro htmlize-memoize (key generator)
"Return the value of GENERATOR, memoized as KEY.
That means that GENERATOR will be evaluated and returned the first time
it's called with the same value of KEY. All other times, the cached
\(memoized) value will be returned."
(let ((value (gensym)))
`(let ((,value (gethash ,key htmlize-memoization-table)))
(unless ,value
(setq ,value ,generator)
(setf (gethash ,key htmlize-memoization-table) ,value))
,value)))
;;; Default methods.
(defun htmlize-default-doctype ()
nil ; no doc-string
;; Note that the `font' output is technically invalid under this DTD
;; because the DTD doesn't allow embedding <font> in <pre>.
"<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01//EN\">"
)
(defun htmlize-default-body-tag (face-map)
nil ; no doc-string
face-map ; shut up the byte-compiler
"<body>")
(defun htmlize-default-pre-tag (face-map)
nil ; no doc-string
face-map ; shut up the byte-compiler
"<pre>")
;;; CSS based output support.
;; Internal function; not a method.
(defun htmlize-css-specs (fstruct)
(let (result)
(when (htmlize-fstruct-foreground fstruct)
(push (format "color: %s;" (htmlize-fstruct-foreground fstruct))
result))
(when (htmlize-fstruct-background fstruct)
(push (format "background-color: %s;"
(htmlize-fstruct-background fstruct))
result))
(let ((size (htmlize-fstruct-size fstruct)))
(when (and size (not (eq htmlize-ignore-face-size t)))
(cond ((floatp size)
(push (format "font-size: %d%%;" (* 100 size)) result))
((not (eq htmlize-ignore-face-size 'absolute))
(push (format "font-size: %spt;" (/ size 10.0)) result)))))
(when (htmlize-fstruct-boldp fstruct)
(push "font-weight: bold;" result))
(when (htmlize-fstruct-italicp fstruct)
(push "font-style: italic;" result))
(when (htmlize-fstruct-underlinep fstruct)
(push "text-decoration: underline;" result))
(when (htmlize-fstruct-overlinep fstruct)
(push "text-decoration: overline;" result))
(when (htmlize-fstruct-strikep fstruct)
(push "text-decoration: line-through;" result))
(nreverse result)))
(defun htmlize-css-insert-head (buffer-faces face-map)
(insert " <style type=\"text/css\">\n <!--\n")
(insert " body {\n "
(mapconcat #'identity
(htmlize-css-specs (gethash 'default face-map))
"\n ")
"\n }\n")
(dolist (face (sort* (copy-list buffer-faces) #'string-lessp
:key (lambda (f)
(htmlize-fstruct-css-name (gethash f face-map)))))
(let* ((fstruct (gethash face face-map))
(cleaned-up-face-name
(let ((s
;; Use `prin1-to-string' rather than `symbol-name'
;; to get the face name because the "face" can also
;; be an attrlist, which is not a symbol.
(prin1-to-string face)))
;; If the name contains `--' or `*/', remove them.
(while (string-match "--" s)
(setq s (replace-match "-" t t s)))
(while (string-match "\\*/" s)
(setq s (replace-match "XX" t t s)))
s))
(specs (htmlize-css-specs fstruct)))
(insert " ." (htmlize-fstruct-css-name fstruct))
(if (null specs)
(insert " {")
(insert " {\n /* " cleaned-up-face-name " */\n "
(mapconcat #'identity specs "\n ")))
(insert "\n }\n")))
(insert htmlize-hyperlink-style
" -->\n </style>\n"))
(defun htmlize-css-text-markup (fstruct-list buffer)
;; Open the markup needed to insert text colored with FACES into
;; BUFFER. Return the function that closes the markup.
;; In CSS mode, this is easy: just nest the text in one <span
;; class=...> tag for each face in FSTRUCT-LIST.
(dolist (fstruct fstruct-list)
(princ "<span class=\"" buffer)
(princ (htmlize-fstruct-css-name fstruct) buffer)
(princ "\">" buffer))
(htmlize-lexlet ((fstruct-list fstruct-list) (buffer buffer))
(lambda ()
(dolist (fstruct fstruct-list)
(ignore fstruct) ; shut up the byte-compiler
(princ "</span>" buffer)))))
;; `inline-css' output support.
(defun htmlize-inline-css-body-tag (face-map)
(format "<body style=\"%s\">"
(mapconcat #'identity (htmlize-css-specs (gethash 'default face-map))
" ")))
(defun htmlize-inline-css-pre-tag (face-map)
(if htmlize-pre-style
(format "<pre style=\"%s\">"
(mapconcat #'identity (htmlize-css-specs (gethash 'default face-map))
" "))
(format "<pre>")))
(defun htmlize-inline-css-text-markup (fstruct-list buffer)
(let* ((merged (htmlize-merge-faces fstruct-list))
(style (htmlize-memoize
merged
(let ((specs (htmlize-css-specs merged)))
(and specs
(mapconcat #'identity (htmlize-css-specs merged) " "))))))
(when style
(princ "<span style=\"" buffer)
(princ style buffer)
(princ "\">" buffer))
(htmlize-lexlet ((style style) (buffer buffer))
(lambda ()
(when style
(princ "</span>" buffer))))))
;;; `font' tag based output support.
(defun htmlize-font-body-tag (face-map)
(let ((fstruct (gethash 'default face-map)))
(format "<body text=\"%s\" bgcolor=\"%s\">"
(htmlize-fstruct-foreground fstruct)
(htmlize-fstruct-background fstruct))))
(defun htmlize-font-pre-tag (face-map)
(if htmlize-pre-style
(let ((fstruct (gethash 'default face-map)))
(format "<pre text=\"%s\" bgcolor=\"%s\">"
(htmlize-fstruct-foreground fstruct)
(htmlize-fstruct-background fstruct)))
(format "<pre>")))
(defun htmlize-font-text-markup (fstruct-list buffer)
;; In `font' mode, we use the traditional HTML means of altering
;; presentation: <font> tag for colors, <b> for bold, <u> for
;; underline, and <strike> for strike-through.
(let* ((merged (htmlize-merge-faces fstruct-list))
(markup (htmlize-memoize
merged
(cons (concat
(and (htmlize-fstruct-foreground merged)
(format "<font color=\"%s\">" (htmlize-fstruct-foreground merged)))
(and (htmlize-fstruct-boldp merged) "<b>")
(and (htmlize-fstruct-italicp merged) "<i>")
(and (htmlize-fstruct-underlinep merged) "<u>")
(and (htmlize-fstruct-strikep merged) "<strike>"))
(concat
(and (htmlize-fstruct-strikep merged) "</strike>")
(and (htmlize-fstruct-underlinep merged) "</u>")
(and (htmlize-fstruct-italicp merged) "</i>")
(and (htmlize-fstruct-boldp merged) "</b>")
(and (htmlize-fstruct-foreground merged) "</font>"))))))
(princ (car markup) buffer)
(htmlize-lexlet ((markup markup) (buffer buffer))
(lambda ()
(princ (cdr markup) buffer)))))
(defun htmlize-buffer-1 ()
;; Internal function; don't call it from outside this file. Htmlize
;; current buffer, writing the resulting HTML to a new buffer, and
;; return it. Unlike htmlize-buffer, this doesn't change current
;; buffer or use switch-to-buffer.
(save-excursion
;; Protect against the hook changing the current buffer.
(save-excursion
(run-hooks 'htmlize-before-hook))
;; Convince font-lock support modes to fontify the entire buffer
;; in advance.
(htmlize-ensure-fontified)
(clrhash htmlize-extended-character-cache)
(clrhash htmlize-memoization-table)
;; It's important that the new buffer inherits default-directory
;; from the current buffer.
(let ((htmlbuf (generate-new-buffer (if (buffer-file-name)
(htmlize-make-file-name
(file-name-nondirectory
(buffer-file-name)))
"*html*")))
(completed nil))
(unwind-protect
(let* ((buffer-faces (htmlize-faces-in-buffer))
(face-map (htmlize-make-face-map (adjoin 'default buffer-faces)))
(places (gensym))
(title (if (buffer-file-name)
(file-name-nondirectory (buffer-file-name))
(buffer-name))))
(when htmlize-generate-hyperlinks
(htmlize-create-auto-links))
(when htmlize-replace-form-feeds
(htmlize-shadow-form-feeds))
;; Initialize HTMLBUF and insert the HTML prolog.
(with-current-buffer htmlbuf
(buffer-disable-undo)
(insert (htmlize-method doctype) ?\n
(format "<!-- Created by htmlize-%s in %s mode. -->\n"
htmlize-version htmlize-output-type)
"<html>\n ")
(put places 'head-start (point-marker))
(insert "<head>\n"
" <title>" (htmlize-protect-string title) "</title>\n"
(if htmlize-html-charset
(format (concat " <meta http-equiv=\"Content-Type\" "
"content=\"text/html; charset=%s\">\n")
htmlize-html-charset)
"")
htmlize-head-tags)
(htmlize-method insert-head buffer-faces face-map)
(insert " </head>")
(put places 'head-end (point-marker))
(insert "\n ")
(put places 'body-start (point-marker))
(insert (htmlize-method body-tag face-map)
"\n ")
(put places 'content-start (point-marker))
(insert (htmlize-method pre-tag face-map) "\n"))
(let ((text-markup
;; Get the inserter method, so we can funcall it inside
;; the loop. Not calling `htmlize-method' in the loop
;; body yields a measurable speed increase.
(htmlize-method-function 'text-markup))
;; Declare variables used in loop body outside the loop
;; because it's faster to establish `let' bindings only
;; once.
next-change text face-list trailing-ellipsis
fstruct-list last-fstruct-list
(close-markup (lambda ())))
;; This loop traverses and reads the source buffer, appending
;; the resulting HTML to HTMLBUF. This method is fast
;; because: 1) it doesn't require examining the text
;; properties char by char (htmlize-next-face-change is used
;; to move between runs with the same face), and 2) it doesn't
;; require frequent buffer switches, which are slow because
;; they rebind all buffer-local vars.
(goto-char (point-min))
(while (not (eobp))
(setq next-change (htmlize-next-face-change (point)))
;; Get faces in use between (point) and NEXT-CHANGE, and
;; convert them to fstructs.
(setq face-list (htmlize-faces-at-point)
fstruct-list (delq nil (mapcar (lambda (f)
(gethash f face-map))
face-list)))
(multiple-value-setq (text trailing-ellipsis)
(htmlize-extract-text (point) next-change trailing-ellipsis))
;; Don't bother writing anything if there's no text (this
;; happens in invisible regions).
(when (> (length text) 0)
;; Open the new markup if necessary and insert the text.
(when (not (equalp fstruct-list last-fstruct-list))
(funcall close-markup)
(setq last-fstruct-list fstruct-list
close-markup (funcall text-markup fstruct-list htmlbuf)))
(princ text htmlbuf))
(goto-char next-change))
;; We've gone through the buffer; close the markup from
;; the last run, if any.
(funcall close-markup))
;; Insert the epilog and post-process the buffer.
(with-current-buffer htmlbuf
(insert "</pre>")
(put places 'content-end (point-marker))
(insert "\n </body>")
(put places 'body-end (point-marker))
(insert "\n</html>\n")
(htmlize-defang-local-variables)
(goto-char (point-min))
(when htmlize-html-major-mode
;; What sucks about this is that the minor modes, most notably
;; font-lock-mode, won't be initialized. Oh well.
(funcall htmlize-html-major-mode))
(set (make-local-variable 'htmlize-buffer-places)
(symbol-plist places))
(run-hooks 'htmlize-after-hook)
(buffer-enable-undo))
(setq completed t)
htmlbuf)
(when (not completed)
(kill-buffer htmlbuf))
(htmlize-delete-tmp-overlays)))))
;; Utility functions.
(defmacro htmlize-with-fontify-message (&rest body)
;; When forcing fontification of large buffers in
;; htmlize-ensure-fontified, inform the user that he is waiting for
;; font-lock, not for htmlize to finish.
`(progn
(if (> (buffer-size) 65536)
(message "Forcing fontification of %s..."
(buffer-name (current-buffer))))
,@body
(if (> (buffer-size) 65536)
(message "Forcing fontification of %s...done"
(buffer-name (current-buffer))))))
(defun htmlize-ensure-fontified ()
;; If font-lock is being used, ensure that the "support" modes
;; actually fontify the buffer. If font-lock is not in use, we
;; don't care because, except in htmlize-file, we don't force
;; font-lock on the user.
(when font-lock-mode
;; In part taken from ps-print-ensure-fontified in GNU Emacs 21.
(when (and (boundp 'jit-lock-mode)
(symbol-value 'jit-lock-mode))
(htmlize-with-fontify-message
(jit-lock-fontify-now (point-min) (point-max))))
(if (fboundp 'font-lock-ensure)
(font-lock-ensure)
;; Emacs prior to 25.1
(with-no-warnings
(font-lock-mode 1)
(font-lock-fontify-buffer)))))
;;;###autoload
(defun htmlize-buffer (&optional buffer)
"Convert BUFFER to HTML, preserving colors and decorations.
The generated HTML is available in a new buffer, which is returned.
When invoked interactively, the new buffer is selected in the current
window. The title of the generated document will be set to the buffer's
file name or, if that's not available, to the buffer's name.
Note that htmlize doesn't fontify your buffers, it only uses the
decorations that are already present. If you don't set up font-lock or
something else to fontify your buffers, the resulting HTML will be
plain. Likewise, if you don't like the choice of colors, fix the mode
that created them, or simply alter the faces it uses."
(interactive)
(let ((htmlbuf (with-current-buffer (or buffer (current-buffer))
(htmlize-buffer-1))))
(when (interactive-p)
(switch-to-buffer htmlbuf))
htmlbuf))
;;;###autoload
(defun htmlize-region (beg end)
"Convert the region to HTML, preserving colors and decorations.
See `htmlize-buffer' for details."
(interactive "r")
;; Don't let zmacs region highlighting end up in HTML.
(when (fboundp 'zmacs-deactivate-region)
(zmacs-deactivate-region))
(let ((htmlbuf (save-restriction
(narrow-to-region beg end)
(htmlize-buffer-1))))
(when (interactive-p)
(switch-to-buffer htmlbuf))
htmlbuf))
(defun htmlize-region-for-paste (beg end)
"Htmlize the region and return just the HTML as a string.
This forces the `inline-css' style and only returns the HTML body,
but without the BODY tag. This should make it useful for inserting
the text to another HTML buffer."
(let* ((htmlize-output-type 'inline-css)
(htmlbuf (htmlize-region beg end)))
(unwind-protect
(with-current-buffer htmlbuf
(buffer-substring (plist-get htmlize-buffer-places 'content-start)
(plist-get htmlize-buffer-places 'content-end)))
(kill-buffer htmlbuf))))
(defun htmlize-region-save-screenshot (beg end)
"Save the htmlized (see `htmlize-region-for-paste') region in
the kill ring. Uses `inline-css', with style information in
`<pre>' tags, so that the rendering of the marked up text
approximates the buffer as closely as possible."
(interactive "r")
(let ((htmlize-pre-style t))
(kill-new (htmlize-region-for-paste beg end)))
(deactivate-mark))
(defun htmlize-make-file-name (file)
"Make an HTML file name from FILE.
In its default implementation, this simply appends `.html' to FILE.
This function is called by htmlize to create the buffer file name, and
by `htmlize-file' to create the target file name.
More elaborate transformations are conceivable, such as changing FILE's
extension to `.html' (\"file.c\" -> \"file.html\"). If you want them,
overload this function to do it and htmlize will comply."
(concat file ".html"))
;; Older implementation of htmlize-make-file-name that changes FILE's
;; extension to ".html".
;(defun htmlize-make-file-name (file)
; (let ((extension (file-name-extension file))
; (sans-extension (file-name-sans-extension file)))
; (if (or (equal extension "html")
; (equal extension "htm")
; (equal sans-extension ""))
; (concat file ".html")
; (concat sans-extension ".html"))))
;;;###autoload
(defun htmlize-file (file &optional target)
"Load FILE, fontify it, convert it to HTML, and save the result.
Contents of FILE are inserted into a temporary buffer, whose major mode
is set with `normal-mode' as appropriate for the file type. The buffer
is subsequently fontified with `font-lock' and converted to HTML. Note
that, unlike `htmlize-buffer', this function explicitly turns on
font-lock. If a form of highlighting other than font-lock is desired,
please use `htmlize-buffer' directly on buffers so highlighted.
Buffers currently visiting FILE are unaffected by this function. The
function does not change current buffer or move the point.
If TARGET is specified and names a directory, the resulting file will be
saved there instead of to FILE's directory. If TARGET is specified and
does not name a directory, it will be used as output file name."
(interactive (list (read-file-name
"HTML-ize file: "
nil nil nil (and (buffer-file-name)
(file-name-nondirectory
(buffer-file-name))))))
(let ((output-file (if (and target (not (file-directory-p target)))
target
(expand-file-name
(htmlize-make-file-name (file-name-nondirectory file))
(or target (file-name-directory file)))))
;; Try to prevent `find-file-noselect' from triggering
;; font-lock because we'll fontify explicitly below.
(font-lock-mode nil)
(font-lock-auto-fontify nil)
(global-font-lock-mode nil)
;; Ignore the size limit for the purposes of htmlization.
(font-lock-maximum-size nil))
(with-temp-buffer
;; Insert FILE into the temporary buffer.
(insert-file-contents file)
;; Set the file name so normal-mode and htmlize-buffer-1 pick it
;; up. Restore it afterwards so with-temp-buffer's kill-buffer
;; doesn't complain about killing a modified buffer.
(let ((buffer-file-name file))
;; Set the major mode for the sake of font-lock.
(normal-mode)
;; htmlize the buffer and save the HTML.
(with-current-buffer (htmlize-buffer-1)
(unwind-protect
(progn
(run-hooks 'htmlize-file-hook)
(write-region (point-min) (point-max) output-file))
(kill-buffer (current-buffer)))))))
;; I haven't decided on a useful return value yet, so just return
;; nil.
nil)
;;;###autoload
(defun htmlize-many-files (files &optional target-directory)
"Convert FILES to HTML and save the corresponding HTML versions.
FILES should be a list of file names to convert. This function calls
`htmlize-file' on each file; see that function for details. When
invoked interactively, you are prompted for a list of files to convert,
terminated with RET.
If TARGET-DIRECTORY is specified, the HTML files will be saved to that
directory. Normally, each HTML file is saved to the directory of the
corresponding source file."
(interactive
(list
(let (list file)
;; Use empty string as DEFAULT because setting DEFAULT to nil
;; defaults to the directory name, which is not what we want.
(while (not (equal (setq file (read-file-name
"HTML-ize file (RET to finish): "
(and list (file-name-directory
(car list)))
"" t))
""))
(push file list))
(nreverse list))))
;; Verify that TARGET-DIRECTORY is indeed a directory. If it's a
;; file, htmlize-file will use it as target, and that doesn't make
;; sense.
(and target-directory
(not (file-directory-p target-directory))
(error "target-directory must name a directory: %s" target-directory))
(dolist (file files)
(htmlize-file file target-directory)))
;;;###autoload
(defun htmlize-many-files-dired (arg &optional target-directory)
"HTMLize dired-marked files."
(interactive "P")
(htmlize-many-files (dired-get-marked-files nil arg) target-directory))
(provide 'htmlize)
;; Local Variables:
;; byte-compile-warnings: (not cl-functions unresolved obsolete)
;; End:
;;; htmlize.el ends here
;;; org-re-reveal.el --- Org export to reveal.js presentations -*- lexical-binding: t; -*-
;; SPDX-License-Identifier: GPL-3.0-or-later
;; Copyright (C) 2013-2018 Yujie Wen and contributors to org-reveal, see:
;; https://github.com/yjwen/org-reveal/commits/master
;; Copyright (C) 2017-2019 Jens Lechtenbörger
;; Copyright (C) 2019 Naoya Yamashita <conao3@gmail.com>
;; Copyright (C) 2019 Ayush Goyal <perfectayush@gmail.com>
;; URL: https://gitlab.com/oer/org-re-reveal
;; Version: 2.12.0
;; Package-Requires: ((emacs "24.4") (org "8.3") (htmlize "1.34"))
;; Keywords: tools, outlines, hypermedia, slideshow, presentation, OER
;; This file is not part of GNU Emacs.
;;; License:
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs; see the file COPYING.
;; If not, see http://www.gnu.org/licenses/ or write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.
;;; Commentary:
;; This package provides Org export functionality to generate HTML
;; presentations with the presentation framework reveal.js.
;;
;; Quickstart:
;; 0. Install reveal.js: https://revealjs.com/
;; 1. Activate org-re-reveal.
;; (a) Place this directory into your load path or install it from MELPA
;; (https://melpa.org/#/getting-started).
;; (b) Load package manually ("M-x load-library" followed by
;; "org-re-reveal") or place "(require 'org-re-reveal)" into your
;; ~/.emacs and restart or customize org-export-backends by adding
;; the symbol re-reveal.
;; 2. Load an Org file and export it to HTML.
;; (a) Make sure that reveal.js is available in your current directory
;; (e.g., as sub-directory or symbolic link).
;; (b) Load "Readme.org" (coming with org-re-reveal).
;; (c) Export to HTML: Press "C-c C-e v v" (write HTML file) or
;; "C-c C-e v b" (write HTML file and open in browser)
;; See "Readme.org" for introduction, details, and features added to
;; org-reveal.
;;
;; Note that emacs-reveal offers a project that embeds org-re-reveal,
;; reveal.js, and various reveal.js plugins:
;; https://gitlab.com/oer/emacs-reveal
;; Its howto, generated from Org source file in GitLab CI environment:
;; https://oer.gitlab.io/emacs-reveal-howto/howto.html
;;
;; The package org-re-reveal grew out of a forked version of org-reveal
;; when upstream development stopped:
;; https://github.com/yjwen/org-reveal/issues/349
;; https://github.com/yjwen/org-reveal/issues/342
;;; Code:
(require 'ox-html)
(require 'cl-lib) ; cl-mapcar and autoloads for:
; cl-loop, cl-letf, cl-assert, cl-case, cl-every,
; cl-delete-duplicates, cl-remove-if
(require 'subr-x) ; string-trim
(require 'url-parse)
(require 'url-util)
(defvar org-re-reveal-keys) ; Silence byte compiler
(defun org-re-reveal-define-backend ()
"Define the back-end for export as reveal.js presentation."
(org-export-define-derived-backend 're-reveal 'html
:menu-entry
`(,(nth 0 org-re-reveal-keys) "Export to reveal.js HTML Presentation"
((,(nth 1 org-re-reveal-keys)
"To file" org-re-reveal-export-to-html)
(,(nth 2 org-re-reveal-keys)
"To file and browse" org-re-reveal-export-to-html-and-browse)
(,(nth 3 org-re-reveal-keys)
"Current subtree to file" org-re-reveal-export-current-subtree)))
:options-alist ; See org-export-options-alist for meaning of parts.
'((:reveal-center nil "reveal_center" org-re-reveal-center t)
(:reveal-control nil "reveal_control" org-re-reveal-control t)
(:reveal-defaulttiming nil "reveal_defaulttiming" org-re-reveal-defaulttiming t)
(:reveal-fragmentinurl nil "reveal_fragmentinurl" org-re-reveal-fragmentinurl t)
(:reveal-generate-ids nil "reveal_generate_ids" org-re-reveal-generate-custom-ids t)
(:reveal-hashonebasedindex nil "reveal_hashonebasedindex" org-re-reveal-hashonebasedindex t)
(:reveal-height nil "reveal_height" org-re-reveal-height t)
(:reveal-history nil "reveal_history" org-re-reveal-history t)
(:reveal-inter-presentation-links nil "reveal_inter_presentation_links" org-re-reveal-inter-presentation-links t)
(:reveal-keyboard nil "reveal_keyboard" org-re-reveal-keyboard t)
(:reveal-klipsify-src nil "reveal_klipsify_src" org-re-reveal-klipsify-src t)
(:reveal-mousewheel nil "reveal_mousewheel" org-re-reveal-mousewheel t)
(:reveal-overview nil "reveal_overview" org-re-reveal-overview t)
(:reveal-pdfseparatefragments nil "reveal_pdfseparatefragments" org-re-reveal-pdfseparatefragments t)
(:reveal-progress nil "reveal_progress" org-re-reveal-progress t)
(:reveal-rolling-links nil "reveal_rolling_links" org-re-reveal-rolling-links t)
(:reveal-single-file nil "reveal_single_file" org-re-reveal-single-file t)
(:reveal-slide-global-footer nil "reveal_global_footer" org-re-reveal-global-footer t)
(:reveal-slide-global-header nil "reveal_global_header" org-re-reveal-global-header t)
(:reveal-slide-number nil "reveal_slide_number" org-re-reveal-slide-number t)
(:reveal-slide-toc-footer nil "reveal_toc_footer" org-re-reveal-toc-footer t)
(:reveal-subtree-with-title-slide nil "reveal_subtree_with_title_slide" org-re-reveal-subtree-with-title-slide t)
(:reveal-width nil "reveal_width" org-re-reveal-width t)
(:reveal-academic-title "REVEAL_ACADEMIC_TITLE" nil nil t)
(:reveal-codemirror-config "REVEAL_CODEMIRROR_CONFIG" nil org-re-reveal-klipse-codemirror newline)
(:reveal-default-frag-style "REVEAL_DEFAULT_FRAG_STYLE" nil org-re-reveal-default-frag-style t)
(:reveal-default-slide-background "REVEAL_DEFAULT_SLIDE_BACKGROUND" nil nil t)
(:reveal-default-slide-background-position "REVEAL_DEFAULT_SLIDE_BACKGROUND_POSITION" nil nil t)
(:reveal-default-slide-background-repeat "REVEAL_DEFAULT_SLIDE_BACKGROUND_REPEAT" nil nil t)
(:reveal-default-slide-background-size "REVEAL_DEFAULT_SLIDE_BACKGROUND_SIZE" nil nil t)
(:reveal-default-slide-background-transition "REVEAL_DEFAULT_SLIDE_BACKGROUND_TRANSITION" nil nil t)
(:reveal-external-plugins "REVEAL_EXTERNAL_PLUGINS" nil org-re-reveal-external-plugins t)
(:reveal-extra-attr "REVEAL_EXTRA_ATTR" nil org-re-reveal-extra-attr nil)
(:reveal-extra-css "REVEAL_EXTRA_CSS" nil org-re-reveal-extra-css newline)
(:reveal-extra-js "REVEAL_EXTRA_JS" nil org-re-reveal-extra-js nil)
(:reveal-extra-options "REVEAL_EXTRA_OPTIONS" nil org-re-reveal-extra-options t)
(:reveal-extra-scripts "REVEAL_EXTRA_SCRIPTS" nil org-re-reveal-extra-scripts nil)
(:reveal-head-preamble "REVEAL_HEAD_PREAMBLE" nil org-re-reveal-head-preamble newline)
(:reveal-highlight-css "REVEAL_HIGHLIGHT_CSS" nil org-re-reveal-highlight-css nil)
(:reveal-highlight-url "REVEAL_HIGHLIGHT_URL" nil org-re-reveal-highlight-url nil)
(:reveal-hlevel "REVEAL_HLEVEL" nil nil t)
(:reveal-init-script "REVEAL_INIT_SCRIPT" nil org-re-reveal-init-script space)
(:reveal-klipse-css-url "REVEAL_KLIPSE_CSS_URL" nil org-re-reveal-klipse-css t)
(:reveal-klipse-extra-config "REVEAL_KLIPSE_EXTRA_CONFIG" nil org-re-reveal-klipse-extra-config newline)
(:reveal-klipse-js-url "REVEAL_KLIPSE_JS_URL" nil org-re-reveal-klipse-js t)
(:reveal-klipse-setup "REVEAL_KLIPSE_SETUP" nil org-re-reveal-klipse-setup t)
(:reveal-margin "REVEAL_MARGIN" nil org-re-reveal-margin t)
(:reveal-mathjax-url "REVEAL_MATHJAX_URL" nil org-re-reveal-mathjax-url t)
(:reveal-max-scale "REVEAL_MAX_SCALE" nil org-re-reveal-max-scale t)
(:reveal-min-scale "REVEAL_MIN_SCALE" nil org-re-reveal-min-scale t)
(:reveal-miscinfo "REVEAL_MISCINFO" nil nil t)
(:reveal-multiplex-id "REVEAL_MULTIPLEX_ID" nil org-re-reveal-multiplex-id nil)
(:reveal-multiplex-secret "REVEAL_MULTIPLEX_SECRET" nil org-re-reveal-multiplex-secret nil)
(:reveal-multiplex-socketio-url "REVEAL_MULTIPLEX_SOCKETIO_URL" nil org-re-reveal-multiplex-socketio-url nil)
(:reveal-multiplex-url "REVEAL_MULTIPLEX_URL" nil org-re-reveal-multiplex-url nil)
(:reveal-plugins "REVEAL_PLUGINS" nil org-re-reveal-plugins t)
(:reveal-postamble "REVEAL_POSTAMBLE" nil org-re-reveal-postamble t)
(:reveal-preamble "REVEAL_PREAMBLE" nil org-re-reveal-preamble t)
(:reveal-root "REVEAL_ROOT" nil org-re-reveal-root t)
(:reveal-script-files "REVEAL_SCRIPT_FILES" nil org-re-reveal-script-files t)
(:reveal-slide-footer "REVEAL_SLIDE_FOOTER" nil org-re-reveal-slide-footer t)
(:reveal-slide-header "REVEAL_SLIDE_HEADER" nil org-re-reveal-slide-header t)
(:reveal-speed "REVEAL_SPEED" nil org-re-reveal-transition-speed t)
(:reveal-talk-qr-code "REVEAL_TALK_QR_CODE" nil nil t)
(:reveal-talk-url "REVEAL_TALK_URL" nil nil t)
(:reveal-theme "REVEAL_THEME" nil org-re-reveal-theme t)
(:reveal-title-slide "REVEAL_TITLE_SLIDE" nil org-re-reveal-title-slide newline)
(:reveal-title-slide-background "REVEAL_TITLE_SLIDE_BACKGROUND" nil nil t)
(:reveal-title-slide-background-position "REVEAL_TITLE_SLIDE_BACKGROUND_POSITION" nil nil t)
(:reveal-title-slide-background-repeat "REVEAL_TITLE_SLIDE_BACKGROUND_REPEAT" nil nil t)
(:reveal-title-slide-background-size "REVEAL_TITLE_SLIDE_BACKGROUND_SIZE" nil nil t)
(:reveal-title-slide-background-transition "REVEAL_TITLE_SLIDE_BACKGROUND_TRANSITION" nil nil t)
(:reveal-title-slide-state "REVEAL_TITLE_SLIDE_STATE" nil nil t)
(:reveal-title-slide-timing "REVEAL_TITLE_SLIDE_TIMING" nil nil t)
(:reveal-toc-slide-class "REVEAL_TOC_SLIDE_CLASS" nil nil t)
(:reveal-toc-slide-state "REVEAL_TOC_SLIDE_STATE" nil nil t)
(:reveal-toc-slide-title "REVEAL_TOC_SLIDE_TITLE" nil org-re-reveal-toc-slide-title t)
(:reveal-trans "REVEAL_TRANS" nil org-re-reveal-transition t))
:translate-alist
'((headline . org-re-reveal-headline)
(inner-template . org-re-reveal-inner-template)
(item . org-re-reveal-item)
(keyword . org-re-reveal-keyword)
(link . org-re-reveal-link)
(latex-environment . org-re-reveal-latex-environment)
(latex-fragment . (lambda (frag contents info)
(setq info (plist-put info :reveal-mathjax t))
(org-html-latex-fragment frag contents info)))
(plain-list . org-re-reveal-plain-list)
(quote-block . org-re-reveal-quote-block)
(section . org-re-reveal-section)
(src-block . org-re-reveal-src-block)
(special-block . org-re-reveal-special-block)
(template . org-re-reveal-template))
:filters-alist '((:filter-parse-tree . org-re-reveal-filter-parse-tree))))
(defun org-re-reveal-define-menu (symbol value)
"Define back-end with (new) key bindings.
SYMBOL must be `org-re-reveal-keys' and VALUE its new value."
(let ((standard (eval (car (get symbol 'standard-value)))))
(cl-assert
(eq symbol 'org-re-reveal-keys) nil
(format "Symbol in org-re-reveal-define-menu unexpected: %s" symbol))
(cl-assert
(= (length standard) (length value))
(format "Value for org-re-reveal-keys must have length %s (same as standard), not %s"
(length standard) (length value)))
(set-default symbol value)
(org-re-reveal-define-backend)))
(defgroup org-export-re-reveal nil
"Options for exporting Org files to reveal.js HTML pressentations."
:tag "Org Export Reveal"
:group 'org-export)
(defcustom org-re-reveal-keys '(?v ?v ?b ?s)
"Define keys for export with org-re-reveal.
This list must contain four characters: The first one triggers export
with org-re-reveal (after \\<org-mode-map> \\[org-export-dispatch]).
The remaining three charaters each invoke a different export variant.
One of those characters must be typed after the first one; the
variants are, in sequence: Export to file, export to file followed by
browsing that file, subtree export to file."
:group 'org-export-re-reveal
:type '(list (character :tag "Key to trigger export with org-re-reveal")
(character :tag "Key for export to file")
(character :tag "Key to browse file after export")
(character :tag "Key for subtree export to file"))
:set #'org-re-reveal-define-menu)
(defcustom org-re-reveal-root "./reveal.js"
"Specify root directory of reveal.js containing js/reveal.js."
:group 'org-export-re-reveal
:type '(radio (const :tag "Online at https://revealjs.com" "https://revealjs.com")
(string :tag "Other directory path")))
(defcustom org-re-reveal-script-files '("lib/js/head.min.js" "js/reveal.js")
"Specify files to initialize reveal.js.
Note that file names here are relative under `org-re-reveal-root'.
On 2018-10-04, head.min.js was removed on the dev branch of reveal.js.
If you are using a version including that removal, customize this variable
to remove the first file name."
:group 'org-export-re-reveal
:type '(repeat string))
(defcustom org-re-reveal-hlevel 1
"Specify minimum level of headings for grouping into vertical slides."
:group 'org-export-re-reveal
:type 'integer)
(defun org-re-reveal--get-hlevel (info)
"Get HLevel value safely for INFO.
If option \"REVEAL_HLEVEL\" is set, retrieve integer value from it,
else get value from custom variable `org-re-reveal-hlevel'."
(let ((hlevel-str (plist-get info :reveal-hlevel)))
(if hlevel-str (string-to-number hlevel-str)
org-re-reveal-hlevel)))
(defcustom org-re-reveal-title-slide 'auto
"If nil or empty string, do not insert a title slide.
Otherwise (`auto' or non-empty string), insert title slide.
When `auto', generate automatic title slide.
When set to a string, use this string as format string for the title
slide, where the following escaping elements are allowed:
%t for the title.
%s for the subtitle.
%a for the author's name.
%e for the author's email.
%d for the date.
%A for the author's academic title (set with #+REVEAL_ACADEMIC_TITLE).
%q for the name of a file to a QR code (set with #+REVEAL_TALK_QR_CODE).
%u for the URL of the presentation (set with #+REVEAL_TALK_URL).
%m for misc information (set with #+REVEAL_MISCINFO).
%% for a literal %.
Alternatively, the string can also be the name of a file with the title
slide's HTML code (containing the above escape sequences)."
:group 'org-export-re-reveal
:type '(choice (const :tag "No title slide" nil)
(const :tag "Auto title slide" 'auto)
(string :tag "Custom title slide")))
(defcustom org-re-reveal-transition "convex"
"Reveal transistion style."
:group 'org-export-re-reveal
:type '(radio (const "none")
(const "fade")
(const "slide")
(const "convex")
(const "concave")
(const "zoom")
(string :tag "Other transition")))
(defcustom org-re-reveal-transition-speed "default"
"Reveal transistion speed."
:group 'org-export-re-reveal
:type '(radio (const "default")
(const "fast")
(const "slow")
(string :tag "Other transition speed")))
(defcustom org-re-reveal-theme "black"
"Reveal theme.
Note that most themes load fonts from remote servers, which (a)
obviously limits offline use of presentations and (b) is not privacy
friendly. See URL `https://github.com/hakimel/reveal.js/issues/2491'
and URL `https://github.com/google/fonts/issues/1495'."
:group 'org-export-re-reveal
:type '(radio (const "beige")
(const "black")
(const "blood")
(const "league")
(const "moon")
(const "night")
(const "serif")
(const "simple")
(const "sky")
(const "solarized")
(const "white")
(string :tag "Other theme"))
:package-version '(org-re-reveal . "2.8.1"))
(defcustom org-re-reveal-extra-js ""
"URL to extra JS file.
If you use this variable, please take the time to report your current
usage at URL `https://gitlab.com/oer/org-re-reveal/issues/31'."
:group 'org-export-re-reveal
:type 'string)
(defcustom org-re-reveal-extra-scripts nil
"List of extra scripts.
Each list element can be the filename or URL of a JavaScript file or an
entire HTML script element.
If relative filenames are used, they must be relative to the presentation's
HTML file."
:group 'org-export-re-reveal
:type '(repeat string)
:package-version '(org-re-reveal . "2.10.0"))
(make-obsolete-variable 'org-re-reveal-extra-js
'org-re-reveal-extra-scripts "org-re-reveal 2.9.0")
(defcustom org-re-reveal-extra-attr nil
"Global Reveal Extra Attrs for all slides."
:group 'org-export-re-reveal
:type '(choice
string
(const nil)))
(defcustom org-re-reveal-extra-css ""
"URL to extra css file."
:group 'org-export-re-reveal
:type 'string)
(defcustom org-re-reveal-multiplex-id ""
"The ID to use for multiplexing.
E.g., per README of reveal.js 3.8.0, generate id and secrete by visiting
URL `https://reveal-js-multiplex-ccjbegmaii.now.sh/'.
To enable multiplex, see `org-re-reveal-plugins'."
:group 'org-export-re-reveal
:type 'string)
(defcustom org-re-reveal-multiplex-secret ""
"The secret to use for the master presentation.
E.g., per README of reveal.js 3.8.0, generate id and secrete by visiting
URL `https://reveal-js-multiplex-ccjbegmaii.now.sh/'.
To enable multiplex, see `org-re-reveal-plugins'."
:group 'org-export-re-reveal
:type 'string)
(defcustom org-re-reveal-multiplex-url
"https://reveal-js-multiplex-ccjbegmaii.now.sh"
"The url of the socketio server.
To enable multiplex, see `org-re-reveal-plugins'."
:group 'org-export-re-reveal
:type 'string
:package-version '(org-re-reveal . "2.1.0"))
(defcustom org-re-reveal-multiplex-socketio-url
"https://cdn.socket.io/socket.io-1.3.5.js"
"The url of the socketio.js library.
To enable multiplex, see `org-re-reveal-plugins'."
:group 'org-export-re-reveal
:type 'string
:package-version '(org-re-reveal . "2.1.0"))
(defcustom org-re-reveal-client-multiplex-filter nil
"If non-nil, a regular expression to filter multiplex client publication.
When using `org-re-reveal-publish-to-reveal-client', by default all Org
files are also published as multiplex client files (which roughly
doubles the amount of time necessary for publication). If you have got
a mix of Org files that use multiplexing and that do not, set to this
variable to a regular expression matching files for which a multiplex
client file should be generated."
:group 'org-export-re-reveal
:type '(choice
regexp
(const nil))
:package-version '(org-re-reveal . "2.12.0"))
(defcustom org-re-reveal-control t
"Reveal control applet."
:group 'org-export-re-reveal
:type 'boolean)
(defcustom org-re-reveal-progress t
"Reveal progress applet."
:group 'org-export-re-reveal
:type 'boolean)
(defcustom org-re-reveal-history nil
"Reveal history applet."
:group 'org-export-re-reveal
:type 'boolean)
(defcustom org-re-reveal-center t
"Reveal center applet."
:group 'org-export-re-reveal
:type 'boolean)
(defcustom org-re-reveal-rolling-links nil
"Reveal use rolling links."
:group 'org-export-re-reveal
:type 'boolean)
(defcustom org-re-reveal-slide-number "c"
"Reveal showing slide numbers."
:group 'org-export-re-reveal
:type '(radio (const :tag "horizontal . vertical slide number" "h.v")
(const :tag "horizontal / vertical slide number" "h/v")
(const :tag "flattened slide number" "c")
(const :tag "flattened slide number / total slides" "c/t")
(string :tag "Other slide number format")))
(defcustom org-re-reveal-keyboard t
"Reveal use keyboard navigation."
:group 'org-export-re-reveal
:type 'boolean)
(defcustom org-re-reveal-mousewheel nil
"Reveal use mousewheel navigation."
:group 'org-export-re-reveal
:type 'boolean)
(defcustom org-re-reveal-fragmentinurl nil
"Reveal use fragmentInURL setting."
:group 'org-export-re-reveal
:type 'boolean)
(defcustom org-re-reveal-hashonebasedindex nil
"Reveal use hashOneBasedIndex setting."
:group 'org-export-re-reveal
:type 'boolean)
(defcustom org-re-reveal-pdfseparatefragments t
"Reveal disable pdfSeparateFragments setting."
:group 'org-export-re-reveal
:type 'boolean)
(defcustom org-re-reveal-defaulttiming nil
"If non-nil, use defaultTiming for speaker notes view.
For indivual timing of specific slides, use \"REVEAL_TITLE_SLIDE_TIMING\" for
the title slide, and assign data-timing attributes to other headlines/slides."
:group 'org-export-re-reveal
:type '(choice integer (const nil)))
(defcustom org-re-reveal-overview t
"Reveal show overview."
:group 'org-export-re-reveal
:type 'boolean)
(defcustom org-re-reveal-subtree-with-title-slide nil
"If t, export title slide also for subtree exports."
:group 'org-export-re-reveal
:type 'boolean)
(defcustom org-re-reveal-width nil
"Slide width as positive integer (pixels) or string (percentage) or nil."
:group 'org-export-re-reveal
:type '(choice integer string (const nil))
:package-version '(org-re-reveal . "1.1.4"))
(defcustom org-re-reveal-height nil
"Slide height as positive integer (pixels) or string (percentage) or nil."
:group 'org-export-re-reveal
:type '(choice integer string (const nil))
:package-version '(org-re-reveal . "1.1.4"))
(defcustom org-re-reveal-margin "-1"
"Slide margin (in a string)."
:group 'org-export-re-reveal
:type 'string)
(defcustom org-re-reveal-min-scale "-1"
"Minimum bound for scaling slide (in a string)."
:group 'org-export-re-reveal
:type 'string)
(defcustom org-re-reveal-max-scale "-1"
"Maximum bound for scaling slide (in a string)."
:group 'org-export-re-reveal
:type 'string)
(defcustom org-re-reveal-extra-options nil
"Extra options to be passed to Reveal.initialize().
Useful to specify options without keyword in org-re-reveal, e.g.,
\"controlsTutorial: false, controlsLayout: 'edges'\".
Individual options are separated by comma.
For the current list of reveal.js options, see URL
`https://github.com/hakimel/reveal.js/#configuration.'"
:group 'org-export-re-reveal
:type '(choice string (const nil)))
(defcustom org-re-reveal-mathjax-url
"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_HTMLorMML"
"Default MathJax URL."
:group 'org-export-re-reveal
:type 'string)
(defcustom org-re-reveal-preamble nil
"Preamble contents."
:group 'org-export-re-reveal
:type '(choice (const nil) string))
(defcustom org-re-reveal-head-preamble nil
"Preamble contents for head part."
:group 'org-export-re-reveal
:type '(choice (const nil) string))
(defcustom org-re-reveal-postamble nil
"Postamble contents."
:group 'org-export-re-reveal
:type '(choice (const nil) string))
(defcustom org-re-reveal-body-attrs nil
"Attribute string to assign to body element.
By default, no attributes are assigned."
:group 'org-export-re-reveal
:type '(choice (const nil) string))
(defcustom org-re-reveal-slide-header nil
"HTML content used as Reveal.js slide header."
:group 'org-export-re-reveal
:type '(choice (const nil) string))
(defcustom org-re-reveal-slide-header-html "<div class=\"slide-header\">%s</div>\n"
"HTML format string to construct slide footer."
:group 'org-export-re-reveal
:type 'string)
(defcustom org-re-reveal-global-header nil
"If non nil, display slide header also on title and toc slide.
Header is defined by `org-re-reveal-slide-header'."
:group 'org-export-re-reveal
:type 'boolean)
(defcustom org-re-reveal-global-footer nil
"If non nil, display slide footer also on title and toc slide.
Footer is defined by `org-re-reveal-slide-footer'."
:group 'org-export-re-reveal
:type 'boolean)
(defcustom org-re-reveal-toc-footer nil
"If non nil, display slide footer also on toc slide.
Footer is defined by `org-re-reveal-slide-footer'."
:group 'org-export-re-reveal
:type 'boolean)
(defcustom org-re-reveal-slide-footer nil
"Specify HTML content used as Reveal.js slide footer."
:group 'org-export-re-reveal
:type '(choice (const nil) string))
(defcustom org-re-reveal-slide-footer-html "<div class=\"slide-footer\">%s</div>\n"
"HTML format string to construct slide footer.
Must constain exactly one %-sequence \"%s\"."
:group 'org-export-re-reveal
:type 'string)
(defcustom org-re-reveal-toc-slide-title "Table of Contents"
"String to display as title of toc slide."
:group 'org-export-re-reveal
:type 'string)
(defcustom org-re-reveal-default-frag-style nil
"Default fragment style."
:group 'org-export-re-reveal
:type '(choice (const nil) string))
(defcustom org-re-reveal-plugins
'(markdown notes search zoom)
"Default builtin plugins.
By default, variables related to multiplex are hidden.
Include 'multiplex in this variable to enable it.
This variable, like any other variable, can be overridden
in the org buffer comments as follows:
#+REVEAL_PLUGINS: (markdown zoom notes multiplex)"
:group 'org-export-re-reveal
:type '(set
(const markdown)
(const highlight)
(const zoom)
(const notes)
(const search)
(const multiplex)
(const :tag "classList (absent from modern reveal.js)" classList)
(const :tag "remotes (absent from modern reveal.js)" remotes))
:package-version '(org-re-reveal . "1.1.11"))
(defcustom org-re-reveal-external-plugins nil
"Additional third-party plugins to load with reveal.js.
This is either an alist or a filename.
In case of an alist, each entry should contain a name and an expression
of the following form:
\"{src: '%srelative/path/from/reveal/root', async:true/false,
condition: jscallbackfunction(){}}\"
In case of a file, its lines must be expressions of the above form.
Note that some plugins have dependencies such as jquery; these must be
included here as well, BEFORE the plugins that depend on them."
:group 'org-export-re-reveal
:type '(choice alist file))
(defcustom org-re-reveal-single-file nil
"Export presentation into one single HTML file.
That file embeds JS scripts and pictures."
:group 'org-export-re-reveal
:type 'boolean)
(defcustom org-re-reveal-inter-presentation-links nil
"If non nil, try to convert links between presentations."
:group 'org-export-re-reveal
:type 'boolean)
(defcustom org-re-reveal-init-script nil
"Custom script to be passed to Reveal.initialize."
:group 'org-export-re-reveal
:type '(choice (const nil) string))
(defcustom org-re-reveal-highlight-css "%r/lib/css/zenburn.css"
"Hightlight.js CSS file."
:group 'org-export-re-reveal
:type 'string)
(defcustom org-re-reveal-highlight-url nil
"Location of Hightlight.js.
If nil (default), the local plugin file is used."
:group 'org-export-re-reveal
:type '(choice (const nil) string))
(defcustom org-re-reveal-note-key-char "n"
"If not nil, register key for Org structure completion for speaker notes.
When `<' followed by the key character are
typed and then the completion key is pressed, which is usually
`TAB', \"#+BEGIN_NOTES\" and \"#+END_NOTES\" is inserted (maybe in
lower-case). See \"Readme.org\" how to make this work with Org version
9.2 or later.
The default value is \"n\". Set the variable to nil to disable
registering the completion."
:group 'org-export-re-reveal
:type '(choice (const nil) string))
(defcustom org-re-reveal-no-htmlize-src nil
"For syntax highlighting with org-re-reveal, three options exist:
1. Use reveal.js plugin highlight for syntax highlighting with highlight.js.
This applies to all source code blocks.
2. If plugin highlight is not enabled, by default the library htmlize
is used.
3. Do not use highlight and customize this variable to t.
This disables syntax highlighting but you can activate htmlize for
individual source code blocks with attributes:
\"#+ATTR_REVEAL: :htmlize t\""
:group 'org-export-re-reveal
:type 'boolean)
(defcustom org-re-reveal-klipsify-src nil
"Set to non-nil to enable live code execution with klipse.
See test-cases/test-klipsify.org in the source repository for examples.
To export a source code block without klipse, use the following:
\"#+ATTR_REVEAL: :no-klipsify t\""
:group 'org-export-re-reveal
:type 'boolean)
(defcustom org-re-reveal-klipse-css "https://storage.googleapis.com/app.klipse.tech/css/codemirror.css"
"Location of the codemirror css file for use with klipse."
:group 'org-export-re-reveal
:type 'string)
(defcustom org-re-reveal-klipse-extra-css "<style>
/* Position computations of klipse get confused by reveal.js's scaling.
Hence, scaling should be disabled with this code. Fix height of code area
with scrollbar (use overflow instead of overflow-y to restore CodeMirror
setting afterwards): */
.reveal section pre { max-height: 70vh; height: auto; overflow: auto; }
/* Reset some reveal.js and oer-reveal settings: */
.reveal section pre .CodeMirror pre { font-size: 2em; box-shadow: none; width: auto; padding: 0.4em; display: block; overflow: visible; }
/* Enlarge cursor: */
.CodeMirror-cursor { border-left: 3px solid black; }
</style>\n"
"CSS string to ensure compatibility between klipse and reveal.js."
:group 'org-export-re-reveal
:type 'string
:package-version '(org-re-reveal . "2.0.1"))
(defcustom org-re-reveal-klipse-codemirror nil
"If not nil, a string to pass as CodeMirror options to \"klipse_setting\"."
:group 'org-export-re-reveal
:type '(choice (const nil) string))
(defcustom org-re-reveal-klipse-js
"https://storage.googleapis.com/app.klipse.tech/plugin/js/klipse_plugin.js"
"Location of the klipse js source code.
The minified version may not work, see URL
`https://github.com/viebel/klipse/issues/334'."
:group 'org-export-re-reveal
:type 'string
:package-version '(org-re-reveal . "1.1.11"))
(defcustom org-re-reveal-klipse-setup
'(("clojure" "selector" "language-klipse")
("html" "selector_eval_html" "language-klipse-html")
("javascript" "selector_eval_js" "language-klipse-javascript")
("js" "selector_eval_js" "language-klipse-js")
("php" "selector_eval_php" "language-klipse-php")
("python" "selector_eval_python_client" "language-klipse-python")
("ruby" "selector_eval_ruby" "language-klipse-ruby")
("scheme" "selector_eval_scheme" "language-klipse-scheme")
("sql" "selector_sql" "language-klipse-sql"))
"Supported klipse languages with selectors.
This is a list of triples (language selectorname selectorvalue).
Each language needs to be the language of an Org source block.
For existing names of klipse selectors, see URL
`https://github.com/viebel/klipse/blob/master/README.md#page-level-configuration'.
If additional languages work for you, maybe you could report that in issue #23
at URL `https://gitlab.com/oer/org-re-reveal/issues/23'?"
:group 'org-export-re-reveal
:type '(repeat
(list
(string :tag "Language")
(string :tag "Selector name")
(string :tag "CSS class")))
:package-version '(org-re-reveal . "1.1.11"))
(defvar org-re-reveal-klipse-languages
(mapcar #'car org-re-reveal-klipse-setup)
"List of languages supported by org-re-reveal.")
(defcustom org-re-reveal-klipse-extra-config nil
"If not nil, extra JavaScript string to execute for klipse initialization.
E.g., window.klipse_settings.editor_type = \"html\"; for SQL."
:group 'org-export-re-reveal
:type '(choice (const nil) string))
(defcustom org-re-reveal-generate-custom-ids t
"If t, generate CUSTOM_IDs for headings that don't have one.
Set to nil to revert to old behavior, where HTML section elements have
content hashes as \"id\" attributes, which change when slide contents
change. With the default of t, generate CUSTOM_ID for headlines
missing such a property, by using the value of the headline's number.
This results in more stable URLs when working on presentations and
reloading slides. You may want to set \"#+OPTIONS: reveal_history:t\"
to see the section identifiers as URL fragments in the address bar,
and you should not disable section numbering (for unnumbered
headlines, hash IDs are used unless a CUSTOM_ID is present).
For CSS code to hide section numbers if necessary, see
URL `https://github.com/yjwen/org-reveal/pull/284'."
:group 'org-export-re-reveal
:type 'boolean
:package-version '(org-re-reveal . "1.1.3"))
(defvar org-re-reveal--slide-id-prefix "slide-"
"Prefix to use in ID attributes of slide elements.")
(defvar org-re-reveal--href-fragment-prefix
(concat "/" org-re-reveal--slide-id-prefix)
"Prefix to use when linking to specific slides.
The default uses a slash between hash sign and slide ID,
which leads to broken links that are not understood outside reveal.js.
See there: https://github.com/hakimel/reveal.js/issues/2276")
(defun org-re-reveal--if-format (fmt val)
"Apply `format' to FMT and VAL if VAL is a non-empty string.
Otherwise, return empty string."
(if (and (stringp val) (> (length val) 0)) (format fmt val) ""))
(defun org-re-reveal--frag-style (frag info)
"Return fragment string according to FRAG and the default fragment style.
FRAG is the fragment style set on element, INFO is a plist
holding contextual information."
(cond
((string= frag t)
(let ((default-frag-style (plist-get info :reveal-default-frag-style)))
(if default-frag-style (format "fragment %s" default-frag-style)
"fragment")))
(t (format "fragment %s" frag))))
(defun org-re-reveal--frag-class (frag info)
"Return proper HTML string description of fragment style.
FRAG is the fragment style set on element, INFO is a plist
holding contextual information."
(and frag
(format " class=\"%s\"" (org-re-reveal--frag-style frag info))))
(defun org-re-reveal--frag-index (index)
"Return attribute string for fragment INDEX if set."
(and index
(format " data-fragment-index=\"%s\"" index)))
(defun org-re-reveal-special-block (special-block contents info)
"Transcode a SPECIAL-BLOCK element from Org to Reveal.
CONTENTS holds the contents of the block. INFO is a plist
holding contextual information.
If the block type is 'NOTES' (case-insensitive), transcode the block
into a Reveal.js slide note. Otherwise, export the block as by the HTML
exporter."
(let ((block-type (org-element-property :type special-block)))
(if (string= (downcase block-type) "notes")
(format "<aside class=\"notes\">\n%s\n</aside>\n" contents)
(org-html-special-block special-block contents info))))
(defun org-re-reveal--add-class (elem value)
"Add VALUE as \"class\" attribute in HTML header element ELEM.
Do nothing if \"class\" attribute is already present."
(let ((match (string-match "\\`<h[1-9][^>]+>" elem)))
(unless match (error "Element no headline: %s" elem))
(let ((tag (match-string 0 elem)))
(if (string-match "class" tag)
elem
(replace-regexp-in-string "\\`\\(<h[1-9][^>]+\\)>"
(format "\\1 class=\"%s\">" value)
elem)))))
(defun org-re-reveal--fix-html-headline (headline contents info)
"Convert HEADLINE with CONTENTS and INFO to HTML.
Call `org-html-headline' to generate initial HTML, remove surrounding
\"div\" tags, and add class attribute to h-element if
\":HTML_HEADLINE_CLASS\" property is present.
Adding a class attribute in ox-reveal.el is a hack which is only
necessary until that functionality has arrived in ox-html.el:
https://lists.gnu.org/archive/html/emacs-orgmode/2018-12/msg00016.html
As that patch has been accepted, the property is called
\":HTML_HEADLINE_CLASS\". Otherwise, \":REVEAL_HEADLINE_CLASS\" would
have been appropriate..."
(let* ((class (org-element-property :HTML_HEADLINE_CLASS headline))
(html (org-html-headline headline contents info))
(nodiv
(if (string-prefix-p "<div" html)
;; Remove the first <div> and the last </div> tags from html
(concat "<"
(mapconcat 'identity
(butlast (cdr (split-string html "<" t)))
"<"))
;; Return the HTML content unchanged
html)))
(if class
(org-re-reveal--add-class nodiv class)
nodiv)))
(defun org-re-reveal--section-attrs (headline info)
"Compute attributes for section element of HEADLINE with INFO.
Return empty string or one starting with a space character."
(let* ((default-slide-background (plist-get info :reveal-default-slide-background))
(default-slide-background-size (plist-get info :reveal-default-slide-background-size))
(default-slide-background-position (plist-get info :reveal-default-slide-background-position))
(default-slide-background-repeat (plist-get info :reveal-default-slide-background-repeat))
(default-slide-background-transition (plist-get info :reveal-default-slide-background-transition))
(attrs (org-html--make-attribute-string
`(:data-transition ,(org-element-property :REVEAL_DATA_TRANSITION headline)
:data-state ,(org-element-property :REVEAL_DATA_STATE headline)
:data-background ,(or (org-element-property :REVEAL_BACKGROUND headline)
default-slide-background)
:data-background-size ,(or (org-element-property :REVEAL_BACKGROUND_SIZE headline)
default-slide-background-size)
:data-background-position ,(or (org-element-property :REVEAL_BACKGROUND_POSITION headline)
default-slide-background-position)
:data-background-repeat ,(or (org-element-property :REVEAL_BACKGROUND_REPEAT headline)
default-slide-background-repeat)
:data-background-transition ,(or (org-element-property :REVEAL_BACKGROUND_TRANS headline)
default-slide-background-transition)))))
(if (> (length attrs) 0) (format " %s" attrs) "")))
;; Copied from org-html-headline and modified to embed org-re-reveal
;; specific attributes.
(defun org-re-reveal-headline (headline contents info)
"Transcode a HEADLINE element from Org to HTML.
CONTENTS holds the contents of the headline. INFO is a plist
holding contextual information."
(unless (org-element-property :footnote-section-p headline)
(if (or (org-export-low-level-p headline info)
(org-element-property :NOSLIDE headline))
;; This is a deep sub-tree: export it as in ox-html.
(org-html-headline headline contents info)
;; Standard headline. Export it as a slide
(let* ((level (org-export-get-relative-level headline info))
(preferred-id (or (org-element-property :CUSTOM_ID headline)
(and (fboundp 'org-export-get-reference)
(org-export-get-reference headline info))
(org-element-property :ID headline)))
(hlevel (org-re-reveal--get-hlevel info))
(header (plist-get info :reveal-slide-header))
(header-div (if header (format org-re-reveal-slide-header-html header) ""))
(footer (plist-get info :reveal-slide-footer))
(footer-div (if footer (format org-re-reveal-slide-footer-html footer) ""))
(first-sibling (org-export-first-sibling-p headline info))
(attrs (org-re-reveal--section-attrs headline info))
(extra-attrs (or (org-element-property :REVEAL_EXTRA_ATTR headline)
(plist-get info :reveal-extra-attr)))
(slide-section-tag (format "<section id=\"%s\"%s%s>\n"
(format "%s%s" org-re-reveal--slide-id-prefix preferred-id)
attrs
(if extra-attrs (format " %s" extra-attrs) "")))
(ret (concat
(if (or (/= level 1) (not first-sibling))
;; Not the first heading. Close previous slide.
(concat
;; Slide footer if any.
footer-div
;; Close previous slide.
"</section>\n"
(if (<= level hlevel)
;; Close previous vertical slide group.
"</section>\n")))
(if (<= level hlevel)
;; Add an extra "<section>" to group following slides
;; into vertical slide group. Transition override
;; attributes are attached at this level, too.
(let ((attrs
(org-html--make-attribute-string
`(:data-transition ,(org-element-property :REVEAL_DATA_TRANSITION headline)))))
(if (string= attrs "")
"<section>\n"
(format "<section %s>\n" attrs))))
;; Start a new slide.
slide-section-tag
;; Slide header if any.
header-div
;; The HTML content of the headline
(org-re-reveal--fix-html-headline headline contents info)
(if (and (= level 1)
(org-export-last-sibling-p headline info))
;; Last head 1. Close all slides.
(concat
;; Slide footer if any
footer-div
"</section>\n</section>\n")))))
ret))))
(defun org-re-reveal--read-list (thing)
"Return THING if it is a list.
Otherwise, `read' THING and return value if it is a list.
Otherwise, raise an error."
(if (listp thing)
thing
(let ((lthing (read thing)))
(if (listp lthing)
lthing
(error "Expected a list, but got: %s" thing)))))
(defun org-re-reveal--parse-listoption (info option)
"Parse and return OPTION in INFO.
That value for OPTION may be a list or a string representing a list."
(org-re-reveal--read-list (plist-get info option)))
(defun org-re-reveal--read-file (file)
"Return the content of FILE."
(with-temp-buffer
(insert-file-contents-literally file)
(buffer-string)))
(defun org-re-reveal--file-url-to-path (url)
"Convert URL that points to local files to file path."
(replace-regexp-in-string
(if (string-equal system-type "windows-nt") "^file:///" "^file://")
"" url))
(defun org-re-reveal--css-label (in-single-file file-name style-id)
"Generate HTML code to include CSS file FILE-NAME.
If IN-SINGLE-FILE is t, the content of FILE-NAME is embedded;
otherwise, a `<link>' label is generated."
(when (and file-name (not (string= file-name "")))
(if in-single-file
;; Single-file
(let ((local-file-name (org-re-reveal--file-url-to-path file-name)))
(if (file-readable-p local-file-name)
(concat "<style type=\"text/css\">\n"
(org-re-reveal--read-file local-file-name)
"\n</style>\n")
;; But file is not readable.
(error "Cannot read %s" file-name)))
;; Not in-single-file
(concat "<link rel=\"stylesheet\" href=\"" file-name "\""
(if style-id (format " id=\"%s\"" style-id))
"/>\n"))))
(defun org-re-reveal--klipsify-header (info)
"Return code (CSS and JavaScript) to activate klipse when indicated by INFO."
(if (plist-get info :reveal-klipsify-src)
(concat (format "<link rel=\"stylesheet\" href=\"%s\"/>\n"
(plist-get info :reveal-klipse-css-url))
org-re-reveal-klipse-extra-css
(format "<script>
window.klipse_settings = {
%s%s
};\n"
(org-re-reveal--if-format
"%s,\n" (plist-get info :reveal-codemirror-config))
(mapconcat (lambda (elem)
(format " %s: '.%s'"
(nth 1 elem) (nth 2 elem)))
(org-re-reveal--parse-listoption
info :reveal-klipse-setup)
",\n"))
(org-re-reveal--if-format
" %s\n" (plist-get info :reveal-klipse-extra-config))
"</script>\n")
""))
(defun org-re-reveal--klipsify-script (info)
"Return script element for klipse when indicated by INFO."
(if (plist-get info :reveal-klipsify-src)
(format "<script src=\"%s\"></script>
<script>
/* Recompute layout upon changes by klipse. Code fragment from
asciidoctor-revealjs-klipse by Timothy Pratley under GPLv3:
https://github.com/timothypratley/asciidoctor-revealjs-klipse/blob/master/docs/docinfo-footer.html */
Reveal.addEventListener( 'slidechanged', function( event ) {
window.dispatchEvent( new Event('resize') );
} );
</script>\n" (plist-get info :reveal-klipse-js-url))
""))
(defun org-re-reveal-stylesheets (info)
"Return HTML code for reveal stylesheets using INFO and `org-re-reveal-root'."
(let* ((root-path (file-name-as-directory (plist-get info :reveal-root)))
(reveal-css (concat root-path "css/reveal.css"))
(theme (plist-get info :reveal-theme))
(theme-css (concat root-path "css/theme/" theme ".css"))
(extra-css (plist-get info :reveal-extra-css))
(in-single-file (plist-get info :reveal-single-file)))
(concat
;; Default embedded style sheets
"<style type=\"text/css\">
.underline { text-decoration: underline; }
</style>
"
;; stylesheets
(mapconcat (lambda (elem) (org-re-reveal--css-label
in-single-file (car elem) (cdr elem)))
(append (list (cons reveal-css nil)
(cons theme-css "theme"))
(mapcar (lambda (a) (cons a nil))
(cl-delete-duplicates
(split-string extra-css "\n" t)
:test #'equal)))
"\n")
;; Include CSS for highlight.js if necessary
(if (org-re-reveal--using-highlight.js info)
(format "<link rel=\"stylesheet\" href=\"%s\"/>\n"
(format-spec (plist-get info :reveal-highlight-css)
`((?r . ,(directory-file-name root-path)))))
"")
;; Include CSS for klipse if necessary
(org-re-reveal--klipsify-header info)
;; print-pdf
(if in-single-file ""
(format "\n<!-- If the query includes 'print-pdf', include the PDF print sheet -->
<script>
if( window.location.search.match( /print-pdf/gi ) ) {
var link = document.createElement( 'link' );
link.rel = 'stylesheet';
link.type = 'text/css';
link.href = '%scss/print/pdf.css';
document.getElementsByTagName( 'head' )[0].appendChild( link );
}
</script>
"
root-path)))))
(defun org-re-reveal-mathjax-scripts (info)
"Return HTML code for declaring MathJax scripts for INFO."
(if (plist-get info :reveal-mathjax)
;; MathJax enabled.
(format "<script type=\"text/javascript\" src=\"%s\"></script>\n"
(plist-get info :reveal-mathjax-url))))
(defun org-re-reveal--read-file-as-string (filename)
"If FILENAME exists as file, return its contents as string.
Otherwise, return nil."
(when (and (stringp filename)
(file-readable-p filename)
(not (file-directory-p filename)))
(with-temp-buffer
(insert-file-contents-literally filename)
(buffer-string))))
(defun org-re-reveal--external-plugins-maybe-from-file (info)
"Create list of plugin dependencies from INFO.
In INFO, `:reveal-external-plugins' can be a list or a filename.
If it is a filename, split lines to produce a list."
(let* ((external-plugins (plist-get info :reveal-external-plugins))
(file-contents (org-re-reveal--read-file-as-string external-plugins)))
(if file-contents
(mapcar (lambda (line) (cons 'dummy line))
(split-string (string-trim file-contents) "\n"))
(org-re-reveal--read-list external-plugins))))
(defun org-re-reveal--external-plugin-init (info root-path)
"Build initialization strings for plugins of INFO under ROOT-PATH.
Parameter INFO determines plugins and their initializations
based on `org-re-reveal-external-plugins'."
(let ((plugins (org-re-reveal--external-plugins-maybe-from-file info)))
(cl-loop for (nil . value) in plugins
collect (format value root-path))))
(defvar org-re-reveal-client-multiplex nil
"Used to cause generation of client html file for multiplex.")
(defun org-re-reveal-scripts--libraries (info)
"Internal function to generate script tags with INFO.
This includes reveal.js libraries in `org-re-reveal-script-files' under
`org-re-reveal-root', and libraries in `org-re-reveal-extra-scripts'."
(let* ((root-path (file-name-as-directory (plist-get info :reveal-root)))
(script-files (org-re-reveal--parse-listoption
info :reveal-script-files))
(root-libs (mapcar (lambda (file) (concat root-path file))
script-files))
(extra-scripts (org-re-reveal--parse-listoption
info :reveal-extra-scripts))
;; Treat extra scripts not starting with <script> as filenames.
(extra-script-files
(cl-remove-if (lambda (s) (string-prefix-p "<script>" s))
extra-scripts))
;; Treat extra scripts starting with <script> as elements.
(extra-script-elements
(cl-remove-if-not (lambda (s) (string-prefix-p "<script>" s))
extra-scripts))
(in-single-file (plist-get info :reveal-single-file)))
(concat
(if in-single-file
(let* ((local-root-path (org-re-reveal--file-url-to-path root-path))
(local-libs (append (mapcar (lambda (file)
(concat local-root-path file))
script-files)
extra-script-files))
(local-libs-exist-p (cl-every #'file-readable-p local-libs)))
(if local-libs-exist-p
;; Embed contents of files.
(mapconcat (lambda (file)
(format "<script>\n%s\n</script>\n"
(org-re-reveal--read-file file)))
local-libs "")
(error
(concat "Cannot read "
(mapconcat 'identity
(cl-remove-if #'file-readable-p local-libs)
", ")))))
;; Embed script files with src.
(mapconcat (lambda (file)
(concat "<script src=\"" file "\"></script>\n"))
(append root-libs extra-script-files) ""))
;; Embed script tags.
(mapconcat 'identity extra-script-elements "\n")
(if extra-script-elements "\n" ""))))
(defun org-re-reveal-scripts--reveal-options (info)
"Internal function for `org-re-reveal-scripts' with INFO."
(format "
controls: %s,
progress: %s,
history: %s,
center: %s,
slideNumber: %s,
rollingLinks: %s,
keyboard: %s,
mouseWheel: %s,
fragmentInURL: %s,
hashOneBasedIndex: %s,
pdfSeparateFragments: %s,
%s
overview: %s,
"
(if (plist-get info :reveal-control) "true" "false")
(if (plist-get info :reveal-progress) "true" "false")
(if (plist-get info :reveal-history) "true" "false")
(if (plist-get info :reveal-center) "true" "false")
(let ((slide-number (plist-get info :reveal-slide-number)))
(if slide-number (format "'%s'" slide-number)
"false"))
(if (plist-get info :reveal-rolling-links) "true" "false")
(if (plist-get info :reveal-keyboard) "true" "false")
(if (plist-get info :reveal-mousewheel) "true" "false")
(if (plist-get info :reveal-fragmentinurl) "true" "false")
(if (plist-get info :reveal-hashonebasedindex) "true" "false")
(if (plist-get info :reveal-pdfseparatefragments) "true" "false")
(let ((timing (plist-get info :reveal-defaulttiming)))
(if timing (format "defaultTiming: %s," timing)
""))
(if (plist-get info :reveal-overview) "true" "false")))
(defun org-re-reveal--to-string (option)
"Return OPTION as string.
If OPTION is an integer > 0, return as string.
If OPTION is a string, embed in quotation marks.
If OPTION is nil, return nil (not the empty string).
Otherwise, raise error."
(cond ((and (integerp option) (> option 0)) (format "%d" option))
((stringp option) (format "\"%s\"" option))
((eq option nil) nil)
(t (error "Option »%s« must be string, positive integer, or nil; not %s"
option (type-of option)))))
(defun org-re-reveal-scripts--main-configures (info)
"Internal function for `org-re-reveal-scripts' with INFO."
(concat
;; slide width
(let ((width (plist-get info :reveal-width)))
(org-re-reveal--if-format "width: %s,\n"
(org-re-reveal--to-string width)))
;; slide height
(let ((height (plist-get info :reveal-height)))
(org-re-reveal--if-format "height: %s,\n"
(org-re-reveal--to-string height)))
;; slide margin
(let ((margin (string-to-number (plist-get info :reveal-margin))))
(if (>= margin 0) (format "margin: %.2f,\n" margin) ""))
;; slide minimum scaling factor
(let ((min-scale (string-to-number (plist-get info :reveal-min-scale))))
(if (> min-scale 0) (format "minScale: %.2f,\n" min-scale) ""))
;; slide maximux scaling factor
(let ((max-scale (string-to-number (plist-get info :reveal-max-scale))))
(if (> max-scale 0) (format "maxScale: %.2f,\n" max-scale) ""))
;; thems and transitions
(format "
theme: Reveal.getQueryHash().theme, // available themes are in /css/theme
transition: Reveal.getQueryHash().transition || '%s', // see README of reveal.js for options
transitionSpeed: '%s',\n"
(plist-get info :reveal-trans)
(plist-get info :reveal-speed))
(let ((options (plist-get info :reveal-extra-options)))
(org-re-reveal--if-format "%s,\n" options))))
(defun org-re-reveal-scripts--multiplex (info)
"Internal function for `org-re-reveal-scripts' with INFO."
(let ((enabled-builtin-plugins
(org-re-reveal--parse-listoption info :reveal-plugins)))
(when (memq 'multiplex enabled-builtin-plugins)
(format
"multiplex: {
secret: %s, // null if client
id: '%s', // id, obtained from socket.io server
url: '%s' // Location of socket.io server
},\n"
(if (eq org-re-reveal-client-multiplex nil)
(format "'%s'" (plist-get info :reveal-multiplex-secret))
(format "null"))
(plist-get info :reveal-multiplex-id)
(plist-get info :reveal-multiplex-url)))))
(defun org-re-reveal-scripts--dependencies (info)
"Internal function for `org-re-reveal-scripts' with INFO."
(let* ((root-path (file-name-as-directory (plist-get info :reveal-root)))
(in-single-file (plist-get info :reveal-single-file))
(enabled-builtin-plugins (org-re-reveal--parse-listoption
info :reveal-plugins)))
;; optional JS library heading
(if in-single-file ""
(concat
"
// Optional libraries used to extend reveal.js
dependencies: [
"
;; JS libraries
(let* ((highlight-url (plist-get info :reveal-highlight-url))
(builtins
`(classList ,(format " { src: '%slib/js/classList.js', condition: function() { return !document.body.classList; } }" root-path)
markdown ,(format " { src: '%splugin/markdown/marked.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } },
{ src: '%splugin/markdown/markdown.js', condition: function() { return !!document.querySelector( '[data-markdown]' ); } }" root-path root-path)
highlight ,(if highlight-url
(format " { src: '%s', async: true, callback: function() { hljs.initHighlightingOnLoad(); } }" highlight-url)
(format " { src: '%splugin/highlight/highlight.js', async: true, callback: function() { hljs.initHighlightingOnLoad(); } }" root-path))
zoom ,(format " { src: '%splugin/zoom-js/zoom.js', async: true, condition: function() { return !!document.body.classList; } }" root-path)
notes ,(format " { src: '%splugin/notes/notes.js', async: true, condition: function() { return !!document.body.classList; } }" root-path)
search ,(format " { src: '%splugin/search/search.js', async: true, condition: function() { return !!document.body.classList; } }" root-path)
remotes ,(format " { src: '%splugin/remotes/remotes.js', async: true, condition: function() { return !!document.body.classList; } }" root-path)
multiplex ,(format " { src: '%s', async: true },\n%s"
(plist-get info :reveal-multiplex-socketio-url)
;; following ensures that either client.js or master.js is included depending on defvar org-re-reveal-client-multiplex value state
(if (not org-re-reveal-client-multiplex)
(progn
(if (not (string= "" (plist-get info :reveal-multiplex-secret)))
(setq org-re-reveal-client-multiplex t))
(format " { src: '%splugin/multiplex/master.js', async: true }" root-path))
(format " { src: '%splugin/multiplex/client.js', async: true }" root-path)))))
(builtin-codes
(mapcar (lambda (p) (plist-get builtins p)) enabled-builtin-plugins))
(external-plugins
(org-re-reveal--external-plugin-init info root-path))
(all-plugins (if external-plugins (append external-plugins builtin-codes) builtin-codes))
(extra-codes (plist-get info :reveal-extra-js))
(total-codes
(if (string= "" extra-codes)
all-plugins
(append (list extra-codes) all-plugins))))
(mapconcat 'identity total-codes ",\n"))
"]\n\n"))))
(defun org-re-reveal-scripts--init-script (info)
"Internal function for `org-re-reveal-scripts' with INFO."
(let ((init-script (plist-get info :reveal-init-script))
(in-single-file (plist-get info :reveal-single-file)))
(if (and (stringp init-script) (> (length init-script) 0))
(concat (if in-single-file "" ",") init-script)
"")))
(defun org-re-reveal-scripts (info)
"Return necessary scripts to initialize reveal.js.
Use INFO and custom variable `org-re-reveal-root'."
(concat
;; Libraries in script tags, including reveal.js itself.
(org-re-reveal-scripts--libraries info)
;; Create <script> tag for Reveal.initialize(...).
"<script>
// Full list of configuration options available here:
// https://github.com/hakimel/reveal.js#configuration
Reveal.initialize({
"
;; plugin configures/frags
(org-re-reveal-scripts--reveal-options info)
;; reveal.js main configures
(org-re-reveal-scripts--main-configures info)
;; multiplexing - depends on defvar 'org-re-reveal-client-multiplex'
(org-re-reveal-scripts--multiplex info)
;; load dependency js
(org-re-reveal-scripts--dependencies info)
;; init-script
(org-re-reveal-scripts--init-script info)
;; end of <script> tag
"});\n</script>\n"))
(defun org-re-reveal-toc (depth info)
"Build a slide of table of contents with DEPTH and INFO."
(let ((toc (org-html-toc depth info)))
(org-re-reveal-toc-1 toc info)))
(defun org-re-reveal-toc-1 (toc info)
"Build table of contents with TOC and INFO."
(when toc
(let* ((toc-slide-with-header (plist-get info :reveal-slide-global-header))
(toc-slide-with-footer (or
(plist-get info :reveal-slide-global-footer)
(plist-get info :reveal-slide-toc-footer)))
(toc-slide-state (plist-get info :reveal-toc-slide-state))
(toc-slide-class (plist-get info :reveal-toc-slide-class))
(toc-slide-title (plist-get info :reveal-toc-slide-title))
(toc (replace-regexp-in-string
"<a href=\"#"
(concat "<a href=\"#" org-re-reveal--href-fragment-prefix) toc))
(toc (replace-regexp-in-string
(org-html--translate "Table of Contents" info)
toc-slide-title toc)))
(concat "<section id=\"table-of-contents-section\""
(when toc-slide-state
(format " data-state=\"%s\"" toc-slide-state))
">\n"
(when toc-slide-with-header
(let ((header (plist-get info :reveal-slide-header)))
(when header (format org-re-reveal-slide-header-html header))))
(if toc-slide-class
(replace-regexp-in-string
"<h\\([1-3]\\)>"
(format "<h\\1 class=\"%s\">" toc-slide-class)
toc)
toc)
(when toc-slide-with-footer
(let ((footer (plist-get info :reveal-slide-footer)))
(when footer (format org-re-reveal-slide-footer-html footer))))
"</section>\n"))))
(defun org-re-reveal-inner-template (contents info)
"Return body of document string after HTML conversion.
CONTENTS is the transcoded contents string. INFO is a plist
holding export options."
(concat
;; Table of contents.
(let ((depth (plist-get info :with-toc)))
(when (and depth
(not (plist-get info :reveal-subtree)))
(org-re-reveal-toc depth info)))
;; Document contents.
contents))
(defun org-re-reveal-parse-keyword-value (value footer keyword info)
"According to VALUE of KEYWORD and INFO, return HTML tags to split slides.
Currently, only the keyword \"split\" is implemented, and VALUE must
start with \"split\". Any following text is inserted literally into
the section tag.
The possibly empty FOOTER is inserted at the end of the slide."
(cl-assert (string-prefix-p "split" value) nil
(format "Unknown REVEAL keyword. Expected \"split\", got: %s"
value))
(let* ((headline (org-export-get-parent-headline keyword))
(split-attrs (substring value 5)) ; Everything after "split"
(real-attrs (if (< 0 (length split-attrs))
split-attrs
(org-re-reveal--section-attrs headline info))))
(format "%s</section>\n<section%s>"
footer real-attrs)))
;; Copied from org-html-format-list-item. Overwrite HTML class
;; attribute when there is attr_html attributes.
(defun org-re-reveal-format-list-item (contents type checkbox attributes info
&optional term-counter-id
headline)
"Format a list item into HTML based on INFO.
Item has CONTENTS, TYPE, may be a CHECKBOX, have ATTRIBUTES, and may have
TERM-COUNTER-ID and HEADLINE."
(let ((attr-html (cond (attributes (format " %s" (org-html--make-attribute-string attributes)))
(checkbox (format " class=\"%s\"" (symbol-name checkbox)))
(t "")))
(checkbox (concat (org-html-checkbox checkbox info)
(and checkbox " ")))
(br (org-html-close-tag "br" nil info)))
(concat
(cl-case type
(ordered
(let* ((counter term-counter-id)
(extra (if counter (format " value=\"%s\"" counter) "")))
(concat
(format "<li%s%s>" attr-html extra)
(when headline (concat headline br)))))
(unordered
(let* ((id term-counter-id)
(extra (if id (format " id=\"%s\"" id) "")))
(concat
(format "<li%s%s>" attr-html extra)
(when headline (concat headline br)))))
(descriptive
(let* ((term term-counter-id))
(setq term (or term "(no term)"))
;; Check-boxes in descriptive lists are associated to tag.
(concat (format "<dt%s>%s</dt>"
attr-html (concat checkbox term))
(format "<dd%s>" attr-html)))))
(unless (eq type 'descriptive) checkbox)
(and contents (org-trim contents))
(cl-case type
(ordered "</li>")
(unordered "</li>")
(descriptive "</dd>")))))
;; Copied from org-html-item, changed to call
;; org-re-reveal-format-list-item.
(defun org-re-reveal-item (item contents info)
"Transcode an ITEM element from Org to Reveal.
CONTENTS holds the contents of the item. INFO is a plist holding
contextual information."
(let* ((plain-list (org-export-get-parent item))
(type (org-element-property :type plain-list))
(counter (org-element-property :counter item))
(attributes (org-export-read-attribute :attr_html item))
(checkbox (org-element-property :checkbox item))
(tag (let ((tag (org-element-property :tag item)))
(and tag (org-export-data tag info)))))
(org-re-reveal-format-list-item
contents type checkbox attributes info (or tag counter))))
(defun org-re-reveal-keyword (keyword contents info)
"Transcode a KEYWORD element from Org to Reveal.
May change custom variables as SIDE EFFECT.
CONTENTS is nil. INFO is a plist holding contextual information."
(let* ((key (org-element-property :key keyword))
(value (org-element-property :value keyword))
(footer (plist-get info :reveal-slide-footer))
(footer-div (if footer
(format org-re-reveal-slide-footer-html footer) "")))
(cl-case (intern key)
(REVEAL (org-re-reveal-parse-keyword-value value footer-div keyword info))
(REVEAL_HTML value)
(HTML value)
;; Handling of TOC at arbitrary position is a hack.
;; We end the previous section by inserting a closing section tag,
;; which will *break* the presentation if other tags are still open.
;; To avoid unbalanced tags, remove the TOC's closing tag.
;; If slide footers are used, insert it before closing the section.
;; In any case, if footers are used, the one of the closed section
;; is sufficient, and the one contained in the TOC needs to be removed.
(TOC (message "Please use #+REVEAL_TOC instead of #+TOC. See Readme.")
(sit-for 2)
(concat footer-div
"</section>\n"
(replace-regexp-in-string
(format "</section>\\|%s"
(format org-re-reveal-slide-footer-html ".*"))
""
(org-re-reveal-toc-1
(org-html-keyword keyword contents info) info))))
(REVEAL_TOC
;; Following code stiched together with snippets from
;; org-html-keyword and org-html-toc.
(when (string-match "\\<headlines\\>" value)
(let* ((depth (and (string-match "\\<[0-9]+\\>" value)
(string-to-number (match-string 0 value))))
(toc-entries
(mapcar (lambda (headline)
(cons (org-html--format-toc-headline headline info)
(org-export-get-relative-level headline info)))
(org-export-collect-headlines info depth))))
(when toc-entries
(let ((toc (concat "<div id=\"text-table-of-contents\">"
(org-html--toc-text toc-entries)
"</div>\n")))
;; Use link format of reveal.js.
(replace-regexp-in-string
"<a href=\"#"
(concat "<a href=\"#" org-re-reveal--href-fragment-prefix)
toc)))))))))
(defun org-re-reveal-embedded-svg (path)
"Embed the SVG content at PATH into Reveal HTML."
(with-temp-buffer
(insert-file-contents-literally path)
(let ((start (re-search-forward "<[ \t\n]*svg[ \t\n]"))
(end (re-search-forward "<[ \t\n]*/svg[ \t\n]*>")))
(concat "<svg " (buffer-substring-no-properties start end)))))
(defun org-re-reveal--format-image-data-uri (link path info)
"Generate the data URI for the image referenced by LINK at PATH with INFO."
(let* ((ext (downcase (file-name-extension path))))
(if (string= ext "svg")
(org-re-reveal-embedded-svg path)
(org-html-close-tag
"img"
(org-html--make-attribute-string
(org-combine-plists
(list :src
(concat
"data:image/"
;; Image type
ext
";base64,"
;; Base64 content
(with-temp-buffer
(insert-file-contents-literally path)
(base64-encode-region 1 (point-max))
(buffer-string))))
;; Get attribute list from parent element
;; Copied from ox-html.el
(let* ((parent (org-export-get-parent-element link))
(link (let ((container (org-export-get-parent link)))
(if (and (eq (org-element-type container) 'link)
(org-html-inline-image-p link info))
container
link))))
(and (eq (org-element-map parent 'link 'identity info t) link)
(org-export-read-attribute :attr_html parent)))))
info))))
(defun org-re-reveal--maybe-replace-in-link (link allow-inter-link)
"Replace hash sign in LINK, affected by ALLOW-INTER-LINK.
If ALLOW-INTER-LINK is nil, only replace hash signs if URL in LINK starts
with it. Otherwise, also replace if the URL does not contain a hostname;
such links are assumed to point into other presentations."
(if (and allow-inter-link
(string-match "<a href=\"\\([^\"]*\\)\"" link))
(let* ((url (match-string 1 link))
(obj (url-generic-parse-url url))
(host (url-host obj)))
(if host
link
(replace-regexp-in-string
"<a href=\"\\([^#]*\\)#"
(concat "<a href=\"\\1#" org-re-reveal--href-fragment-prefix)
link)))
(replace-regexp-in-string
"<a href=\"#"
(concat "<a href=\"#" org-re-reveal--href-fragment-prefix)
link)))
(defun org-re-reveal--internal-link-class (link info)
"Check if LINK is internal, given INFO, and maybe assign class.
The direction of the link is assigned as class attribute to the link
and to its parent via \"attr_html\":
If link points backward (to previous content), class \"backwardlink\"
is assigned, else \"forwardlink\".
Assigning the class to \"attr_html\" of parent is based on a hack in
`org-html-link', while use of \"attr_html\" of the link itself
requires a version of org-mode as of 2018-12-08 or newer."
(let ((target (or (ignore-errors (org-export-resolve-id-link link info))
(ignore-errors (org-export-resolve-fuzzy-link link info)))))
(when target
(let* ((lbegin (org-element-property :begin link))
(tbegin (org-element-property :begin target))
(direction (if (< tbegin lbegin)
"backwardlink"
"forwardlink"))
(parent (org-export-get-parent-element link))
(attrs (org-combine-plists
(org-export-read-attribute :attr_html parent)
(org-export-read-attribute :attr_html link)))
(class (plist-get attrs :class))
(newclass (if class (concat direction " " class) direction))
(newattrs (mapconcat (lambda (elem) (format "%s" elem))
(plist-put attrs :class newclass)
" ")))
(org-element-put-property parent :attr_html (list newattrs))
(org-element-put-property link :attr_html (list newattrs))))))
(defun org-re-reveal-link (link desc info)
"Transcode a LINK object with DESC and INFO from Org to Reveal.
The result is identical to ox-html expect for image links.
When `org-re-reveal-single-file' is t,
the result is the Data URI of the referenced image."
(let* ((want-embed-image (and (plist-get info :reveal-single-file)
(plist-get info :html-inline-images)
(string= "file" (org-element-property :type link))
(org-export-inline-image-p
link (plist-get info :html-inline-image-rules))))
(allow-inter-link (plist-get info :reveal-inter-presentation-links))
(raw-path (org-element-property :path link))
(clean-path (org-re-reveal--file-url-to-path raw-path))
(can-embed-image (and want-embed-image
(file-readable-p clean-path))))
(if can-embed-image
(org-re-reveal--format-image-data-uri link clean-path info)
(if want-embed-image
(error "Cannot embed image %s" raw-path)
(org-re-reveal--internal-link-class link info)
(org-re-reveal--maybe-replace-in-link (org-html-link link desc info)
allow-inter-link)))))
(defun org-re-reveal-latex-environment (latex-env contents info)
"Transcode a LaTeX environment from Org to Reveal.
LATEX-ENV is the Org element. CONTENTS is the contents of the environment.
INFO is a plist holding contextual information."
(setq info (plist-put info :reveal-mathjax t))
(let ((attrs (org-export-read-attribute :attr_html latex-env)))
(format "<div%s>\n%s\n</div>\n"
(if attrs (concat " " (org-html--make-attribute-string attrs)) "")
(org-html-latex-environment latex-env contents info))))
(defun org-re-reveal-plain-list (plain-list contents info)
"Transcode a PLAIN-LIST element from Org to Reveal.
CONTENTS is the contents of the list. INFO is a plist holding
contextual information.
Extract and set `attr_html' to plain-list tag attributes."
(ignore info) ; Silence byte compiler
(let ((tag (cl-case (org-element-property :type plain-list)
(ordered "ol")
(unordered "ul")
(descriptive "dl")))
(attrs (org-export-read-attribute :attr_html plain-list)))
(format "%s<%s%s>\n%s\n</%s>%s"
(if (string= org-html-checkbox-type 'html) "<form>" "")
tag
(if attrs (concat " " (org-html--make-attribute-string attrs)) "")
contents
tag
(if (string= org-html-checkbox-type 'html) "</form>" ""))))
(defun org-re-reveal-format-spec (info)
"Return format specification with INFO.
Formatting extends `org-html-format-spec' as follows:
misc information and academic title."
(append (org-html-format-spec info)
`((?A . ,(org-export-data
(plist-get info :reveal-academic-title) info))
(?m . ,(org-export-data
(plist-get info :reveal-miscinfo) info))
(?q . ,(url-encode-url
(org-export-data
(plist-get info :reveal-talk-qr-code) info)))
(?u . ,(url-encode-url
(org-export-data
(plist-get info :reveal-talk-url) info))))))
(defun org-re-reveal--build-pre-postamble (type info)
"Depending on TYPE, return preamble or postamble for INFO as string, or nil."
(let ((section (plist-get info (intern (format ":reveal-%s" type))))
(spec (org-re-reveal-format-spec info)))
(when section
(let ((section-contents
(if (functionp (intern section)) (funcall (intern section) info)
;; else section is a string.
(format-spec section spec))))
(when (org-string-nw-p section-contents)
(org-element-normalize-string section-contents))))))
(defun org-re-reveal-section (section contents info)
"Transcode a SECTION element from Org to Reveal.
CONTENTS holds the contents of the section. INFO is a plist
holding contextual information."
;; Just return the contents. No "<div>" tags.
(ignore section info) ; Silence byte compiler
contents)
(defun org-re-reveal--using-highlight.js (info)
"Check with INFO whether highlight.js plugin is enabled."
(let ((reveal-plugins (condition-case nil
(car (read-from-string (plist-get info :reveal-plugins)))
(end-of-file nil)
(wrong-type-argument nil))))
(memq 'highlight (or (and reveal-plugins (listp reveal-plugins) reveal-plugins)
org-re-reveal-plugins))))
(defun org-re-reveal--buffer-substring-html-escape (start end)
"Convert buffer substring characters from plain text to HTML equivalent.
START and END are character positions as used by `buffer-substring'.
Conversion is done by escaping special HTML chars."
(org-html-encode-plain-text (buffer-substring start end)))
(defun org-re-reveal-src-block (src-block contents info)
"Transcode a SRC-BLOCK element from Org to Reveal.
INFO is a plist holding contextual information. CONTENTS is unused."
(ignore contents) ; Silence byte compiler
(if (org-export-read-attribute :attr_html src-block :textarea)
(org-html--textarea-block src-block)
(let* ((use-highlight (org-re-reveal--using-highlight.js info))
(use-htmlize (or (not org-re-reveal-no-htmlize-src)
(org-export-read-attribute
:attr_reveal src-block :htmlize)))
(lang (org-element-property :language src-block))
(caption (org-export-get-caption src-block))
(code (if (and (not use-highlight) use-htmlize)
(org-html-format-code src-block info)
(cl-letf (((symbol-function
'org-html-htmlize-region-for-paste)
#'org-re-reveal--buffer-substring-html-escape))
(org-html-format-code src-block info))))
(frag (org-export-read-attribute :attr_reveal src-block :frag))
(findex (org-export-read-attribute :attr_reveal src-block :frag_idx))
(code-attribs (or (org-export-read-attribute
:attr_reveal src-block :code_attribs) ""))
(label (let ((lbl (org-element-property :name src-block)))
(if (not lbl) ""
(format " id=\"%s\"" lbl))))
(klipse-setup (org-re-reveal--parse-listoption
info :reveal-klipse-setup))
(klipsify (and (member lang (mapcar #'car klipse-setup))
(plist-get info :reveal-klipsify-src)
(not (org-export-read-attribute
:attr_reveal src-block :no-klipsify)))))
(if (not lang)
(format "<pre %s%s>\n%s</pre>"
(or (org-re-reveal--frag-class frag info) " class=\"example\"")
label
code)
(if klipsify
(let* ((triple (assoc lang klipse-setup))
(selectorclass (nth 2 triple)))
(concat
"<pre><code class=\"" selectorclass "\" " code-attribs ">\n"
(if (string= lang "html")
(replace-regexp-in-string
"'" "&#39;"
(replace-regexp-in-string
"<" "&lt;"
(replace-regexp-in-string
">" "&gt;"
(replace-regexp-in-string
"&" "&amp;"
(cl-letf (((symbol-function
'org-html-htmlize-region-for-paste)
#'buffer-substring))
(org-html-format-code src-block info))))))
(replace-regexp-in-string "'" "&#39;" code))
"</code></pre>\n"))
(format
"<div class=\"org-src-container\">\n%s%s\n</div>"
(if (not caption) ""
(format "<label class=\"org-src-name\">%s</label>"
(org-export-data caption info)))
(if use-highlight
(format "\n<pre%s%s><code class=\"%s %s\" %s>%s</code></pre>"
(or (org-re-reveal--frag-class frag info) "")
(or (org-re-reveal--frag-index findex) "")
label lang code-attribs code)
(format "\n<pre %s%s%s>%s</pre>"
(or (org-re-reveal--frag-class frag info)
(format " class=\"src src-%s\"" lang))
(or (org-re-reveal--frag-index findex) "")
label code))))))))
(defun org-re-reveal-quote-block (quote-block contents info)
"Transcode a QUOTE-BLOCK element from Org to Reveal.
CONTENTS holds the contents of the block INFO is a plist holding
contextual information."
(format "<blockquote%s>\n%s</blockquote>"
(let ((frag (org-re-reveal--frag-class
(org-export-read-attribute
:attr_reveal quote-block :frag) info)))
(if frag
(concat " " frag)
""))
contents))
(defun org-re-reveal--auto-title-slide-template (info)
"Generate the automatic title slide template with INFO."
(let* ((spec (org-re-reveal-format-spec info))
(title (org-export-data (plist-get info :title) info))
(author (cdr (assq ?a spec)))
(email (cdr (assq ?e spec)))
(date (cdr (assq ?d spec))))
(concat
(when (and (plist-get info :with-title)
(org-string-nw-p title))
(concat "<h1 class=\"title\">" title "</h1>"))
(when (and (plist-get info :with-author)
(org-string-nw-p author))
(concat "<h2 class=\"author\">" author "</h2>"))
(when (and (plist-get info :with-email)
(org-string-nw-p email))
(concat "<h2 class=\"email\">" email "</h2>"))
(when (and (plist-get info :with-date)
(org-string-nw-p date))
(concat "<h2 class=\"date\">" date "</h2>"))
(when (plist-get info :time-stamp-file)
(concat "<p class=\"date\">"
(org-html--translate "Created" info)
": "
(format-time-string
(plist-get info :html-metadata-timestamp-format))
"</p>")))))
(defun org-re-reveal-template (contents info)
"Return complete document string after HTML conversion.
CONTENTS is the transcoded contents string.
INFO is a plist holding export options."
(concat
(format "<!DOCTYPE html>\n<html%s>\n<head>\n"
(org-re-reveal--if-format " lang=\"%s\"" (plist-get info :language)))
"<meta charset=\"utf-8\"/>\n"
(org-re-reveal--if-format "<title>%s</title>\n"
(org-export-data (plist-get info :title) info))
(org-re-reveal--if-format "<meta name=\"author\" content=\"%s\"/>\n"
(org-element-interpret-data (plist-get info :author)))
(org-re-reveal--if-format "<meta name=\"description\" content=\"%s\"/>\n"
(plist-get info :description))
(org-re-reveal--if-format "<meta name=\"keywords\" content=\"%s\"/>\n"
(plist-get info :keywords))
(org-re-reveal-stylesheets info)
(org-re-reveal-mathjax-scripts info)
(org-re-reveal--build-pre-postamble 'head-preamble info)
(org-element-normalize-string (plist-get info :html-head))
(org-element-normalize-string (plist-get info :html-head-extra))
"</head>\n<body"
(org-re-reveal--if-format " %s" org-re-reveal-body-attrs)
">\n"
(org-re-reveal--build-pre-postamble 'preamble info)
"<div class=\"reveal\">
<div class=\"slides\">\n"
;; Title slides
(let ((title-slide (plist-get info :reveal-title-slide)))
(when (and (or (eq 'auto title-slide)
(and (stringp title-slide) (< 0 (length title-slide))))
(or (not (plist-get info :reveal-subtree))
(plist-get info :reveal-subtree-with-title-slide)))
(let ((title-slide-background (plist-get info :reveal-title-slide-background))
(title-slide-background-size (plist-get info :reveal-title-slide-background-size))
(title-slide-background-position (plist-get info :reveal-title-slide-background-position))
(title-slide-background-repeat (plist-get info :reveal-title-slide-background-repeat))
(title-slide-background-transition (plist-get info :reveal-title-slide-background-transition))
(title-slide-state (plist-get info :reveal-title-slide-state))
(title-slide-timing (plist-get info :reveal-title-slide-timing))
(title-slide-with-header (plist-get info :reveal-slide-global-header))
(title-slide-with-footer (plist-get info :reveal-slide-global-footer)))
(concat "<section id=\"sec-title-slide\""
(when title-slide-background
(concat " data-background=\"" title-slide-background "\""))
(when title-slide-background-size
(concat " data-background-size=\"" title-slide-background-size "\""))
(when title-slide-background-position
(concat " data-background-position=\"" title-slide-background-position "\""))
(when title-slide-background-repeat
(concat " data-background-repeat=\"" title-slide-background-repeat "\""))
(when title-slide-background-transition
(concat " data-background-transition=\"" title-slide-background-transition "\""))
(when title-slide-state
(concat " data-state=\"" title-slide-state "\""))
(when title-slide-timing
(concat " data-timing=\"" title-slide-timing "\""))
">\n"
(when title-slide-with-header
(let ((header (plist-get info :reveal-slide-header)))
(when header (format org-re-reveal-slide-header-html header))))
(cond ((eq title-slide nil) nil)
((stringp title-slide)
(let* ((file-contents
(org-re-reveal--read-file-as-string title-slide))
(title-string (or file-contents title-slide)))
(format-spec title-string
(org-re-reveal-format-spec info))))
((eq title-slide 'auto) (org-re-reveal--auto-title-slide-template info)))
"\n"
(when title-slide-with-footer
(let ((footer (plist-get info :reveal-slide-footer)))
(when footer (format org-re-reveal-slide-footer-html footer))))
"</section>\n"))))
contents
"</div>
</div>\n"
(org-re-reveal--build-pre-postamble 'postamble info)
(org-re-reveal-scripts info)
(org-re-reveal--klipsify-script info)
"</body>
</html>\n"))
(defun org-re-reveal-filter-parse-tree (tree backend info)
"Do filtering before parsing TREE.
TREE is the parse tree being exported. BACKEND is the export
back-end used. INFO is a plist-used as a communication channel.
BACKEND must be (or be derived from) `re-reveal'.
Modify the TREE in two ways:
First, map each `attr_reveal' attribute to corresponding
`attr_html' attributes.
Second, if `org-re-reveal-generate-custom-ids' is t (or option
\"reveal_generate_ids\" is t), generate \"CUSTOM_ID\" values for
section headings that do not have one already."
(cl-assert (org-export-derived-backend-p backend 're-reveal) nil
(format "Function org-re-reveal-filter-parse-tree called on unexpected backend: %s" backend))
(let ((default-frag-style (plist-get info :reveal-default-frag-style)))
(org-element-map tree (remq 'item org-element-all-elements)
(lambda (elem) (org-re-reveal-append-frag elem default-frag-style))))
(when (plist-get info :reveal-generate-ids)
(let ((numbering (org-export--collect-headline-numbering tree info)))
(dolist (pair numbering nil)
(let ((headline (car pair))
(number (cdr pair)))
(when (org-export-numbered-headline-p headline info)
(let ((section-number (mapconcat #'number-to-string number "-")))
(when (and (> (length section-number) 0)
(not (org-element-property :CUSTOM_ID headline)))
(org-element-put-property headline :CUSTOM_ID section-number))))))))
;; Return the updated tree.
tree)
(defun org-re-reveal--update-attr-html (elem frag default-style
&optional frag-index frag-audio)
"Update ELEM's attr_html attribute with reveal's fragment attributes.
FRAG is the fragment style, a DEFAULT-STYLE may be used;
optional FRAG-INDEX and FRAG-AUDIO may indicate fragment positions
and audio files."
(let ((attr-html (org-element-property :attr_html elem)))
(when (and frag (not (string= frag "none")))
(push (if (string= frag t)
(if default-style (format ":class fragment %s" default-style)
":class fragment")
(format ":class fragment %s" frag))
attr-html)
(when frag-index
;; Index positions should be numbers or the minus sign.
(cl-assert (or (integerp frag-index)
(eq frag-index '-)
(and (not (listp frag-index))
(not (char-equal
(string-to-char frag-index) ?\())))
nil "Index cannot be a list: %s" frag-index)
(push (format ":data-fragment-index %s" frag-index) attr-html))
(when (and frag-audio (not (string= frag-audio "none")))
(push (format ":data-audio-src %s" frag-audio) attr-html)))
(org-element-put-property elem :attr_html attr-html)))
(defun org-re-reveal-append-frag (elem default-style)
"Append transformed fragment from ELEM with DEFAULT-STYLE.
Read fragment from ELEM and append transformed fragment attribute to ELEM's
attr_html plist."
(let ((frag (org-export-read-attribute :attr_reveal elem :frag))
(frag-index (org-export-read-attribute :attr_reveal elem :frag_idx))
(frag-audio (org-export-read-attribute :attr_reveal elem :audio)))
(when frag
(if (and (string= (org-element-type elem) 'plain-list)
(char-equal (string-to-char frag) ?\())
(let* ((items (org-element-contents elem))
(frag-list (car (read-from-string frag)))
(frag-list (if default-style
(mapcar (lambda (s)
"Replace t with default-style"
(if (string= s t) default-style
s))
frag-list)
frag-list))
(itemno (length items))
(style-list (make-list itemno default-style))
;; Make sure that we have enough fragments. Duplicate the
;; last element of frag-list so that frag-list and items
;; have the same length.
(last-frag (car (last frag-list)))
(tail-list (make-list
(- itemno (length frag-list)) last-frag))
(frag-list (append frag-list tail-list))
;; Concerning index positions and audio files, check later
;; that their number is OK.
(frag-index (if frag-index
(car (read-from-string frag-index))
(make-list itemno nil)))
(frag-audio (when frag-audio
(car (read-from-string frag-audio)))))
;; As we are looking at fragments in lists, we make sure
;; that other specs are lists of proper length.
(cl-assert (listp frag-index) t
"Must use list for index positions, not: %s")
(when frag-index
(cl-assert (= (length frag-index) itemno) nil
"Use one index per item! %s has %d, need %d"
frag-index (length frag-index) (length items)))
(cl-assert (listp frag-audio) t
"Must use list for audio files! %s")
(when frag-audio
(cl-assert (= (length frag-audio) itemno) nil
"Use one audio file per item! %s has %d, need %d"
frag-audio (length frag-audio) itemno))
(if frag-audio
(cl-mapcar 'org-re-reveal--update-attr-html
items frag-list style-list frag-index frag-audio)
(cl-mapcar 'org-re-reveal--update-attr-html
items frag-list style-list frag-index)))
(org-re-reveal--update-attr-html
elem frag default-style frag-index frag-audio))
elem)))
(defun org-re-reveal-export-to-html
(&optional async subtreep visible-only body-only ext-plist backend)
"Export current buffer to a reveal.js HTML file.
Optional ASYNC, SUBTREEP, VISIBLE-ONLY, BODY-ONLY, EXT-PLIST are passed
to `org-export-to-file'.
Optional BACKEND must be `re-reveal' or a backend derived from it."
(interactive)
(let* ((backend (or backend 're-reveal))
(extension (concat "." org-html-extension))
(file (org-export-output-file-name extension subtreep))
(clientfile (org-export-output-file-name (concat "_client" extension) subtreep))
(org-html-container-element "div"))
(setq org-re-reveal-client-multiplex nil)
(org-export-to-file backend file
async subtreep visible-only body-only ext-plist)
;; Export the client HTML file if org-re-reveal-client-multiplex is set true
;; by previous call to org-export-to-file
(if org-re-reveal-client-multiplex
(org-export-to-file backend clientfile
async subtreep visible-only body-only ext-plist))
file))
(defun org-re-reveal-export-to-html-and-browse
(&optional async subtreep visible-only body-only ext-plist)
"Export current buffer to a reveal.js and browse HTML file.
Optional ASYNC, SUBTREEP, VISIBLE-ONLY, BODY-ONLY, EXT-PLIST are passed
to `org-re-reveal-export-to-html'."
(interactive)
(browse-url-of-file
(expand-file-name
(org-re-reveal-export-to-html
async subtreep visible-only body-only ext-plist))))
(defun org-re-reveal-export-current-subtree
(&optional async subtreep visible-only body-only ext-plist)
"Export current subtree to a Reveal.js HTML file.
Optional ASYNC, SUBTREEP, VISIBLE-ONLY, BODY-ONLY, EXT-PLIST are passed
to `org-re-reveal-export-to-html'."
(interactive)
(org-narrow-to-subtree)
(let ((ret (org-re-reveal-export-to-html
async subtreep visible-only body-only
(plist-put ext-plist :reveal-subtree t))))
(widen)
ret))
;;;###autoload
(defun org-re-reveal-publish-to-reveal
(plist filename pub-dir &optional backend)
"Publish an Org file to HTML.
FILENAME is the filename of the Org file to be published. PLIST
is the property list for the given project. PUB-DIR is the
publishing directory. Optional BACKEND may specify a derived export
backend.
Return output file name."
(let ((org-re-reveal-client-multiplex nil))
(org-publish-org-to
(or backend 're-reveal) filename ".html" plist pub-dir)))
;;;###autoload
(defun org-re-reveal-publish-to-reveal-client
(plist filename pub-dir &optional backend)
"Publish an Org file to HTML as multiplex client.
FILENAME is the filename of the Org file to be published. PLIST
is the property list for the given project. PUB-DIR is the
publishing directory. Optional BACKEND may specify a derived export
backend.
If `org-re-reveal-client-multiplex-filter' is non-nil, use it as regular
expression to only publish FILENAME if it matches this regular expression.
Return output file name."
(if (or (not org-re-reveal-client-multiplex-filter)
(string-match org-re-reveal-client-multiplex-filter filename))
(let ((org-re-reveal-client-multiplex t))
(org-publish-org-to
(or backend 're-reveal) filename "_client.html" plist pub-dir))
(message "File '%s' not published (not matched by '%s')."
filename org-re-reveal-client-multiplex-filter)
nil)
:package-version '(org-re-reveal . "2.12.0"))
;; Register auto-completion for speaker notes.
(when org-re-reveal-note-key-char
(add-to-list 'org-structure-template-alist
(if (version< org-version "9.2")
(list org-re-reveal-note-key-char "#+BEGIN_NOTES\n\?\n#+END_NOTES")
(cons org-re-reveal-note-key-char "notes"))))
;;; Extract version string.
;;;###autoload
(defun org-re-reveal-version ()
"Display version string for org-re-reveal from Lisp file."
(interactive)
(let ((lisp-file
(concat (file-name-sans-extension (locate-library "org-re-reveal"))
".el")))
(with-temp-buffer
(insert-file-contents lisp-file)
(goto-char (point-min))
(re-search-forward "^;; Version: \\([0-9.]+\\)$")
(message "org-re-reveal version %s" (match-string 1)))))
(provide 'org-re-reveal)
;;; org-re-reveal.el ends here
;;; ox-rss.el --- RSS 2.0 Back-End for Org Export Engine
;; Copyright (C) 2013-2015 Bastien Guerry
;; Author: Bastien Guerry <bzg@gnu.org>
;; Keywords: org, wp, blog, feed, rss
;; This file is not yet part of GNU Emacs.
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This library implements an RSS 2.0 back-end for Org exporter, based
;; on the `html' back-end.
;;
;; It requires Emacs 24.1 at least.
;;
;; It provides two commands for export, depending on the desired output:
;; `org-rss-export-as-rss' (temporary buffer) and `org-rss-export-to-rss'
;; (as a ".xml" file).
;;
;; This backend understands three new option keywords:
;;
;; #+RSS_EXTENSION: xml
;; #+RSS_IMAGE_URL: http://myblog.org/mypicture.jpg
;; #+RSS_FEED_URL: http://myblog.org/feeds/blog.xml
;;
;; It uses #+HTML_LINK_HOME: to set the base url of the feed.
;;
;; Exporting an Org file to RSS modifies each top-level entry by adding a
;; PUBDATE property. If `org-rss-use-entry-url-as-guid', it will also add
;; an ID property, later used as the guid for the feed's item.
;;
;; The top-level headline is used as the title of each RSS item unless
;; an RSS_TITLE property is set on the headline.
;;
;; You typically want to use it within a publishing project like this:
;;
;; (add-to-list
;; 'org-publish-project-alist
;; '("homepage_rss"
;; :base-directory "~/myhomepage/"
;; :base-extension "org"
;; :rss-image-url "http://lumiere.ens.fr/~guerry/images/faces/15.png"
;; :html-link-home "http://lumiere.ens.fr/~guerry/"
;; :html-link-use-abs-url t
;; :rss-extension "xml"
;; :publishing-directory "/home/guerry/public_html/"
;; :publishing-function (org-rss-publish-to-rss)
;; :section-numbers nil
;; :exclude ".*" ;; To exclude all files...
;; :include ("index.org") ;; ... except index.org.
;; :table-of-contents nil))
;;
;; ... then rsync /home/guerry/public_html/ with your server.
;;
;; By default, the permalink for a blog entry points to the headline.
;; You can specify a different one by using the :RSS_PERMALINK:
;; property within an entry.
;;; Code:
(require 'ox-html)
(declare-function url-encode-url "url-util" (url))
;;; Variables and options
(defgroup org-export-rss nil
"Options specific to RSS export back-end."
:tag "Org RSS"
:group 'org-export
:version "24.4"
:package-version '(Org . "8.0"))
(defcustom org-rss-image-url "https://orgmode.org/img/org-mode-unicorn-logo.png"
"The URL of the image for the RSS feed."
:group 'org-export-rss
:type 'string)
(defcustom org-rss-extension "xml"
"File extension for the RSS 2.0 feed."
:group 'org-export-rss
:type 'string)
(defcustom org-rss-categories 'from-tags
"Where to extract items category information from.
The default is to extract categories from the tags of the
headlines. When set to another value, extract the category
from the :CATEGORY: property of the entry."
:group 'org-export-rss
:type '(choice
(const :tag "From tags" from-tags)
(const :tag "From the category property" from-category)))
(defcustom org-rss-use-entry-url-as-guid t
"Use the URL for the <guid> metatag?
When nil, Org will create ids using `org-icalendar-create-uid'."
:group 'org-export-rss
:type 'boolean)
;;; Define backend
(org-export-define-derived-backend 'rss 'html
:menu-entry
'(?r "Export to RSS"
((?R "As RSS buffer"
(lambda (a s v b) (org-rss-export-as-rss a s v)))
(?r "As RSS file" (lambda (a s v b) (org-rss-export-to-rss a s v)))
(?o "As RSS file and open"
(lambda (a s v b)
(if a (org-rss-export-to-rss t s v)
(org-open-file (org-rss-export-to-rss nil s v)))))))
:options-alist
'((:description "DESCRIPTION" nil nil newline)
(:keywords "KEYWORDS" nil nil space)
(:with-toc nil nil nil) ;; Never include HTML's toc
(:rss-extension "RSS_EXTENSION" nil org-rss-extension)
(:rss-image-url "RSS_IMAGE_URL" nil org-rss-image-url)
(:rss-feed-url "RSS_FEED_URL" nil nil t)
(:rss-categories nil nil org-rss-categories))
:filters-alist '((:filter-final-output . org-rss-final-function))
:translate-alist '((headline . org-rss-headline)
(comment . (lambda (&rest args) ""))
(comment-block . (lambda (&rest args) ""))
(timestamp . (lambda (&rest args) ""))
(plain-text . org-rss-plain-text)
(section . org-rss-section)
(template . org-rss-template)))
;;; Export functions
;;;###autoload
(defun org-rss-export-as-rss (&optional async subtreep visible-only)
"Export current buffer to an RSS buffer.
If narrowing is active in the current buffer, only export its
narrowed part.
If a region is active, export that region.
A non-nil optional argument ASYNC means the process should happen
asynchronously. The resulting buffer should be accessible
through the `org-export-stack' interface.
When optional argument SUBTREEP is non-nil, export the sub-tree
at point, extracting information from the headline properties
first.
When optional argument VISIBLE-ONLY is non-nil, don't export
contents of hidden elements.
Export is done in a buffer named \"*Org RSS Export*\", which will
be displayed when `org-export-show-temporary-export-buffer' is
non-nil."
(interactive)
(let ((file (buffer-file-name (buffer-base-buffer))))
(org-icalendar-create-uid file 'warn-user)
(org-rss-add-pubdate-property))
(org-export-to-buffer 'rss "*Org RSS Export*"
async subtreep visible-only nil nil (lambda () (text-mode))))
;;;###autoload
(defun org-rss-export-to-rss (&optional async subtreep visible-only)
"Export current buffer to an RSS file.
If narrowing is active in the current buffer, only export its
narrowed part.
If a region is active, export that region.
A non-nil optional argument ASYNC means the process should happen
asynchronously. The resulting file should be accessible through
the `org-export-stack' interface.
When optional argument SUBTREEP is non-nil, export the sub-tree
at point, extracting information from the headline properties
first.
When optional argument VISIBLE-ONLY is non-nil, don't export
contents of hidden elements.
Return output file's name."
(interactive)
(let ((file (buffer-file-name (buffer-base-buffer))))
(org-icalendar-create-uid file 'warn-user)
(org-rss-add-pubdate-property))
(let ((outfile (org-export-output-file-name
(concat "." org-rss-extension) subtreep)))
(org-export-to-file 'rss outfile async subtreep visible-only)))
;;;###autoload
(defun org-rss-publish-to-rss (plist filename pub-dir)
"Publish an org file to RSS.
FILENAME is the filename of the Org file to be published. PLIST
is the property list for the given project. PUB-DIR is the
publishing directory.
Return output file name."
(let ((bf (get-file-buffer filename)))
(if bf
(with-current-buffer bf
(org-icalendar-create-uid filename 'warn-user)
(org-rss-add-pubdate-property)
(write-file filename))
(find-file filename)
(org-icalendar-create-uid filename 'warn-user)
(org-rss-add-pubdate-property)
(write-file filename) (kill-buffer)))
(org-publish-org-to
'rss filename (concat "." org-rss-extension) plist pub-dir))
;;; Main transcoding functions
(defun org-rss-headline (headline contents info)
"Transcode HEADLINE element into RSS format.
CONTENTS is the headline contents. INFO is a plist used as a
communication channel."
(if (> (org-export-get-relative-level headline info) 1)
(org-export-data-with-backend headline 'html info)
(unless (org-element-property :footnote-section-p headline)
(let* ((email (org-export-data (plist-get info :email) info))
(author (and (plist-get info :with-author)
(let ((auth (plist-get info :author)))
(and auth (org-export-data auth info)))))
(htmlext (plist-get info :html-extension))
(hl-number (org-export-get-headline-number headline info))
(hl-home (file-name-as-directory (plist-get info :html-link-home)))
(hl-pdir (plist-get info :publishing-directory))
(hl-perm (org-element-property :RSS_PERMALINK headline))
(anchor (org-export-get-reference headline info))
(category (org-rss-plain-text
(or (org-element-property :CATEGORY headline) "") info))
(pubdate0 (org-element-property :PUBDATE headline))
(pubdate (let ((system-time-locale "C"))
(if pubdate0
(format-time-string
"%a, %d %b %Y %H:%M:%S %z"
(org-time-string-to-time pubdate0)))))
(title (org-rss-plain-text
(or (org-element-property :RSS_TITLE headline)
(replace-regexp-in-string
org-bracket-link-regexp
(lambda (m) (or (match-string 3 m)
(match-string 1 m)))
(org-element-property :raw-value headline))) info))
(publink
(or (and hl-perm (concat (or hl-home hl-pdir) hl-perm))
(concat
(or hl-home hl-pdir)
(file-name-nondirectory
(file-name-sans-extension
(plist-get info :input-file))) "." htmlext "#" anchor)))
(guid (if org-rss-use-entry-url-as-guid
publink
(org-rss-plain-text
(or (org-element-property :ID headline)
(org-element-property :CUSTOM_ID headline)
publink)
info))))
(if (not pubdate0) "" ;; Skip entries with no PUBDATE prop
(format
(concat
"<item>\n"
"<title>%s</title>\n"
"<link>%s</link>\n"
"<author>%s (%s)</author>\n"
"<guid isPermaLink=\"false\">%s</guid>\n"
"<pubDate>%s</pubDate>\n"
(org-rss-build-categories headline info) "\n"
"<description><![CDATA[%s]]></description>\n"
"</item>\n")
title publink email author guid pubdate contents))))))
(defun org-rss-build-categories (headline info)
"Build categories for the RSS item."
(if (eq (plist-get info :rss-categories) 'from-tags)
(mapconcat
(lambda (c) (format "<category><![CDATA[%s]]></category>" c))
(org-element-property :tags headline)
"\n")
(let ((c (org-element-property :CATEGORY headline)))
(format "<category><![CDATA[%s]]></category>" c))))
(defun org-rss-template (contents info)
"Return complete document string after RSS conversion.
CONTENTS is the transcoded contents string. INFO is a plist used
as a communication channel."
(concat
(format "<?xml version=\"1.0\" encoding=\"%s\"?>"
(symbol-name org-html-coding-system))
"\n<rss version=\"2.0\"
xmlns:content=\"http://purl.org/rss/1.0/modules/content/\"
xmlns:wfw=\"http://wellformedweb.org/CommentAPI/\"
xmlns:dc=\"http://purl.org/dc/elements/1.1/\"
xmlns:atom=\"http://www.w3.org/2005/Atom\"
xmlns:sy=\"http://purl.org/rss/1.0/modules/syndication/\"
xmlns:slash=\"http://purl.org/rss/1.0/modules/slash/\"
xmlns:georss=\"http://www.georss.org/georss\"
xmlns:geo=\"http://www.w3.org/2003/01/geo/wgs84_pos#\"
xmlns:media=\"http://search.yahoo.com/mrss/\">"
"<channel>"
(org-rss-build-channel-info info) "\n"
contents
"</channel>\n"
"</rss>"))
(defun org-rss-build-channel-info (info)
"Build the RSS channel information."
(let* ((system-time-locale "C")
(title (org-export-data (plist-get info :title) info))
(email (org-export-data (plist-get info :email) info))
(author (and (plist-get info :with-author)
(let ((auth (plist-get info :author)))
(and auth (org-export-data auth info)))))
(date (format-time-string "%a, %d %b %Y %H:%M:%S %z")) ;; RFC 882
(description (org-export-data (plist-get info :description) info))
(lang (plist-get info :language))
(keywords (plist-get info :keywords))
(rssext (plist-get info :rss-extension))
(blogurl (or (plist-get info :html-link-home)
(plist-get info :publishing-directory)))
(image (url-encode-url (plist-get info :rss-image-url)))
(ifile (plist-get info :input-file))
(publink
(or (plist-get info :rss-feed-url)
(concat (file-name-as-directory blogurl)
(file-name-nondirectory
(file-name-sans-extension ifile))
"." rssext))))
(format
"\n<title>%s</title>
<atom:link href=\"%s\" rel=\"self\" type=\"application/rss+xml\" />
<link>%s</link>
<description><![CDATA[%s]]></description>
<language>%s</language>
<pubDate>%s</pubDate>
<lastBuildDate>%s</lastBuildDate>
<generator>%s</generator>
<webMaster>%s (%s)</webMaster>
<image>
<url>%s</url>
<title>%s</title>
<link>%s</link>
</image>
"
title publink blogurl description lang date date
(concat (format "Emacs %d.%d"
emacs-major-version
emacs-minor-version)
" Org-mode " (org-version))
email author image title blogurl)))
(defun org-rss-section (section contents info)
"Transcode SECTION element into RSS format.
CONTENTS is the section contents. INFO is a plist used as
a communication channel."
contents)
(defun org-rss-timestamp (timestamp contents info)
"Transcode a TIMESTAMP object from Org to RSS.
CONTENTS is nil. INFO is a plist holding contextual
information."
(org-html-encode-plain-text
(org-timestamp-translate timestamp)))
(defun org-rss-plain-text (contents info)
"Convert plain text into RSS encoded text."
(let (output)
(setq output (org-html-encode-plain-text contents)
output (org-export-activate-smart-quotes
output :html info))))
;;; Filters
(defun org-rss-final-function (contents backend info)
"Prettify the RSS output."
(with-temp-buffer
(xml-mode)
(insert contents)
(indent-region (point-min) (point-max))
(buffer-substring-no-properties (point-min) (point-max))))
;;; Miscellaneous
(defun org-rss-add-pubdate-property ()
"Set the PUBDATE property for top-level headlines."
(let (msg)
(org-map-entries
(lambda ()
(let* ((entry (org-element-at-point))
(level (org-element-property :level entry)))
(when (= level 1)
(unless (org-entry-get (point) "PUBDATE")
(setq msg t)
(org-set-property
"PUBDATE" (format-time-string
(cdr org-time-stamp-formats)))))))
nil nil 'comment 'archive)
(when msg
(message "Property PUBDATE added to top-level entries in %s"
(buffer-file-name))
(sit-for 2))))
(provide 'ox-rss)
;;; ox-rss.el ends here
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment