Last update: 2023-11-22

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">&lt;2023-11-20 Mon&gt;</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 が参考になります。

(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 "--" "&#x2013;" value))))
https://orgmode.org/worg/dev/org-export-reference.html

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)))

脚注

1

C-c . または M-x org-time-stamp

2

C-c C-e または M-x org-export-dispatch