Jintrick.netagenda2007年06月アーカイブ → 2007年06月25日

邦訳HTML文書支援#03 目次生成

今回のスクリプトの肝は、HTMLHeadingElementたちを"document order"で生成するジェネレータと、そのジェネレータのnext()をfor each文で呼んでツリー構造を持ったリストを作成する関数とを分離し、よりパフォーマンスの高いジェネレータを選択可能にした点にある。

document.createTableOfContents = function(// may throw StopIteration when there is no headings.
	/* Headings generator */headings){ 
	var h = headings.next();
	return UL(h);
	function UL(h){
		var ul = document.createElement("UL");
		var li = LI(h);
		ul.appendChild(li);
		for each (let h_ in headings) {
			if (h.level == h_.level) {
				li = LI(h_);
				ul.appendChild(li);
			} else if (h.level < h_.level) {
				li.appendChild(UL(h_));
			} else {
				break;
			}
		}
		return ul;
	}
	function LI(h){
		var li = document.createElement("LI");
		li.appendChild(document.createTextNode(h.textContent).linkTo(h));
		return li;
	}
};

ただし見出し要素を生成するジェネレータは、前回yieldした見出し要素を保持し、見出しレベルが上位に変化した場合にもう一度同じ見出し要素をyieldしなければならない。上記のメソッド内で、見出しレベルの上位への変化があった際、見出しひとつが余分に「消費」されてしまうからだ。カスタムイテレータなら恐らく美しく書けるが、ジェネレータの方が比較的容易に書くことができるためこの方法を採用した。

BODY要素の直下に見出し要素がフラットに並んでいるなら、ジェネレータは次のようにnextSiblingプロパティを使って書ける。

function headings(){
	var currentNode = document.body.firstChild;
	var prevHeading = null;
	do {
		if (currentNode instanceof HTMLHeadingElement) {
			yield currentNode;
			if (prevHeading && prevHeading.level > currentNode.level)
				yield currentNode;
			prevHeading = currentNode;
		}
	} while (currentNode = currentNode.nextSibling)
}

ジェネレータはこの関数をコールして生成されたオブジェクトである点に注意する。

document.createTableOfContents(headings); // error
document.createTableOfContents(headings()); // ok

webmaster@jintrick.net
公開: 2007年06月25日
カテゴリ: Javascript