< CSS で章番号を生成する | window.onload を待たずに処理を開始する >

December 22, 2009

目次フレームを自動生成する

Javascript で HTML 文書中の h1 ~ h6 を抜き出して、目次を作成するスクリプトを作成しました。見出し要素に id が振られていなければ自動生成します。自動生成した場合にやたらと "-0" が付くのはご愛嬌…。


2010.01.06 10:40 追記
自動生成した id の階層がおかしかったのを修正したものはこちら

とりあえず、Google Chrome 3.0.195.38 と IE 8 で動作確認しました。あんまりきれいじゃないです。

さらに、CSS で目次部分を左側のフレームっぽく表示させるようにしています。なんちゃって擬似フレーム。これも動作確認環境は同じ。


2009.12.22 22:50 追記
Firefox 3.5 で確認したところ、目次が全部 "undefined" ってなっちゃってました。
Firefox は Element.innerText って使えないんでしたな… とりあえず、Element.textContent を優先的に使うように修正してみました。

サンプルの HTML / toc.js / toc.css

HTML 側では、toc.js と toc.css を読込むだけです。

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja">
    <head>
        <meta http-equiv="content-type" content="application/xhtml+xml; charset=utf-8"/>
        <meta http-equiv="content-script-type" content="text/javascript"/>
        <meta http-equiv="content-style-type" content="text/css"/>
        <link href="toc.css" rel="stylesheet" type="text/css"/> 
        <script type="text/javascript" src="toc.js"></script>
        <!-- 省略… -->
    </head>
    <body>
        <h1>1章</h1>
        <h2>1章-1節</h2>
        <h3>1章-1節-1項</h3>
        <!-- 省略… -->
    </body>
</html>

自動生成する内容は、若干カスタマイズできます。toc.js を読込んだより下の行で以下のように書けば。

<script type="text/javascript">//<![CDATA[
toc.listtag = "ol";    // 番号つきリストに
toc.tocId = "tableOfContents";    // 目次部分の id を別の値に
toc.defaultIdPrefix = "section";    // 見出し要素の自動生成 id のプリフィックス
//]]>
</script>
// toc.js
var toc = function ()
{
    function Node (element, level, idprefix, sindex)
    {
        this.element = element;
        this.level = level;
        this.idprefix = idprefix;
        this.sindex = sindex;
        this.firstChild = null;
        this.nextSibling = null;
        
        if (element && !element.id)
            element.id = this.idprefix + "-" + this.sindex;
    }
    
    Node.prototype.append = function (element, level)
        {
            if (this.nextSibling != null)
            {
                this.nextSibling.append (element, level);
                return;
            }
            
            switch (level - this.level)
            {
            case 0:
                this.nextSibling = new Node (element, level, this.idprefix, this.sindex + 1);
                break;
                
            case 1:
                if (this.firstChild == null)
                    this.firstChild = new Node (element, level, this.idprefix + "-" + this.sindex, 0);
                else
                    this.firstChild.append (element, level);
                break;
                
            default:
                if (this.firstChild == null)
                    this.firstChild = new Node (null, this.level + 1, this.idprefix + "-" + this.sindex, 0);
                this.firstChild.append (element, level);
                break;
                
            }
        };
        
    Node.prototype.createIndex = function (listTagName)
        {
            var li = document.createElement ("li");
            if (this.element != null)
            {
                var ancher = document.createElement ("a");
                ancher.setAttribute ("href", "#" + this.element.id);
                var text = document.createTextNode (this.element.textContent || this.element.innerText);
                ancher.appendChild (text);
                li.appendChild (ancher);
            }
            if (this.firstChild)
            {
                var list = document.createElement (listTagName);
                var node = this.firstChild;
                while (node != null)
                {
                    list.appendChild (node.createIndex (listTagName));
                    node = node.nextSibling;
                }
                li.appendChild (list);
            }
            return li;
        };
    
    var scanElements = function (idprefix)
        {
            var elements = document.getElementsByTagName ("*");
            var root = new Node (null, 0, idprefix, 0);
            for (var i = 0; i < elements.length; i ++)
            {
                var element = elements [i];
                if (element.nodeName.match (/^h([1-9])$/i))
                {
                    var number = RegExp.$1 - 0;
                    root.append (element, number);
                }
            }
            return root;
        };
        
    var createIndex = function (root, listTagName)
        {
            var list = document.createElement (listTagName);
            var node = root.firstChild;
            while (node != null)
            {
                list.appendChild (node.createIndex (listTagName));
                node = node.nextSibling;
            }
            return list;
        };
        
    return new function ()
    {
        // public
        this.listtag = "ul";
        this.tocId = "toc";
        this.defaultIdPrefix = "hedding";
        
        this.create = function ()
            {
                var root = scanElements (this.defaultIdPrefix);
                var tocelem = document.createElement ("div");
                tocelem.id = this.tocId;
                tocelem.appendChild (createIndex (root, this.listtag));
                document.body.appendChild (tocelem);
            };
    } ();
} ();
window.onload = function () { toc.create (); };
/* toc.css */
html
{
    /* start - for pseudo frame */
    display: block;
    height: 100%;
    width: 100%;
    margin: 0;
    padding: 0;
    overflow: hidden;
    /* end   - for pseudo frame */
}

body
{
    padding-left: 1.5em;

    /* start - for pseudo frame */
    display: block;
    height: 100%;
    margin: 0;
    overflow: auto;
    /* end   - for pseudo frame */

    /* toc frame width */
    margin-left: 200px;
}

#toc
{
    padding: 0.5em;

    /* start - for pseudo frame */
    position: absolute;
    left: 0;
    top: 0;
    height: 100%;
    overflow: auto;
    /* end   - for pseudo frame */

    /* toc frame width */
    width: 200px;
}

/* edit as you like. */
#toc ul
{
    padding-left: 1em;
}
#toc li
{
    list-style-type: circle;
    marker-offset: 0;
}

トラックバック

このエントリーにトラックバック:
http://frog.raindrop.jp/cgi-bin/mt/mt-tb.cgi/2464

コメント

コメントする

※ コメントスパム対策のため、コメント本文はおはよう、こんにちわ、こんばんわのいずれかより始めるようにしてください。

name:
email:

※ 必要ですが、表示しません。

url:
情報を保存する ?