読者です 読者をやめる 読者になる 読者になる

org-modeで他形式へのエクスポート時の処理をカスタマイズする

2014/02/28 設定に不足ありまして追記しました。すいません。

最近ようやくorg-modeを触り始めています。
手始めにGithubリポジトリのREADMEをorg形式で書いてみたのですが、
コードハイライトが効いていなくて、どうしようかとしばし瞑想したところ、
org-modeには他形式への変換機能があることを思い出し、markdown形式へ変換してみました。
(markdown形式への変換はバージョン忘れましたが、最近のから可能になったとのこと。)

大体うまく変換できてる感じでしたが、
コードブロックがハイライトが効かない記述(行頭にスペース4つ)でした。
ハイライトが効く記述(バッククォート3つ)は純正の仕様ではなかったと思ったので、 当然だろうなという感じはしました。
が、そうは言ってもハイライト無いのは見づらいし、いちいち手直しするのも 自分の性格上耐え切れなくなるのは目に見えていたので、変換の処理を眺めて方法を模索しました。

結論

以下の設定を記述することで実現できました。

(add-to-list 'org-export-backends 'md)
(require 'ox)

(defun ~org-md-src-block (src-block contents info)
  (let* ((lang (org-element-property :language src-block))
         (lang (cond ((not (stringp lang))   "")
                     ((string= lang "cperl") "perl")
                     (t                      lang)))
         (value (org-element-property :value src-block)))
    (format "```%s\n%s```" lang value)))

(dolist (b org-export--registered-backends)
  (when (eq (org-export-backend-name b) 'md)
    (let* ((tr-alist (org-export-backend-transcoders b))
           (src (assq 'src-block tr-alist))
           (tr-alist (delq src tr-alist)))
      (push '(src-block . ~org-md-src-block) tr-alist)
      (setf (org-export-backend-transcoders b) tr-alist))))

設定の内容

dolistの部分

変換処理を実施するバックエンドの定義はorg-export-backendという構造体に保持されており、
org-export--registered-backendsにそれらは格納されています。
で、文書内の各構成要素の変換は、org-export-backendのtranscodersプロパティの値に沿って実施されます。
で、markdownへの変換は以下のように定義されていました。

(org-export-define-derived-backend 'md 'html
  :export-block '("MD" "MARKDOWN")
  :filters-alist '((:filter-parse-tree . org-md-separate-elements))
  :menu-entry
  '(?m "Export to Markdown"
       ((?M "To temporary buffer"
        (lambda (a s v b) (org-md-export-as-markdown a s v)))
    (?m "To file" (lambda (a s v b) (org-md-export-to-markdown a s v)))
    (?o "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)))))))
  :translate-alist '((bold . org-md-bold)
             (code . org-md-verbatim)
             (comment . (lambda (&rest args) ""))
             (comment-block . (lambda (&rest args) ""))
             (example-block . org-md-example-block)
             (fixed-width . org-md-example-block)
             (footnote-definition . ignore)
             (footnote-reference . ignore)
             (headline . org-md-headline)
             (horizontal-rule . org-md-horizontal-rule)
             (inline-src-block . org-md-verbatim)
             (inner-template . org-md-inner-template)
             (italic . org-md-italic)
             (item . org-md-item)
             (line-break . org-md-line-break)
             (link . org-md-link)
             (paragraph . org-md-paragraph)
             (plain-list . org-md-plain-list)
             (plain-text . org-md-plain-text)
             (quote-block . org-md-quote-block)
             (quote-section . org-md-example-block)
             (section . org-md-section)
             (src-block . org-md-example-block)
             (template . org-md-template)
             (verbatim . org-md-verbatim)))

translate-alist引数の値がtranscodersプロパティに格納されます。
各構成要素を表すシンボルに対応した関数が変換の際に呼び出される感じで、
今回変更したいコードブロックの処理は、src-blockキーの値であるorg-md-example-blockが
実施しているとわかりました。
この関数は他の要素の変換でも呼ばれているので、adviceで挙動を変えるのではなく
別の関数を呼び出すように変更することにしました。

defunの部分

で、その関数の定義をどうすれば良いか知るために、org-md-example-blockの定義を見ると 以下のようになっていました。

(defun org-md-example-block (example-block contents info)
  "Transcode EXAMPLE-BLOCK element into Markdown format.
CONTENTS is nil.  INFO is a plist used as a communication
channel."
  (replace-regexp-in-string
   "^" "    "
   (org-remove-indentation
    (org-element-property :value example-block))))

処理内容から変換した文字列を返せば良いとわかりました。
言語名の部分(#+BEGIN_SRC xxx)の取得方法がわかりませんでしたが、 example-blockをデバッグ出力したらlanguage属性に格納されているとわかりました。

あと、私の環境ではPerlのハイライトは#+BEGIN_SRCにcperlを指定しないとダメなので、
cperlだったらperlに直す処理も追加してます。