;;; ox-my-html.el --- HTML Derived Backend -*- lexical-binding: t; -*-
;; Copyright (C) 2011-2023 Free Software Foundation, Inc.
;; Author: Carsten Dominik
;; Nicolas Goaziou
;; Maintainer: Gerard Vermeulen
;; Keywords: org, hypermedia
;; This file is part of GNU Emacs.
;; GNU Emacs 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.
;; GNU Emacs 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 .
;;; Commentary:
;; This library implements a HTML derived backend for Org export:
;; 1. The Org ox-beamer library shows how to code derived backends.
;; 2. The function org-my-html-link is an edited org-html-link copy.
;; 3. It borrows code from https://emacs.stackexchange.com/a/57433 to
;; embed SVG images in exported HTML files.
;;; Code:
(require 'org-macs)
(org-assert-version)
(require 'cl-lib)
(require 'ox-html)
(require 'nxml-mode)
;;; Define Backend
(org-export-define-derived-backend 'my-html 'html
:menu-entry
'(?h 1
((?M "As my-HTML buffer" org-my-html-export-as-html)
(?m "As my-HTML file" org-my-html-export-to-html)
(?O "As my-HTML file and open"
(lambda (a s v b)
(if a (org-my-html-export-to-html t s v b)
(org-open-file (org-my-html-export-to-html nil s v b)))))))
:options-alist
'((:with-html-svg-embedding "HTML_EMBED_SVG" ;; groks nil as "nil" in Org file!
"html-embed-svg" org-html-embed-svg))
:translate-alist
'((link . org-my-html-link)))
;;; User Configuration Variables
;;;; Links :: Embed SVG
(defcustom org-html-embed-svg nil
"Non-nil means embed SVG images into exported my-HTML pages,
otherwise link to SVG images from exported my-HTML pages. Use
#+OPTIONS: html-embed-svg:t
or
#+OPTIONS: html-embed-svg:nil
to enable or disable SVG embedding in Org files."
:group 'org-export-html
:version "30.0"
:type 'boolean)
;;; Transcode Functions
;;;; Link
(defun org--my-html-embed-svg-p (link)
"Check whether LINK links to an SVG file to embed."
(and (not (org-element-contents link))
(let ((case-fold-search t))
(string-match-p ".svg\\'" (org-element-property :path link)))))
(defun org--my-html-svg-contents (path)
"Return the SVG contents of the file named PATH."
;; https://emacs.stackexchange.com/a/57433
(with-temp-buffer
(insert-file-contents path)
(with-syntax-table nxml-mode-syntax-table
;; Barf if contents contains no valid SVG.
(while (and (search-forward " (length html-ext) 0) "."))
(link-org-files-as-html-maybe
(lambda (raw-path info)
;; Treat links to `file.org' as links to `file.html', if
;; needed. See `org-html-link-org-files-as-html'.
(save-match-data
(cond
((and (plist-get info :html-link-org-files-as-html)
(let ((case-fold-search t))
(string-match "\\(.+\\)\\.org\\(?:\\.gpg\\)?$" raw-path)))
(concat (match-string 1 raw-path) dot html-ext))
(t raw-path)))))
(type (org-element-property :type link))
(raw-path (org-element-property :path link))
;; Ensure DESC really exists, or set it to nil.
(desc (org-string-nw-p desc))
(path
(cond
((member type '("http" "https" "ftp" "mailto" "news"))
(url-encode-url (concat type ":" raw-path)))
((string= "file" type)
;; During publishing, turn absolute file names belonging
;; to base directory into relative file names. Otherwise,
;; append "file" protocol to absolute file name.
(setq raw-path
(org-export-file-uri
(org-publish-file-relative-name raw-path info)))
;; Possibly append `:html-link-home' to relative file
;; name.
(let ((home (and (plist-get info :html-link-home)
(org-trim (plist-get info :html-link-home)))))
(when (and home
(plist-get info :html-link-use-abs-url)
(file-name-absolute-p raw-path))
(setq raw-path (concat (file-name-as-directory home) raw-path))))
;; Maybe turn ".org" into ".html".
(setq raw-path (funcall link-org-files-as-html-maybe raw-path info))
;; Add search option, if any. A search option can be
;; relative to a custom-id, a headline title, a name or
;; a target.
(let ((option (org-element-property :search-option link)))
(if (not option) raw-path
(let ((path (org-element-property :path link)))
(concat raw-path
"#"
(org-publish-resolve-external-link option path t))))))
(t raw-path)))
(attributes-plist
(org-combine-plists
;; Extract attributes from parent's paragraph. HACK: Only
;; do this for the first link in parent (inner image link
;; for inline images). This is needed as long as
;; attributes cannot be set on a per link basis.
(let* ((parent (org-export-get-parent-element link))
(link (let ((container (org-export-get-parent link)))
(if (and (eq 'link (org-element-type container))
(org-html-inline-image-p link info))
container
link))))
(and (eq link (org-element-map parent 'link #'identity info t))
(org-export-read-attribute :attr_html parent)))
;; Also add attributes from link itself. Currently, those
;; need to be added programmatically before `org-my-html-link'
;; is invoked, for example, by backends building upon HTML
;; export.
(org-export-read-attribute :attr_html link)))
(attributes
(let ((attr (org-html--make-attribute-string attributes-plist)))
(if (org-string-nw-p attr) (concat " " attr) ""))))
(cond
;; Link type is handled by a special function.
((org-export-custom-protocol-maybe link desc 'html info))
;; Embed SVG.
((and (plist-get info :with-html-svg-embedding)
(org--my-html-embed-svg-p link))
(org--my-html-svg-contents path))
;; Image file.
((and (plist-get info :html-inline-images)
(org-export-inline-image-p
link (plist-get info :html-inline-image-rules)))
(org-html--format-image path attributes-plist info))
;; Radio target: Transcode target's contents and use them as
;; link's description.
((string= type "radio")
(let ((destination (org-export-resolve-radio-link link info)))
(if (not destination) desc
(format "%s "
(org-export-get-reference destination info)
attributes
desc))))
;; Links pointing to a headline: Find destination and build
;; appropriate referencing command.
((member type '("custom-id" "fuzzy" "id"))
(let ((destination (if (string= type "fuzzy")
(org-export-resolve-fuzzy-link link info)
(org-export-resolve-id-link link info))))
(pcase (org-element-type destination)
;; ID link points to an external file.
(`plain-text
(let ((fragment (concat org-html--id-attr-prefix path))
;; Treat links to ".org" files as ".html", if needed.
(path (funcall link-org-files-as-html-maybe
destination info)))
(format "%s "
path fragment attributes (or desc destination))))
;; Fuzzy link points nowhere.
(`nil
(format "%s "
(or desc
(org-export-data
(org-element-property :raw-link link) info))))
;; Link points to a headline.
(`headline
(let ((href (org-html--reference destination info))
;; What description to use?
(desc
;; Case 1: Headline is numbered and LINK has no
;; description. Display section number.
(if (and (org-export-numbered-headline-p destination info)
(not desc))
(mapconcat #'number-to-string
(org-export-get-headline-number
destination info) ".")
;; Case 2: Either the headline is un-numbered or
;; LINK has a custom description. Display LINK's
;; description or headline's title.
(or desc
(org-export-data
(org-element-property :title destination) info)))))
(format "%s " href attributes desc)))
;; Fuzzy link points to a target or an element.
(_
(if (and destination
(memq (plist-get info :with-latex) '(mathjax t))
(eq 'latex-environment (org-element-type destination))
(eq 'math (org-latex--environment-type destination)))
;; Caption and labels are introduced within LaTeX
;; environment. Use "ref" or "eqref" macro, depending on user
;; preference to refer to those in the document.
(format (plist-get info :html-equation-reference-format)
(org-html--reference destination info))
(let* ((ref (org-html--reference destination info))
(org-html-standalone-image-predicate
#'org-html--has-caption-p)
(counter-predicate
(if (eq 'latex-environment (org-element-type destination))
#'org-html--math-environment-p
#'org-html--has-caption-p))
(number
(cond
(desc nil)
((org-html-standalone-image-p destination info)
(org-export-get-ordinal
(org-element-map destination 'link #'identity info t)
info '(link) 'org-html-standalone-image-p))
(t (org-export-get-ordinal
destination info nil counter-predicate))))
(desc
(cond (desc)
((not number) "No description for this link")
((numberp number) (number-to-string number))
(t (mapconcat #'number-to-string number ".")))))
(format "%s " ref attributes desc)))))))
;; Coderef: replace link with the reference name or the
;; equivalent line number.
((string= type "coderef")
(let ((fragment (concat "coderef-" (org-html-encode-plain-text path))))
(format "%s "
fragment
(format "class=\"coderef\" onmouseover=\"CodeHighlightOn(this, \
'%s');\" onmouseout=\"CodeHighlightOff(this, '%s');\""
fragment fragment)
attributes
(format (org-export-get-coderef-format path desc)
(org-export-resolve-coderef path info)))))
;; External link with a description part.
((and path desc)
(format "%s "
(org-html-encode-plain-text path)
attributes
desc))
;; External link without a description part.
(path
(let ((path (org-html-encode-plain-text path)))
(format "%s " path attributes path)))
;; No path, only description. Try to do something useful.
(t
(format "%s " desc)))))
;;; End-user functions
;;;###autoload
(defun org-my-html-export-as-html
(&optional async subtreep visible-only body-only ext-plist)
"Export current buffer to a MY-HTML 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.
When optional argument BODY-ONLY is non-nil, only write code
between \"\" and \"\" tags.
EXT-PLIST, when provided, is a property list with external
parameters overriding Org default settings, but still inferior to
file-local settings.
Export is done in a buffer named \"*Org HTML PLUS Export*\", which
will be displayed when `org-export-show-temporary-export-buffer'
is non-nil."
(interactive)
(org-export-to-buffer 'my-html "*Org MY-HTML Export*"
async subtreep visible-only body-only ext-plist
(lambda () (set-auto-mode t))))
;;;###autoload
(defun org-my-html-export-to-html
(&optional async subtreep visible-only body-only ext-plist)
"Export current buffer to a MY-HTML 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.
When optional argument BODY-ONLY is non-nil, only write code
between \"\" and \"\" tags.
EXT-PLIST, when provided, is a property list with external
parameters overriding Org default settings, but still inferior to
file-local settings.
Return output file's name."
(interactive)
(let* ((extension (concat
(when (> (length org-html-extension) 0) ".")
(or (plist-get ext-plist :html-extension)
org-html-extension
"html")))
(file (org-export-output-file-name extension subtreep))
(org-export-coding-system org-html-coding-system))
(org-export-to-file 'my-html file
async subtreep visible-only body-only ext-plist)))
(provide 'ox-my-html)
;;; ox-my-html.el ends here