/*!*
 * mjl.js
 * MITSUE-LINKS JavaScript Library
 * Version 2.1.1
 * 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.
 */
/*jslint browser: true, forin: true, es5: true, undef: true, eqeqeq: true, bitwise: true, newcap: true */
(function(window) {

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


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


// ------------------------------------
// 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
//     [\u0009-\u000D]\u0020\u00A0\u2028\u2029
//   ES5
//     [\u0009-\u000D]\u0020\u00A0\u2028\u2029\uFEFF
//
// 基本的な空白文字:
//   " "  \u0020 space
//   "\t" \u0009 horizontal tab <HT>
//   "\n" \u000A line feed (new line) <LF>
//   "\v" \u000B vertical tab <VT>
//   "\f" \u000C form feed <FF>
//   "\r" \u000D carriage return <CR>
//
// 各実装の \s 対応:
//   IE6,7,8, Sf3
//     [\u0009-\u000D]\u0020
//   Fx3.0, 3.5
//     [\u0009-\u000D]\u0020\u00A0            [\u2000-\u200A]\u200B\u2028\u2029            \u3000
//   Fx3.6, Sf4, Ch4 (ES5 - \uFEFF)
//     [\u0009-\u000D]\u0020\u00A0\u1680\u180E[\u2000-\u200A]      \u2028\u2029\u202F\u205F\u3000
//   Op9.2, 9.5, 9.6
//     [\u0009-\u000D]\u0020\u00A0\u1680      [\u2000-\u200A]\u200B\u2028\u2029\u202F      \u3000
//   Op10.0x, 10.1x
//     [\u0009-\u000D]\u0020\u00A0            [\u2000-\u200A]\u200B\u2028\u2029\u202F      \u3000
//   Op10.5x (ES5 + \u200B - \uFEFF)
//     [\u0009-\u000D]\u0020\u00A0\u1680\u180E[\u2000-\u200A]\u200B\u2028\u2029\u202f\u205F\u3000
// ------------------------------------
// Note:
//   一部処理系に含まれ、一時期 ES5 ドラフトにも追加されていた \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 = "[\\u0009-\\u000D\\u0020\\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.1" // バージョン情報
};


// ----------------------------------------------------------------------------
// 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 && "version" in window.opera) ? "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 = !(
        // IE6,7: WAI-ARIA 未対応
        (ret.trident && ret.version < 8  ) ||
        // Op9.2: tabindex 未対応
        // Op9.5: click 時、不正な位置に outline が表示されることがある
        (ret.opera   && ret.version < 9.6)
    );

    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 n1.isSameNode && n2.isSameNode ? n1.isSameNode(n2) : n1 === n2;
} : function(n1, n2) {                                    // Trident
    // BUG Trident: === はプロパティ経由でアクセスすると偽となるため利用不可
    //              例: false === (window === window.window)
    /*jslint eqeqeq: false */
    return n1 == n2;
};
/*jslint eqeqeq: true */


// ----------------------------------------------------------------------------
// 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),
            o;
        for (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#space-separated-tokens
//   http://www.w3.org/TR/html5/infrastructure.html#space-separated-tokens
MJL.getCDATA = (function() {
    var cache = { // 処理済 CDATA 関連値のキャッシュ
        // "与文字列" : {
        //     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   = {},
                t, token;
            // 重複判定
            for (t = 0; 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: ハッシュに URI エンコードされた文字列がある場合、
    //            hash プロパティが URI デコードした文字列を返してしまう
    // see also: https://bugzilla.mozilla.org/show_bug.cgi?id=483304
    var prop = MJL.ua.gecko ? "href" : "hash"; // デコードしない href で代用
    return function(obj) {
        // Op: "?" 前後に HTML5 空白文字がある場合、hash にも反映される
        //       -> HTML5 空白文字は明示的に除外
        var str = MJL.trim("string" === typeof obj ? obj : ((
                !obj ? window.location : "window" in obj ? obj.location : obj
            )[prop] || "")),
            pos = str.indexOf("#");
        return pos < 0 ? "" : str.substring(pos+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() {
    // 追加プロパティ名は HTML5 custom data attribute に準拠
    // see also:
    //   http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#embedding-custom-non-visible-data
    //   http://www.w3.org/TR/html5/dom.html#embedding-custom-non-visible-data
    var prop   = "data-MJL-id-"+(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,
        cdata;
    if (className) {
        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 className = elem.className,
            classes   = className ? [className] : [],
            items     = MJL.getCDATA(name).items,
            nitems    = items.length,
            i;
        for (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,
            after;
        if (before) {
            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),
            q;
        for (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],
            items, nitems, paths, i;
        // XPath 式をキャッシュ
        if (!path) {
            // 複数 class 値に対応
            items  = MJL.getCDATA(name).items;
            nitems = items.length;
            paths  = new Array(nitems);
            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, type, options) {
            event[options.init].apply(event, [type].concat(options.args));
        };

        // 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, type, options) {
                var args = options.args;
                // options[2] 以降は全て無視
                event.initEvent(type, args[0], args[1]);
            };
        }

        return function(node, type, options) {
            // 初期化メソッドに与える引数が未指定なら既定値を利用
            if (!options) {
                options = this._TYPES[level][type] || this._TYPES[level].DEFAULT;
                // 遅延評価が必要なものは評価後の返値を利用
                if ("function" === typeof options) {
                    options = options(node);
                }
            }
            // event オブジェクト生成・初期化
            var event = document.createEvent(options.type);
            initEvent(event, type, options);
            return this._origins[type] ? this._dispatchOrigin(node, type, event)
                                       : node.dispatchEvent(event);
        };
    })() : document.fireEvent ? function(node, type, options) { // Trident
        // 初期化メソッドに与える引数が未指定なら既定値を利用
        if (!options) {
            options = this._TYPES.DOM3[type] || this._TYPES.DOM3.DEFAULT;
            // 遅延評価が必要なものは評価後の返値を利用
            if ("function" === typeof options) {
                options = options(node);
            }
        }
        // event オブジェクト生成・初期化
        var event          = document.createEventObject();
        event.type         = type;
        event.cancelBubble = false === options.args[0];
        // 発送
        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 : "DOM インタフェイス名",
    //             init : "初期化メソッド名",
    //             args : [初期化メソッド第2引数, ..., 初期化メソッド第n引数]
    //         }, ...
    //     }, ...
    // }
    _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,  args : [true,  true]},
                // 定義済イベント種類 (仕様の一部のみ)
                // 1.6.2. Mouse event types
                // see also: http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-eventgroupings-mouseevents
                "click"     : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, args : [true,  true,  window, 1, 0, 0, 0, 0, false, false, false, false, 0, null]},
                "mousedown" : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, args : [true,  true,  window, 1, 0, 0, 0, 0, false, false, false, false, 0, null]},
                "mouseup"   : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, args : [true,  true,  window, 1, 0, 0, 0, 0, false, false, false, false, 0, null]},
                "mouseover" : function(relatedTarget) {
                    return    {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, args : [true,  true,  window, 0, 0, 0, 0, 0, false, false, false, false, 0, relatedTarget]};
                },
                "mousemove" : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, args : [true,  false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null]},
                "mouseout"  : function(relatedTarget) {
                    return    {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, args : [true,  true,  window, 0, 0, 0, 0, 0, false, false, false, false, 0, relatedTarget]};
                },
                // 1.6.5. HTML event types
                // see also: http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-eventgroupings-htmlevents
                "load"      : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  args : [false, false]},
                "unload"    : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  args : [false, false]},
                "abort"     : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  args : [true,  false]},
                "error"     : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  args : [true,  false]},
                "select"    : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  args : [true,  false]},
                "change"    : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  args : [true,  false]},
                //"submit"    : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  args : [true,  true]}, // same DEFAULT
                // BUG DOM2: なぜか cancelable が false
                //             -> DOM3 にあわせておく
                //"reset"     : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  args : [true,  true]}, // same DEFAULT
                "focus"     : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  args : [false, false]},
                "blur"      : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  args : [false, false]},
                "resize"    : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  args : [true,  false]},
                "scroll"    : {type : HTML_EVENT,  init : INIT_HTML_EVENT,  args : [true,  false]}
            };
        })(),
        DOM3 : (function() {
            // カテゴリ
            var UI_EVENT    = "UIEvent",
                EVENT       = "Event",
                MOUSE_EVENT = "MouseEvent",
            // 初期化メソッド名
                INIT_UI_EVENT    = "initUIEvent",
                INIT_EVENT       = "initEvent",
                INIT_MOUSE_EVENT = "initMouseEvent";
            return {
                // 未定義時のデフォルト
                "DEFAULT"   : {type : EVENT,       init : INIT_EVENT,       args : [true,  true]},
                // 定義済イベント種類 (仕様の一部のみ)
                // 5.2.1 User Interface Event Types
                // see also: http://www.w3.org/TR/DOM-Level-3-Events/#events-uievents
                "focus"     : {type : UI_EVENT,    init : INIT_UI_EVENT,    args : [false, false, window, 0]},
                "blur"      : {type : UI_EVENT,    init : INIT_UI_EVENT,    args : [false, false, window, 0]},
                // 5.2.2 Basic Event Types
                // see also: http://www.w3.org/TR/DOM-Level-3-Events/#events-basicevents
                "load"      : {type : EVENT,       init : INIT_EVENT,       args : [false, false]},
                "unload"    : {type : EVENT,       init : INIT_EVENT,       args : [false, false]},
                "abort"     : {type : EVENT,       init : INIT_EVENT,       args : [true,  false]},
                "error"     : {type : EVENT,       init : INIT_EVENT,       args : [true,  false]},
                "select"    : {type : EVENT,       init : INIT_EVENT,       args : [true,  false]},
                "change"    : {type : EVENT,       init : INIT_EVENT,       args : [true,  false]},
                //"submit"    : {type : EVENT,       init : INIT_EVENT,       args : [true,  true]}, // same DEFAULT
                //"reset"     : {type : EVENT,       init : INIT_EVENT,       args : [true,  true]}, // same DEFAULT
                "resize"    : {type : EVENT,       init : INIT_EVENT,       args : [true,  false]},
                "scroll"    : {type : EVENT,       init : INIT_EVENT,       args : [false, false]},
                // 5.2.3 Mouse Event Types
                // see also: http://www.w3.org/TR/DOM-Level-3-Events/#events-mouseevents
                "click"     : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, args : [true,  true,  window, 1, 0, 0, 0, 0, false, false, false, false, 0, null]},
                "mousedown" : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, args : [true,  true,  window, 1, 0, 0, 0, 0, false, false, false, false, 0, null]},
                "mousemove" : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, args : [true,  true,  window, 0, 0, 0, 0, 0, false, false, false, false, 0, null]},
                "mouseover" : function(relatedTarget) {
                    return    {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, args : [true,  true,  window, 0, 0, 0, 0, 0, false, false, false, false, 0, relatedTarget]};
                },
                "mouseout"  : function(relatedTarget) {
                    return    {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, args : [true,  true,  window, 0, 0, 0, 0, 0, false, false, false, false, 0, relatedTarget]};
                },
                "mouseup"   : {type : MOUSE_EVENT, init : INIT_MOUSE_EVENT, args : [true,  true,  window, 1, 0, 0, 0, 0, false, false, false, false, 0, null]}
            };
        })()
    },

    // イベント実行後のアフターケア処理をラッピング
    _wrapAfterCare : function(listener) {
        return function(event) {
            if (!event && window.event) {
                event = window.event; // Trident: 独自仕様
            }
            // 実行
            if (false === listener(event)) {
                MJL.event.cancel(event);
            }
        };
    },

    //
    // オリジナルイベント
    //
    _origins : {
        // リサイズ
        // BUG IE7,8: resize イベントで無限ループが発生しやすい
        // BUG IE6,7: 意図しない resize イベントが発生する
        resize : MJL.ua.trident && MJL.ua.version < 9 ? {
            init : 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); // 実行許可
                    }
                });
            }
        // BUG Sf2,3: window.dispatchEvent 未定義
        } : !window.dispatchEvent ? {
            init : function() {
                // ネイティブ resize イベントを使用
                window.addEventListener("resize", function() {
                    MJL.event.dispatch(window, "resize");
                }, false);
            }
        } : undefined,

        // フォントリサイズ
        fontresize : {
            init : function(node, type, listener) {
                var data = this._getOriginData(document, type);
                // 初回実行
                MJL.style.setFontResizeTarget(document.body);
                // 疑似イベント
                data.id = setInterval(function() {
                    if (MJL.style.isFontResized()) {
                        MJL.event.dispatch(document, "fontresize");
                    }
                }, 1000);
            },
            remove : function(node, type, listener) {
                var data = this._getOriginData(node, type);
                if (data.listeners.length <= 0 && !isNaN(data.id)) {
                    clearInterval(data.id);
                    data.id = null;
                }
            }
        },

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

    // オリジナルイベント 関連データ 取得
    _getOriginData : function(node, type) {
        var data = MJL.getData(node);
        // イベント関連データ 生成
        if (!data.event) {
            data.event = {
                // "イベント種類" : データ, ...
            };
        }
        // 種類別データ 生成
        if (!data.event[type]) {
            data.event[type] = {
                init      : false, // 初期化実行是非 (是: true, 非: false)
                listeners : []     // イベントリスナ集合
            };
        }
        // data そのものでなく、種類別のデータを返す
        return data.event[type];
    },

    // オリジナルイベント 追加
    _addOrigin : function(node, type, listener) {
        var data       = this._getOriginData(node, type),
            origin     = this._origins[type],
            nlisteners = data.listeners.length;
        // 初期化
        if (!data.init) {
            if (origin && origin.init) {
                origin.init.apply(this, arguments);
            }
            data.init = true;
        }
        // 追加
        if (origin && origin.add) {
            origin.add.apply(this, arguments);
        }
        data.listeners[nlisteners] = listener;
        // 独自イベントリスナを返す
        //   -> 追加したイベントリスナ集合における配列インデックス
        return nlisteners;
    },

    // オリジナルイベント 削除
    _removeOrigin : function(node, type, listener) {
        var data   = this._getOriginData(node, type),
            origin = this._origins[type];
        // 初期化済でないと意味がない
        if (data.init) {
            if (origin && origin.remove) {
                origin.remove.apply(this, arguments);
            }
            delete data.listeners[listener];
        }
        return listener;
    },

    // オリジナルイベント 発送 (= fire)
    _dispatchOrigin : function(node, type, event) {
        var data       = this._getOriginData(node, type),
            listeners  = data.listeners,
            nlisteners = listeners.length,
            l, listener;
        for (l = 0; l < nlisteners; l++) {
            listener = listeners[l];
            if (listener) {
                listener(event);
            }
        }
    }
};


// ------------------------------------
// BUG Op9.x: フルページズーム時に window オブジェクトで resize イベント未発生
// ------------------------------------
if (MJL.ua.opera && MJL.ua.version < 10) {
    // 疑似イベントでズームを探知
    //   -> window オブジェクトへ reisze イベント発送
    MJL.event.add(window, "load", function() {
        // BUG Op9.2: resize イベントが body 要素からも発送されるため
        //            window オブジェクトに設定したリスナが2回連続起動する
        if (MJL.ua.version < 9.3) {
            MJL.event.add(document.body, "resize", function() {
                return false; // 無視させる
            });
        }
        // ズーム時に:
        //   * ViewPort サイズは増減
        //   * window.innerXXX 値は未変化
        // であることを利用
        var size, width, height;
        // 値設定
        function setVal() {
            size   = MJL.vp.getSize();
            width  = window.innerWidth;
            height = window.innerHeight;
        }
        // resize 時は値設定を実行し、次のチェックに備える
        MJL.event.add(window, "resize", setVal);
        // 疑似イベント
        setInterval(function() {
            var now = MJL.vp.getSize();
            if (width  === window.innerWidth  &&
                height === window.innerHeight &&
                (size.width !== now.width || size.height !== now.height)) {
                MJL.event.dispatch(window, "resize");
            }
        }, 100);
        // 初回実行
        setVal();
    });
}


// ------------------------------------
// メモリリーク防止
// ------------------------------------
MJL.event.add(window, "unload", function() {
    var stack  = MJL.event._stack,
        nstack = stack.length,
        s;
    // イベントスタックの全イベントに対し remove を実行
    for (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    = [],
                    l;
                for (l = 0; l < nlinks; l++) {
                    if (links[l].getAttribute("rel") === rel) {
                        ret.push(links[l]);
                    }
                }
                return ret;
            },
        // 初回時のみ実行する初期化
            initFirst = function() {
                var links  = getLinkElements("alternate stylesheet"),
                    nlinks = links.length,
                    l;
                // 一旦 true を設定しないと disabled プロパティが有効にならない
                for (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, ret, nret, r;
            // link 要素参照による代替スタイルシートを document.styleSheets に
            // 強制追加
            for (l = 0; l < nlinks; l++) {
                tmp[l] = links[l].disabled;
                links[l].disabled = false;
            }
            // StyleSheetList は live なので配列化して変更を保存
            ret  = MJL.convArray(document.styleSheets);
            nret = ret.length;
            // 元に戻す
            for (l = 0; l < nlinks; l++) {
                links[l].disabled = tmp[l];
            }
            // BUG WebKit: CSSStyleSheets オブジェクトの disabled プロパティを
            //             操作してもスタイルの有効・無効を設定できない
            for (r = 0; r < nret; r++) {
                // スタイル参照ノードの disable プロパティを利用すれば
                // スタイル有効・無効を設定可能
                ret[r] = ret[r].ownerNode;
            }
            // 各アイテムは各スタイル参照ノード
            return ret;
        };
    })() : MJL.ua.opera && MJL.ua.version < 9.5 ? function() { // Op9.2
        // BUG Op9.2: CSSStyleSheets オブジェクトの disabled プロパティは
        //            対応するスタイル参照ノードの disable プロパティに1度は
        //            値を設定しないとスタイルの有効・無効を設定できない
        var sheets  = MJL.convArray(document.styleSheets),
            nsheets = sheets.length,
            s;
        for (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 オブジェクト, ...], ...
            },
            s, title;
        for (s = 0; s < nsheets; s++) {
            title = sheets[s].title;
            if (title) {
                // 初回時 データ構造生成
                if (!(title in ret)) {
                    ret[title] = [];
                }
                ret[title].push(sheets[s]);
            }
        }
        return ret;
    },

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

    // 計算済スタイルプロパティ値 取得
    getComputed : (function() {
        var dv      = document.defaultView, // window オブジェクト
            condPx  = /\d\s*px$/i,          // px 単位
            condNum = /^\d/,                // 数値
            getSize = (                     // width/height 用カスタム関数 取得
                // IE6,7,8:   currentStyle では明示的に設定しない限り
                //            px 値をとれない
                // BUG Op9.2: getComputedStyle で取得した値が不正
                MJL.ua.trident || (MJL.ua.opera && MJL.ua.version < 9.5)
            ) ? function(type, p1str, p2str) {
                type  = "client"+type.charAt(0).toUpperCase()+type.substring(1);
                p1str = "padding"+p1str;
                p2str = "padding"+p2str;
                return function(elem) {
                    var p1 = parseInt(
                            this.getComputed(elem, p1str), DIG_DEC
                        ) || 0,
                        p2 = parseInt(
                            this.getComputed(elem, p2str), DIG_DEC
                        ) || 0,
                        clientSize, zoom;
                    // BUG IE6,7: hasLayout が false だと clientXXX が 0 になる
                    //            場合がある (例: dt, dd 要素)
                    if (
                        MJL.ua.trident               &&
                        MJL.ua.version < 8           &&
                        !elem.currentStyle.hasLayout
                    ) {
                        // 強制的に hasLayout を true にして clientXXX を算出
                        zoom = elem.style.zoom;
                        elem.style.zoom = 1;
                        clientSize = elem[type];
                        elem.style.zoom = zoom;
                    } else {
                        clientSize = elem[type];
                    }
                    // clientXXX は padding を含む為、除算
                    clientSize -= p1 + p2;
                    // width/height に負値は存在しない
                    //   -> 丸める
                    return (0 < clientSize ? clientSize : 0)+"px";
                };
            // BUG WebKit: フルページズーム時に width/height 値へズーム倍率が
            //             乗算され、レンダリング時の width/height 値 (実測値、
            //             getComputedStyle 取得値) が意図通りにならない
            } : MJL.ua.webkit && 4 <= MJL.ua.version ? function(type) {
                // 実測値 = width/height 値 * ズーム倍率
                //   -> ズーム倍率を除算すれば、意図通りの実測値を得られる
                //   -> width/height 値 (＝返値) になる
                return 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
                    );
                };
            } : undefined,
            // 各プロパティ計算値算出用 カスタム関数
            // 通常取得では意図通りの値が得られない場合に利用
            customs = {
                width    : getSize ? getSize("width", "Left", "Right")
                                   : undefined,
                height   : getSize ? getSize("height", "Top", "Bottom")
                                   : undefined,
                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 = "";
                        // BUG IE6,7: Quirks モード時のみ elem が次のパターンで
                        //            ある場合、fontSize プロパティを参照すると
                        //            例外「引数が無効です。」が発生
                        //              * DOM ツリーに追加されていない (100%)
                        //              * 何らかの理由 (条件不明)
                        // 補足
                        //   * IE8 では未再現
                        //   * OS バージョンは無関係
                        try {
                            // 例外が発生したら強制的に無効化して回避
                            ret = elem.currentStyle.fontSize;
                        } catch (e) {
                            // EMPTY
                        }
                        if (!ret || ret in keywords || condPer.test(ret)) {
                            ret = "1em";
                        }
                        return ret;
                    };
                })() : undefined
            };

        return dv && dv.getComputedStyle ? function(elem, prop) { // W3C DOM
            return customs[prop] ? customs[prop].call(this, elem)
                                 : dv.getComputedStyle(elem, null)[prop];
        } : DE.currentStyle ? function(elem, prop) {              // Trident
            var ret = customs[prop] ? customs[prop].call(this, elem)
                                    : elem.currentStyle[prop],
                jsss, rtss;
            // px 以外の数値は px 値に変換
            if (!condPx.test(ret) && condNum.test(ret)) {
                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;
        } : undefined; // スタイル取得が不可能なら何もしない
    })(),

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

    // フォントリサイズ是非 (是: true, 非: false)
    isFontResized : (function() {
        // 対象要素のみチェック
        function check() {
            var target = this._fontTarget,
                ret    = false,
                data, size, nowSize;
            // 対象要素の実体が生成されるまで何もしない
            if (target) {
                data    = MJL.getData(target);
                size    = data.beforeFontSize;
                nowSize = MJL.style.getComputed(target, "fontSize");
                if (size !== nowSize) {
                    data.beforeFontSize = nowSize;
                    // 初回実行時は true を返さない
                    //   -> 前回実行時の値がない場合 (undefined === size)
                    ret = !!size;
                }
            }
            return ret;
        }
        // 実体
        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 nativeTextZoom = 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;
                        nativeTextZoom = true;
                    }
                }
                // テキストズームの発生是非によってとりうる動作が変わる
                //   true:  対象要素に設定されている font-size 値に関わらず
                //          サイズ増減が発生していることが保障されている
                //          常にフォントリサイズ true
                //   false: 対象要素のチェックが必要
                return nativeTextZoom || check.call(this);
            };
        })() : 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.options = {         // オプション
        // 対象要素群 収集関数
        collect : this.collect.def,
        // Cookie 是非 (是: MJL.Cookie 第2引数, 非: null)
        cookie  : {
            path     : "/",
            fileUnit : false
        },
        // class 属性値
        active  : "active" // アクティブ状態
    };
    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];
                    }
                }
            }
            // 基点要素名をキャッシュ
            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,
            o;
        for (o = 0; o < nobjs; o++) {
            objs[o]._set(title);
        }
    },

    // 対象要素群 収集関数
    collect : {
        def : function(parent) {
            var targets = [],
                t;
            for (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 decodeURIComponent(MJL.getHash(elem));
            },
            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 targets  = this.targets,
                active   = this.options.active,
                befores  = (
                    targets[this.title] || EMPTY_OBJECT
                ).element || EMPTY_ARRAY,
                afters   = (
                    targets[title]      || EMPTY_OBJECT
                ).element || EMPTY_ARRAY,
                nbefores = befores.length,
                nafters  = afters.length,
                b, a;
            for (b = 0; b < nbefores; b++) {
                MJL.removeClassName(befores[b], active);
                MJL.event.dispatch(befores[b], "blur");
            }
            for (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,
            t;
        for (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,
            title;
        for (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 オブジェクトのキャッシュ
                // "Cookie 名" : RegExp オブジェクト, ...
            };
        return function(name) {
            if (!name || "string" !== typeof name) {
                // 「Cookie 名 '???' は無効です」
                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 key ? all[key] : all;
    },

    // データ保存
    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 を 1970-01-01T00:00:00 (UTC) にしてから Cookie 値を全て削除
        //   -> 過去なので即反映される
        // 現在の時間値を負値にすればよい
        this.params["max-age"] = -(new Date()).getTime();
        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    = "",
            date;
        if (!isNaN(maxage)) {
            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   = {     // オプション
        collect : this.collect.def, // 対象要素群 収集関数
        active  : "",               // アクティブ化 class 属性値
        disable : ""                // 無効化 class 属性値
    };
    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,
                d;
            for (d = 0; d < ndescendants; d++) {
                MJL.event.dispatch(descendants[d], type);
            }
        }
        target.status = status;
    },

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

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

    // ------------------------------------
    // Private
    // ------------------------------------
    // ロールオーバー ON/OFF 時 URI 文字列置換条件
    _SWITCHERS : {
        on  : {cond : /(\.[^\.]+)$/, replace : "_on$1"+'?'+Math.random()},
        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   = [],
                        i;
                    for (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 = [],
                    a;
                for (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      = [],
            t;
        for (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,
            t;
        for (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,
            t, target, types, ntypes, p, type;
        for (t = 0; t < ntargets; t++) {
            target = this.targets[t];
            types  = this._TYPES[target.name].types;
            ntypes = types.length;
            for (p = 0; p < ntypes; p++) {
                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 mime = window.navigator.mimeTypes[TYPE], // NPAPI アクセス
                ret  = [0, 0, 0, 0],                     // 返値
                cond,                                    // 抽出条件
                str,                                     // バージョン文字列
                rest,                                    // 抽出結果
                plugin, nrest, r;
            // 各 Plugin API から取得
            if (mime) {                  // NPAPI 互換
                cond = /(\d+)\.(\d+)(?:\s*[rd\.](\d+))?(?:\s*[bd](\d+))?$/;
                str  = mime.enabledPlugin ? mime.enabledPlugin.description : "";
            } else if (MJL.ua.activex) { // ActiveX
                cond = /(\d+),(\d+),(\d+),(\d+)/;
                try {
                    // 7 以降
                    str = (
                        new window.ActiveXObject(ACTIVEX_NAME+".7")
                    ).GetVariable(ACTIVEX_VERSION);
                } catch (e7) {
                    try {
                        // 6.x
                        plugin = new window.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 window.ActiveXObject(ACTIVEX_NAME)
                            ).GetVariable(ACTIVEX_VERSION);
                        } catch (e5) {
                            // 3.x 以前はサポートしない
                            str = ""; // "6,0,21,0" をリセット
                        }
                    }
                }
            } // no else
            // NPAPI, ActiveX 自体が無効化されている場合は何もしない
            // cond, rest に値が代入されていないはず (= undefined)
            if (cond) {
                rest = cond.exec(str); // 抽出
                // バージョン情報抽出成功
                if (rest) {
                    ENABLE = true; // 抽出可能 <=> Plugin 有効
                    nrest  = rest.length;
                    for (r = 1; r < nrest; r++) {
                        ret[r-1] = parseInt(rest[r], DIG_DEC) || 0; // 整数化
                    }
                }
            }
            return ret;
        })(),

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

        // バージョン比較
        compVersion : function(/* ... */) {
            var args   = MJL.isArray(arguments[0]) ? arguments[0] : arguments,
                nargs  = args.length,
                vers   = this.version,
                nvers  = vers.length,
                length = nvers < nargs ? nvers : nargs, // 最大計算量
                ret    = 0,
                l, arg;
            // 最大計算量 (最悪: バージョン配列長) まで実行するか
            // 値が一致しなくなるまで比較
            for (l = 0; l < length && 0 === ret; l++) {
                arg = args[l];
                if (isNaN(arg)) {
                    /* 「??? '???/???' は数値ではありません」 */
                    throw new Error(
                        "'"+arg+"' ("+l+"/"+length+") is not a number"
                    );
                }
                // 小数点以下切捨
                arg = Math.floor(arg);
                // プラグインバージョン < 比較バージョン:  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"),
                nparams = params.length,
                p;
            for (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.alt = (
                        this.options.minVerMsg && this.enable
                    ) ? this._createMinVerMsg() : this._createAlt(),
                    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,
                e;
            for (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, a;
            // 属性の全移植
            for (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 = {                // オプション
        collect : this.collect.def, // 対象要素群 収集関数
        name    : "_blank"          // ウインドウ識別子
    };
    this.params = {                 // ウインドウに渡すパラメタ
        // null: パラメタを渡さない
        // 状態
        left   : null,    // 横位置
        top    : null,    // 縦位置
        height : null,    // 縦幅
        width  : null,    // 横幅
        // 表示切替 (非推奨)
        // "yes": 表示, "no": 非表示
        menubar  : "yes", // メニューバー
        toolbar  : "yes", // ツールバー
        location : "yes", // ロケーションバー
        status   : "yes"  // ステータスバー
    };

    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);
            // パラメタは先に文字列化する
            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
                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);
            };

        // HTTP/HTTPS スキームの URI を指定属性値に持つ要素を収集
        var collectHTTP = document.querySelectorAll ? function(parent, name, attr) {
            return MJL.convArray(parent.querySelectorAll(
                name+'['+attr+'^="http:"], '+name+'['+attr+'^="https:"]'
            ));
        } : function(parent, name, attr) {
            // 線形探索
            var elems  = parent.getElementsByTagName(name),
                nelems = elems.length,
                ret    = [],
                e;
            for (e = 0; e < nelems; e++) {
                if (isHTTP(elems[e], attr)) {
                    ret.push(elems[e]);
                }
            }
            return ret;
        };

        return {
            // デフォルト
            def : function(parent) {
                var types = this._TYPES,
                    ret   = this._parentName in types ? [parent] : [],
                    type;
                for (type in types) {
                    ret.push(MJL.convArray(
                        parent.getElementsByTagName(type)
                    ));
                }
                return ret.concat.apply([], ret);
            },
            // http/https スキームの URI を持つものだけ
            http : function(parent) {
                var types = this._TYPES,
                    pname = this._parentName,
                    ret   = (
                        pname in types && isHTTP(parent, types[pname].uri)
                    ) ? [parent] : [],
                    type;
                for (type in types) {
                    ret.push(collectHTTP(parent, type, types[type].uri));
                }
                return ret.concat.apply([], ret);
            }
        };
    })(),

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

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

    // 処理種類
    _TYPES : {
        a : {
            type  : "click",
            uri   : "href",
            event : function(event, id) {
                this.open(id, this._getName());
                return false;
            }
        },
        form : {
            type  : "submit",
            uri   : "action",
            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);
                // 何も return しない (= イベント伝搬は停止しない)
                // submit イベントを発生させる
            }
        }
    },

    // 特殊な意味を持つウインドウ識別子の判定
    _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,
            types    = this._TYPES,
            target, type, t;
        for (t = 0; t < ntargets; t++) {
            target = targets[t];
            type   = types[MJL.getName(target)];
            this.targets[t] = {
                element : target,
                uri     : target.getAttribute(type.uri) || "",
                event   : MJL.event.add(target, type.type, MJL.event.bind(
                    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 = [],
            p, i;
        for (p in this.params) {
            if (null !== this.params[p]) {
                ret.push(p+"="+this.params[p]);
            }
        }
        for (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 = {
        // 対象要素群 収集関数
        collect   : this.collect.def,
        // Cookie 是非 (是: MJL.Cookie 第2引数, 非: null)
        cookie    : {},
        // 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;
    // resize イベント発送是非 (是: true, 非: false)
    this._resized = 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._setResizeChecker();
        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, container;
        // タブパネルに class 属性値を付与
        for (id in items) {
            MJL.addClassName(items[id].panel, panel);
        }
        if (!this.stat) {
            // 動的モードでは必要な要素を挿入
            container = this.container;
            // コンテナ配置
            content.parentNode.insertBefore(container, content);
            // コンテナに必要な要素を追加
            container.appendChild(this.list);
            container.appendChild(content);
        }
    },

    // アクティブタブ 変更
    active : function(id) {
        // アクティブ ID
        var aid = this._getActiveId(),
            active, before, after;
        // アクティブ ID と異なる -> 実行
        if (id !== aid) {
            // resize イベント発送チェック開始
            this._resized = false;
            // 対象 ID が不正
            if (!this._isValidId(id)) {
                id = aid; // 現在のアクティブ ID を使用
            }
            // アクティブ状態 class 属性値
            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);
            // WAI-ARIA アクティブタブ 変更
            this._activeARIA(aid, id); // フォーカス制御前の実行必須
            // フォーカス制御
            if (this._created) { // 生成中は何もしない
                // 明示的フォーカス
                // BUG WebKit: click イベント時に focus イベント未発生
                //               -> focus() で発生させているので問題なし
                after.anchor.focus();
                // 明示的 blur イベント発送
                // 未フォーカス -> after (click で直接フォーカス)
                //   * blur 未発生
                // before -> after
                //   1. click イベントハンドラ実行前に blur 発生
                //   2. before に active がある状態のまま
                //   3. MJL.Rollover のアクティベートチェックで before に抵触
                //   4. アクティベートがうまくいかない
                MJL.event.dispatch(before.anchor, "blur");
            }
            // 事後処理
            this._setActiveId(id);
            this._setCookie();
            // 強制再描画
            //   * 生成中は表示状態のままなので、再描画は不要
            //   * タブパネル内に HeightEqualizer がある場合
            //     ロード時非表示 -> 表示 すると:
            //       1. 非表示要素はスタイル計算値が保障されない
            //          絶対配置要素は width/height が内容に従うので不整値化
            //       2. ロード時の状態で HeightEqualizer 初期化、height 設定
            //       3. HeightEqualizer 対象要素は親要素の resize を認識不能
            //          非表示 -> 表示時に HeightEqualizer 対象要素は何もしない
            //       4. 不正な height 値が設定された HeightEqualizer 対象要素を
            //          表示
            //       5. HeightEqualizer 対象要素が崩れてしまう
            // IE6,7,8: タブ切替によって body 要素の高さが変化しうる場合、
            //          resize イベントが発生するため forcedraw を発送すると
            //          HeightEqualizer 再描画が2重に発生
            //            -> resize イベントの発生有無を監視してイベント未発生
            //               の場合のみ forcedraw を発送
            if (this._created && !this._resized) {
                // IE6,7,8: フォーカス制御用 focus() 実行前に判定すると resize
                //          イベントが確実に発送されるパターンであっても
                //          resize イベント未発送状態のまま
                //            -> 必ず focus() 実行後に処理
                //          reflow 未発生のため resize イベント未発生と思われる
                //          focus() は強制的に reflow を発生させる効果がある？
                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 > ハッシュ > 静的アクティブ化 > 先頭アイテム
        function() { // Cookie
            return this._getCookie();
        },
        function() { // ハッシュ
            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)) {
                    this.active(this._items[num].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 = "",
            r;
        // 文書からアクティブ ID を取得しておく
        this._setActiveIdByDocument();
        // 検索
        for (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,
            id;
        for (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 = decodeURIComponent(MJL.getHash(elem));
        // 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, // 追加順序
            t, id;
        for (t = 0; t < ntargets; t++) {
            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 = "",
            titles;
        if (!this.stat) {
            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,
            childs, nchilds, c, 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");
            }
            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 (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
                ));
            }
        }
    },

    // resize イベント発送チェック 設定
    _setResizeChecker : function() {
        MJL.event.add(window, "resize", MJL.event.bind(function() {
            this._resized = true;
        }, this));
    },

    //
    // 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.anchor.setAttribute("aria-selected", "false");
            after.anchor.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.anchor.tabIndex = before.panel.tabIndex = -1;
            after.anchor.tabIndex  = after.panel.tabIndex  = 0;
        }
    },

    // 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: presentation 設定
                tab.setAttribute("role", "presentation");
                // role: tab 設定
                anchor.setAttribute("role", "tab");
                anchor.setAttribute("id", item.ariaId);
                anchor.setAttribute("aria-flowto", id);
                anchor.setAttribute("aria-controls", id);
                anchor.setAttribute("aria-selected", "false");
                // role: tabpanel 設定
                panel.setAttribute("role", "tabpanel");
                panel.setAttribute("aria-labelledby", item.ariaId);
                panel.setAttribute("aria-hidden", "true");
                // 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();
        this._setAutoResize();
    },

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

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

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

    // 自動リサイズ時の実行イベント情報
    _EVENTS : (function() {
        function listener(event) {
            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 targets  = this.targets,
            ntargets = 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(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() {
        if (this.options.resize) {
            var events  = this._EVENTS,
                nevents = events.length,
                e, event;
            for (e = 0; e < nevents; e++) {
                event = events[e];
                MJL.event.add(
                    event.target,
                    event.type,
                    MJL.event.bind(event.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),
                e;
            for (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
    };
})();
// ----------------------------------------------------------------------------
})(this.window);

