日々のコンピュータ情報の集積と整理

Dr.ウーパのコンピュータ備忘録

2015年2月21日土曜日

完成コード配布 - 3rd(3) - Bloggerの記事に目次をJavaScriptで自動的に付与する

3rd シリーズによって実現される目次
3rd シリーズによって実現される目次

イントロダクション

この記事は「そうだ!Bloggerの記事に目次を付けよう!」から始まる一連の「Bloggerの記事に目次をJavaScriptで自動的に付与する」シリーズの構成記事です。
(3rd シリーズ)

記事一覧は「Bloggerの記事に目次をJavaScriptで自動的に付与する - サポートページ」よりどうぞ。


3nd シリーズでは、2nd シリーズで作成した目次機能に対して、実際に使ってみて改善した方がよい点を改善していきます。

3rd シリーズの前回の記事は「見出しが複数行になった場合の見栄え改良 - 3rd(2) - Bloggerの記事に目次をJavaScriptで自動的に付与する」です。


今回の目的

3rd シリーズで修正・機能追加を行った、目次自動生成プログラムとスタイルシートの配布を行います。

スタイルシート

#auto-generated-index_title 
{
    border-style:solid;
    border-width:1px;
    border-color:#999999;
    background-color:#eeeeee;
    font-weight:bold;
    margin:10px 20px 0px 20px;
    padding: 0px 0px 0px 8px;
}
#auto-generated-index_content
{
    border-style:solid;
    border-width:1px;
    border-color:#999999;
    background-color:#ffffff;
    font-weight:normal;
    margin:0px 20px 20px 20px;
}
#auto-generated-index_content_h1
{
    text-indent:-1em;
    padding-left:1em;
    margin-left:1em;
}
#auto-generated-index_content_h2
{
    text-indent:-1em;
    padding-left:1em;
    margin-left:2em;
}
#auto-generated-index_content_h3
{
    text-indent:-1em;
    padding-left:1em;
    margin-left:3em;
}
#auto-generated-index_content_h4
{
    text-indent:-1em;
    padding-left:1em;
    margin-left:4em;
}
#auto-generated-index_content_h5
{
    text-indent:-1em;
    padding-left:1em;
    margin-left:5em;
}
#auto-generated-index_content_h6
{
    text-indent:-1em;
    padding-left:1em;
    margin-left:6em;
}
#auto-generated-index_help
{
    float:right;
    font-weight:bold;
    border-width:1px;
    border-style:solid;
    width:1em;
    text-align:center;
}


ソースコード

(function () {

    /* 生成処理制御パラメータ */
    var min_auto_generated_index_items = 2;         /* 見出しの数がこの数以上になった場合に見出しを挿入する */

    /* 目次挿入先要素の id の正規表現 */
    var regExp_id_obj_insert_index = new RegExp("^post-body-");

    /* 見出し内のタグを目次として使用するかどうか */
    var use_headline_htmlTag = false;

    /* 目次のタイトル */
    var index_title = "目次";

    /* 目次のヘルプを表示するかどうか */
    var view_help = true;

    /* 
    見出しに細工がされていたとしても、セキュリティ上の脅威を防止する 
    このフラグを true にすると、use_headline_htmlTag の値にかかわらず、見出し内のタグは無効になります
    */
    var index_secure_run = true;
    if (index_secure_run) {
        use_headline_htmlTag = false;
    }


    /* ページ読み込み時に目次の生成・挿入処理を実行 */
    generateIndex();


    /* --- lib --- */
    /* 
    HTML を エスケープする
    html : エスケープしたい html
         
    エスケープ後の文字列を返す

    注意:
    {本文に出力する html ソースをエスケープする場合}にのみ使用します
    タグの属性値 や URL などには使用してはなりません。
    */
    function escapeHTML(html) {
        /*
        要素のテキストデータとして文字列を設定して、
        その要素の HTML を取得すると、HTML 文書として表示する場合に、
        エスケープしなければならない文字がエスケープされることを利用
        */
        var obj = document.createElement('div');
        obj.appendChild(document.createTextNode(html));
        return obj.innerHTML;
    }

    /*
    要素に設定されている html から、htmlタグを削除した html を取得
    obj : element オブジェクト
    htmlタグを削除した html を返す


    注意:
    element.textContent が使用可能なブラウザでは、script タグや style タグの中身を取得しますが、
    element.textContent が使用できず、element.innerText が使用可能なブラウザでは、script タグや style タグの中身は取得できません

    */
    function getHTMLWithoutHTMLTagFromElement(obj) {

        /*
        html のタグ要素を削除したテキストとして要素の値を取得した後、
        エスケープ処理する
        */
        var headline_text = obj.textContent || obj.innerText;
        return escapeHTML(headline_text);
    }

    /* --- Main --- */

    /* 
    目次の生成・挿入処理 
    */
    function generateIndex() {
        /* 投稿本文が格納されている div 要素を発見し、目次を挿入する */
        var divs = document.getElementsByTagName("div");
        for (var i = 0; i < divs.length; i++) {
            if (regExp_id_obj_insert_index.test(divs[i].id)) {
                /* 投稿本文が格納されている div 要素発見時の処理 */
                generateIndexForObj(divs[i]);
                break;
            }
        }
    }

    /* 
    目次の生成・挿入処理
    obj : 目次の挿入先
    */
    function generateIndexForObj(obj) {

        /* 目次として使用する見出しの一覧のHTML */
        var html_item = "";

        /* 見出しの数 */
        var item_count = 0;

        /*
        再帰的に見出しを検索し、目次の HTML を作成する
        */
        generateIndexHTMLRecursive(obj);
        function generateIndexHTMLRecursive(obj) {

            /* 見出しの列挙 */
            for (var i = 0; i < obj.childNodes.length; i++) {

                /* 見出しタグの場合、見出しのレベルに応じて書式を設定して記録する */
                var originalTagName = obj.childNodes[i].tagName;
                if (originalTagName !== void 0) {                 /* void 0 = undefined なので、originalTagName が undefined でなければ処理する */

                    var tagName = originalTagName.toLowerCase();
                    if (tagName.lastIndexOf("h", 0) == 0) {

                        var level = Number(tagName.substr(1, tagName.length - 1));      /* タグ hx の x の取り出し */
                        if (!isNaN(level)) {

                            /* アンカー用の id */
                            var anchor_id = "auto-generated-index_target" + item_count;

                            if (index_secure_run) {

                                /* 既存の id が細工されている場合の脆弱性を防止するため、既存の id は用いず、新しくジャンプ用の要素を追加する */
                                var secure_anchor = document.createElement("a");
                                secure_anchor.setAttribute("id", anchor_id);
                                obj.childNodes[i].insertBefore(secure_anchor, obj.childNodes[i].firstChild);

                            } else {

                                /* アンカー設定
                                すでに id が設定されている場合は、その id をアンカーに用いる */
                                if (obj.childNodes[i].id != "") {
                                    anchor_id = obj.childNodes[i].id;
                                } else {
                                    obj.childNodes[i].id = anchor_id;
                                }
                            }

                            /* 見出しの内容 */
                            var headline = "";
                            if (use_headline_htmlTag) {
                                headline = obj.childNodes[i].innerHTML;
                            } else {
                                /* 見出し内の HTMLタグを使用しない場合には、HTML タグを削除する */
                                headline = getHTMLWithoutHTMLTagFromElement(obj.childNodes[i]);
                            }

                            html_item += "<div id=\"auto-generated-index_content_h" + level + "\"><a href=\"#" + anchor_id + "\">" + headline + "</a></div>\r\n";

                            item_count++;
                        }
                    }
                }

                /* 子ノードに対して再帰的に再帰的に見出しを検索 */
                generateIndexHTMLRecursive(obj.childNodes[i]);
            }
        }


        /* 目次のHTMLを本文へ挿入 */
        if (item_count >= min_auto_generated_index_items) {

            var help_html = "<span id=\"auto-generated-index_help\"><a href=\"http://upa-pc.blogspot.jp/p/addindex.html\" title=\"この目次について\" rel=\"nofollow\">?</a></span>";


            /* 目次の HTML を事前に要素に変換 */
            var index_obj = document.createElement("div");
            index_obj.setAttribute("id", "auto-generated-index");
            index_obj.innerHTML = "<div id=\"auto-generated-index_title\">" + index_title +
                    (view_help ? help_html : "") +
                     " </div>" +
                     "<div id=\"auto-generated-index_content\">" + html_item + "</div>" +
                     (index_secure_run ? "<!-- セキュアな目次 -->" : "");


            /* 本文に目次を挿入 */
            obj.insertBefore(index_obj, obj.firstChild);
        }
    }

})();

ソースコード(最適化後)

特にパラメータを変更する必要が無い場合、最適化後のソースコードを使えば、そのまま自分でソースコードを最適化する必要がなく、そのままソースコードを貼り付けることができます。
(function(){function n(a){function f(d){for(var b=0;b<d.childNodes.length;b++){var e=d.childNodes[b].tagName;if(void 0!==e&&(e=e.toLowerCase(),0==e.lastIndexOf("h",0)&&(e=Number(e.substr(1,e.length-1)),!isNaN(e)))){var a="auto-generated-index_target"+h;if(l){var c=document.createElement("a");c.setAttribute("id",a);d.childNodes[b].insertBefore(c,d.childNodes[b].firstChild)}else""!=d.childNodes[b].id?a=d.childNodes[b].id:d.childNodes[b].id=a;c="";if(m)c=d.childNodes[b].innerHTML;else{var c=d.childNodes[b],
c=c.textContent||c.innerText,g=document.createElement("div");g.appendChild(document.createTextNode(c));c=g.innerHTML}k+='<div id="auto-generated-index_content_h'+e+'"><a href="#'+a+'">'+c+"</a></div>\r\n";h++}f(d.childNodes[b])}}var k="",h=0;f(a);if(h>=p){var g=document.createElement("div");g.setAttribute("id","auto-generated-index");g.innerHTML='<div id="auto-generated-index_title">'+q+(r?'<span id="auto-generated-index_help"><a href="http://upa-pc.blogspot.jp/p/addindex.html" title="この目次について" rel="nofollow">?</a></span>':
"")+' </div><div id="auto-generated-index_content">'+k+"</div>"+(l?"\x3c!-- セキュアな目次 --\x3e":"");a.insertBefore(g,a.firstChild)}}var p=2,s=/^post-body-/,m=!1,q="目次",r=!0,l=!0,m=!1;(function(){for(var a=document.getElementsByTagName("div"),f=0;f<a.length;f++)if(s.test(a[f].id)){n(a[f]);break}})()})();


補足:Blogger に設置する場合

投稿ページにのみ目次を表示するためと、HTMLテンプレートの修正にて正常にJavaScriptコードを埋め込むために、

<!-- 目次の自動生成 START -->
<b:if cond='data:blog.pageType == &quot;item&quot;'>
<!-- 投稿ページにのみ目次を表示する -->
<script type='text/javascript'>
//<![CDATA[
<!--



//-->
//]]>
</script>
</b:if>
<!-- 目次の自動生成 END -->

の間にソースコードを記載しています。



「Bloggerの記事に目次をJavaScriptで自動的に付与する」シリーズ 記事一覧へ




関連記事

関連記事を読み込み中...

同じラベルの記事を読み込み中...