elispによるHTML文書の目次生成

AKIYAMA Kouhei

2002年10月11日

追記

2004-01-27矢代武嗣さんのページリンク自動生成機能を追加したhtmlcontents.elが公開されています。本記事で課題として棚上げされている「目次と本文との間をaタグでつなげる機能」を実現してくださいました。感謝。スクリプト置き場のページから入手出来ますので、web検索等でたどり着いた方は是非お試しください。リンクを張るために章番号が必須になっているので、そのあたりは注意しましょう。
2004-01-27本文中のコードとhtmlcontents.elの取り扱いについてはGPLに従ってください。

1.概要

html文書を書いていると最後に目次を作るのがとても面倒くさく感じます。自動的に作らせたい、それもEmacs上でできれば言うことない、と思い良いプログラムがないかwww上を検索してみましたが見つかりませんでした。そこでelispの勉強もかねて自分で作ることにしました。

この文書ではそうして作られたHTML文書の目次を生成するelispプログラムを紹介します。このプログラムはHTML文書内のH要素を拾い出し、それを元に目次をHTMLのリスト(OL, LI)で出力します。

2.コード

作成したコードを以下に示します。

;;; h要素を検索します。
;;; マッチした文字列はmatch-beginning等を使って取得してください。
;;; 検索に失敗したらmake-html-contents-search-endをtにします。
(defun make-html-contents-search ()
  (setq make-html-contents-search-end
	(not
	 (re-search-forward
	  "<h\\([0-9]\\)>\\(\\([0-9]+\\.\\)*\\)\\(.+\\)</h[0-9]>"
	  nil t)))
)

;;; バッファ全体からh要素のツリーを作成します。
(defun make-html-contents-tree ()
  (save-excursion
    (goto-char (point-min))
    (let ((make-html-contents-search-end))
      (make-html-contents-search)
      (if make-html-contents-search-end
	  nil
	(make-html-contents-node)))))



;;;この関数が返すリストは(h h-num h-name child...)です。
;;;例:(1 "" "test" (2 "1." "a") (2 "2." "b"))
;;;この関数が呼び出される前にmake-html-contents-searchが呼び出されていること。
;;;この関数から帰ったときは直前にmake-html-contents-searchが呼び出されていると考えて良い。
;;;その場合、検索に失敗している場合もあるので、make-html-contents-search-endもチェックすること。
(defun make-html-contents-node ()
  ;;自分についての情報をh-nodeに記録する
  (let* (
	 (h
	  (string-to-number
	   (buffer-substring (match-beginning 1) (match-end 1))))
	 (h-node
	  (list
	   h;階層
	   (buffer-substring (match-beginning 2) (match-end 2));番号
	   (buffer-substring (match-beginning 4) (match-end 4));名前
	   )))
    ;;子についての情報をh-nodeの末尾に追加していく
    (make-html-contents-search)
    (while
	(and
	 (not make-html-contents-search-end)
	 (< h
	    (string-to-number
	     (buffer-substring (match-beginning 1) (match-end 1)))));階層
      (setq h-node (append h-node (list (make-html-contents-node))))
      )
    
    h-node
    )
)

(defun make-html-contents-print (node)
  (if node
      (progn
	(insert "<li>")
	;;(insert (nth 1 node))
	(insert (nth 2 node))
	(let ((p (nthcdr 3 node)))
	  (if p
	      (progn
		(insert "\n<ol>\n")
		(while p
		  (make-html-contents-print (car p))
		  (setq p (cdr p))
		  )
		(insert "</ol>")
		)
	    )
	  )
	(insert "</li>\n")
	)))


(defun make-html-contents ()
  "バッファ内のhtml文書のh要素を調べて目次を生成します。"
  (interactive)
  ;;ツリー作成
  (let ((tree (make-html-contents-tree)))
    ;;htmlリスト出力
    (if tree
	(progn
	  (insert "<ul>\n")
	  (make-html-contents-print tree)
	  (insert "</ul>\n")
	  )
      )
    )
  )
    

3.使用方法

例えば以下のようなHTML文書があったとします。

<h1>test</h1>
<h2>1.aaaa</h2>
<h3>1.1.bbbb</h3>
<h4>1.1.1.cccc</h4>
<h4>1.1.2.dddd</h4>
<h3>1.2.eeee</h3>
<h2>2.ffff</h2>
<h2>3.gggg</h2>
<h2>4.hhhh</h2>
<h2>5.iiii</h2>
<h2>6.jjjj</h2>
    

ここから目次を生成するには以下のようにします。

  1. htmlcontents.elをロードする。
  2. 目次を挿入したい場所にポイントを移動する。
  3. M-x make-html-contentsと入力する。

この手順を実行すると以下のような出力が得られます。インデントが気に入らなければhtml-helper-mode等でindent-regionをすればよいでしょう。

<ul>
<li>test
<ol>
<li>aaaa
<ol>
<li>bbbb
<ol>
<li>cccc</li>
<li>dddd</li>
</ol></li>
<li>eeee</li>
</ol></li>
<li>ffff</li>
<li>gggg</li>
<li>hhhh</li>
<li>iiii</li>
<li>jjjj</li>
</ol></li>
</ul>
    

4.解説

make-html-contents関数は内部から以下の二つの関数を呼びます。

make-html-contents-treeはhtml文書内のH要素を検索し、H要素の親子関係を表すツリーを作成します。例えば使用方法のところで示したhtml文書の場合、make-html-contents-treeは以下のようなツリー(リスト)を返します。

(1 "" "test"
  (2 "1." "aaaa"
    (3 "1.1." "bbbb"
      (4 "1.1.1." "cccc")
      (4 "1.1.2." "dddd"))
    (3 "1.2." "eeee"))
  (2 "2." "ffff")
  (2 "3." "gggg")
  (2 "4." "hhhh")
  (2 "5." "iiii")
  (2 "6." "jjjj"))
    

一方のmake-html-contents-printはmake-html-contents-treeが返すツリーをHTML形式でバッファに出力する関数です。

make-html-contentsはmake-html-contents-treeを呼び出し、返ってきたツリーをmake-html-contents-printに渡すだけです。こうして目次の生成が行われます。

残り二つの関数make-html-contents-nodeとmake-html-contents-searchはmake-html-contents-treeから呼び出されます。

make-html-contents-nodeはツリーの一つのノード以下を作成する関数です。この関数が再帰的に呼び出されることでツリー全体が構築されます。

make-html-contents-searchはH要素を探し出すre-search-forward呼び出しを行います。

5.課題

課題を以下に挙げます。そのうち何とかしましょう。これらが無ければどうしようもない気がしますが……。


Last modified: 2004-01-27 23:26:02+0900