/*!*
 * mjl.js
 * MITSUE-LINKS JavaScript Library
 * Version 2.1.0
 * Copyright (C) 2008-2010 MITSUE-LINKS
 * @source: http://www.mitsue.co.jp/service/produce/mjl.html
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * As additional permission under GNU GPL version 3 section 7, you
 * may distribute non-source (e.g., minimized or compacted) forms of
 * that code without the copy of the GNU GPL normally required by
 * section 4, provided you include this license notice and a URL
 * through which recipients can access the Corresponding Source.
 */
(function(window, document) { // 圧縮効率向上＆キャッシュによる高速化

// MJL が既に定義されていることはありえない
if ("MJL" in window) {
    // 「MJL ??? は既に定義されています」
    throw new Error("MJL "+window.MJL.version+" has already been defined");
}


// ----------------------------------------------------------------------------
// 定数
// ----------------------------------------------------------------------------
var DE           = document.documentElement,      // ルート要素
    TEST_NODE    = document.createElement("div"), // テスト用ノード
    DIG_DEC      = 10,                            // 10進数
    EMPTY_OBJECT = {},                            // 未代入時用 空オブジェクト
    EMPTY_ARRAY  = [],                            // 未代入時用 空配列
    COND_HASH    = /#([^#]+)$/;                   // ハッシュ文字列


// ------------------------------------
// HTML5 空白文字 (space characters)
// ------------------------------------
//   " "  \u0020 SPACE
//   "\t" \u0009 CHARACTER TABULATION (tab)
//   "\n" \u000A LINE FEED (LF)
//   "\f" \u000C FORM FEED (FF)
//   "\r" \u000D CARRIAGE RETURN (CR)
// see also:
//   http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#space-character
//   http://www.w3.org/TR/html5/infrastructure.html#space-character
var COND_SPC_STR = "[ \\t\\n\\f\\r]+",                                     // 正規表現
    COND_SPC     = new RegExp(COND_SPC_STR, "g"),                          // 空白文字
    COND_TRIM    = new RegExp("^"+COND_SPC_STR+"|"+COND_SPC_STR+"$", "g"); // trim 用文字列先頭・末尾空白


// ------------------------------------
// "\s" 空白文字
// ES5 "7.2 White Space" 準拠 (Zs 含む) とする
// ------------------------------------
// 各仕様の \s 対応 (Zs 除く):
//   ES3
//     \u0020\t\n\u000B\f\r\u00A0\u2028\u2029
//   ES5
//     \u0020\t\n\u000B\f\r\u00A0\u2028\u2029\uFEFF
//
// 各実装の \s 対応:
//   IE6, IE7, IE8, Sf2, Sf3
//     \u0020\t\n\u000B\f\r
//   Fx2, Fx3, Fx3.5
//     \u0020\t\n\u000B\f\r\u00A0            [\u2000-\u200A]\u200B\u2028\u2029            \u3000
//   Fx3.6, Sf4, Ch3 (ES5 - \uFEFF 状態)
//     \u0020\t\n\u000B\f\r\u00A0\u1680\u180E[\u2000-\u200A]      \u2028\u2029\u202f\u205f\u3000
//   Op9.2, 9.5, 9.6
//     \u0020\t\n\u000B\f\r\u00A0\u1680      [\u2000-\u200A]\u200B\u2028\u2029\u202f      \u3000
//   Op10.0, Op10.1
//     \u0020\t\n\u000B\f\r\u00A0            [\u2000-\u200A]\u200B\u2028\u2029\u202f      \u3000
// ------------------------------------
// Note:
//   \u200B は ES5 で明示的に除外された
//   see also:
//     http://wiki.ecmascript.org/lib/exe/fetch.php?media=es3.1:es5_candidate_errata_july_9.pdf
//     https://mail.mozilla.org/pipermail/es5-discuss/2009-June/002867.html
var COND_SPC_S_STR = "[ \\t\\n\\u000B\\f\\r\\u00A0\\u1680\\u180E\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000\\uFEFF]+",
    COND_TRIM_S    = new RegExp("^"+COND_SPC_S_STR+"|"+COND_SPC_S_STR+"$", "g");


// ------------------------------------
// CDATA 条件
// ------------------------------------
var COND_CDATA_ITEM_START = "(?:^|"+COND_SPC_STR+")",     // アイテム定義開始
    COND_CDATA_ITEM_END   = "(?:(?="+COND_SPC_STR+")|$)"; // アイテム定義終了


// ----------------------------------------------------------------------------
// MJL Namespace
// ----------------------------------------------------------------------------
var MJL = window.MJL = {
    version : "2.1.0" // バージョン情報
};


// ----------------------------------------------------------------------------
// ua: User Agent 情報
// ----------------------------------------------------------------------------
MJL.ua = (function() {
    // ActiveX 有無
    var activex = "ActiveXObject" in window;
    // テストノード ノード名
    var nodeName = TEST_NODE.nodeName;
    // サポートプロパティによる判定
    var name = activex                   ? "trident" :
               "Components" in window    ? "gecko"   :
               "defaultstatus" in window ? "webkit"  :
               "opera" in window         ? "opera"   : "unknown";
    // return 値
    var ret = {
        // レンダリングエンジン名
        name    : name,
        // レンダリングエンジン是非 (是: true, 非: false)
        gecko   : false,
        opera   : false,
        webkit  : false,
        trident : false,
        unknown : false,
        // JavaScript エンジン是非・バージョン
        v8      : "chrome" in window ? "localStorage" in window ? 4 : 3 : 0,
        // Quirks モード是非 (是: true, 非: false)
        quirks  : "BackCompat" === document.compatMode,
        // XML モード是非 (是: true, 非 (HTML モード): false)
        xml     : nodeName === nodeName.toLowerCase(),
        // レンダリングエンジンバージョン
        version : 0,
        // WAI-ARIA 対応有無 (有: true, 無: false)
        aria    : true,
        // ActiveX 有無 (有: true, 無: false)
        activex : activex
    };
    // レンダリングエンジン是非
    ret[name] = true;
    // レンダリングエンジンバージョン
    ret.version = ret.opera   ? Number(window.opera.version()) :
                  ret.webkit  ? window.Worker ? 4 : document.evaluate ? 3 : 2 :
                  ret.gecko   ? document.elementFromPoint ? 3 : 2 :
                  ret.trident ? document.documentMode || (
                      "maxHeight" in TEST_NODE.style ? 7 : 6
                  ) : 0;
    // WAI-ARIA 対応有無
    ret.aria = !(
        // Op9.2: tabindex 未対応
        // Op9.5: click 時、不正な位置に outline が表示されることがある
        (ret.opera && ret.version < 9.6) ||
        // IE6,7: WAI-ARIA 未対応
        (ret.trident && ret.version < 8)
    );

    return ret;
})();


// ----------------------------------------------------------------------------
// trim: 文字列先頭・末尾の空白文字を削除
// ----------------------------------------------------------------------------
MJL.trim = (function() {
    // 空白文字種類
    var types = {
        // "種類": {
        //     cond  : /空白文字 条件/,
        //     cache : { // 削除前・後文字列対応
        //         "削除前文字列" : "削除後文字列", ...
        //     }
        // }, ...
        def : {  // 通常: HTML5
            cond  : COND_TRIM,
            cache : {}
        },
        full : { // 完全: ES5
            cond  : COND_TRIM_S,
            cache : {}
        }
    };
    // 実際の動作
    function trim(str, type) {
        var data  = types[(type && type in types) ? type : "def"],
            cache = data.cache,
            ret   = cache[str];
        // 未キャッシュなら新規生成
        if (!ret) {
            // String.prototype.trim は UA 毎の \s 実装に依存
            //   -> 差異が発生しうる為、利用しない
            cache[str] = ret = str.replace(data.cond, "");
        }
        return ret;
    }
    // 種類を MJL.trim のプロパティとして登録
    for (var t in types) {
        trim[t] = t;
    }
    return trim;
})();


// ----------------------------------------------------------------------------
// isObject: オブジェクト判定
// ----------------------------------------------------------------------------
MJL.isObject = function(obj) {
    var type = typeof obj;
    // null は除外
    return ("object" === type || "function" === type) && null !== obj;
};


// ----------------------------------------------------------------------------
// isArray: Array オブジェクト判定
// ----------------------------------------------------------------------------
MJL.isArray = Array.isArray ? function(obj) { // ES5
    return Array.isArray(obj);
} : function(obj) {                           // ES3
    // 異なるグローバルオブジェクトで作成された Array オブジェクトは考慮しない
    return obj instanceof Array;
};


// ----------------------------------------------------------------------------
// isSameNode: 同一ノード是非
// ----------------------------------------------------------------------------
MJL.isSameNode = document.isSameNode ? function(n1, n2) { // W3C DOM
    // WebKit: window.isSameNode 未定義
    return (window !== n1 && window !== n2) ? n1.isSameNode(n2) : (n1 === n2);
} : function(n1, n2) {                                    // Trident
    return n1 === n2;
};


// ----------------------------------------------------------------------------
// convArray: 与オブジェクトを配列に変換
// ----------------------------------------------------------------------------
MJL.convArray = (function() {
    var conv = (
        // BUG IE6,7,8: DOMNodeList に Array.prototype.slice.call が使えない
        MJL.ua.trident                        ||
        // BUG Sf2: DOMNodeList に Array.prototype.slice.call を使うと
        //          各アイテムが undefined になる
        (MJL.ua.webkit && MJL.ua.version < 3) ||
        // BUG Op9.2: StyleSheetList に Array.prototype.slice.call が使えない
        (MJL.ua.opera && MJL.ua.version < 9.5)
    ) ? function(obj) {
        var nobj = obj.length,
            ret  = new Array(nobj);
        for (var o = 0; o < nobj; o++) {
            ret[o] = obj[o];
        }
        return ret;
    } : function(obj) {
        return Array.prototype.slice.call(obj);
    };

    return function(obj) {
        return MJL.isArray(obj)                ? obj       :
               MJL.isObject(obj) && obj.length ? conv(obj) : [];
    };
})();


// ----------------------------------------------------------------------------
// convNode: 与文字列を DOM Node に変換
// ----------------------------------------------------------------------------
MJL.convNode = function(str) {
    TEST_NODE.innerHTML = str;
    var node = TEST_NODE.firstChild,
        ret  = null;
    // 生成される要素数に応じて返値が変化
    if (node) {
        if (1 < TEST_NODE.childNodes.length) { // Document Fragment に格納
            ret = document.createDocumentFragment();
            while (node) {
                ret.appendChild(node);
                node = node.nextSibling;
            }
        } else {                               // ノード自身のみ
            ret = node;
        }
    }
    return ret;
};


// ----------------------------------------------------------------------------
// bind: 引数束縛
// ----------------------------------------------------------------------------
MJL.bind = function(func, obj /* ... */) {
    // 追加の引数があればそれらも束縛
    var args = 2 < arguments.length ? MJL.convArray(arguments).slice(2) : [];
    return function(/* ... */) {
        return func.apply(
            // 常に this 値として束縛
            obj,
            // 引数あり: 束縛引数と連結
            // 引数なし: 束縛引数のみ
            0 < arguments.length ? args.concat(MJL.convArray(arguments))
                                 : args
        );
    };
};


// ----------------------------------------------------------------------------
// getCDATA: 各種処理済 CDATA 関連値集合 取得
// ----------------------------------------------------------------------------
// see also: http://www.whatwg.org/specs/web-apps/current-work/multipage/common-microsyntaxes.html#set-of-space-separated-tokens
MJL.getCDATA = (function() {
    var cache = {
        // "与文字列" : {
        //     str   : "正規化済 CDATA 文字列 (重複トークン除外済)",
        //     items : ["ユニークな空白区切トークン", ...],
        //     assoc : {
        //         "ユニークな空白区切トークン" : "キーと同じ文字列", ...
        //     },
        //     cond  : /包含チェック用 正規表現/
        // }, ...
    };
    return function(str) {
        var ret = cache[str];
        // 未キャッシュなら新規生成
        if (!ret) {
            if (!MJL.trim(str)) {
                // 「CDATA 値が空白文字のみです」
                throw new Error("CDATA value only has space characters");
            }
            var cdata   = MJL.trim(str.replace(COND_SPC, " ")),
                tokens  = cdata.split(COND_SPC),
                ntokens = tokens.length,
                items   = [],
                assoc   = {};
            // 重複判定
            for (var t = 0, token; t < ntokens; t++) {
                token = tokens[t];
                if (!(token in assoc)) {
                    items.push(token);
                    assoc[token] = token;
                }
            }
            // 生成
            cache[str] = ret = {
                str   : items.join(" "),
                items : items,
                assoc : assoc,
                cond  : new RegExp(COND_CDATA_ITEM_START+"(?:"+items.join(
                    "|"
                )+")"+COND_CDATA_ITEM_END, "g")
            };
            // 先行投資
            // 次回以降に処理不要の文字列が与えられた場合に備える
            if (!(ret.str in cache)) {
                cache[ret.str] = ret;
            }
        }
        return ret;
    };
})();


// ----------------------------------------------------------------------------
// getHash: ハッシュ取得
// ----------------------------------------------------------------------------
MJL.getHash = (function() {
    // BUG Gecko: エンコードされたハッシュが与えられている場合、location.hash
    //            のみデコードされたものを返してしまう
    // see also: https://bugzilla.mozilla.org/show_bug.cgi?id=378962
    var prop = MJL.ua.gecko ? "href" : "hash"; // Gecko なら href を利用
    return function(gobj) {
        if (!gobj) {
            gobj = window;
        }
        return (
            // "#" は除外
            COND_HASH.exec(gobj.location[prop] || "") || EMPTY_ARRAY
        )[1] || "";
    };
})();


// ----------------------------------------------------------------------------
// getName: ノード名取得
// ----------------------------------------------------------------------------
MJL.getName = MJL.ua.xml ? MJL.ua.opera ? function(node) { // Opera XML モード
    // ELEMENT_NODE   : localName
    // ATTRIBUTE_NODE : localName
    // 他種ノード     : nodeName (null === localName の場合)
    var localName = node.localName;
    // BUG Op: innerHTML 経由で生成したノードを NS なしコレクタ
    //         (例: getElementsByTagName) で収集すると、ノード単体が
    //         HTML モード化して localName 値が大文字になる
    // namespaceURI 有無でノード単体の XML/HTML モードを判定可能
    if (localName && !node.namespaceURI) {
        localName = localName.toLowerCase(); // HTML モード
    }
    return localName || node.nodeName;
} : function(node) {                                       // XML モード
    // ELEMENT_NODE   : localName
    // ATTRIBUTE_NODE : localName
    // 他種ノード     : nodeName (null === localName の場合)
    return node.localName || node.nodeName;
} : (function() {
    var cache = { // 小文字化済ノード名のキャッシュ
        // "取得ノード名" : "小文字化済ノード名", ...
    };
    return function(node) {                                // HTML モード
        var name = node.nodeName,
            ret  = cache[name];
        if (!ret) {
            cache[name] = ret = node.nodeName.toLowerCase(); // 常に小文字
        }
        return ret;
    };
})();


// ----------------------------------------------------------------------------
// getData: 要素ノードに束縛したデータ構造を取得
// ----------------------------------------------------------------------------
MJL.getData = (function() {
    var prop   = "MJL_"+(new Date()).getTime(), // 追加プロパティ名
        nextId = 0,                             // 次に利用する ID
        data   = {                              // 実データ
            // id : { ... }, ...
        };
    return function(node) {
        var id  = MJL.isSameNode(node, window)   ? "window"   :
                  MJL.isSameNode(node, document) ? "document" : null,
            ret = null;
        // 特殊オブジェクト ないし 要素ノードのみ
        if (id || 1 === node.nodeType) {
            // data キー取得
            // 特殊オブジェクトには専用名を付与
            id = id || (prop in node ? node[prop] : (node[prop] = nextId++));
            // データ構造を定義
            if (!(id in data)) {
                data[id] = {};
            }
            // 参照渡し
            ret = data[id];
        }
        return ret;
    };
})();


// ----------------------------------------------------------------------------
// hasClassName: 指定 class 属性値の保有是非
// ----------------------------------------------------------------------------
MJL.hasClassName = function(elem, name) {
    var className = ("string" === typeof elem) ? elem : elem.className,
        ret       = false;
    if (className) {
        var cdata = MJL.getCDATA(name);
        ret = (className === cdata.str) || ( // 同一なら最短
            // AND 条件
            // 一致 class 属性値の数は与 class 属性値の全トークン数より
            // 多くなければならない
            cdata.items.length <= (
                // 文字列全体に対し実行 (exec では g オプションが効かない)
                className.match(cdata.cond) || EMPTY_ARRAY
            ).length
        );
    }
    return ret;
};


// ----------------------------------------------------------------------------
// addClassName: 要素の class 属性値に指定値を追加
// ----------------------------------------------------------------------------
MJL.addClassName = function(elem, name) {
    if (!MJL.hasClassName(elem, name)) {
        var items     = MJL.getCDATA(name).items,
            nitems    = items.length,
            className = elem.className,
            classes   = className ? [className] : [];
        for (var i = 0; i < nitems; i++) {
            if (!MJL.hasClassName(className, items[i])) {
                classes.push(items[i]);
            }
        }
        if (0 < classes.length) {
            elem.className = classes.join(" ");
        }
    }
};


// ----------------------------------------------------------------------------
// removeClassName: 要素の class 属性値から指定値を削除
// ----------------------------------------------------------------------------
MJL.removeClassName = (function() {
    // BUG IE6,7: "className" を操作しないと class 属性が変化しない
    var attr = (MJL.ua.trident && MJL.ua.version < 8) ? "className" : "class";
    return function(elem, name) {
        var before = elem.className;
        if (before) {
            var after = MJL.trim(before.replace(MJL.getCDATA(name).cond, " "));
            if (before !== after) {
                // BUG IE8: 属性ノードを削除しても className 値に反映されない
                //            -> className 値も明示的に消去する必要がある
                elem.className = after;
                // 値がなければ属性ごと削除
                if (!after) {
                    elem.removeAttribute(attr);
                }
            }
        }
    };
})();


// ----------------------------------------------------------------------------
// getElementsByXPath: XPath による要素収集
// ----------------------------------------------------------------------------
MJL.getElementsByXPath = document.evaluate && (
    // BUG Ch3: XML モード時に名前空間接頭辞を使用した XPath を与えると
    //          "NAMESPACE_ERR: DOM Exception 14" 例外発生
    //            -> Ch4 未満では XPath 自体を無効化して対処
    // see also:
    //   http://code.google.com/p/chromium/issues/detail?id=671
    //   https://bugs.webkit.org/show_bug.cgi?id=30128
    //   http://trac.webkit.org/changeset/49191
    !MJL.ua.v8 || 4 <= MJL.ua.v8
) ? (function() {
    var cache  = {     // XPathExpression オブジェクトのキャッシュ
            // "XPath" : XPathExpression, ...
        },
        result = null; // 2回目実行以降の使いまわし用 XPathResult オブジェクト

    // カスタム名前空間リゾルバ関数 取得
    function getResolver(parent) {
        var resolver = document.createNSResolver(
            (parent.ownerDocument || parent).documentElement
        );
        return function(prefix) {
            // 次の順で適切な namespace を返す:
            //   1. デフォルトリゾルバから取得
            //   2. 探索開始ノードの名前空間
            //   3. ドキュメントルートノードの名前空間
            //   4. 一致なし (空文字列: null と同義)
            return resolver.lookupNamespaceURI(prefix) ||
                   parent.namespaceURI                 ||
                   DE.namespaceURI                     || "";
        };
    }

    // XPath 文字列変換
    var getPath = MJL.ua.opera ? (function() { // Opera
        // BUG Op: HTML モードの文書で名前空間接頭辞を利用した XPath を指定した
        //         場合、"NAMESPACE_ERR" 例外発生
        // BUG Op: XML モードの文書で名前空間接頭辞を利用した XPath を指定した
        //         場合、カスタム名前空間リゾルバ関数が適切でも取得に失敗する
        var cond  = /(^|[^x])x:/g, // 名前空間接頭辞 判別
            cache = {              // 置換操作済 XPath のキャッシュ
                // "変換前 XPath" : "変換済 XPath", ...
            };
        return function(path) {
            var ret = cache[path];
            if (!ret) {
                // 名前空間接頭辞を強制除去
                cache[path] = ret = path.replace(cond, "$1");
            }
            return ret;
        };
    })() : undefined; // 他 UA では不要

    // 実行関数
    return function(parent, path) {
        var exp = cache[path];
        // 未キャッシュなら新規生成
        if (!exp) {
            cache[path] = exp = document.createExpression(
                getPath ? getPath(path) : path, // 必要なら変換
                getResolver(parent)
            );
        }
        // result には次の値が入る:
        //   初回実行:  null
        //   2回目以降: 前回実行時に evaluate が返した XPathResult オブジェクト
        // BUG Gecko: XPathResult オブジェクトを都度生成させる (= null) と
        //            大量の主記憶を浪費するうえにほとんど開放しない
        //              -> XPathResult オブジェクト使いまわしで軽減可能
        // result の参照コストが大きいため query としてキャッシュ
        var query  = (result = exp.evaluate(parent, 7, result)),
            nquery = query.snapshotLength,
            ret    = new Array(nquery);
        for (var q = 0; q < nquery; q++) { // 配列に変換
            ret[q] = query.snapshotItem(q);
        }
        return ret;
    };
})() : undefined;


// ----------------------------------------------------------------------------
// getElementsByClassName: class 属性値による要素収集
// ----------------------------------------------------------------------------
// Note:
//   NodeList は常に Array へ変換する
// ----------------------------------------------------------------------------
MJL.getElementsByClassName = document.getElementsByClassName ? function(parent, name) {
    return MJL.convArray(
        // BUG Fx3: 複数属性値指定の際、同一値が利用されていると
        //          うまく認識されない (例: "x x")
        parent.getElementsByClassName(MJL.getCDATA(name).str)
    );
} : document.querySelectorAll ? (function() {
    var cache = { // CSS セレクタのキャッシュ
        // "class 属性値" : "CSS セレクタ", ...
    };
    return function(parent, name) {
        var query = cache[name];
        // CSS セレクタをキャッシュ
        if (!query) {
            // 複数 class 値に対応
            cache[name] = query = "."+MJL.getCDATA(name).items.join(".");
        }
        return MJL.convArray(parent.querySelectorAll(query));
    };
})() : MJL.getElementsByXPath ? (function() {
    var cache = { // XPath 式のキャッシュ
        // "class 属性値" : "XPath 式", ...
    };
    return function(parent, name) {
        var path = cache[name];
        // XPath 式をキャッシュ
        if (!path) {
            // 複数 class 値に対応
            var items  = MJL.getCDATA(name).items,
                nitems = items.length,
                paths  = new Array(nitems),
                i;
            for (i = 0; i < nitems; i++) {
                paths[i] = '[contains(concat(" ",@class," ")," '+items[i]+' ")]';
            }
            cache[name] = path = ".//*"+paths.join("");
        }
        return MJL.getElementsByXPath(parent, path);
    };
})() : function(parent, name) {
    // 線形探索
    var nodes  = parent.getElementsByTagName("*"),
        nnodes = nodes.length,
        ret    = [],
        node, n;
    for (n = 0; n < nnodes; n++) {
        node = nodes[n];
        // 1: ELEMENT_NODE
        if (1 === node.nodeType && MJL.hasClassName(node, name)) {
            ret.push(node);
        }
    }
    return ret;
};


// ----------------------------------------------------------------------------
// getChildElements: 子要素収集
// ----------------------------------------------------------------------------
// Note:
//   children プロパティは利用しない
//     BUG Trident: children では object 要素の子要素が取得できない
//     BUG Sf2: children が getElementsByTagName("*") と同じ意味
//   他の UA では XPath が利用できる
// ----------------------------------------------------------------------------
MJL.getChildElements = MJL.getElementsByXPath ? function(parent, name, exclude) {
    return MJL.getElementsByXPath(
        parent,
        exclude ? './*[not(self::x:'+name+')]' :
        name    ? './x:'+name                  : './*'
    );
} : function(parent, name, exclude) {
    // 線形探索
    var node = parent.firstChild,
        ret  = [];
    if (arguments.length < 2) { // 無条件
        while (node) {
            // 1: ELEMENT_NODE
            if (1 === node.nodeType) {
                ret.push(node);
            }
            node = node.nextSibling;
        }
    } else if (name) {          // 条件指定
        exclude = !!exclude;
        while (node) {
            // 1: ELEMENT_NODE
            if (1 === node.nodeType && exclude !== (name === MJL.getName(node))) {
                ret.push(node);
            }
            node = node.nextSibling;
        }
    }
    return ret;
};


// ----------------------------------------------------------------------------
// vp: ViewPort 操作インタフェイス
// ----------------------------------------------------------------------------
MJL.vp = (function() {
    // ViewPort としてどの要素を利用しているか
    var elem = MJL.ua.quirks ? "body" : "documentElement";
    return {
        // ViewPort サイズ (スクロールバー除外)
        getSize : (MJL.ua.webkit && MJL.ua.version < 3) ? function() {
            // Sf2
            return {
                width  : window.innerWidth,
                height : window.innerHeight
            };
        } : (MJL.ua.opera && MJL.ua.version < 9.5) ? function() {
            // Op9.2
            return {
                width  : document.body.clientWidth,
                height : document.body.clientHeight
            };
        } : function() {
            // 一般
            // ViewPort 要素の差異を考慮
            return {
                width  : document[elem].clientWidth,
                height : document[elem].clientHeight
            };
        }
    };
})();


// ----------------------------------------------------------------------------
// event: 汎用イベント処理
// ----------------------------------------------------------------------------
// 本イベントラッパーは window の unload 時に add メソッドにより追加された
// 全イベントを remove している
// see also:
//   http://support.microsoft.com/kb/929874/
//   http://www.microsoft.com/japan/msdn/ie/general/ie_leak_patterns.aspx
//   http://d.hatena.ne.jp/zorio/20070626/1182875782
//   http://d.hatena.ne.jp/zorio/20070918/1190135017
//   http://ajaxian.com/archives/ie-memory-leaks-be-gone
//   http://ajaxian.com/archives/ies-memory-leak-fix-greatly-exaggerated
// ----------------------------------------------------------------------------
MJL.event = {
    // --------------------------------
    // Public
    // --------------------------------
    // 追加
    add : function(node, type, listener, useCapture) {
        var ret = this._wrapAfterCare(listener);
        useCapture = true === useCapture;
        if (this._origins[type]) {         // オリジナル
            ret = this._addOrigin(node, type, ret);
        } else {
            if (node.addEventListener) {   // W3C DOM
                node.addEventListener(type, ret, useCapture);
            } else if (node.attachEvent) { // Trident
                node.attachEvent("on"+type, ret);
            }
            // メモリリーク防止 (window.onunload を除く)
            if (window !== node && "unload" !== type) {
                // イベントをスタックにストア
                this._stack.push([node, type, ret, useCapture]);
            }
        }
        return ret;
    },

    // 削除
    remove : function(node, type, listener, useCapture) {
        var ret = listener;
        useCapture = true === useCapture;
        if (this._origins[type]) {             // オリジナル
            ret = this._removeOrigin(node, type, listener, useCapture);
        } else if (node.removeEventListener) { // W3C DOM
            node.removeEventListener(type, listener, useCapture);
        } else if (node.detachEvent) {         // Trident
            node.detachEvent("on"+type, listener);
        }
        return ret;
    },

    // 発送 (= fire)
    dispatch : document.createEvent ? (function() {             // W3C DOM
        // DOM Events レベル
        // BUG Fx2: DOM3 は未サポート部分が多い為使わない
        var level = (MJL.ua.gecko && MJL.ua.version < 3) ? "DOM2" : "DOM3";
        // event オブジェクト初期化
        var initEvent = function(event, init, type, options) {
            event[init].apply(event, [type].concat(options));
        };

        // Sf2 専用設定
        if (MJL.ua.webkit && MJL.ua.version < 3) {
            // BUG Sf2: DOM2 しかサポートしていない
            // BUG Sf2: document.createEvent に未サポートの種類を与えると
            //          "DOM Exception 9" 例外発生
            level = "DOM2";
            // BUG Sf2: 初期化メソッドは initEvent しかサポートしていない
            // BUG Sf2: initEvent.apply が未定義
            initEvent = function(event, init, type, options) {
                // options[2] 以降は全て無視
                event.initEvent(type, options[0], options[1]);
            };
        }

        return function(node, type, options) {
            // イベント種類別 初期化情報
            var data = this._TYPES[level][
                type in this._TYPES[level] ? type : "DEFAULT"
            ];
            // event オブジェクト
            var event = document.createEvent(data.type);
            // 初期化メソッドに与える引数が未指定なら既定値を利用
            if (!options) {
                // 遅延評価が必要なものは返値を利用
                options = ("function" === typeof data.def) ? data.def(node)
                                                           : data.def;
            }
            // 初期化
            initEvent(event, data.init, type, options);
            return this._origins[type] ? this._dispatchOrigin(node, type, event)
                                       : node.dispatchEvent(event);
        };
    })() : document.fireEvent ? function(node, type, options) { // Trident
        var event = document.createEventObject();
        event.type         = type;
        event.cancelBubble = options ? !options[0] : false;
        // cancelable (キャンセル可否) は設定不可の為未サポート
        //   -> options[1] は常に無視
        return this._origins[type] ? this._dispatchOrigin(node, type, event)
                                   : node.fireEvent("on"+type, event);
    } : undefined,

    // デフォルト動作キャンセル
    preventDefault : function(event) {
        if (event.preventDefault) { // W3C DOM
            event.preventDefault();
        } else {                    // Trident
            event.returnValue = false;
        }
    },

    // 伝搬停止
    stopPropagation : function(event) {
        if (event.stopPropagation) { // W3C DOM
            event.stopPropagation();
        } else {                     // Trident
            event.cancelBubble = true;
        }
    },

    // 全キャンセル動作を実行
    cancel : function(event) {
        this.preventDefault(event);
        this.stopPropagation(event);
    },

    // イベントが発生したノードを取得
    getTarget : function(event) {
        return event.target || event.srcElement || null;
    },

    // 引数束縛 (専用版)
    bind : function(func, obj /* ... */) {
        // 追加の引数があればそれらも束縛
        var args = 2 < arguments.length ? MJL.convArray(arguments).slice(2) : [];
        return function(event) {
            if (!event && window.event) {
                event = window.event; // Trident: 独自仕様
            }
            // 必ず Event オブジェクトが第1引数になるよう調整
            return func.apply(obj, [event].concat(args));
        };
    },

    // --------------------------------
    // Private
    // --------------------------------
    // メモリリーク防止用イベントスタック (window の unload を除く)
    _stack : [],

    // DOM Events デフォルト設定一覧
    // {
    //     DOM Events レベル : {
    //         イベント名 : {
    //             type : カテゴリ,
    //             init : 初期化メソッド名,
    //             // 通常
    //             def  : [パラメタデフォルト値, ...]
    //             // relatedTarget 必要時
    //             def : function(relatedTarget) {
    //                 return [パラメタデフォルト値, ...];
    //             }
    //         }, ...
    //     }, ...
    // }
    _TYPES : {
        DOM2 : (function() {
            // カテゴリ
            var MOUSE_EVENT = "MouseEvents",
                HTML_EVENT  = "HTMLEvents",
            // 初期化メソッド名
                INIT_MOUSE_EVENT = "initMouseEvent",
                INIT_HTML_EVENT  = "initEvent";
            return {
                // 未定義時のデフォルト
                "DEFAULT"     : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  def : [true,  true]},
                // 定義済イベント種類 (仕様の一部のみ)
                "click"       : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, def : [true,  true,  window, 1, 0, 0, 0, 0, false, false, false, false, 0, null]},
                "mousedown"   : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, def : [true,  true,  window, 1, 0, 0, 0, 0, false, false, false, false, 0, null]},
                "mouseup"     : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, def : [true,  true,  window, 1, 0, 0, 0, 0, false, false, false, false, 0, null]},
                "mouseover"   : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, def : function(relatedTarget) { return [true,  true,  window, 0, 0, 0, 0, 0, false, false, false, false, 0, relatedTarget]; }},
                "mousemove"   : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, def : [true,  true,  window, 0, 0, 0, 0, 0, false, false, false, false, 0, null]},
                "mouseout"    : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, def : function(relatedTarget) { return [true,  true,  window, 0, 0, 0, 0, 0, false, false, false, false, 0, relatedTarget]; }},
                "load"        : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  def : [false, false]},
                "unload"      : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  def : [false, false]},
                "abort"       : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  def : [true,  false]},
                "error"       : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  def : [true,  false]},
                "select"      : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  def : [true,  false]},
                "change"      : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  def : [true,  false]},
                "focus"       : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  def : [false, false]},
                "blur"        : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  def : [false, false]},
                "resize"      : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  def : [true,  false, window, 1]},
                "scroll"      : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  def : [true,  false, window, 1]}
            };
        })(),
        DOM3 : (function() {
            // カテゴリ
            var UI_EVENT    = "UIEvent",
                MOUSE_EVENT = "MouseEvent",
                EVENT       = "Event",
            // 初期化メソッド名
                INIT_UI_EVENT    = "initUIEvent",
                INIT_MOUSE_EVENT = "initMouseEvent",
                INIT_EVENT       = "initEvent";
            return {
                // 未定義時のデフォルト
                "DEFAULT"     : {type : EVENT,       init : INIT_EVENT,       def : [true,  true]},
                // 定義済イベント種類 (仕様の一部のみ)
                "focus"       : {type : UI_EVENT,    init : INIT_UI_EVENT,    def : [false, false, window, 1]},
                "blur"        : {type : UI_EVENT,    init : INIT_UI_EVENT,    def : [false, false, window, 1]},
                "click"       : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, def : [true,  true,  window, 1, 0, 0, 0, 0, false, false, false, false, 0, null]},
                "dblclick"    : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, def : [true,  true,  window, 2, 0, 0, 0, 0, false, false, false, false, 0, null]},
                "mousedown"   : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, def : [true,  true,  window, 1, 0, 0, 0, 0, false, false, false, false, 0, null]},
                "mouseup"     : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, def : [true,  true,  window, 1, 0, 0, 0, 0, false, false, false, false, 0, null]},
                "mouseover"   : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, def : function(relatedTarget) { return [true,  true,  window, 0, 0, 0, 0, 0, false, false, false, false, 0, relatedTarget]; }},
                "mousemove"   : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, def : [true,  true,  window, 0, 0, 0, 0, 0, false, false, false, false, 0, null]},
                "mouseout"    : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, def : function(relatedTarget) { return [true,  true,  window, 0, 0, 0, 0, 0, false, false, false, false, 0, relatedTarget]; }},
                "load"        : {type : EVENT,       init : INIT_EVENT,       def : [false, false]},
                "unload"      : {type : EVENT,       init : INIT_EVENT,       def : [false, false]},
                "abort"       : {type : EVENT,       init : INIT_EVENT,       def : [true,  false]},
                "error"       : {type : EVENT,       init : INIT_EVENT,       def : [true,  false]},
                "select"      : {type : EVENT,       init : INIT_EVENT,       def : [true,  false]},
                "change"      : {type : EVENT,       init : INIT_EVENT,       def : [true,  false]},
                "resize"      : {type : UI_EVENT,    init : INIT_UI_EVENT,    def : [true,  false, window, 1]},
                "scroll"      : {type : UI_EVENT,    init : INIT_UI_EVENT,    def : [true,  false, window, 1]}
            };
        })()
    },

    // イベント実行後のアフターケア処理をラッピング
    _wrapAfterCare : function(listener) {
        return function(event) {
            if (!event && window.event) {
                event = window.event; // Trident: 独自仕様
            }
            // 実行
            // イベントリスナの戻り値が false なら各種キャンセル実行
            if (false === listener(event)) {
                MJL.event.cancel(event);
            }
        };
    },

    //
    // オリジナルイベント
    //
    _origins : {
        // リサイズ (Trident ONLY)
        // BUG IE7,8: resize イベントで無限ループが発生しやすい
        // BUG IE6,7: 意図しない resize イベントが発生する
        resize : MJL.ua.trident ? function() {
            // 実行許可フラグ (許可: true, 不許可: false)
            var enable = true;
            // setTimeout 用実行許可
            function changeEnable() {
                enable = true;
            }
            // このリスナだけ登録 (他のリスナはスタックから実行)
            window.attachEvent("onresize", function() {
                // 実行許可が出ている時のみ実行
                if (enable) {
                    enable = false; // 実行不許可
                    // 全登録リスナを一括実行
                    MJL.event.dispatch(window, "resize");
                    // このタイミングで setTimeout による遅延評価を行うと
                    // 全登録リスナの実行後に changeEnable を評価する
                    // 登録リスナの中に resize イベントを誘発するものがある
                    // 場合、次の順序で評価が行われるらしい
                    //   1. 元 resize
                    //   2. 登録リスナ
                    //   3. 登録リスナによって誘発された resize
                    //   4. changeEnable
                    // setTimeout を使わないと、登録リスナによって誘発
                    // された resize の前に changeEnable が実行されてしまう
                    setTimeout(changeEnable, 0); // 実行許可
                }
            });
        // WebKit: window.dispatchEvent 未定義
        } : !window.dispatchEvent ? function() {
            // ネイティブ resize イベントを使用
            window.addEventListener("resize", function() {
                MJL.event.dispatch(window, "resize");
            }, false);
        } : undefined,

        // フォントリサイズ
        fontresize : function() {
            // 疑似イベント
            setInterval(MJL.bind(function() {
                if (MJL.style.isFontResized()) {
                    this.dispatch(document, "fontresize");
                }
            }, this), 1000);
        },

        // 強制再描画
        forcedraw : function() {/* EMPTY */}
    },

    // オリジナルイベント 追加
    _addOrigin : function(node, type, listener) {
        var data = MJL.getData(node);
        // 1回だけ初期化
        if (!data.event) {
            data.event = {};
        }
        if (!data.event[type]) {
            this._origins[type].call(this, node);
            data.event[type] = []; // イベントリスナ集合
        }
        var nitems = data.event[type].length;
        // 追加
        data.event[type][nitems] = listener;
        return nitems;
    },

    // オリジナルイベント 削除
    _removeOrigin : function(node, type, listener) {
        var data = MJL.getData(node);
        if (data.event && data.event[type]) {
            delete data.event[type][listener];
        }
        return listener;
    },

    // オリジナルイベント 発送 (= fire)
    _dispatchOrigin : function(node, type, event) {
        var data   = MJL.getData(node),
            items  = (data.event || EMPTY_OBJECT)[type] || EMPTY_ARRAY,
            nitems = items.length;
        for (var i = 0; i < nitems; i++) {
            items[i](event);
        }
    }
};


// ------------------------------------
// BUG Op9.x: フルページズーム時に window オブジェクトで resize イベント未発生
// ------------------------------------
if (MJL.ua.opera && MJL.ua.version < 10) {
    // 疑似イベントでズームを探知
    //   -> window オブジェクトへ reisze イベント発送
    MJL.event.add(window, "load", function() {
        // ズーム時に ViewPort サイズが増減することを併用
        // 但し、何らかの DOM スタイル操作で ViewPort のスクロールバーが
        // 縦横同時に出現した場合、ズームされなくとも true を返す場合がある
        var size = MJL.vp.getSize(); // ViewPort サイズ
        // 疑似イベント
        setInterval(function() {
            var now = MJL.vp.getSize();
            if (size.width !== now.width || size.height !== now.height) {
                size = now;
                MJL.event.dispatch(window, "resize");
            }
        }, 100);
    });
}


// ------------------------------------
// メモリリーク防止
// ------------------------------------
MJL.event.add(window, "unload", function() {
    var stack  = MJL.event._stack,
        nstack = stack.length;
    // イベントスタックの全イベントに対し remove を実行
    for (var s = 0; s < nstack; s++) {
        MJL.event.remove.apply(MJL.event, stack[s]);
    }
});


// ----------------------------------------------------------------------------
// style: stylesheet オブジェクトリスト 操作インタフェイス
// ----------------------------------------------------------------------------
MJL.style = {
    // --------------------------------
    // Public
    // --------------------------------
    // StyleSheetList オブジェクト取得
    getSheets : MJL.ua.webkit ? (function() {                    // WebKit
        // BUG WebKit: document.styleSheets は link 要素参照による代替スタイル
        //             を含まない
        //             (xml-stylesheet 命令参照の代替スタイルは含む)
        // link 要素 収集
        var getLinkElements = document.querySelectorAll ? function(rel) { // Sf3
            return MJL.convArray(document.querySelectorAll(
                "html > head > link[rel=\""+rel+"\"]"
            ));
        } : function(rel) {                                               // Sf2
            // 線形探索
            var links  = document.getElementsByTagName("link"),
                nlinks = links.length,
                ret    = [];
            for (var l = 0; l < nlinks; l++) {
                if (links[l].getAttribute("rel") === rel) {
                    ret.push(links[l]);
                }
            }
            return ret;
        };

        // 初回時のみ実行する初期化
        var initFirst = function() {
            var links  = getLinkElements("alternate stylesheet"),
                nlinks = links.length;
            // 一旦 true を設定しないと disabled プロパティが有効にならない
            for (var l = 0; l < nlinks; l++) {
                // 要素を無効化 (document.styleSheets 追加のスイッチ)
                links[l].disabled = true;
                // 以降の収集では代替スタイルでない名前つきスタイルシートも
                // 含めたいので、代替スタイルをすべて通常スタイルに変更
                links[l].rel = "stylesheet";
            }
            initFirst = undefined;
        };

        return function() {
            // 初回時のみ初期化
            if (initFirst) {
                initFirst();
            }
            // link 要素は収集しなおす
            var links  = getLinkElements("stylesheet"),
                nlinks = links.length,
                tmp    = new Array(nlinks),
                l;
            // link 要素参照による代替スタイルシートを document.styleSheets に
            // 強制追加
            for (l = 0; l < nlinks; l++) {
                tmp[l] = links[l].disabled;
                links[l].disabled = false;
            }
            // StyleSheetList は live なので配列化して変更を保存
            var ret = MJL.convArray(document.styleSheets);
            // 元に戻す
            for (l = 0; l < nlinks; l++) {
                links[l].disabled = tmp[l];
            }
            // BUG WebKit: CSSStyleSheets オブジェクトの disabled プロパティを
            //             操作してもスタイルの有効・無効を設定できない
            for (var r = 0; r < ret.length; r++) {
                // スタイル参照ノードの disable プロパティを利用すれば
                // スタイル有効・無効を設定可能
                ret[r] = ret[r].ownerNode;
            }
            // 各アイテムは各スタイル参照ノード
            return ret;
        };
    })() : (MJL.ua.opera && MJL.ua.version < 9.5) ? function() { // Opera 9.2
        // BUG Op9.2: CSSStyleSheets オブジェクトの disabled プロパティは
        //            対応するスタイル参照ノードの disable プロパティに1度は
        //            値を設定しないとスタイルの有効・無効を設定できない
        var sheets  = MJL.convArray(document.styleSheets),
            nsheets = sheets.length;
        for (var s = 0; s < nsheets; s++) {
            // 同一の値を設定 (有効化)
            sheets[s].ownerNode.disabled = sheets[s].disabled;
        }
        return sheets;
    } : function() {                                             // Others
        // 特に何もしなくてよい
        return MJL.convArray(document.styleSheets);
    },

    // 名前つきスタイルシートの連想配列を取得
    getTitledSheets : function() {
        var sheets  = this.getSheets(),
            nsheets = sheets.length,
            ret     = {
                // "title 属性値" : [CSSStyleSheet オブジェクト, ...], ...
            };
        for (var s = 0; s < nsheets; s++) {
            var title = sheets[s].title;
            if (title) {
                // 初回時 データ構造生成
                if (!(title in ret)) {
                    ret[title] = [];
                }
                ret[title].push(sheets[s]);
            }
        }
        return ret;
    },

    // 現在アクティブな名前つきスタイルシートの title 属性値を取得
    getActiveTitle : function(sheets) {
        var ret = "";
        if (!sheets) {
            sheets = this.getTitledSheets();
        }
        for (var title in sheets) {
            if (!sheets[title][0].disabled) {
                ret = title;
                break;
            }
        }
        return ret;
    },

    // 名前つきスタイルシート title に切替
    switchAlt : function(title) {
        var sheets = this.getTitledSheets(),
            active = this.getActiveTitle(sheets),
            ret    = active;
        // 最小限の要素のみ対象として切替
        if (title && title in sheets) {
            // 現在アクティブなスタイル群
            var befores  = (active && active !== title) ? sheets[active] : [],
                nbefores = befores.length;
            // 現在アクティブなスタイルと同一タイトルが指定されたら
            // 何もしない
            if (0 < nbefores) {
                // 次にアクティブとなるスタイル群
                var afters  = sheets[title],
                    nafters = afters.length;
                for (var b = 0; b < nbefores; b++) {
                    befores[b].disabled = true;
                }
                for (var a = 0; a < nafters; a++) {
                    afters[a].disabled = false;
                }
            }
            ret = title;
        }
        return ret;
    },

    // 計算済スタイルプロパティ値 取得
    getComputed : (function() {
        // キャッシュ
        var dv = document.defaultView;
        // 文字列の先頭1文字を大文字に変換
        function ucfirst(str) {
            return str.charAt(0).toUpperCase() + str.substring(1);
        }
        // width/height 用カスタム関数 取得
        function getSize(type, p1str, p2str) {
            var ret;
            // IE: currentStyle では明示的に設定しない限り px 値をとれない
            // BUG Op9.2: getComputedStyle で取得した値が不正
            if (MJL.ua.trident || (MJL.ua.opera && MJL.ua.version < 9.5)) {
                type  = "client"  + ucfirst(type);
                p1str = "padding" + ucfirst(p1str);
                p2str = "padding" + ucfirst(p2str);
                ret = function(elem) {
                    var p1 = parseInt(
                        this.getComputed(elem, p1str), DIG_DEC
                    ) || 0;
                    var p2 = parseInt(
                        this.getComputed(elem, p2str), DIG_DEC
                    ) || 0;
                    // BUG IE6,7: hasLayout が false だと clientXXX が 0 になる
                    //            場合がある (例: dt, dd 要素)
                    var clientSize;
                    if (MJL.ua.trident     &&
                        MJL.ua.version < 8 &&
                        !elem.currentStyle.hasLayout) {
                        // 強制的に hasLayout を true にして clientXXX を算出
                        var zoom = elem.style.zoom;
                        elem.style.zoom = 1;
                        clientSize = elem[type];
                        elem.style.zoom = zoom;
                    } else {
                        clientSize = elem[type];
                    }
                    // clientXXX は padding を含む為、除算
                    return (0 < clientSize ? (clientSize-p1-p2) : 0)+"px";
                };
            // BUG WebKit: フルページズーム時に width/height 値へズーム倍率が
            //             乗算され、レンダリング時の width/height 値 (実測値、
            //             getComputedStyle 取得値) が意図通りにならない
            } else if (MJL.ua.webkit && 4 <= MJL.ua.version) {
                // 実測値 = width/height 値 * ズーム倍率
                //   -> ズーム倍率を除算すれば、意図通りの実測値を得られる
                //   -> width/height 値 (＝返値) になる
                ret = function(elem) {
                    // ズーム倍率算出 単位取得用要素
                    var unit = MJL.style._getUnitElem();
                    // 返値 = 実測値 / ズーム倍率
                    return parseInt(
                        parseInt( // 実測値
                            dv.getComputedStyle(elem, null)[type],
                            DIG_DEC
                        ) / (     // ズーム倍率
                            // WebKit ソースコードより
                            //   clientXXX = 実測値 / ズーム倍率
                            // ∴
                            //   ズーム倍率 = 実測値 / clientXXX
                            // see also: http://trac.webkit.org/browser/trunk/WebCore/dom/Element.cpp
                            parseInt(
                                dv.getComputedStyle(unit, null).height,
                                DIG_DEC
                            // BUG WebKit: 縮小ズーム時に clientHeight 値が
                            //             -1px される (浮動小数点誤差？)
                            ) / 10000 // Unit Element 'height' px 値を直接指定
                        ),
                        DIG_DEC
                    );
                };
            }
            return ret;
        }
        // 各プロパティ計算値算出用 カスタム関数
        // 通常取得では意図通りの値が得られない場合に利用
        var customs = {
            width    : getSize("width", "left", "right"),
            height   : getSize("height", "top", "bottom"),
            fontSize : MJL.ua.trident ? (function() {
                // IE: currentStyle では明示的に設定しない限り px 値をとれない
                var condPer  = /%$/, // % 単位
                    keywords = {     // 各種キーワード一覧
                        // absolute-size
                        'xx-small' : true,
                        'x-small'  : true,
                        'small'    : true,
                        'medium'   : true,
                        'large'    : true,
                        'x-large'  : true,
                        'xx-large' : true,
                        // relative-size
                        'larger'   : true,
                        'smaller'  : true,
                        // others
                        'inherit'  : true
                    };
                // % 単位ないし各種キーワードが指定されている場合、強制的に
                // "1em" を返させる
                // px 変換を通すと適切な値に変換してくれる
                return function(elem) {
                    var ret = elem.currentStyle.fontSize;
                    if (ret in keywords || condPer.test(ret)) {
                        ret = "1em";
                    }
                    return ret;
                };
            })() : undefined
        };

        if (dv && dv.getComputedStyle) { // W3C DOM
            return function(elem, prop) {
                return customs[prop] ? customs[prop].call(this, elem)
                                     : dv.getComputedStyle(elem, null)[prop];
            };
        } else if (DE.currentStyle) {    // Trident
            var condPx  = /\d\s*px$/i, // px 単位
                condNum = /^\d/;       // 数値
            return function(elem, prop) {
                var ret = customs[prop] ? customs[prop].call(this, elem)
                                        : elem.currentStyle[prop];
                // px 以外の数値は px 値に変換
                if (!condPx.test(ret) && condNum.test(ret)) {
                    var jsss = elem.style.left,
                        rtss = elem.runtimeStyle.left;
                    // left プロパティに値を詰め、pixelLeft で単位変換
                    elem.runtimeStyle.left = elem.currentStyle.left;
                    elem.style.left = ret || 0;
                    ret = elem.style.pixelLeft + "px";
                    // 元の値に戻す
                    elem.style.left = jsss;
                    elem.runtimeStyle.left = rtss;
                }
                return ret;
            };
        }
        // 「'getComputedStyle' もしくは 'currentStyle' がサポートされていません」
        throw new Error("'getComputedStyle' or 'currentStyle' is not supported");
    })(),

    // フォントリサイズ判定用 font-size 取得要素 設定
    setFontResizeTarget : function(target) {
        // load 前後どちらでも利用可能
        //   前: 本関数     -> 初期値設定
        //   後: 初期値設定 -> 本関数
        if (!this._fontTarget || !MJL.isSameNode(this._fontTarget, target)) {
            this._fontTarget = target;
            this.isFontResized();
        }
    },

    // フォントリサイズ是非 (是: true, 非: false)
    isFontResized : (function() {
        // 前回計算時の情報
        var size   = 0,    // 計算値
            target = null; // 対象要素
        // 初期値設定
        MJL.event.add(window, "load", function() {
            // body 要素は load 後に取得可能になる為、この時点で設定
            if (!MJL.style._fontTarget) {
                MJL.style._fontTarget = target = document.body;
            }
            // isFontResized() を呼ぶと sameNode が true になってしまう為
            // 単一呼出
            size = MJL.style.getComputed(target, "fontSize");
        });
        // 対象要素のみチェック
        function check() {
            var nowTarget = MJL.style._fontTarget; // 対象要素
            // 対象要素の実体が生成されるまで何もしない
            if (nowTarget) {
                var nowSize  = MJL.style.getComputed(nowTarget, "fontSize"),
                    sameNode = MJL.isSameNode(target, nowTarget);
                // 対象要素が変更された
                if (!sameNode) {
                    target = nowTarget;
                }
                if (size !== nowSize) {
                    size = nowSize;
                    // 対象要素と前回計算時 対象要素の比較結果で返値を変更
                    //   true:  対象要素が同一
                    //          size は対象要素の前回計算値
                    //          比較対象になりうる
                    //   false: 対象要素が違う
                    //          size は setFontResizeTarget() 実行前に
                    //          設定されていた要素の計算値
                    //          比較対象になりえない
                    return sameNode;
                }
            }
            return false;
        }
        // 実体
        return (MJL.ua.webkit && MJL.ua.version < 3) ? (function() { // Sf2
            // BUG Sf2: テキストズームでは font-size プロパティ計算値が未変化
            //          テキストズーム発生是非を検出するコードが必要
            var body     = null, // body 要素
                unit     = null, // Unit Element
                bodySize = 0,    // body 要素 font-size
                unitSize = 0;    // Unit Element サイズ
            // 初期値設定
            MJL.event.add(window, "load", function() {
                body     = document.body;
                unit     = MJL.style._getUnitElem();
                bodySize = MJL.style.getComputed(body, "fontSize");
                unitSize = unit.offsetWidth;
            });
            // テキストズーム発生是非を検出するコードを含めたチェック
            return function() {
                // テキストズーム発生是非 (是: true, 非: false)
                var nativeTextZoomed = false;
                // 対象要素の実体が生成されるまで何もしない
                if (body && unit) {
                    var nowBodySize = MJL.style.getComputed(body, "fontSize"),
                        nowUnitSize = unit.offsetWidth;
                    // Unit Element サイズのみ変更されている
                    //   -> テキストズーム発生
                    if (bodySize === nowBodySize && unitSize !== nowUnitSize) {
                        bodySize = nowBodySize;
                        unitSize = nowUnitSize;
                        nativeTextZoomed = true;
                    }
                }
                // テキストズームの発生是非によってとりうる動作が変わる
                //   true:  対象要素に設定されている font-size 値に関わらず
                //          サイズ増減が発生していることが保障されている
                //          常にフォントリサイズ true
                //   false: 対象要素のチェックが必要
                return nativeTextZoomed || check();
            };
        })() : check; // Others
    })(),

    // --------------------------------
    // Private
    // --------------------------------
    // フォントリサイズ判定用 font-size 取得要素
    _fontTarget : null,

    // WebKit ONLY: 単位取得用要素取得
    _getUnitElem : function() {
        // 1回目のみ実行される処理
        var elem = document.createElement("div");
        elem.style.margin = elem.style.padding = "0";
        elem.style.display = "block";
        elem.style.width = "1em";
        elem.style.position = "absolute";
        elem.style.top = elem.style.left = "-999em";
        elem.style.borderStyle = "none";
        elem.style.overflow = "hidden";
        // BUG WebKit: getComputed 内 width/height カスタム計算で利用
        //             有効桁数: 小数第2位
        elem.style.height = "10000px";
        // TBD: body 要素の存在を監視する必要あり
        document.body.appendChild(elem);
        // 2回目以降に実行される処理 (自己置換)
        this._getUnitElem = function() { return elem; };
        return elem;
    }
};


// ----------------------------------------------------------------------------
// Switcher: スタイルスイッチャ
// ----------------------------------------------------------------------------
MJL.style.Switcher = function(/* ... */) {
    this.parent  = null; // 基点要素 (親要素)
    this.targets = {     // 対象要素群
        // "スタイルタイトル" : {
        //     element : [対象要素, ...],
        //     event   : イベントリスナ
        // }, ...
    };
    this.classes = {     // class 属性値対応
        active : "active" // アクティブ状態
    };
    this.options = {     // オプション
        // Cookie 是非 (是: MJL.Cookie 第2引数, 非: null)
        cookie  : {
            path     : "/",
            fileUnit : false
        },
        // 対象要素群 収集関数
        collect : this.collect.def
    };
    this.title   = "";   // アクティブスタイルタイトル (オブジェクト別)

    this._parentName = "";    // 基点要素名
    this._cookie     = null;  // MJL.Cookie オブジェクト

    // 生成オブジェクト登録
    this._objects.push(this);

    this.setOptions.apply(this, arguments);
};

MJL.style.Switcher.prototype = {
    // ------------------------------------
    // Public
    // ------------------------------------
    // オプション設定
    setOptions : function(parent, optional) {
        if (0 < arguments.length) {
            this.parent = parent;
            if (MJL.isObject(optional)) {
                for (var o in this.options) {
                    if (o in optional) {
                        this.options[o] = optional[o];
                    }
                }
                if ("classes" in optional) {
                    var classes = optional.classes;
                    for (var c in this.classes) {
                        if (c in classes) {
                            this.classes[c] = classes[c];
                        }
                    }
                }
            }
            // 基点要素名をキャッシュ
            this._parentName = MJL.getName(parent);
        }
    },

    // 生成
    create : function(/* ... */) {
        this.setOptions.apply(this, arguments);
        this._setTargets();
        this._setEvents();
        this._createCookie();
        // 生成時は単一オブジェクトのみ対象として実行
        //   -> 全オブジェクトを対象にする必要はない (パフォーマンス悪化)
        // Cookie 取得は文書内で1回のみに留める
        this._set(this._title || this._getCookie());
    },

    // スタイル設定 (全オブジェクト対象)
    set : function(title) {
        // 同一プロトタイプから生成された全オブジェクトに対し実行
        var objs  = this._objects,
            nobjs = objs.length;
        for (var o = 0; o < nobjs; o++) {
            objs[o]._set(title);
        }
    },

    // 対象要素群 収集関数
    collect : {
        def : function(parent) {
            var targets = [];
            for (var t in this._TYPES) {
                targets = targets.concat(
                    this._TYPES[t].collect.call(this, parent)
                );
            }
            return targets;
        }
    },

    // ------------------------------------
    // Private
    // ------------------------------------
    // 同一プロトタイプから生成された全オブジェクト
    _objects : [],

    // アクティブスタイルタイトル (全オブジェクト共通)
    _title : "",

    // 対象要素の各種情報
    _TYPES : {
        // 要素名 : {
        //     getTitle : スタイルタイトル取得関数,
        //     collect  : 要素収集関数
        // }, ...
        a : {
            getTitle : function(elem) {
                return (COND_HASH.exec(
                    elem.getAttribute("href") || ""
                ) || EMPTY_ARRAY)[1] || "";
            },
            collect : function(parent) {
                // parent の子孫要素にある a 要素を収集
                // parent 自身が該当要素なら parent のみ収集
                return ("a" === this._parentName) ? [parent] : MJL.convArray(
                    parent.getElementsByTagName("a")
                );
            }
        }
    },

    // イベントリスナ
    _listener : function(event, title) {
        this.set(title);
        // スタイル設定時に変更されたフォーカスを、イベント基点に戻す
        var target = MJL.event.getTarget(event);
        if (target) {
            MJL.event.dispatch(target, "focus");
        }
        // 何もさせない
        return false;
    },

    // スタイル設定 (単一オブジェクト対象)
    _set : function(title) {
        // 切替
        //   -> 内部でアクティブスタイル判定を行っているのでそのまま渡す
        title = MJL.style.switchAlt(title);
        // オブジェクト別
        if (this.title !== title) {
            // focus/blur 制御
            // 対象スイッチが存在しないなら何もしない
            //   -> 他のスイッチャ
            var active   = this.classes.active,
                befores  = (
                    this.targets[this.title] || EMPTY_OBJECT
                ).element || EMPTY_ARRAY,
                afters   = (
                    this.targets[title]      || EMPTY_OBJECT
                ).element || EMPTY_ARRAY,
                nbefores = befores.length,
                nafters  = afters.length;
            for (var b = 0; b < nbefores; b++) {
                MJL.removeClassName(befores[b], active);
                MJL.event.dispatch(befores[b], "blur");
            }
            for (var a = 0; a < nafters; a++) {
                MJL.addClassName(afters[a], active);
                MJL.event.dispatch(afters[a], "focus");
            }
            // アクティブスタイルタイトル 変更
            this.title = title;
        }
        // 全オブジェクト共通
        if (this._title !== title) {
            // アクティブスタイルタイトル 変更
            MJL.style.Switcher.prototype._title = title;
            this._setCookie();
        }
    },

    // 対象要素を取得
    _setTargets : function() {
        var targets  = this.options.collect.call(this, this.parent),
            ntargets = targets.length;
        for (var t = 0; t < ntargets; t++) {
            var target = targets[t],
                name   = MJL.getName(target),
                title  = this._TYPES[name].getTitle.call(this, target);
            // タイトルを取得できた要素のみ収集
            if (title) {
                if (title in this.targets) { // 対象要素の追加のみ
                    this.targets[title].element.push(target);
                } else {                     // 初回生成
                    // イベントリスナの生成はこの時点のみ
                    this.targets[title] = {
                        element : [target],
                        event   : MJL.event.bind(this._listener, this, title)
                    };
                }
            }
        }
    },

    // イベント設定
    _setEvents : function() {
        var targets = this.targets;
        for (var title in targets) {
            var elems    = targets[title].element,
                nelems   = elems.length,
                listener = targets[title].event;
            for (var e = 0; e < nelems; e++) {
                MJL.event.add(elems[e], "click", listener);
            }
        }
    },

    //
    // Cookie
    //
    // Cookie 項目名
    _COOKIE_NAME : "MJL.style.Switcher",
    // Cookie 連想配列キー
    _COOKIE_KEY  : "title",

    // Cookie 生成
    _createCookie : function() {
        if (!this._cookie && this.options.cookie) {
            this._cookie = new MJL.Cookie(
                this._COOKIE_NAME, this.options.cookie
            );
        }
    },

    // Cookie 値取得
    _getCookie : function() {
        return this.options.cookie ? this._cookie.get(this._COOKIE_KEY) : "";
    },

    // Cookie 値設定
    _setCookie : function() {
        if (this.options.cookie) {
            this._cookie.set(this._COOKIE_KEY, this._title);
        }
    }
}; // END MJL.style.Switcher.prototype


// ----------------------------------------------------------------------------
// Cookie: クッキー制御
// ----------------------------------------------------------------------------
MJL.Cookie = function(/* ... */) {
    this.name = "";             // 項目名
    this.params = {             // Cookie 設定可能パラメタ
        "path"    : "",
        "domain"  : "",
        "max-age" : 31536000,   // 1年 (60*60*24*365)
        "secure"  : false
    };
    this.options = {            // オプション
        fileUnit : true,        // ファイル単位で管理 (是: true, 非: false)
        index    : "index.html" // インデックスファイル名
    };

    this._nameCond = null; // 項目名抽出条件

    this.setOptions.apply(this, arguments);
};

MJL.Cookie.prototype = {
    // ------------------------------------
    // Public
    // ------------------------------------
    // オプション設定
    setOptions : function(name, optional) {
        if (MJL.isObject(optional)) {
            for (var o in this.options) {
                if (o in optional) {
                    this.options[o] = optional[o];
                }
            }
            for (var p in this.params) {
                if (p in optional) {
                    this.params[p] = optional[p];
                }
            }
        }
        // this.options を利用する為、設定後に実行
        this.setName(name);
    },

    // 項目名設定
    setName : (function() {
        var dirCond = /\/$/,  // ディレクトリ是非
            bsCond  = /\\/g,  // バックスラッシュ是非
            bsTo    = "\\\\", // バックスラッシュ置換後文字列
            cache   = {};     // RegExp オブジェクトのキャッシュ
        return function(name) {
            if (!name || "string" !== typeof name) {
                // 「クッキー名 '???' は無効です」
                throw new Error("Cookie name '"+name+"' is invalid");
            }
            // ファイル単位管理 有効処理
            if (this.options.fileUnit) {
                var path = window.location.pathname; // ファイルパス
                // ファイルパスがディレクトリ＆ this.options.index に指定あり
                //   -> this.options.index を追加してアイテムを共有
                if (dirCond.test(path) && "" !== this.options.index) {
                    path += this.options.index;
                }
                name += "@"+path; // 名前@パス
            }
            if (!(name in cache)) {
                // キャッシュがない場合のみ新規生成
                cache[name] = new RegExp(
                    // Trident: ローカルファイルパスが Windows ファイルシステム
                    //          の値と同じ
                    //          バックスラッシュをエスケープしないと意図した
                    //          正規表現にならない
                    "(?:^|;)[\\s]*"+name.replace(bsCond, bsTo)+"=([^;]+)(?:;|$)"
                );
            }
            this.name = name;
            this._nameCond = cache[name];
        };
    })(),

    // データ取得
    get : function(key) {
        var all = this._getAll();
        return (0 < arguments.length) ? ( // 引数あり: 指定 Cookie 値
            // 文字列 (空文字列は除く) を厳密に判定
            (key && "string" === typeof key && key in all) ? all[key] : undefined
        ) : all;                          // 引数なし: 全 Cookie 集合
    },

    // データ保存
    set : function(key, value) {
        var all    = this._getAll(),
            values = [],
            a;
        all[key] = value; // 値をセット (既にある場合は上書き)
        for (a in all) {
            // Cookie 格納可能形式にエンコード
            // このタイミングで key と value 毎にエンコードするのは
            // デリミタとして使っている文字列に影響を与えない為
            values.push(encodeURIComponent(a)+":"+encodeURIComponent(all[a]));
        }
        // 値がある時のみ実行
        if (0 < values.length) {
            document.cookie = this.name+"="+values.join(",")+this._getParamStr();
        }
    },

    // データ全削除
    remove : function() {
        var tmpAge = this.params["max-age"]; // 既存値を退避
        // max-age を 0 にしてから Cookie 値を全て削除 -> 即反映される
        this.params["max-age"] = 0;
        document.cookie = this.name+"="+this._getParamStr();
        this.params["max-age"] = tmpAge;     // 既存値に戻す
    },

    // ------------------------------------
    // Private
    // ------------------------------------
    // 文字列 slice 用デリミタ
    _DELIMITERS : {
        // 正規表現で \s を利用しているのは余分な空白文字を削除する為
        // 空白文字が残存していると key-value 認識がうまくいかない
        item : /\s*\,\s*/, // アイテム (key-value 対) 間
        hash : /\s*:\s*/   // key-value 間
    },

    // パラメタ設定変換
    _param2 : {
        "path" : {
            cond : function(v) { return v; }, // 条件
            conv : function(v) { return v; }  // 設定値変換
        },
        "domain" : {
            cond : function(v) { return v; },
            conv : function(v) { return v; }
        },
        "max-age" : {
            cond : function(v) { return !isNaN(v); },
            conv : function(v) { return v; }
        },
        "secure" : {
            cond : function(v) { return v; },
            conv : function(v) { return (v ? "sequre" : ""); }
        }
    },

    // 全データ取得
    _getAll : function() {
        var match = this._nameCond.exec(document.cookie || ""),
            ret   = {},
            delims, hashDelim, items, nitems, i, tmp;
        // 正規表現で該当グループのみ切出
        if (match) {
            // デリミタ条件をキャッシュ
            delims    = this._DELIMITERS;
            hashDelim = delims.hash;
            // アイテム単位 (key-value 対) に分割
            items  = match[1].split(delims.item);
            nitems = items.length;
            for (i = 0; i < nitems; i++) {
                // key と value に分割
                tmp = items[i].split(hashDelim);
                // このタイミングで key と value 毎にデコードするのは
                // デリミタとして使っている文字列に影響を与えない為
                ret[decodeURIComponent(tmp[0])] = decodeURIComponent(tmp[1]);
            }
        }
        return ret;
    },

    // オプションマージ済文字列取得
    _getParamStr : function() {
        var compats = [], // 有効なパラメタ集合
            params  = this.params,
            param2  = this._param2,
            p, expires, str;
        for (p in params) {
            // 条件に適合したパラメタのみ収集
            if (param2[p].cond(params[p])) {
                compats.push(p+"="+param2[p].conv(params[p]));
            }
        }
        // BUG Trident, WebKit: max-age 未対応、expires を使うしかない
        expires = this._getExpiresStr();
        if (expires) {
            compats.push(expires);
        }
        str = compats.join(";");
        // 収集したパラメタを単一文字列に変換
        return str ? ";"+str : "";
    },

    // expires 設定用文字列取得
    _getExpiresStr : function() {
        // max-age から算出する
        var maxage = this.params["max-age"],
            ret    = "";
        if (!isNaN(maxage)) {
            var date = new Date();
            date.setTime(date.getTime() + maxage);
            ret = "expires="+date.toGMTString();
        }
        return ret;
    }
}; // END MJL.Cookie.prototype


// ----------------------------------------------------------------------------
// Rollover: ロールオーバー
// ----------------------------------------------------------------------------
MJL.Rollover = function(/* ... */) {
    this.parent    = null; // 基点要素 (親要素)
    this.targets   = [     // 対象要素集合
        // {
        //     id          : ID (配列添字),
        //     name        : 要素名,
        //     element     : 対象要素,
        //     status      : "on" || "off", // "on": 効果 ON, "off": 効果 OFF
        //     path        : {
        //         def : デフォルトのパス,
        //         on  : 効果 ON 時のパス,
        //         off : 効果 OFF 時のパス
        //     },
        //     descendants : [対象要素である子孫要素, ...],
        //     events      : {
        //         イベント種類 : イベントリスナ, ...
        //     }
        // }, ...
    ];
    this.activeId  = -1;   // アクティブ要素 ID
    this.options   = {     // オプション
        active  : "",              // アクティブクラス名
        disable : "",              // 無効化クラス名
        collect : this.collect.def // 対象要素群 収集関数
    };
    this.switchers = {     // 属性値変換対応
        on  : this._SWITCHERS.on,
        off : this._SWITCHERS.off
    };

    this._parentName = ""; // 基点要素名

    this.setOptions.apply(this, arguments);
};

MJL.Rollover.prototype = {
    // ------------------------------------
    // Public
    // ------------------------------------
    // オプション設定
    setOptions : function(parent, optional) {
        if (0 < arguments.length) {
            this.parent = parent;
            // 各種オプション値設定
            if (MJL.isObject(optional)) {
                for (var o in this.options) {
                    if (o in optional) {
                        this.options[o] = optional[o];
                    }
                }
                if ("switchers" in optional) {
                    var switchers = optional.switchers;
                    for (var s in this.switchers) {
                        if (s in switchers) {
                            this.switchers[s] = switchers[s];
                        }
                    }
                }
            }
            // 基点要素名をキャッシュ
            this._parentName = MJL.getName(parent);
        }
    },

    // 生成
    create : function(/* ... */) {
        this.setOptions.apply(this, arguments);
        this._setTargets();
        this._setEvents();
        if (this.options.active) {
            this.reload();
        }
    },

    // 変更
    change : function(event, id) {
        var target = this.targets[id],
            active = this._isActive(target.element),
            status = active ? "on" :
                     event  ? this._TYPE2STATUS[event.type] : target.status;
        if (this._TYPES[target.name].change) {
            target.element.setAttribute("src", target.path[status]);
        } else {
            var type         = this._STATUS2TYPE[status],
                descendants  = this.targets[id].descendants,
                ndescendants = descendants.length;
            for (var d = 0; d < ndescendants; d++) {
                MJL.event.dispatch(descendants[d], type);
            }
        }
        target.status = status;
    },

    // 再描画
    reload : function() {
        var ntargets = this.targets.length;
        for (var t = 0; t < ntargets; t++) {
            this.change(null, t);
        }
    },

    // 対象要素群 収集関数
    collect : {
        def : function(parent) {
            var targets = [];
            for (var t in this._TYPES) {
                targets = targets.concat(
                    this._TYPES[t].collect.call(this, parent)
                );
            }
            return targets;
        }
    },

    // ------------------------------------
    // Private
    // ------------------------------------
    // ロールオーバー ON/OFF 時 URI 文字列置換条件
    _SWITCHERS : {
        on  : {cond : /(\.[^\.]+)$/, replace : "_o$1"},
        off : {cond : "",            replace : ""}
    },

    // イベント種類 - 実行種類 対応
    _TYPE2STATUS : {
        mouseover : "on",
        mouseout  : "off",
        focus     : "on",
        blur      : "off"
    },

    // 実行種類 - イベント種類 対応
    _STATUS2TYPE : {
        on  : "mouseover",
        off : "mouseout"
    },

    // 対象要素の各種情報
    _TYPES : {
        // 要素名 : {
        //     change  : 当該要素の効果適用是非 (是: true, 非: false)
        //               false の場合、子孫要素収集を行う
        //     types   : 
        //     collect : 要素収集関数
        // }, ...
        img : {
            change  : true,
            types   : ["mouseover", "mouseout"],
            collect : function(parent) {
                // parent の img 子孫要素を収集
                // parent 自身が該当するなら parent のみ収集
                return ("img" === this._parentName) ? [parent] : MJL.convArray(
                    parent.getElementsByTagName("img")
                );
            }
        },
        input : {
            change  : true,
            types   : ["mouseover", "mouseout", "focus", "blur"],
            collect : (function() {
                var collect = document.querySelectorAll ? function(parent) {
                    return MJL.convArray(
                        parent.querySelectorAll('input[type="image"]')
                    );
                } : function(parent) {
                    // 線形探索
                    var inputs  = parent.getElementsByTagName("input"),
                        ninputs = inputs.length,
                        elems   = [];
                    for (var i = 0; i < ninputs; i++) {
                        if ("image" === inputs[i].getAttribute("type")) {
                            elems.push(inputs[i]);
                        }
                    }
                    return elems;
                };
                return function(parent) {
                    // parent の input[type="image"] 子孫要素を収集
                    // parent 自身が該当するなら parent のみ収集
                    return (
                        "input" === this._parentName &&
                        "image" === parent.getAttribute("type")
                    ) ? [parent] : collect(parent);
                };
            })()
        },
        a : {
            change  : false,
            types   : ["focus", "blur"],
            collect : function(parent) {
                // parent の a 子孫要素を収集
                // parent 自身が該当するなら parent のみ収集
                var as    = (
                        "a" === this._parentName
                    ) ? [parent] : parent.getElementsByTagName("a"),
                    nas   = as.length,
                    elems = [];
                for (var a = 0; a < nas; a++) {
                    if (0 < as[a].getElementsByTagName("img").length) {
                        elems.push(as[a]);
                    }
                }
                return elems;
            }
        }
    },

    // 効果有効是非 (是: true, 非: false)
    _isEnable : function(elem) {
        if (this.options.disable) {
            do {
                // 同時指定時は disable 優先
                if (MJL.hasClassName(elem, this.options.disable)) {
                    return false;
                }
                // 基点要素より親には遡らない
            } while (
                !MJL.isSameNode(elem, this.parent) && (elem = elem.parentNode)
            );
        }
        return true; // 何もなかった
    },

    // アクティブ是非 (是: true, 非: false)
    _isActive : function(elem) {
        if (this.options.active) {
            do {
                if (MJL.hasClassName(elem, this.options.active)) {
                    return true;
                }
                // 基点要素より親には遡らない
            } while (
                !MJL.isSameNode(elem, this.parent) && (elem = elem.parentNode)
            );
        }
        return false; // 何もなかった
    },

    // 該当しない要素を除外
    _filter : function(targets) {
        var ntargets = targets.length,
            ret      = [];
        for (var t = 0; t < ntargets; t++) {
            if (this._isEnable(targets[t])) {
                ret.push(targets[t]);
            }
        }
        return ret;
    },

    // 対象要素を取得
    _setTargets : function() {
        var targets = this._filter(
            this.options.collect.call(this, this.parent)
        );
        var ntargets = targets.length,
            sw       = this.switchers;
        for (var t = 0; t < ntargets; t++) {
            var target      = targets[t],
                name        = MJL.getName(target),
                change      = this._TYPES[name].change,
                descendants = change ? null : this._TYPES.img.collect.call(
                    this, target
                );
            if (change || 0 < descendants.length) {
                var def = targets[t].getAttribute("src") || "",
                    on  = "",
                    off = "";
                if (def) {
                    on  = def.replace(sw.on.cond,  sw.on.replace);
                    off = def.replace(sw.off.cond, sw.off.replace);
                }
                this.targets.push({
                    id          : t,
                    name        : name,
                    element     : target,
                    status      : "off",
                    path        : {
                        def : def,
                        on  : on,
                        off : off
                    },
                    descendants : descendants,
                    events      : {}
                });
                // ON/OFF 時の画像をキャッシュ
                // デフォルトと同一の場合は何もしない
                if (def !== on)  { this._addCache(on); }
                if (def !== off) { this._addCache(off); }
            }
        }
    },

    // イベント設定
    _setEvents : function() {
        var targets  = this.targets,
            ntargets = targets.length;
        for (var t = 0; t < ntargets; t++) {
            var target = this.targets[t],
                types  = this._TYPES[target.name].types,
                ntypes = types.length;
            for (var p = 0; p < ntypes; p++) {
                var type = types[p];
                target.events[type] = MJL.event.add(
                    target.element,
                    type,
                    MJL.event.bind(this.change, this, t)
                );
            }
        }
    },

    // キャッシュ生成
    _addCache : (function() {
        var cache = { // img 要素キャッシュ
            // "画像パス" : img 要素, ...
        };
        return function(src) {
            // 同一ファイルが既にキャッシュ済: キャッシュ不要
            // 但し、同一パスのみ対応する
            // 故に、例えば次のパスが全て同一ファイル示していても全てキャッシュ
            //    /img/test.png
            //    img/test.png
            //    ./img/test.png
            //    http://foo/img/test.png
            var img = cache[src];
            if (!img) {
                // img 要素を生成し、キャッシュに格納
                cache[src] = img = document.createElement("img");
                img.setAttribute("src", src);
            }
        };
    })()
}; // END MJL.Rollover.prototype


// ----------------------------------------------------------------------------
// Flash: Flash プラグイン インタフェイス
// ----------------------------------------------------------------------------
MJL.Flash = function(/* ... */) {
    this.node    = null;   // DOM ノード
    this.alt     = null;   // 代替要素
    this.options = {       // オプション
        activate  : false, // IE 用アクティブ化是非 (是: true, 非: false)
        version   : 0,     // 最小対応バージョン
        minVerMsg : null   // 指定未満バージョン時メッセージ
    };
    this.params  = {       // param 要素 name/value 属性値対
        // name : value
    };
    // 正常な object 要素の出力是非 (是: true, 非: false)
    this.validCreated = false;
    this.setOptions.apply(this, arguments);
};

MJL.Flash.prototype = (function() {
    var TYPE            = "application/x-shockwave-flash",
        CLASSID         = "clsid:d27cdb6e-ae6d-11cf-96b8-444553540000",
        ACTIVEX_NAME    = "ShockwaveFlash.ShockwaveFlash",
        ACTIVEX_VERSION = "$version",
        ENABLE          = false;

    return {
        // ------------------------------------
        // Public
        // ------------------------------------
        // MIME Type
        type : TYPE,
        // Class ID (ActiveX)
        classid : CLASSID,

        // バージョン
        // 形式：[major, minor, revision, debug]
        version : (function() {
            var cond,                                    // 抽出条件
                str,                                     // バージョン文字列
                rest,                                    // 抽出結果
                ret  = [0, 0, 0, 0],                     // 返値
                mime = window.navigator.mimeTypes[TYPE]; // NPAPI アクセス
            // 各 Plugin API から取得
            if (mime) { // NPAPI 互換
                cond = /(\d+)\.(\d+)(?:\s*[rd\.](\d+))?(?:\s*[bd](\d+))?$/;
                str  = mime.enabledPlugin ? mime.enabledPlugin.description : "";
            } else {    // ActiveX
                cond = /(\d+),(\d+),(\d+),(\d+)/;
                try {
                    // 7 以降
                    str = (
                        new ActiveXObject(ACTIVEX_NAME+".7")
                    ).GetVariable(ACTIVEX_VERSION);
                } catch (e7) {
                    try {
                        // 6.x
                        var plugin = new ActiveXObject(ACTIVEX_NAME+".6");
                        str = "6,0,21,0"; // 6.x first
                        // 6.0.22 - 6.0.29 では GetVariable でクラッシュ
                        // 6,0,47 以上なら AllowScriptAccess が利用可能
                        // AllowScriptAccess で例外を投げさせて回避
                        plugin.AllowScriptAccess = "always";
                        str = plugin.GetVariable(ACTIVEX_VERSION);
                    } catch (e6) {
                        try {
                            // 5.x, 4.x
                            // 3.x は GetVariale で例外を投げる
                            str = (
                                new ActiveXObject(ACTIVEX_NAME)
                            ).GetVariable(ACTIVEX_VERSION);
                        } catch (e5) {
                            // 3.x 以前はサポートしない
                            str = ""; // "6,0,21,0" をリセット
                        }
                    }
                }
            }
            // 抽出
            rest = cond.exec(str);
            // バージョン情報抽出成功
            if (rest) {
                ENABLE = true; // 抽出可能 <=> Plugin 有効
                for (var r = 1, nrest = rest.length; r < nrest; r++) {
                    ret[r-1] = parseInt(rest[r], DIG_DEC) || 0; // 整数化
                }
            }
            return ret;
        })(),

        // Plugin 有効/無効 (有効: true, 無効: false)
        enable : ENABLE, // チェックは version の判定処理内で済ませてある

        // バージョン比較
        compVersion : function(/* ... */) {
            var vers   = this.version,                    // バージョン配列
                nvers  = vers.length,                     // バージョン配列長
                args   = arguments,                       // 引数
                nargs  = args.length,                     // 引数長
                length = (nvers < nargs) ? nvers : nargs, // 最大計算量
                ret    = 0;                               // 返値
            // 最大計算量 (最悪: バージョン配列長) まで実行するか
            // 値が一致しなくなるまで比較
            for (var l = 0; l < length && 0 === ret; l++) {
                // 小数点以下切捨
                var arg = parseInt(args[l], DIG_DEC);
                if (isNaN(arg)) {
                    /* 「引数 ??? '???' は数値ではありません」 */
                    throw new Error(
                        "Argument "+l+" '"+args[l]+"' is not a number"
                    );
                }
                // プラグインバージョン < 比較バージョン:  1
                // プラグインバージョン = 比較バージョン:  0
                // プラグインバージョン > 比較バージョン: -1
                ret = (vers[l] < arg) ?  1 :
                      (vers[l] > arg) ? -1 : 0;
            }
            return ret; // 最終的な比較状態を返す
        },

        // オプション設定
        setOptions : function(node, optional) {
            if (0 < arguments.length) {
                // object 要素以外はありえない
                if (1 !== node.nodeType || "object" !== MJL.getName(node)) {
                    // 「ノード '???' が 'object' 要素ではありません」
                    throw new Error("Node '"+node+"' is not 'object' element");
                }
                this.node = node;
                this.validCreated = false;
                if (MJL.isObject(optional)) {
                    for (var o in this.options) { // オプション抽出
                        if (o in optional) {
                            this.options[o] = optional[o];
                        }
                    }
                }
                this._setParams();
                this._setOptionsByParams(); // optional 引数に優先する
            }
        },

        // 要素生成
        create : function(/* ... */) {
            this.setOptions.apply(this, arguments);
            this._switchNode();
            this._activate();
            return this.node;
        },

        // ------------------------------------
        // Private
        // ------------------------------------
        // param 要素を抽出し、name/value 属性値を this.params に設定
        _setParams : function() {
            var params = MJL.getChildElements(this.node, "param");
            var nparams = params.length;
            for (var p = 0; p < nparams; p++) {
                this.params[params[p].getAttribute("name")] =
                    params[p].getAttribute("value");
            }
        },

        // param 要素 name/value からオプションを抽出
        _setOptionsByParams : function() {
            for (var o in this.options) {
                if (o in this.params) {
                    this.options[o] = this.params[o];
                }
            }
        },

        // ノード切替
        _switchNode : function() {
            // 条件分岐による切替ノード分岐
            if (this.enable) {
                if (this.compVersion(this.options.version) <= 0) {
                    this.validCreated = true; // 正常な object 要素を出力
                    return;
                }
                // 代替コンテンツ表示ケース
                var alt = (
                    this.options.minVerMsg && this.enable
                ) ? this._createMinVerMsg() : this._createAlt();
                this.alt = alt;
                var node = this.node;
                // object 要素内の代替要素と object 要素自身を置換
                node.parentNode.replaceChild(alt, node);
                // ノード切替
                this.node = alt;
            } // plugin 無効なら何もしない
        },

        // 特別ノード生成
        _createMinVerMsg : function() {
            // String: DOM 文字列
            // その他: DOM ノード
            return (
                "string" === typeof this.options.minVerMsg
            ) ? MJL.convNode(this.options.minVerMsg) : this.options.minVerMsg;
        },

        // 代替コンテンツ取得
        _createAlt : MJL.ua.trident ? function() { // Trident
            // BUG IE6,7: childNodes で代替コンテンツが取得不能
            // BUG IE7: 条件コメント <![endif]--> はテキストノードとして
            //          レンダリングされてしまう (未解決)
            // innerHTML を使うと param 要素を除く代替コンテンツのみ取得可能
            return MJL.convNode(this.node.innerHTML);
        } : function() {                           // Others
            var df     = document.createDocumentFragment(),
                elems  = MJL.getChildElements(
                    this.node.cloneNode(true), "param", true
                ),
                nelems = elems.length;
            for (var e = 0; e < nelems; e++) {
                df.appendChild(elems[e]);
            }
            return df;
        },

        // IE ONLY: ActiveX アクティブ化
        _activate : function() {
            // ActiveX アクティブ化は、KB945007 累積パッチ群適用コンピュータには
            // 不要となった
            // see also: http://support.microsoft.com/kb/945007
            // BUG IE6,7: object 要素を DOM ツリーへ追加後に ActiveX をアクティブ
            //            にしないとレンダリングしない
            // see also: http://www.microsoft.com/japan/msdn/workshop/author/dhtml/overview/activating_activex.aspx#loading
            if (MJL.ua.activex && this.options.activate && this.validCreated) {
                this._setCopyObject();
                // ActiveX アクティブ化スイッチは type/classid 属性値
                this.node.setAttribute("type", this.type);
           }
        },

        // IE ONLY: object 要素のコピーを設定
        _setCopyObject : function() {
            // cloneNode によるコピーでは ActiveX アクティブ化ができない
            var obj    = document.createElement("object"),
                root   = this.node.cloneNode(true),
                node   = root.firstChild,
                attrs  = this.node.attributes,
                nattrs = attrs.length,
                name, value;
            // 属性の全移植
            for (var a = 0; a < nattrs; a++) {
                name  = attrs[a].name;
                value = attrs[a].value;
                if (
                    value              && // 無意味な値は無視
                    "null" !== value   &&
                    name               &&
                    "type" !== name    && // ActiveX アクティブ化スイッチは無視
                    "classid" !== name
                ) {
                    obj.setAttribute(name, value);
                }
            }
            // 子孫ノードを複製
            while (node) {
                obj.appendChild(node);
                node = root.firstChild;
            }
            // DOM ツリー上にある object 要素と置換
            this.node.parentNode.replaceChild(obj, this.node);
            this.node = obj;
        }
    };
})(); // END MJL.Flash.prototype

// ユーティリティ
MJL.Flash.version     = MJL.Flash.prototype.version;
MJL.Flash.enable      = MJL.Flash.prototype.enable;
MJL.Flash.compVersion = MJL.Flash.prototype.compVersion;


// ----------------------------------------------------------------------------
// Window: 新規ウインドウ生成
// ----------------------------------------------------------------------------
MJL.Window = function(/* ... */) {
    this.parent = null;            // 基点要素 (親要素)
    this.targets = [               // 対象要素群
        // {
        //     node  : 対象要素
        //     uri   : オープン URI
        //     event : イベントリスナ
        //     ref   : 開いたウインドウへのリファレンス
        // }
    ];
    this.options = {               // オプション
        name    : "_blank",        // ウインドウ識別子
        collect : this.collect.def // 対象要素群 収集関数
    };
    this.params = {                // ウインドウに渡すパラメタ
        // null: パラメタを渡さない
        // 状態
        left   : null,             // 横位置
        top    : null,             // 縦位置
        height : null,             // 縦幅
        width  : null,             // 横幅
        // 表示切替 (非推奨)
        // "yes": 表示, "no": 非表示
        menubar  : "yes",          // メニューバー
        toolbar  : "yes",          // ツールバー
        location : "yes",          // ロケーションバー
        status   : "yes"           // ステータスバー
    };

    this._type       = this._TYPES.a; // 処理種類
    this._parentName = "";            // 基点要素名
    this._paramStr   = "";            // 文字列化したパラメタ

    this.setOptions.apply(this, arguments);
};

MJL.Window.prototype = {
    // ------------------------------------
    // Public
    // ------------------------------------
    // オプション設定
    setOptions : function(parent, optional) {
        if (0 < arguments.length) {
            this.parent = parent;
            // 各種オプション値設定
            if (MJL.isObject(optional)) {
                for (var o in this.options) {
                    if (o in optional) {
                        this.options[o] = optional[o];
                    }
                }
                for (var p in this.params) {
                    if (p in optional) {
                        this.params[p] = this._normalizeParam(optional[p]);
                    }
                }
            }
            // ウインドウ識別子の正規化と判定はなるべく早いタイミングで実行
            this.options.name = this._judgeName(this.options.name);
            // 基点要素名をキャッシュ
            this._parentName = MJL.getName(parent);
            // 基点要素名から処理種類を判別
            if (this._parentName in this._TYPES) {
                this._type = this._TYPES[this._parentName];
            }
            // パラメタは先に文字列化する
            this._paramStr = this._getParamStr();
        }
    },

    // 生成
    create : function(/* ... */) {
        this.setOptions.apply(this, arguments);
        this._setTargets();
    },

    // ウインドウオープン
    open : function(id, name) {
        this.targets[id].ref = window.open(
            this.targets[id].uri,
            name,
            this._paramStr
        );
    },

    // 対象要素群 収集関数
    collect : (function() {
        var condHTTP = /^https?:/, // HTTP/HTTPS スキーム判定
            isHTTP   = (           // URI 文字列 HTTP/HTTPS スキーム有無
                // BUG IE6,7: URI 文字列が含まれる属性値の場合、相対パス指定
                //            であっても getAttribute を経由すると絶対パス指定
                //            になってしまう
                // see also: http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
                MJL.ua.trident && MJL.ua.version < 8
            ) ? function(node, attr) { // IE6,7
                var uri = node.getAttribute(attr, 2); // 独自拡張で回避
                return uri && condHTTP.test(uri);
            } : function(node, attr) { // Others
                var uri = node.getAttribute(attr);
                return uri && condHTTP.test(uri);
            };

        // a 要素収集
        function collectAnchor(parent) {
            return MJL.convArray(parent.getElementsByTagName("a"));
        }

        // a 要素収集 (HTTP/HTTPS スキーム)
        var collectAnchorHTTP = document.querySelectorAll ? function(parent) {
            return MJL.convArray(parent.querySelectorAll(
                'a[href^="http:"], a[href^="https:"]'
            ));
        } : function(parent) {
            // 線形探索
            var anchors  = parent.getElementsByTagName("a"),
                nanchors = anchors.length,
                elems    = [],
                a;
            for (a = 0; a < nanchors; a++) {
                if (isHTTP(anchors[a], "href")) {
                    elems.push(anchors[a]);
                }
            }
            return elems;
        };

        // form 子孫 submit 系要素収集
        var collectForm = document.querySelectorAll ? function(parent) {
            return MJL.convArray(parent.querySelectorAll(
                'input[type="image"], input[type="submit"], button[type="submit"]'
            ));
        } : function(parent) {
            // 線形探索
            var inputs   = parent.getElementsByTagName("input"),
                buttons  = parent.getElementsByTagName("button"),
                ninputs  = inputs.length,
                nbuttons = buttons.length,
                elems    = [],
                type, i, b;
            for (i = 0; i < ninputs; i++) {
                type = inputs[i].getAttribute("type");
                if ("image" === type || "submit" === type) {
                    elems.push(inputs[i]);
                }
            }
            for (b = 0; b < nbuttons; b++) {
                type = buttons[b].getAttribute("type");
                if ("submit" === type) {
                    elems.push(buttons[b]);
                }
            }
            return elems;
        };

        return {
            // デフォルト
            def : function(parent) {
                return ("form" === this._parentName) ? collectForm(parent) :
                       ("a"    === this._parentName) ? [parent]            :
                                                       collectAnchor(parent);
            },
            // http/https スキームの URI を持つものだけ
            http : function(parent) {
                return (isHTTP(parent, "action") || isHTTP(parent, "href")) ? (
                    ("form" === this._parentName) ? collectForm(parent) :
                    ("a"    === this._parentName) ? [parent]            : []
                ) : collectAnchorHTTP(parent);
            }
        };
    })(),

    // ------------------------------------
    // Private
    // ------------------------------------
    // ウインドウ識別子が _blank の時に利用する使い捨て識別子の前置詞
    _NAME_PREFIX : "MJL_window_ITEM_",

    // 変更不可パラメタ
    _IMMUTABLE_PARAMS : {
        resizable  : "yes",
        scrollbars : "yes"
    },

    // 処理種類
    _TYPES : {
        a : {
            uri : function(elem) {
                return elem.getAttribute("href") || "";
            },
            event : function(event, id) {
                this.open(id, this._getName());
                return false;
            }
        },
        form : {
            uri : function() {
                return "";
            },
            event : function(event, id) {
                var name = this._getName();
                this.open(id, name);
                // target 属性を設定することで
                //   1. 何もロードされていない名称 name の window を開く
                //   2. target 属性に name を設定しておく
                //   3. submit 時に target 属性値と同一名の window へロード
                // となり、結果的に好きな名称の window へコンテンツをロード
                // させることが可能になる
                this.parent.setAttribute("target", name);
            }
        }
    },

    // 特殊な意味を持つウインドウ識別子の判定
    _judgeName : function(name) {
        // ウインドウ識別子は必ず指定しなければならない
        if ("string" !== typeof name || "" === name) {
            // 「ウインドウ名 '???' は無効です」
            throw new Error("Window name '"+name+"' is invalid");
        }
        // 特殊な意味を持つウインドウ識別子を指定してはならない
        // _blank は例外
        if ("_self" === name || "_parent" === name || "_top" === name) {
            // 「ウインドウ名 '???' は特殊です：使用してはなりません」
            throw new Error("Window name '"+name+"' is special one: MUST NOT use");
        }
        return name;
    },

    // 対象要素を設定
    _setTargets : function() {
        var targets  = this.options.collect.call(this, this.parent),
            ntargets = targets.length;
        for (var t = 0; t < ntargets; t++) {
            this.targets[t] = {
                element : targets[t],
                uri     : this._type.uri(targets[t]),
                event   : MJL.event.add(targets[t], "click", MJL.event.bind(
                    this._type.event, this, t
                )),
                ref     : null
            };
        }
    },

    // ウインドウ識別子を取得
    _getName : (function() {
        var nameIndex = 0;
        return function() {
            // _blank なら特殊名を利用 (_blank は使わない)
            //   -> _blank 時の動作をエミュレート
            // form で利用している hack の動作条件が「window に name がある」
            // ことである為
            return (
                "_blank" === this.options.name
            ) ? this._NAME_PREFIX+nameIndex++ : this.options.name;
        };
    })(),

    // 実際に window.open へ与えるパラメタ文字列取得
    _getParamStr : function() {
        var ret = [];
        for (var p in this.params) {
            if (null !== this.params[p]) {
                ret.push(p+"="+this.params[p]);
            }
        }
        for (var i in this._IMMUTABLE_PARAMS) {
            ret.push(i+"="+this._IMMUTABLE_PARAMS[i]);
        }
        return ret.join(",");
    },

    // 与パラメタ値を正規化
    _normalizeParam : function(value) {
        return (true  === value) ? "yes" :
               (false === value) ? "no"  : value;
    }
};


// ----------------------------------------------------------------------------
// Tab: タブインタフェイス生成
// ----------------------------------------------------------------------------
MJL.Tab = function(/* ... */) {
    this.container = null;  // コンテナ要素
    this.content   = null;  // タブパネル包含要素 (メインドキュメント)
    this.list      = null;  // タブリスト要素 (タブになる部分)
    this.id        = "";    // タブパネル包含要素の id 属性値
    this.activeId  = "";    // アクティブタブ ID
    this.stat      = false; // 静的モード是非 (是: true, 非: false)
    // class 属性値対応
    this.classes = {
        container : "tabContainer", // コンテナ
        list      : "tabList",      // タブリスト
        panel     : "tabPanel",     // タブパネル
        title     : "tabTitle",     // タブタイトル
        active    : "active",       // アクティブ状態
        stat      : "static"        // 静的モード
    };
    // オプション
    this.options = {
        // Cookie 是非 (是: MJL.Cookie 第2引数, 非: null)
        cookie  : {},
        // 内容要素群 収集関数
        collect : this.collect.def,
        // WAI-ARIA 対応 ON/OFF (ON: true, OFF: false)
        aria    : true
    };
    // id - アイテム集合対応
    this.items = {
        // id : { // 内容要素 id 属性値
        //    num    : 追加順序
        //    id     : id 属性値
        //    panel  : タブパネル要素
        //    title  : タブタイトル要素 (dynamic ONLY)
        //    tab    : タブ要素
        //    anchor : ページ内リンク用 a 要素
        //    ariaId : aria-labelledby 参照用 id 属性値
        // }, ...
    };

    // id - アイテム集合
    // this.items と同内容を、追加順に保存
    this._items = [];
    // 文書から取得したアクティブ ID
    this._activeIdByDocument = "";
    // 要素生成の完了是非 (是: true, 非: false)
    this._created = false;
    // MJL.Cookie オブジェクト
    this._cookie = null;

    this.setOptions.apply(this, arguments);
};

MJL.Tab.prototype = {
    // ------------------------------------
    // Public
    // ------------------------------------
    // オプション設定
    setOptions : function(content, optional) {
        if (0 < arguments.length) {
            this.content = content;
            this.id = content.getAttribute("id") || "";
            // 各種オプション値設定
            if (MJL.isObject(optional)) {
                for (var o in this.options) {
                    if (o in optional) {
                        this.options[o] = optional[o];
                    }
                }
                if ("classes" in optional) {
                    var classes = optional.classes;
                    for (var c in this.classes) {
                        if (c in classes) {
                            this.classes[c] = classes[c];
                        }
                    }
                }
            }
            // id 不在: Cookie 制御不可能 -> 強制 OFF
            if (!this.id) {
                this.options.cookie = null;
            }
            // WAI-ARIA 未対応: 強制 OFF
            if (!MJL.ua.aria) {
                this.options.aria = false;
            }
        }
    },

    // 生成
    create : function(/* ... */) {
        this._created = false;
        this.setOptions.apply(this, arguments);
        this._judgeStatic();
        this._getContents();
        this._createContainer();
        this._createList();
        this._setEvents();
        this._createCookie();
        this._setARIA();
        this.replace();
        this.active();
        this._created = true;
    },

    // コンテナを置換
    replace : function() {
        var items   = this.items,
            content = this.content,
            panel   = this.classes.panel,
            id;
        // reflow (再フロー) 削減対策 開始
        content.style.display = "none";               // reflow: 1
        // タブパネル包含要素に class 属性値を付与
        for (id in items) {
            MJL.addClassName(items[id].panel, panel); // no reflow
        }
        if (this.stat) { 
            // reflow 削減対策 終了
            content.style.display = "";               // reflow: 2
        } else {
            // 動的モードでは必要な要素を挿入
            var container = this.container,
                root      = content.parentNode;
            // コンテナ配置
            container.style.display = "none";
            root.insertBefore(container, content);    // no reflow
            // コンテナに必要な要素を追加
            container.appendChild(this.list);
            container.appendChild(content);           // no reflow
            // reflow 削減対策 終了
            // コンテナ表示前に実行して連続 reflow を防止
            content.style.display = "";               // no reflow
            // コンテナ表示
            container.style.display = "";             // reflow: 2
        }
    },

    // アクティブタブを変更
    active : function(id) {
        // アクティブ ID
        var aid = this._getActiveId();
        // アクティブ ID と異なる -> 実行
        if (id !== aid) {
            if (!this._isValidId(id)) { // 対象 ID が不正
                id = aid;               // 現在のアクティブ ID を使用
            }
            // アクティブ状態 class 属性値
            var active = this.classes.active,
            // アクティブなアイテム
                before = this.items[aid],
                after  = this.items[id];
            // アクティブ ID -> 非アクティブ
            MJL.removeClassName(before.tab, active);
            MJL.removeClassName(before.panel, active);
            // 対象 ID -> アクティブ
            MJL.addClassName(after.tab, active);
            MJL.addClassName(after.panel, active);
            // focus/blur 制御
            if (this._created) { // 生成中は何もしない
                // 明示的 blur イベント発送
                // 未フォーカス -> after (click で直接フォーカス)
                //   blur 未発生
                // before -> after
                //   1. click イベントハンドラ実行前に blur 発生
                //   2. before に active がある状態のまま
                //   3. MJL.Rollover のアクティベートチェックで before に抵触
                //   4. アクティベートがうまくいかない
                MJL.event.dispatch(before.anchor, "blur");
                // BUG Op9.5,9.6: focus() メソッドで focus イベント未発生
                // BUG Webkit: click イベント時に focus イベント未発生
                if (MJL.ua.webkit || (MJL.ua.opera && MJL.ua.version < 10)) {
                    MJL.event.dispatch(after.anchor, "focus"); // 明示的に発送
                }
            }
            // 事後処理
            this._activeARIA(aid, id);
            this._setActiveId(id);
            this._setCookie();
            // 強制再描画
            //   タブパネル内に HeightEqualizer がある場合
            //   ロード時非表示 -> 表示 すると:
            //     1. 非表示要素はスタイル計算値が保障されない
            //        絶対配置要素は width/height が内容に従うので不整値化
            //     2. ロード時の状態で HeightEqualizer 初期化、height 設定
            //     3. HeightEqualizer 対象要素は親要素の resize を認識できない
            //        非表示 -> 表示時に HeightEqualizer 対象要素は何もしない
            //     4. 不正値で height 設定された HeightEqualizer 対象要素を表示
            //     5. 崩れる
            // Trident: forcedraw させたい時は resize も走るのでイベントリスナ
            //          が2重起動してしまう
            //            -> forcedraw を発送しないことで対応
            if (!MJL.ua.trident) {
                MJL.event.dispatch(window, "forcedraw");
            }
        }
    },

    // 内容要素群 収集関数
    collect : {
        // デフォルト
        def : function(parent) {
            // タブパネル包含要素の子要素を収集
            return MJL.getChildElements(parent);
        }
    },

    // ------------------------------------
    // Private
    // ------------------------------------
    // 自動生成 id 属性値接頭語
    _ID_PREFIX : "MJL_TAB_ITEM_",

    // アクティブ ID 取得処理群
    _ACTIVE_ID_RULES : [
        // 優先順位
        //   Cookie > URI > ドキュメント > 先頭アイテム
        function() { // Cookie
            return this._getCookie();
        },
        function() { // URI
            return MJL.getHash();
        },
        function() { // ドキュメント
            return this._activeIdByDocument;
        },
        function() { // 先頭アイテム
            return this._items[0].id;
        }
    ],

    // 各種イベント
    _EVENTS : {
        "click" : function(event, id) {
            this.active(id);
            MJL.event.preventDefault(event); // ページ内遷移防止
        },
        "keydown" : function(event, id) {
            if (!event.ctrlKey && !event.altKey && !event.shiftKey) {
                var num = this.items[id].num,
                    max = this._items.length - 1;
                switch (event.keyCode) {
                case 37: // DOM_VK_LEFT
                case 38: // DOM_VK_UP
                    // 前のタブを選択
                    num--;
                    // 疑似 双方向循環連結リスト
                    if (num < 0) { num = max; } // 配列末尾
                    break;
                case 39: // DOM_VK_RIGHT
                case 40: // DOM_VK_DOWN
                    // 次のタブを選択
                    num++;
                    // 疑似 双方向循環連結リスト
                    if (max < num) { num = 0; } // 配列先頭
                    break;
                default: // 何もしない
                    num = NaN;
                    break;
                }
                if (this._isValidNum(num)) {
                    var target = this._items[num];
                    if (this._created) {
                        target.anchor.focus();
                    }
                    this.active(target.id);
                    MJL.event.preventDefault(event); // スクロール防止
                }
            }
        }
    },

    // 静的モード判定
    _judgeStatic : function() {
        this.stat = MJL.hasClassName(this.content, this.classes.stat);
        if (this.stat) {
            // 静的モードなら class 属性値削除
            // CSS ON, JS OFF の時、情報は伝達するように
            // 例: .tabContainer .tabs.static {position:static;}
            MJL.removeClassName(this.content, this.classes.stat);
        }
    },

    // 適正な ID 値の是非 (是: true, 非: false)
    _isValidId : function(id) {
        return "" !== id && id in this.items;
    },

    // 適正なタブ番号の是非 (是: true, 非: false)
    _isValidNum : function(num) {
        return !(isNaN(Number(num)) || undefined === this._items[num]);
    },

    // アクティブ ID 取得
    _getActiveId : function() {
        // 1回目のみ実行される処理
        var rules    = this._ACTIVE_ID_RULES,
            nrules   = rules.length,
            activeId = "";
        // 文書からアクティブ ID を取得しておく
        this._setActiveIdByDocument();
        // 検索
        for (var r = 0; r < nrules; r++) {
            activeId = rules[r].call(this);
            if (this._isValidId(activeId)) {
                break;
            }
        }
        // アクティブ ID の不在はありえない
        if (!activeId) {
            // 「アクティブ ID を取得できません」
            throw new Error("Tab active ID can not be acquired");
        }
        // 2回目以降の呼出で利用される処理 (自己置換)
        this._getActiveId = function() { return this.activeId; };
        // 1回目のみ
        this._setActiveId(activeId);
        return activeId;
    },

    // 文書からアクティブ ID を設定
    _setActiveIdByDocument : function() {
        var active = this.classes.active;
        for (var id in this.items) {
            if (MJL.hasClassName(this.items[id].panel, active)) {
                this._activeIdByDocument = id;
                // アクティブ class 属性値削除
                MJL.removeClassName(this.items[id].panel, active);
                if (this.stat) {
                    // 静的モードではタブからも削除
                    MJL.removeClassName(this.items[id].tab, active);
                }
                return;
            }
        }
    },

    // アクティブ ID 設定
    _setActiveId : function(id) {
        if (this._isValidId(id)) {
            this.activeId = id;
        } // id が不正なら何もしない
    },

    // ID 取得
    _getId : (function() {
        // id 属性値用インデックス値
        // 同一文書内での同一 ID 使用は禁止 (id 属性値の仕様)
        //   -> idIndex のインクリメントで対応
        var idIndex = 0;
        return function(elem) {
            var id = "";
            if (this.stat) {
                id = elem.getAttribute("id"); // id 属性値取得
                // id 属性値の不在はありえない
                if (!id) {
                    // 「'id' 属性値 '???' は無効です」
                    throw new Error("'id' attribute value '"+id+"' is invalid");
                }
            } else {
                id = this._ID_PREFIX+idIndex; // 生成
                elem.setAttribute("id", id);  // この時点で付与
                idIndex++;
            }
            return id;
        };
    })(),

    // a 要素 href 属性値から ID を取得
    _getIdByHref : function(elem) {
        var id = (
            COND_HASH.exec(elem.getAttribute("href") || "") || EMPTY_ARRAY
        )[1] || "";
        // ID (ハッシュ値) の不正はありえない
        if (!this._isValidId(id)) {
            // 「'href' 属性値中のハッシュ値 '???' は無効です」
            throw new Error("Hash value '#"+id+"' in 'href' attribute value is invalid");
        }
        return id;
    },

    // タブパネルから id - アイテム集合を取得
    _getContents : function() {
        var targets  = this.options.collect.call(this, this.content),
            ntargets = targets.length,
            num      = 0; // 追加順序
        for (var t = 0; t < ntargets; t++) {
            var id = this._getId(targets[t]);
            // id 属性値の重複はありえない
            if (id in this.items) {
                // 「'id' 属性値 '???' が重複しています」
                throw new Error("'id' attribute value '"+id+"' overlaps");
            }
            // アイテム生成
            this.items[id] = this._items[num] = {
                num    : num,
                id     : id,
                panel  : targets[t],
                title  : this._getTitle(targets[t]),
                tab    : null,
                anchor : null,
                ariaId : null
            };
            num++; // 追加時のみ増加
        }
    },

    // タブタイトル取得
    _getTitle : function(elem) {
        var title = "";
        if (!this.stat) {
            var titles = MJL.getElementsByClassName(elem, this.classes.title);
            // タブタイトルの不在はありえない
            if (titles.length < 1) {
                // 「タブタイトルが見つかりません」
                throw new Error("Tab-title is not found");
            }
            title = titles[0];
        }
        return title;
    },

    // タブタイトルの子孫ノードを複製
    _cloneTitle : function(id) {
        var df    = document.createDocumentFragment(),
            title = this.items[id].title.cloneNode(true),
            node  = title.firstChild;
        // 要素以外のノードも複製
        while (node) {
            df.appendChild(node);
            node = title.firstChild;
        }
        return df; // Document Fragment をそのまま使う
    },

    // コンテナ生成
    _createContainer : function() {
        var container = this.classes.container,
            cont      = null;
        if (this.stat) {
            cont = this.content.parentNode;
            // 最大で root まで辿る
            while (cont && !MJL.hasClassName(cont, container)) {
                cont = cont.parentNode;
            }
            // コンテナの不在はありえない
            if (!cont) {
                // 「コンテナが見つかりません」
                throw new Error("Container is not found");
            }
        } else {
            cont = document.createElement("div");
            MJL.addClassName(cont, container);
        }
        this.container = cont;
    },

    // タブリスト生成
    _createList : function() {
        var items = this.items,
            list  = null,
            id, item, li, a;
        if (this.stat) { // 既にある: 取得
            list = MJL.getElementsByClassName(this.container,
                                              this.classes.list)[0];
            // タブリストの不在はありえない
            if (!list) {
                // 「タブリストが見つかりません」
                throw new Error("Tab-list is not found");
            }
            var childs  = MJL.getChildElements(list),
                nchilds = childs.length;
            // タブ数とタブパネル数が異なってはならない
            if (nchilds !== this._items.length) {
                // 「タブ数とタブパネル数が異なります」
                throw new Error("Tab number ("+this._items.length+") and Tab-panel number ("+nchilds+") are different");
            }
            for (var c = 0; c < nchilds; c++) {
                li          = childs[c];
                a           = this._getAnchorElement(li);
                item        = items[this._getIdByHref(a)];
                item.tab    = li;
                item.anchor = a;
            }
        } else {         // まだない: 生成
            list = document.createElement("ul");
            MJL.addClassName(list, this.classes.list);
            for (id in items) {
                item        = items[id];
                item.tab    = li = document.createElement("li");
                item.anchor = a  = document.createElement("a");
                list.appendChild(li)
                    .appendChild(a)
                    .appendChild(this._cloneTitle(id));
                a.setAttribute("href", "#"+id);
            }
        }
        this.list = list;
    },

    // ページ内リンク用 a 要素取得 (static ONLY)
    _getAnchorElement : function(parent) {
        var elem = parent.getElementsByTagName("a")[0];
        // a 要素の不在はありえない
        if (!elem) {
            // 「タブ内 'a' 要素が見つかりません」
            throw new Error("'a' element in Tab is not found");
        }
        return elem;
    },

    // イベント設定
    _setEvents : function() {
        for (var id in this.items) {
            for (var type in this._EVENTS) {
                MJL.event.add(this.items[id].tab, type, MJL.event.bind(
                    this._EVENTS[type], this, id
                ));
            }
        }
    },

    //
    // WAI-ARIA
    // see also: http://www.w3.org/TR/wai-aria-practices/#tabpanel
    //
    // WAI-ARIA 用 id 属性値接頭語
    _ARIA_ID_PREFIX : "MJL_TAB_ARIA_",

    // WAI-ARIA アクティブタブ＆コンテンツ切替
    _activeARIA : function(aid, id) {
        if (this.options.aria) {
            // アクティブなアイテム
            var before = this.items[aid],
                after  = this.items[id];
            // state: selected
            before.tab.setAttribute("aria-selected", "false");
            after.tab.setAttribute("aria-selected", "true");
            // state: hidden
            before.panel.setAttribute("aria-hidden", "true");
            after.panel.setAttribute("aria-hidden", "false");
            // state: activedescendant
            this.list.setAttribute("aria-activedescendant", after.ariaId);
            // tabindex 制御
            // アクティブタブのみ通常フローでフォーカス
            before.tab.tabIndex = before.panel.tabIndex = -1;
            after.tab.tabIndex  = after.panel.tabIndex  = 0;
            // focus/blur 制御
            if (this._created) {
                after.tab.focus();
            }
        }
    },

    // WAI-ARIA 設定
    _setARIA : function() {
        if (this.options.aria) {
            var items = this.items,
                id, item, tab, anchor, panel;
            // role: presentation 設定
            this.container.setAttribute("role", "presentation");
            this.content.setAttribute("role", "presentation");
            // role: tablist 設定
            // activedescendant は active 時に設定
            this.list.setAttribute("role", "tablist");
            // 各アイテム別設定
            for (id in items) {
                this._setIdARIA(id);
                item   = items[id];
                tab    = item.tab;
                anchor = item.anchor;
                panel  = item.panel;
                // role: tab 設定
                tab.setAttribute("role", "tab");
                tab.setAttribute("id", item.ariaId);
                tab.setAttribute("aria-flowto", id);
                tab.setAttribute("aria-controls", id);
                tab.setAttribute("aria-selected", "false");
                // role: presentation 設定
                anchor.setAttribute("role", "presentation");
                // role: tabpanel 設定
                panel.setAttribute("role", "tabpanel");
                panel.setAttribute("aria-labelledby", item.ariaId);
                panel.setAttribute("aria-hidden", "true");
                // tabindex 設定
                tab.tabIndex = anchor.tabIndex = panel.tabIndex = -1;
            }
        }
    },

    // WAI-ARIA 専用 ID 設定
    _setIdARIA : (function() {
        // ARIA id 属性値用インデックス値
        // 同一文書内での同一 ID 使用は禁止 (id 属性値の仕様)
        //   -> idIndex のインクリメントで対応
        var idIndex = 0;
        return function(id) {
            // 静的に id が振られているならそのまま利用
            var item   = this.items[id],
                ariaId = item.tab.getAttribute("id");
            // id が振られていなければ生成
            if (!ariaId) {
                ariaId = this._ARIA_ID_PREFIX+idIndex;
                idIndex++;
            }
            item.ariaId = ariaId;
        };
    })(),

    //
    // Cookie
    //
    // Cookie 項目名
    _COOKIE_NAME : "MJL.Tab",

    // Cookie 生成
    _createCookie : function() {
        if (!this._cookie && this.options.cookie) {
            this._cookie = new MJL.Cookie(
                this._COOKIE_NAME, this.options.cookie
            );
        }
    },

    // Cookie 値取得
    _getCookie : function() {
        return this.options.cookie ? this._cookie.get(this.id) : "";
    },

    // Cookie 値設定
    _setCookie : function() {
        if (this.options.cookie) {
            this._cookie.set(this.id, this._getActiveId());
        }
    }
}; // END MJL.Tab.prototype


// ----------------------------------------------------------------------------
// HeightEqualizer: 指定要素等高
// ----------------------------------------------------------------------------
MJL.HeightEqualizer = function(/* ... */) {
    this.parent  = null;            // 基点要素 (親要素)
    this.targets = [];              // 対象要素群
    this.options = {                // オプション
        groupBy : 0,                // グルーピング要素数
        collect : this.collect.def, // 対象要素群 収集関数
        resize  : true              // 自動リサイズ是非 (ON: true, OFF: false)
    };

    this.setOptions.apply(this, arguments);
};

MJL.HeightEqualizer.prototype = {
    // ------------------------------------
    // Public
    // ------------------------------------
    // オプション設定
    setOptions : function(parent, optional) {
        if (0 < arguments.length) {
            this.parent = parent;
            if (MJL.isObject(optional)) {
                for (var o in this.options) {
                    if (o in optional) {
                        this.options[o] = optional[o];
                    }
                }
            }
        }
    },

    // 生成
    create : function(/* ... */) {
        this.setOptions.apply(this, arguments);
        this.targets = this.options.collect.call(this, this.parent);
        this.set();
        if (this.options.resize) {
            this._setAutoResize();
        }
    },

    // スタイル設定
    set : function() {
        // BUG Trident: setTimeout で並列化すると resize 時無限ループ
        // 一度スタイルを開放しないと前回設定値により通常フロー時の値がとれない
        this.release();
        var heights  = this._getHeights(),
            ntargets = this.targets.length;
        for (var t = 0; t < ntargets; t++) {
            this.targets[t].style.height = heights[t];
        }
    },

    // スタイル開放
    release : function() {
        var ntargets = this.targets.length;
        for (var t = 0; t < ntargets; t++) {
            this.targets[t].style.height = "";
        }
    },

    // 対象要素群 収集関数
    collect : {
        // デフォルト
        def : function(parent) {
            // parent の子要素を収集
            return MJL.getChildElements(parent);
        }
    },

    // ------------------------------------
    // Private
    // ------------------------------------
    // 設定する単位
    _UNIT : "px",

    // 自動リサイズ時の実行イベント情報
    _EVENTS : (function() {
        function listener() {
            this.set();
            return false;
        }
        return [
            {type : "resize",     target : window,   listener : listener},
            {type : "fontresize", target : document, listener : listener},
            {type : "forcedraw",  target : window,   listener : listener}
        ];
    })(),

    // 各要素のレンダリング後 height 最大値取得
    _getHeights : function() {
        var ntargets = this.targets.length,
            heights  = new Array(ntargets),  // 対象要素の height 集合
            groupBy  = this.options.groupBy,
            max      = 0,                    // height 最大値
            t;
        // あらかじめ height を全計算させる
        for (t = 0; t < ntargets; t++) {
            // レンダリング後 height 値を取得 (単位 px, 数値のみ)
            heights[t] = parseInt(
                MJL.style.getComputed(this.targets[t], "height"), DIG_DEC
            );
        }
        // groupBy オプション有効・無効判定
        //   groupBy 有効範囲: 1 < groupBy < ntargets
        //   有効範囲外では groupBy が意味喪失する
        if (groupBy < 2 || ntargets <= groupBy) { // 無効
            // 全対象要素に対する height 最大値
            max = Math.max.apply(Math, heights) + this._UNIT;
            for (t = 0; t < ntargets; t++) {
                heights[t] = max;
            }
        } else {                                  // 有効
            for (t = 0; t < ntargets; t++) {
                // groupBy 個ずつの height 最大値
                if (0 === t % groupBy) {
                    max = Math.max.apply(
                        Math, heights.slice(t, t+groupBy)
                    ) + this._UNIT;
                }
                heights[t] = max;
            }
        }
        return heights;
    },

    // 自動リサイズ設定
    _setAutoResize : function() {
        var events  = this._EVENTS,
            nevents = events.length;
        for (var e = 0; e < nevents; e++) {
            MJL.event.add(
                events[e].target,
                events[e].type, 
                MJL.event.bind(events[e].listener, this)
            );
        }
    }
}; // END MJL.HeightEqualizer.prototype


// ----------------------------------------------------------------------------
// enable: 機能許可インタフェイス (簡易実行ラッパー)
// ----------------------------------------------------------------------------
MJL.enable = (function() {
    // MJL.enable.getRunner 実体
    function getRunner(NewObj) {
        return function(className, optional) {
            var elems  = MJL.getElementsByClassName(document, className),
                nelems = elems.length,
                ret    = new Array(nelems);
            for (var e = 0; e < nelems; e++) {
                ret[e] = new NewObj(elems[e], optional);
                ret[e].create();
            }
            return ret;
        };
    }
    return {
        // 各オブジェクトへのクラス名ベースインタフェイス
        rollover        : getRunner(MJL.Rollover),
        flash           : getRunner(MJL.Flash),
        window          : getRunner(MJL.Window),
        tab             : getRunner(MJL.Tab),
        styleSwitcher   : getRunner(MJL.style.Switcher),
        heightEqualizer : getRunner(MJL.HeightEqualizer),
        // クラス別ベースインタフェイス関数を返す関数
        getRunner : getRunner
    };
})();
// ----------------------------------------------------------------------------
})(window, document);

