Markdownへのexport時にtimestampがHTMLになるのを避けたい Markdown Emacs OrgMode
Org Modeで文章を書いているときは、キーバインド C-c . 1で現在の時刻や日付を簡単に挿入できます。
<2023-11-21 火>
さらに、 C-c C-e 2から様々な形式の文書にExportできます。 もちろん、Markdown形式にExportすることもできます。
日付のExport問題
残念なことに、Org文章に挿入した日付は、MarkdownにExportするとHTMLタグになってしまいます。
試しに、日付を含む次のOrgファイルをMarkdownにExportしてみます。
* Exporting a date <2023-11-20>
org-export-string-as
を使うと引数の文字列をExport対象として渡すことができます。
<<sample.org>>
には上の文章が入ると思ってください。
(org-export-string-as <<sample.org>> 'md)
# Exporting a date <span class="timestamp-wrapper"><span class="timestamp"><2023-11-20 Mon></span></span>
Markdown用に定義されていないTranscoder関数がある場合、 継承元のHTML用の関数が変換に利用される仕様になっています。日付はその一例です。 Plain Textに変換されるならまだしも、可読性の低いHTMLに変換されてしまうのは避けたいところです。
Markdown用のExport Backendを探す
Export用の設定や変換用の関数(Transcoder)が定義されたまとまりはBackendとして構造化されています。
org-export-get-backend
関数を使って目当てのBackendの内容を確認することができます。
Markdownは md
というBackend名で登録されています。
(org-export-get-backend 'md)
org-export-backend md
#s(org-export-backend md html ((bold . org-md-bold) (center-block . org-md--convert-to-html) (code . org-md-verbatim) (drawer . org-md--identity) (dynamic-block . org-md--identity) (example-block . org-md-example-block) (export-block . org-md-export-block) (fixed-width . org-md-example-block) (headline . org-md-headline) (horizontal-rule . org-md-horizontal-rule) (inline-src-block . org-md-verbatim) (inlinetask . org-md--convert-to-html) (inner-template . org-md-inner-template) (italic . org-md-italic) (item . org-md-item) (keyword . org-md-keyword) (latex-environment . org-md-latex-environment) (latex-fragment . org-md-latex-fragment) (line-break . org-md-line-break) (link . org-md-link) (node-property . org-md-node-property) (paragraph . org-md-paragraph) (plain-list . org-md-plain-list) (plain-text . org-md-plain-text) (property-drawer . org-md-property-drawer) (quote-block . org-md-quote-block) (section . org-md-section) (special-block . org-md--convert-to-html) (src-block . org-md-example-block) (table . org-md--convert-to-html) (template . org-md-template) (verbatim . org-md-verbatim)) ((:md-footnote-format nil nil org-md-footnote-format) (:md-footnotes-section nil nil org-md-footnotes-section) (:md-headline-style nil nil org-md-headline-style) (:md-toplevel-hlevel nil nil org-md-toplevel-hlevel)) ((:filter-parse-tree . org-md-separate-elements)) nil (109 "Export to Markdown" ((77 "To temporary buffer" (lambda (a s v b) (org-md-export-as-markdown a s v))) (109 "To file" (lambda (a s v b) (org-md-export-to-markdown a s v))) (111 "To file and open" (lambda (a s v b) (if a (org-md-export-to-markdown t s v) (org-open-file (org-md-export-to-markdown nil s v))))))))
timestamp用のTranscoderを追加する
timestamp用のTranscoderの実装は org-html-timestamp
が参考になります。
https://orgmode.org/worg/dev/org-export-reference.html(defun org-html-timestamp (timestamp _contents info) "Transcode a TIMESTAMP object from Org to HTML. CONTENTS is nil. INFO is a plist holding contextual information." (let ((value (org-html-plain-text (org-timestamp-translate timestamp) info))) (format "<span class=\"timestamp-wrapper\"><span class=\"timestamp\">%s</span></span>" (replace-regexp-in-string "--" "–" value))))
Markdown用に org-md-timestamp
を定義します。
timestampを受け取ってverbatim( `<date>`
)に変換することにします。
(defun org-md-timestamp (timestamp _contents info) "Transcode a TIMESTAMP object from Org to Markdown. CONTENTS is nil. INFO is a plist holding contextual information." (format "`%s`" (plist-get (car (cdr timestamp)) :raw-value)))
定義したTranscoderをmdのBackendに追加します。
Backendは org-export-backend
という構造体なので、
org-export-backend-transcoders
を使ってTranscoder一覧の取得や更新ができます。
(let* ((be (org-export-get-backend 'md)) (tr (org-export-backend-transcoders be))) ;; timestamp用のTranscoderを追加する (push '(timestamp . org-md-timestamp) tr) ;; Transcoderを追加したリストでBackendを上書きする (setf (org-export-backend-transcoders be) tr))
((timestamp . org-md-timestamp)...
((timestamp . org-md-timestamp) (bold . org-md-bold) (center-block . org-md--convert-to-html) (code . org-md-verbatim) (drawer . org-md--identity) (dynamic-block . org-md--identity) (example-block . org-md-example-block) (export-block . org-md-export-block) (fixed-width . org-md-example-block) (headline . org-md-headline) (horizontal-rule . org-md-horizontal-rule) (inline-src-block . org-md-verbatim) (inlinetask . org-md--convert-to-html) (inner-template . org-md-inner-template) (italic . org-md-italic) (item . org-md-item) (keyword . org-md-keyword) (latex-environment . org-md-latex-environment) (latex-fragment . org-md-latex-fragment) (line-break . org-md-line-break) (link . org-md-link) (node-property . org-md-node-property) (paragraph . org-md-paragraph) (plain-list . org-md-plain-list) (plain-text . org-md-plain-text) (property-drawer . org-md-property-drawer) (quote-block . org-md-quote-block) (section . org-md-section) (special-block . org-md--convert-to-html) (src-block . org-md-example-block) (table . org-md--convert-to-html) (template . org-md-template) (verbatim . org-md-verbatim))
(timestamp . org-md-timestamp)
が先頭に追加されました。
試す
この状態で、冒頭のsample.orgをもう一度MarkdownにExportしてみます。
(org-export-string-as <<sample.org>> 'md)
# Exporting a date `<2023-11-20>`
いい感じにExportされました。
ちなみにox-gfm(GitHub Flavored Markdown)はmdを継承しているので、この修正のおかげで同様の出力を得られます。
(org-export-string-as <<sample.org>> 'gfm)
# Exporting a date `<2023-11-20>`
まとめ
init.el
に記載する場合は次のようにすると良いでしょう。
(with-eval-after-load 'ox-md (defun org-md-timestamp (timestamp _contents info) "Transcode a TIMESTAMP object from Org to Markdown. CONTENTS is nil. INFO is a plist holding contextual information." (format "`%s`" (plist-get (car (cdr timestamp)) :raw-value))) (let* ((be (org-export-get-backend 'md)) (tr (org-export-backend-transcoders be))) ;; timestamp用のTranscoderを追加する (push '(timestamp . org-md-timestamp) tr) ;; Transcoderを追加したリストでBackendを上書きする (setf (org-export-backend-transcoders be) tr)))