2020-05-26 ,

org-modeのHTMLエクスポート時にimgタグのalt属性をcaptionからつける

org-mode文書をHTMLでエクスポートしたとき、imgタグのalt=属性(代替テキスト)はデフォルトで画像のファイル名になります。

<img src="./suica.jpg" alt="suica.jpg" />

altがファイル名というのはなんとも気が利かない感じがします。 altを任意の文字列にする方法は Images and XHTML export に書かれていて、画像リンクの前に #+ATTR_HTML: :alt 文字列 と書きます。

#+ATTR_HTML: :alt おいしそうなすいか
[[file:./suica.jpg]]

一方画像にはキャプションを付けることが可能で、画像リンクの前に #+CAPTION: 文字列 と書くと画像の下に「図1:文字列」のようなキャプションがつきます。

#+CAPTION: おいしそうなすいか
#+ATTR_HTML: :alt おいしそうなすいか
[[file:./suica.jpg]]

キャプションには大抵は画像の内容を要約した文が書かれますから、altもキャプションと同じで良いのではないでしょうか? キャプションがあるのだからそもそも代替テキストは不要なのではないか(画像が表示されないときはキャプションを読めば良いのではないか)という気がしなくもありませんが、一応つけるとしたらキャプションと同じ内容で十分なことは多いでしょう。

#+CAPTION:#+ATTR_HTML: の両方を書くのは面倒ですしミスも発生します。私は先に #+CAPTION: だけ使って文書を書き上げてから正規表現置換で #+ATTR_HTML: を生成していたのですが、生成後に #+CAPTION: を修正したときに #+ATTR_HTML: を修正し忘れることが何度もありました。

というわけでエクスポート時にaltを #+CAPTION: から決めるようにするのが次のコードです。

(defun org-altcaption--get-caption (paragraph info)
  "段落に設定されているキャプション文字列を取得する。

org-html-paragraph関数内の「;; Standalone image.」の部分より。"
  (if paragraph
      (let ((raw (org-export-data (org-export-get-caption paragraph) info)))
        (if (org-string-nw-p raw) raw nil))))

(defun org-altcaption--get-link-parent (link info)
"linkの親要素を取得する。ただし、linkが最初のリンクでない場合はnil。

org-html-link関数内より。"
  ;; Extract caption 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))
         parent)))

(defvar org-altcaption--link nil)

(defun org-altcaption--org-html-link (old-func link desc info)
  "org-html-linkに対するaround advice"
  ;; Pass link to org-altcaption--org-html--format-image function
  (let ((org-altcaption--link link))
    (funcall old-func link desc info)))

(defun org-altcaption--org-html--format-image (old-func source attributes info)
  "org-html--format-imageに対するaround advice"
  ;; Add alt attribute if link has caption
  (if (and org-altcaption--link (null (plist-get attributes :alt)))
      (let ((caption (org-altcaption--get-caption (org-altcaption--get-link-parent org-altcaption--link info) info)))
        (when caption
          (setq attributes (plist-put attributes :alt caption))
          ;;(message "caption=%s" caption)
          )))
  ;; Call original function
  (funcall old-func source attributes info))


(defun org-altcaption-activate ()
  (interactive)
  (advice-add #'org-html-link :around #'org-altcaption--org-html-link)
  (advice-add #'org-html--format-image :around #'org-altcaption--org-html--format-image))

(defun org-altcaption-deactivate ()
  (interactive)
  (advice-remove #'org-html-link #'org-altcaption--org-html-link)
  (advice-remove #'org-html--format-image #'org-altcaption--org-html--format-image))

org-altcaption-activate で有効化、 org-altcaption-deactivate で無効化します。adviceを使って既存の関数をフックしているのでorgのバージョンが上がると動かなくなるかもしれません(9.3.6で確認)。

こんなこと簡単に実現できるだろう、と思いきや結構難しいんですこれが。Orgの文法のうまくできていないところに片足を突っ込んでいる感じです。

Orgでは個別のリンク一つ一つにプロパティを指定することが困難です。例えば段落中のリンクに target="_blank" 属性を設定したい(リンク先を別のウィンドウで開きたい)場合次のように書くのですが

#+ATTR_HTML: :target _blank
山といえば [[https://www.pref.yamanashi.jp/][山梨県]] と [[http://www.pref.shizuoka.jp/index.html][静岡県]] 。富士山にまたがるこの二つの県は……

この場合最初のリンク(山梨県)にしか target="_blank" は設定されません。次のように書くと当然二つの段落に分かれてしまいます。

#+ATTR_HTML: :target _blank
山といえば [[https://www.pref.yamanashi.jp/][山梨県]] と
#+ATTR_HTML: :target _blank
[[http://www.pref.shizuoka.jp/index.html][静岡県]] 。富士山にまたがるこの二つの県は……

属性はリンクに設定されるのではなく段落に対して設定されます。リンクをエクスポートするときは、リンクを包む親要素(段落)に対する属性指定を調べてそれを適用します。属性はリンクについているのではなく親要素についています。これはキャプションも同じで、親要素を調べなければキャプション文字列は分かりません。さらに属性が適用されるのは段落中の最初のリンクのみ。二つ目以降のリンクには適用されない仕様です。キャプションの場合はスタンドアロンな画像(行内にある画像ではなくブロックを形成する画像)にしかキャプションは付けられない仕様なのであまり気にする必要は無いのかもしれませんが、いちおう二つ目の画像リンクには適用しない方がいいでしょう。

ということをするのが org-altcaption--get-link-parent 関数になります。このロジックは org-html-link 関数(ox-html.el)の中にあるものを拝借しました。

リンクそのものにプロパティを指定する文法の提案をどこかで見かけたような気がするのですがうろ覚え。

おいしそうなすいか
図1: おいしそうなすいか