/*! mjl.js

 * MITSUE-LINKS JavaScript Library

 * Version 2.0.5

 * Copyright (C) 2008-2009 MITSUE-LINKS

 *

 * 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/>.

 */

// ----------------------------------------------------------------------------

// Trident window object property faster

// ----------------------------------------------------------------------------

/*@cc_on eval((function(){var ps="document external self top parent setInterval clearInterval setTimeout clearTimeout".split(" ");var c=[];for(var i=0,l=ps.length,p=null;i<l;i++){p=ps[i];window["_"+p]=window[p];c.push(p+"=_"+p);}return "var "+c.join(",");})()); @*/





// ----------------------------------------------------------------------------

// MJL Namespace

// ----------------------------------------------------------------------------

var MJL = {};





// ----------------------------------------------------------------------------

// version: バージョン情報

// ----------------------------------------------------------------------------

MJL.version = "2.0.5";





// ----------------------------------------------------------------------------

// inherit: 継承の実現

// ----------------------------------------------------------------------------

MJL.inherit = function(target, parent) {

    // __proto__ を用いた方法

    if (undefined !== target.__proto__ &&

        undefined !== target.prototype.__proto__) {

        // __proto__ を用いてプロトタイプチェーンへ直接接続する

        target.prototype.__proto__ = parent.prototype;

        target.__proto__ = parent;

        // 同じプロトタイプチェーンに接続されているなら成功とみなす

        if (parent.prototype.isPrototypeOf &&

            parent.prototype.isPrototypeOf(target.prototype)) {

            return target;

        }

        // 念のため、余分に付与された __proto__ は delete しない

        // ReadOnly で上書き不可の場合があったらマズいため

    }

    // __proto__ が使えない場合（= 失敗した場合）

    // new による方法

    target.prototype = new parent();

    try {

        // parent の constructor になったままなので元に戻す

        target.prototype.constructor = target;

    } catch (e) {

        throw Error("can't substitution 'constructor': "+e);

    }

    return target;

};





// ----------------------------------------------------------------------------

// convArray: 与オブジェクトを配列に変換

// ----------------------------------------------------------------------------

MJL.convArray = function(obj) {

    if (obj instanceof Array) { return obj; }

    try {

        return Array.prototype.slice.call(obj);

    } catch (e) {

        // BUG IE8 Beta 2: Array.prototype.slice.call でエラー

        var nobj = obj.length;

        var ret  = new Array(nobj);

        for (var o = 0; o < nobj; o++) {

            ret[o] = obj[o];

        }

        return ret;

    }

};





// ----------------------------------------------------------------------------

// sanitize: 文字列の不要部分を削除

// ----------------------------------------------------------------------------

MJL.sanitize = (function() {

    var sol = /^[\s\t]+/; // 行頭空白

    var eol = /[\s\t]+$/; // 行末空白

    return function(str) {

        return str.replace(sol, "").replace(eol, "");

    };

})();





// ----------------------------------------------------------------------------

// ua: User Agent 情報

// ----------------------------------------------------------------------------

MJL.ua = (function() {

    // Trident は条件付コンパイルによる確実な判定を利用

    /*@cc_on @if (@_jscript) var type = "trident"; @else @*/

    // サポートプロパティによる判定

    var type = (undefined !== window.opera)         ? "opera"  :

               (undefined !== window.Components)    ? "gecko"  :

               (undefined !== window.defaultstatus) ? "webkit" : "unknown";

    /*@end @*/

    var ret = {

        // レンダリングエンジン是非 (是: true, 非: false)

        gecko   : false,

        opera   : false,

        webkit  : false,

        trident : false,

        unknown : false,

        // Quirks モード是非 (是: true, 非: false)

        quirks : ("BackCompat" == document.compatMode),

        // レンダリングエンジンバージョン (Opera & WebKit & Trident)

        version : ("opera"   == type) ? Number(window.opera.version()) :

                  ("webkit"  == type) ? document.evaluate ? 3 : 2      :

                  ("trident" == type) ?

                      ("undefined" != typeof document.documentMode) ? document.documentMode :

            ("undefined" != typeof external.SqmEnabled) ? 7 : 6 : 0,

        // ActiveX 有無 (有: true, 無: false)

        activex : ("undefined" != typeof ActiveXObject)

    };

    ret[type] = true; // レンダリングエンジン是非 設定

    return ret;

})();





// ----------------------------------------------------------------------------

// convNode: 与テキストを DOM Node に変換

// ----------------------------------------------------------------------------

MJL.convNode = function(text) {

    // 生成される要素数に応じて返値が変化

    //  1: ノード自身

    // 他: Document Fragment に格納された並列ノード

    var tmp = document.createElement("div");

    tmp.innerHTML = text; // 現状は innerHTML を利用

    var childs = tmp.childNodes;

    var nchilds = childs.length;

    var ret = null;

    if (nchilds <= 1) {

        ret = childs[0];

    } else {

        ret = document.createDocumentFragment();

        for (var c = 0; c < nchilds; c++) {

            ret.appendChild(childs[0]);

        }

    }

    return ret;

};





// ----------------------------------------------------------------------------

// getClassNameRegExp: class 属性値 単一値取得用 RegExp オブジェクト生成

// ----------------------------------------------------------------------------

MJL.getClassNameRegExp = (function() {

    var cache = {}; // RegExp オブジェクトのキャッシュ

    return function(name) {

        if (undefined === cache[name]) {

            // キャッシュがない場合のみ新規生成

            cache[name] = new RegExp("(?:^|[\\s\\t]+)"+name+"(?:[\\s\\t]+|$)");

        }

        return cache[name];

    };

})();





// ----------------------------------------------------------------------------

// hasClassName: 要素が指定 class 属性値を持っているか

// ----------------------------------------------------------------------------

MJL.hasClassName = function(elem, name) {

    if ("string" != typeof name) { return false; }

    return MJL.getClassNameRegExp(name).test(elem.className);

};





// ----------------------------------------------------------------------------

// addClassName: 要素の class 属性値に指定値を追加

// ----------------------------------------------------------------------------

MJL.addClassName = function(elem, name) {

    if (MJL.hasClassName(elem, name)) { return; }

    elem.className = elem.className ? elem.className+" "+name : name;

};





// ----------------------------------------------------------------------------

// removeClassName: 要素の class 属性値から指定値を削除

// ----------------------------------------------------------------------------

MJL.removeClassName = function(elem, name) {

    if (!MJL.hasClassName(elem, name)) { return; }

    var className = MJL.sanitize(

        elem.className.replace(MJL.getClassNameRegExp(name), " ")

    );

    if (className) {

        elem.className = className;

    } else {

        // 値がなければ属性ごと削除

        elem.removeAttribute("class");

        // BUG IE6,7: "className" を操作しないと class 属性が変化しない

        elem.removeAttribute("className");

    }

};





// ----------------------------------------------------------------------------

// getElementsByClassName: class 属性値による要素収集

// ----------------------------------------------------------------------------

// Note:

//   速度向上のため NodeList オブジェクトなら Array に変換しておく

// ----------------------------------------------------------------------------

MJL.getElementsByClassName = (function() {

    // 実装されている場合: MJL へのラッピングのみ

    if (document.getElementsByClassName) {

         return function(parent, name) {

            return MJL.convArray(parent.getElementsByClassName(name));

         };

    // Selectors API が利用できる

    } else if (document.querySelectorAll) {

        return function(parent, name) {

            return MJL.convArray(parent.querySelectorAll("."+name));

        };

    // XPath が利用できる

    } else if (document.evaluate) {

        return function(parent, name) {

            var query = document.evaluate( // XPath で収集

                './/*[contains(concat(" ",@class," ")," '+name+' ")]',

                parent,

                null,

                XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,

                null

            );

            var nquery = query.snapshotLength;

            var elems = new Array(nquery);

            for (var i = 0; i < nquery; i++) { // 配列に変換

                elems[i] = query.snapshotItem(i);

            }

            return elems;

        };

    // どの代替手段も利用できない: 線形探索

    } else {

        return function(parent, name) {

            var nodes = parent.getElementsByTagName("*");

            var nnodes = nodes.length;

            var elems = [];

            for (var n = 0; n < nnodes; n++) { // 全要素を線形探索

                if (MJL.hasClassName(nodes[n], name)) {

                    elems.push(nodes[n]);

                }

            }

            return elems;

        };

    }

})();





// ----------------------------------------------------------------------------

// getElementsByChildNodes: 子要素収集

// ----------------------------------------------------------------------------

MJL.getElementsByChildNodes = (function() {

    // XPath が利用できる

    if (document.evaluate) {

        return function(parent, nodeName, eq) {

            var query = document.evaluate( // XPath で収集

                !nodeName      ? './*'                          :

                (false === eq) ? './*[not(self::'+nodeName+')]' : './'+nodeName,

                parent,

                null,

                XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,

                null

            );

            var nquery = query.snapshotLength;

            var elems = new Array(nquery);

            for (var i = 0; i < nquery; i++) { // 配列に変換

                elems[i] = query.snapshotItem(i);

            }

            return elems;

        };

    // どの代替手段も利用できない: 線形探索

    } else {

        return function(parent, nodeName, eq) {

            // childNodes のうち、要素 (ELEMENT_NODE) のみを収集

            var nodes  = parent.childNodes;

            var nnodes = nodes.length;

            var elems = [];

            if (false !== eq) { eq = true; }

            for (var n = 0; n < nnodes; n++) {

                if (1 == nodes[n].nodeType &&

                    (!nodeName ||

                     eq == (nodeName == nodes[n].nodeName.toLowerCase()))

                ) {

                    elems.push(nodes[n]);

                }

            }

            return elems;

        };

    }

})();





// ----------------------------------------------------------------------------

// vp: ViewPort 操作インタフェイス

// ----------------------------------------------------------------------------

MJL.vp = (function() {

    // ViewPort としてどの要素を利用しているか

    var elem = MJL.ua.quirks ? "body" : "documentElement";



    return {

        // ViewPort サイズ (スクロールバー除外)

        // 一般:

        // 標準 document.documentElement.clientWidth

        //      document.documentElement.clientHeight

        // 互換 document.body.clientWidth

        //      document.body.clientHeight

        // Op9.2:

        //   標準 document.body.clientWidth

        //        document.body.clientHeight

        //   互換 document.body.clientWidth

        //        document.body.clientHeight

        // Sf2:

        //   標準 window.innerWidth

        //        window.innerHeight

        //   互換 window.innerWidth

        //        window.innerHeight

        getInnerSize : function() {

            var w = 0;

            var h = 0;

            if (MJL.ua.webkit && MJL.ua.version < 3) {

                // Safari 2.x before

                w = window.innerWidth;

                h = window.innerHeight;

            } else if (MJL.ua.opera && MJL.ua.version < 9.5) {

                // Opera 9.2x before

                w = document.body.clientWidth;

                h = document.body.clientHeight;

            } else {

                // Anothers

                w = document[elem].clientWidth;

                h = document[elem].clientHeight;

            }

            return {width : w, height : h};

        },



        // フルページサイズ (スクロール含)

        getFullSize : function() {

            return {

                width  : document[elem].scrollWidth,

                height : document[elem].scrollHeight

            };

        },



        // スクロールオフセット

        getScrollPosition : function() {

            return {

                x : (window.pageXOffset || document[elem].scrollLeft),

                y : (window.pageYOffset || document[elem].scrollTop)

            };

        }

    };

})();





// ----------------------------------------------------------------------------

// event: 汎用イベント処理

// ----------------------------------------------------------------------------

// Note:

// Trident のメモリリークは、DOM ノード上にある循環参照要素に対しては修正された

// （DOM ノードへ append されていない要素が循環参照している場合は未修正）

// 故に、本イベントリスナラッパーはメモリリーク対策を講じていない

// 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, _nowrap) {

        // _nowrap: 隠し引数

        //          true: listener のラッピングとストアを実行しない

        var wrap = _nowrap ? listener : this._wrapAfterCare(listener);

        var ret = wrap;

        if (this._origins[type]) {          // オリジナル

            ret = this._origins[type].add(node, wrap, useCapture);

        } else if (node.addEventListener) { // W3C DOM

            node.addEventListener(type, wrap, useCapture);

        } else if (node.attachEvent) {      // Trident

            node.attachEvent("on"+type, wrap);

        } else {

            ret = null;

        }

        if (!_nowrap && null !== ret && "unload" != type) {

            this._store(node, type, ret, useCapture);

        }

        return ret;

    },



    // 削除

    remove : function(node, type, listener, useCapture) {

        var ret = listener;

        if (this._origins[type]) {             // オリジナル

            ret = this._origins[type].remove(node, 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;

    },



    // イベントが発生したノードを取得

    getCurrentNode : function(event) {

        return event.currentTarget     ? event.currentTarget     :

               event.srcElement        ? event.srcElement        :

               window.event.srcElement ? window.event.srcElement : null;

    },



    // window オブジェクトの load イベント終了是非 (是: true, 非: false)

    windowLoaded : false,



    // --------------------------------

    // Private

    // --------------------------------

    // 種類別イベント一覧

    _types : {/* EMPTY */},



    // イベント毎の情報を設定

    _store : function(node, type, listener, useCapture) {

        if (undefined === this._types[type]) {

            this._types[type] = [];

        }

        this._types[type].push({

            node       : node,

            listener   : listener,

            useCapture : useCapture

        });

    },



    // オリジナルイベント一覧

    _origins : {/* EMPTY */}, // 後で追加



    // スタック生成

    _createStack : function(type) {

        // this._origins に間借りする形で領域生成

        if (undefined === this._origins[type]._callStack) {

            this._origins[type]._callStack = {

                id        : 0,

                listeners : {} // Array ではなく Object を利用

            };

        }

    },



    // スタック取得

    _getStack : function(type) {

        // プロパティの情報をカプセル化しておく

        return this._origins[type]._callStack;

    },



    // スタックに listener を追加

    _addStack : function(type, listener) {

        this._createStack(type);

        var stack = this._getStack(type);

        var id = stack.id;

        stack.listeners[id] = listener;

        stack.id++;

        return id; // id を listener として返す

    },



    // スタックから listener を削除

    _removeStack : function(type, id) {

        var stack = this._getStack(type);

        // 何もしないなら false を返す

        if (undefined === stack ||

            undefined === stack.listeners[id]) { return false; }

        delete stack.listeners[id];

        return true; // 何かをしたら true を返す

    },



    // スタックにある listener を全実行

    _runStack : function(type) {

        var stack = this._getStack(type);

        if (undefined === stack) { return; }

        for (var s in stack.listeners) {

            stack.listeners[s]();

        }

    },



    // イベント実行後のアフターケア処理をラッピング

    _wrapAfterCare : function(listener) {

        return function(event) {

            var ret = listener.apply(listener, arguments);

            if (false === ret) {

                // イベントリスナの戻り値が false なら各種キャンセル実行

                if (event.preventDefault) {    // 既定の動作を防止

                    event.preventDefault();    // Others

                } else {

                    event.returnValue = false; // Trident

                }

                if (event.stopPropagation) {   // イベント伝搬を停止

                    event.stopPropagation();   // Others

                } else {

                    event.cancelBubble = true; // Trident

                }

            }

        };

    }

};





// ------------------------------------

// メモリリーク防止

// ------------------------------------

MJL.event.add(window, "unload", function() {

    // add した全イベントに対して remove を実行

    var types = MJL.event._types;

    for (var type in types) {

        var list = types[type];

        var nlist = list.length;

        for (var l = 0; l < nlist; l++) {

            MJL.event.remove(list[l].node,

                             type,

                             list[l].listener,

                             list[l].useCapture);

        }

    }

}, false, true); // このイベントだけは _nowrap





// ------------------------------------

// MJL.event.windowLoaded 処理

// ------------------------------------

MJL.event.add(window, "load", function() {

    MJL.event.windowLoaded = true;

}, false);





// ------------------------------------

// 独自イベント: フォントリサイズ

// ------------------------------------

// trident では resize イベントを利用

//   -> setInterval ではかなり CPU リソースを消費するため

// ------------------------------------

MJL.event._origins.fontresize = {

    add : (function() {

        if (MJL.ua.trident) {

            return function(node, listener, useCapture) {

                return MJL.event.add(MJL.style._getUnitElem(),

                                     "resize",

                                     listener,

                                     useCapture);

            };

        } else {

            var run = false; // 実行フラグ

            return function(node, listener, useCapture) {

                var id = MJL.event._addStack("fontresize", listener);

                if (!run) {

                    run = true;

                    setInterval(function() { // 疑似イベント

                        if (MJL.style.isZoomed()) {

                            MJL.event._runStack("fontresize");

                        }

                    }, 1000); // チェックインターバル (ms)

                }

                return id;

            };

        }

    })(),

    remove : (function() {

        if (MJL.ua.trident) {

            return function(node, listener, useCapture) {

                return MJL.event.remove(MJL.style._getUnitElem(),

                                        "resize",

                                        listener,

                                        useCapture);

            };

        } else {

            return function(node, listener, useCapture) {

                return MJL.event._removeStack("fontresize", listener);

            };

        }

    })()

};





// ----------------------------------------------------------------------------

// style: stylesheet オブジェクトリスト 操作インタフェイス

// ----------------------------------------------------------------------------

MJL.style = {

    // --------------------------------

    // Public

    // --------------------------------

    // title が付されたシートの連想配列を取得

    // return: {title 文字列 : [CSSStyleSheet オブジェクト, ...]

    getWithTitles : function() {

        // WebKit: Alternate Stylesheets を認識しない

        //         document.styleSheets を用いず別関数で対応

        var sheets = MJL.ua.webkit ? this._getList() : document.styleSheets;

        var nsheets = sheets.length;

        var ret = {}; // return するオブジェクト

        for (var i = 0; i < nsheets; i++) {

            var title = sheets[i].title;

            if (!title) { continue; } // title 属性値がなければ無視

            // 初回追加なら Array オブジェクトを生成

            if (undefined == ret[title]) { ret[title] = []; }

            ret[title].push(sheets[i]);

        }

        return ret;

    },



    // title 属性値 title のスタイル群に切り替え

    switchAlt : function(title) {

        var title2nodes = this.getWithTitles();

        if (undefined === title2nodes[title]) { return false; }

        for (var t in title2nodes) {

            var nodes = title2nodes[t];

            var nnodes = nodes.length;

            for (var n = 0; n < nnodes; n++) {

                nodes[n].disabled = true;

            }

        }

        var targetNodes = title2nodes[title];

        var ntargetNodes = targetNodes.length;

        for (var tn = 0; tn < ntargetNodes; tn++) {

            targetNodes[tn].disabled = false;

        }

        return true;

    },



    // 計算済スタイルプロパティ値

    getComputed : (function() {

        var customs = {

            height : {

                enable : (MJL.ua.trident ||

                          (MJL.ua.opera && MJL.ua.version < 9.5)),

                calc : function(elem, prop, ret) {

                    var p1 = parseInt(

                        this.getComputed(elem, "paddingTop")

                    ) || 0;

                    var p2 = parseInt(

                        this.getComputed(elem, "paddingBottom")

                    ) || 0;

                    // BUG IE7: hasLayout が false だと無条件で clientHeight が

                    //          0 になる場合がある (例: dt, dd 要素)

                    var ch;

                    if (MJL.ua.trident      &&

                        7 <= MJL.ua.version &&

                        !elem.currentStyle.hasLayout) {

                        // 強制的に hasLayout を true 化し clientHeight を算出

                        var zoom = elem.style.zoom;

                        elem.style.zoom = 1;

                        ch = elem.clientHeight;

                        elem.style.zoom = zoom;

                    } else {

                        ch = elem.clientHeight;

                    }

                    // clientXXX は padding を含むため除算

                    return (ch <= 0) ? 0 : (ch-p1-p2)+"px";

                }

            },

            fontSize : {

                enable : (MJL.ua.trident ||

                          (MJL.ua.webkit && MJL.ua.webkit < 3)),

                calc : function(elem, prop, ret) {

                    // Unit Element による判定

                    return this._getUnitElem().offsetWidth+"px";

                }

            }

        };

        if (document.defaultView && document.defaultView.getComputedStyle) {

            var dv = document.defaultView; // キャッシュ

            return function(elem, prop) {

                var ret = dv.getComputedStyle(elem, null)[prop];

                // カスタム計算

                if (customs[prop] && customs[prop].enable) {

                    ret = customs[prop].calc.apply(this, [elem, prop, ret]);

                }

                return ret;

            };

        } else if (document.documentElement &&

                   document.documentElement.currentStyle) {

            return function(elem, prop) {

                var ret = elem.currentStyle[prop];

                // カスタム計算

                if (customs[prop] && customs[prop].enable) {

                    ret = customs[prop].calc.apply(this, [elem, prop, ret]);

                // px 以外の数値

                } else if (!this._cond.px.test(ret) &&

                           this._cond.num.test(ret)) {

                    var jsss = elem.style.left;

                    var rtss = elem.runtimeStyle.left;

                    // left プロパティに値を詰め、pixelLeft で単位変換

                    elem.runtimeStyle.left = elem.currentStyle.left;

                    elem.style.left = ret || 0;

                    ret = elem.style.pixelLeft;

                    // 元の値に戻す

                    elem.style.left = jsss;

                    elem.runtimeStyle.left = rtss;

                }

                return ret;

            };

        } else {

            throw Error("not supported getting computed style");

        }

    })(),



    // ズーム是非 (是: true, 非: false)

    // フルページズーム、テキストズーム両方に対応

    // body 要素の font-size 増減にも対応 (スタイルスイッチ、直接変更など)

    isZoomed : (function() {

        // font-size の Computed Style + ViewPort サイズを利用

        // BUG Opera: ズーム時に Computed Style 値が再計算されない

        if (MJL.ua.opera) {

            var innserSize = 0; // ViewPort サイズ

            var fontSize   = 0; // font-size の Computed Style

            // ViewPort サイズがなぜか増減することを併用

            // 但し、何らかの DOM スタイル操作で ViewPort のスクロールバーが

            // 縦横同時に出現した場合、ズームされなくとも true を返す

            // font-size の Computed Style はスタイルスイッチ & 直接変更

            MJL.event.add(window, "load", function() {

                innerSize = MJL.vp.getInnerSize();

                fontSize  = MJL.style.getComputed(document.body, "fontSize");

            }, false);

            return function() {

                var nowInnerSize = MJL.vp.getInnerSize();

                var nowFontSize  = MJL.style.getComputed(document.body,

                                                         "fontSize");

                if (innerSize.width  == nowInnerSize.width  &&

                    innerSize.height == nowInnerSize.height &&

                    fontSize          == nowFontSize) { return false; }

                innerSize = nowInnerSize;

                fontSize  = nowFontSize;

                return true;

            };

        // font-size の Computed Style を利用

        } else {

            var size = 0;

            MJL.event.add(window, "load", function() {

                size = MJL.style.getComputed(document.body, "fontSize");

            }, false);

            return function() {

                var nowsize = MJL.style.getComputed(document.body, "fontSize");

                if (size == nowsize) { return false; }

                size = nowsize;

                return true;

            }

        }

    })(),



    // --------------------------------

    // Private

    // --------------------------------

    // 判定用正規表現

    _cond : {

        px  : /\d\s*px$/i,                  // px 単位

        num : /^\d/,                        // 数値

        rel : /(?:^|\s)stylesheet(?:\s|$)/i // rel 属性 stylesheet

    },



    // 単位取得用要素

    _unitElem : null,



    // 単位取得用要素取得

    _getUnitElem : function() {

        if (null === this._unitElem) {

            var elem = document.createElement("div");

            elem.style.display  = "block";

            elem.style.width    = "1em";

            elem.style.height   = "1em";

            elem.style.position = "absolute";

            elem.style.top      = "-999em";

            elem.style.left     = "-999em";

            document.body.appendChild(elem);

            this._unitElem = elem;

        }

        return this._unitElem;

    },



    // スタイルシート読込リストを取得 (link 要素のみ)

    // Sf2,3 Alternate StyleSheets バグ対応にのみ利用すること

    _getList : function() {

        var links = document.getElementsByTagName("link");

        var nlinks = links.length;

        var ret = [];

        for (var l = 0; l < nlinks; l++) {

            if (this._cond.rel.test(links[l].getAttribute("rel"))) {

                ret.push(links[l]);

            }

        }

        return ret;

    }

};





// ----------------------------------------------------------------------------

// Switcher: スタイルスイッチャ

// ----------------------------------------------------------------------------

MJL.style.Switcher = function(/* arguments */) {

    this.parent = null; // 基点要素 (親要素)

    this.targets = [    // 対象要素群

        // {node:対象要素, title:スタイルタイトル}, ...

    ];

    var obj = this;

    this.options = {    // オプション

        collect : obj._collectDefault // 対象要素群 収集関数

    };



    // MJL.Cookie オブジェクト

    this._cookie = new MJL.Cookie(this._COOKIE_STATUS.name,

                                  this._COOKIE_STATUS.optional);



    this.setOptions.apply(this, arguments);

};



MJL.style.Switcher.prototype = {

    // ------------------------------------

    // Public

    // ------------------------------------

    // オプション設定

    setOptions : function(parent, optional) {

        if (arguments.length < 1) { return; }

        this.parent = parent;

        if (null !== optional && "object" == typeof optional) {

            for (var o in this.options) {

                if (undefined === optional[o]) { continue; }

                this.options[o] = optional[o];

            }

        }

    },



    // 生成

    create : function(/* arguments */) {

        this.setOptions.apply(this, arguments);

        this._setTargets();

        this._setEvent();

        this._getCookie();

    },



    // スタイルを title に設定

    set : function(title) {

        MJL.style.switchAlt(title);

        this._setCookie(title);

    },



    // ------------------------------------

    // Private

    // ------------------------------------

    // Cookie 設定値

    _COOKIE_STATUS : {

        name     : "MJL.style.Switcher", // 名称

        key      : "title",              // 連想配列キー

        optional : {                     // 詳細設定

            path     : "/",

            fileUnit : false

        }

    },



    // 対象要素群 収集関数（既定）

    _collectDefault : function(parent) {

        // parent の子孫要素にある a 要素を収集

        // parent 自身が a 要素なら parent のみ収集

        var anchors = ("a" == parent.nodeName.toLowerCase()) ?

            [parent] : parent.getElementsByTagName("a");

        var nanchors = anchors.length;

        var targets = [];

        // タイトルを取得できた要素を収集

        for (var a = 0; a < nanchors; a++) {

            var attr = anchors[a].getAttribute("href");

            // ページ内アンカーの ID 値をタイトルに同定

            var index = attr.lastIndexOf("#");

            var title = (-1 == index) ? "" : attr.substring(index+1);

            if (title) {

                targets.push({node:anchors[a], title:title});

            }

        }

        return targets;

    },



    // 対象要素を収集・設定

    _setTargets : function() {

        this.targets = this.options.collect.call(this, this.parent);

    },



    // 対象要素に対しイベントを設定

    _setEvent : function() {

        var ntargets = this.targets.length;

        for (var t = 0; t < ntargets; t++) {

            MJL.event.add(this.targets[t].node,

                          "click",

                          this._getEventListener(t),

                          false);

        }

    },



    // イベントリスナ用クロージャを取得

    _getEventListener : function(id) {

        var obj = this;

        return function() {

            obj.set(obj.targets[id].title);

            return false;

        };

    },



    // Cookie 値を取得

    _getCookie : function() {

        var title = this._cookie.get(this._COOKIE_STATUS.key);

        if (title) {

            this.set(title);

        }

    },



    // Cookie 値を設定

    _setCookie : function(title) {

        this._cookie.set(this._COOKIE_STATUS.key, title);

    }

}; // END MJL.style.Switcher.prototype





// ----------------------------------------------------------------------------

// Cookie: クッキー制御

// ----------------------------------------------------------------------------

MJL.Cookie = function(/* arguments */) {

    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 = { // 項目名抽出条件

        str    : "",   // 文字列

        regexp : null  // 正規表現

    };



    this.setOptions.apply(this, arguments);

};



MJL.Cookie.prototype = {

    // ------------------------------------

    // Public

    // ------------------------------------

    // オプション設定

    setOptions : function(name, optional) {

        if (null !== optional && "object" == typeof optional) {

            for (var o in this.options) {

                if (undefined === optional[o]) { continue; }

                this.options[o] = optional[o];

            }

            for (var p in this.params) {

                if (undefined === optional[p]) { continue; }

                this.params[p] = optional[p];

            }

        }

        // this.options を利用するため設定後に実行

        this.setName(name);

    },



    // 項目名設定

    setName : function(name) {

        if (!name || "string" != typeof name) {

            throw Error("invalid cookie name ("+name+")");

        }

        // ファイル単位管理有効

        if (this.options.fileUnit) {

            var path = window.location.pathname; // ファイルパス

            // ファイルパスがディレクトリで、this.options.index に指定があれば

            // this.options.index を追加してアイテムを共有させる

            if (this._DIR_COND.test(path) && this.options.index) {

                path += this.options.index;

            }

            name += "@"+path; // 名前@パス

        } // ファイル単位管理無効なら処理しないだけ

        this.name = name;

        this._nameCond.str = name+"=";

        this._nameCond.regexp = new RegExp("^"+name+"=");

    },



    // データ取得

    get : function(key) {

        var all = this._getAll();

        return ("string" == typeof key) ? all[key] : all;

    },



    // データ保存

    set : function(key, value) {

        // キーと値が片方でもなければ何もしない

        if (undefined === key || undefined === value) { return; }

        var items = this._getAll();

        var values = [];

        items[key] = value; // 値をセット (既にある場合は上書き)

        for (var i in items) {

            if (undefined === items[i]) { continue; }

            // Cookie 格納可能形式にエンコード

            values.push(encodeURIComponent(i)+":"+encodeURIComponent(items[i]));

        }

        // 値がある時のみ実行

        if (0 < values.length) {

            document.cookie = this.name+"="+values.join(",")+this._getParamStr();

        }

    },



    // データ全削除

    remove : function() {

        var tmpAge = this.params["max-age"];

        this.params["max-age"] = 0;

        document.cookie = this.name+"="+this._getParamStr();

        this.params["max-age"] = tmpAge;

    },



    // ------------------------------------

    // Private

    // ------------------------------------

    // window.location.pathname 値がディレクトリか否か

    _DIR_COND : /\/$/i,



    // 文字列 slice 用デリミタ

    _DELIMITERS : {

        // 正規表現で \s を利用しているのは余分な空白文字を削除するため

        // 空白文字が残存していると key-value 認識がうまくいかない

        item  : /\s*;\s*/,  // Cookie 項目間

        value : /\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 all = document.cookie;

        if (!all) { return {}; }

        var items = {};

        var datas = all.split(this._DELIMITERS.item);

        var ndatas = datas.length;

        for (var d = 0; d < ndatas; d++) {

            if (0 == datas[d].indexOf(this._nameCond.str)) {

                var values = datas[d].replace(this._nameCond.regexp, "")

                                     .split(this._DELIMITERS.value);

                var nvalues = values.length;

                for (var v = 0; v < nvalues; v++) {

                    var tmp = values[v].split(this._DELIMITERS.hash);

                    items[decodeURIComponent(tmp[0])] = decodeURIComponent(tmp[1]);

                }

                break;

            }

        }

        return items;

    },



    // オプションマージ済文字列取得

    _getParamStr : function() {

        var compats = []; // 有効なパラメタ集合

        for (var p in this.params) {

            // 条件に適合したパラメタのみ収集

            if (this._param2[p].cond(this.params[p])) {

                compats.push(p+"="+this._param2[p].conv(this.params[p]));

            }

        }

        // BUG IE, WebKit: max-age 未対応、expires を使うしかない

        var expires = this._getExpiresStr();

        if (expires) { compats.push(expires); }

        // 収集したパラメタを単一文字列に変換

        var str = compats.join(";");

        return (("" == str) ? "" : ";"+str);

    },



    // expires 設定用文字列取得

    _getExpiresStr : function() {

        // max-age から算出する

        var maxage = this.params["max-age"];

        if (isNaN(maxage)) { return ""; }

        var date = new Date();

        date.setTime(date.getTime() + maxage);

        return "expires="+date.toGMTString();

    }

}; // END MJL.Cookie.prototype





// ----------------------------------------------------------------------------

// Rollover: ロールオーバー

// ----------------------------------------------------------------------------

MJL.Rollover = function(/* arguments */) {

    // 対象要素集合

    this.targets = [

        // {element:要素, events:{イベント名:イベントリスナ ...}} ...

    ];

    // 有効にするクラス名

    this.enable = "";

    // オプション

    this.options = {

        // 無効にするクラス名

        disable : "foo",

        // 属性値変換対応

        switchers : {

            on  : {cond : /(\.[^\.]+)$/g, replace : "_on$1"},

            off : {cond : "",             replace : ""}

        }

    };



    this.setOptions.apply(this, arguments);

};



MJL.Rollover.prototype = {

    // ------------------------------------

    // Public

    // ------------------------------------

    // オプション設定

    setOptions : function(enable, optional) {

        if (arguments.length < 1) { return; }

        this.enable = enable;

        // 各種オプション値設定

        if (null !== optional && "object" == typeof optional) {

            for (var o in this.options) {

                if (undefined === optional[o]) { continue; }

                this.options[o] = optional[o];

            }

        }

    },



    // 生成

    create : function(/* arguments */) {

        this.setOptions.apply(this, arguments);

        this._setTargets();

        this._setEvents();

    },



    // ------------------------------------

    // Private

    // ------------------------------------

    // 対象にする要素の種類-条件対応

    _TYPES : (function() {

        // 要素 elem に対し、種類 type の効果をかけるクロージャを生成する

        // クロージャを生成

        function getEventGetter(type) {

            // this.targets への push が行われる時に実行される

            return function(elem) {

                var value = elem.getAttribute("src").replace(

                    this.options.switchers[type].cond,

                    this.options.switchers[type].replace

                );

                // このタイミングでキャッシュをとるのがベスト

                this._addCache(value);

                return function() {

                    elem.setAttribute("src", value);

                };

            };

        }



        // 要素 elem の子孫に img 要素に対し、種類 type の効果をかける

        // クロージャを生成するクロージャを生成

        function getDescendantEventGetter(type) {

            // this.targets への push が行われる時に実行される

            return function(elem) {

                var imgs = elem.getElementsByTagName("img");

                var nimgs = imgs.length;

                var getters = getEventGetter(type);

                var events = new Array(nimgs);

                for (var i = 0; i < nimgs; i++) {

                    events[0] = getters.call(this, imgs[i]);

                }

                return function() {

                    for (var i = 0; i < nimgs; i++) {

                        events[i]();

                    }

                };

            };

        }



        // 対応

        return {

            img : {

                isTarget : function() { return true; }, // 追加判定条件

                getters : { // イベントとイベントリスナ生成用クロージャ

                    mouseover : getEventGetter("on"),

                    mouseout  : getEventGetter("off")

                }

            },

            input : {

                isTarget : function(elem) {

                    return ("image" == elem.getAttribute("type"));

                },

                getters : {

                    mouseover : getEventGetter("on"),

                    mouseout  : getEventGetter("off"),

                    focus     : getEventGetter("on"),

                    blur      : getEventGetter("off")

                }

            },

            a : {

                isTarget : function(elem) {

                    var imgs = elem.getElementsByTagName("img");

                    return (0 < imgs.length);

                },

                getters : {

                    focus : getDescendantEventGetter("on"),

                    blur  : getDescendantEventGetter("off")

                }

            }

        };

    })(),



    // 自身 or 直近祖先要素の有効是非 (enable: true, disable: false)

    _isEnable : function(node) {

        // 無効にするクラス名が未指定なら無条件で enable

        if (!this.options.disable) { return true; }

        // 無効にするクラス名が指定されている

        do {

            // 同時指定時は disable 優先

            if (MJL.hasClassName(node, this.options.disable)) { return false; }

            if (MJL.hasClassName(node, this.enable)) { return true; }

            node = node.parentNode;

        } while (node);

        // 判別不能だった場合は例外とせず disable

        return false;

    },



    // 要素 elem 対象要素是非 (是: true, 非: false)

    _isTarget : function(elem) {

        var name = elem.nodeName.toLowerCase();

        if (undefined === this._TYPES[name]) { return false; }

        if (this._TYPES[name].isTarget(elem) && this._isEnable(elem)) {

            return true;

        }

        return false;

    },



    // 対象要素取得

    _getElements : function() {

        var ret = []; // 取得した要素の集合

        var targets = MJL.getElementsByClassName(document, this.enable);

        var ntargets = targets.length;



        for (var e = 0; e < ntargets; e++) {

            // 取得した要素自身が対象要素の場合もありうる -> 追加

            if (this._isTarget(targets[e])) {

                ret.push(targets[e]);

            }

            // 取得した要素の子孫要素に対し検索をかける

            for (var n in this._TYPES) {

                var childs = targets[e].getElementsByTagName(n);

                var nchilds = childs.length;

                for (var c = 0; c < nchilds; c++) {

                    if (this._isTarget(childs[c])) {

                        ret.push(childs[c]);

                    }

                }

            }

        }

        return ret;

    },



    // 対象要素とイベントリスナのコレクションを生成

    _setTargets : function() {

        var elems = this._getElements();

        var nelems = elems.length;

        for (var e = 0; e < nelems; e++) {

            var name = elems[e].nodeName.toLowerCase();

            var item = {element : elems[e], events : {}};

            var getters = this._TYPES[name].getters;

            for (var v in getters) {

                // call しないと this が違ったままなので注意

                item.events[v] = getters[v].call(this, elems[e]);

            }

            this.targets.push(item);

        }

    },



    // イベント設定

    _setEvents : function() {

        var ntargets = this.targets.length;

        for (var t = 0; t < ntargets; t++) {

            for (var type in this.targets[t].events) {

                MJL.event.add(this.targets[t].element,

                              type,

                              this.targets[t].events[type],

                              false);

            }

        }

    },



    // キャッシュ生成

    _addCache : (function() {

        // キャッシュした内容はクロージャに保存

        var caches = {};

        return function(src) {

            // 同一ファイルが既にキャッシュ済 -> キャッシュ不要

            // 但し、同一パスのみ対応する

            // 故に、例えば次のパスが全て同一ファイル示していても全てキャッシュ

            //    /img/test.png

            //    img/test.png

            //    ./img/test.png

            //    http://foo/img/test.png

            if (caches[src]) { return; }

            // img 要素を生成し、キャッシュに格納

            caches[src] = document.createElement("img");

            caches[src].setAttribute("src", src);

        };

    })()

}; // END MJL.Rollover.prototype





// ----------------------------------------------------------------------------

// Flash: Flash プラグイン インタフェイス

// ----------------------------------------------------------------------------

MJL.Flash = function(/* arguments */) {

    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 = {

    // ------------------------------------

    // Public

    // ------------------------------------

    // MIME Type

    type : "application/x-shockwave-flash",

    // Plugin URL

    pluginurl : "http://www.adobe.com/go/getflashplayer",

    // Class ID (ActiveX)

    classid : "clsid:d27cdb6e-ae6d-11cf-96b8-444553540000",

    // Code Base (ActiveX)

    codebase : "http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab",



    // バージョン

    // 形式：{major:Number, minor:Number, revision:Number, debug:Number}

    version : (function() {

        var str = "";

        try {         // NPAPI 互換

            str = navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin.description;

            str.match(/(\d+)\.(\d+)(?:\s*[r\.](\d+))?(?:\s*[bd](\d+))?$/); // バージョン抽出

        } catch (e) { // ActiveX

            try {

                // 7 以降

                str = (new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7")).GetVariable("$version");

            } catch (e) {

                try {

                    // 6.x

                    var plugin = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");

                    str = "6,0,21,0"; // 6.x first

                    // 6.0.22 - 6.0.29 では GetVariable("$version") でクラッシュ

                    // 6,0,47 以上なら AllowScriptAccess が利用可能

                    // AllowScriptAccess で例外を投げさせて回避

                    plugin.AllowScriptAccess = "always";

                    str = plugin.GetVariable("$version");

                } catch (e) {

                    try {

                        // 5.x, 4.x

                        // 3.x は GetVariale() で例外を投げる

                        str = (new ActiveXObject("ShockwaveFlash.ShockwaveFlash")).GetVariable("$version");

                    } catch (e) {

                        // 3.x 以前はサポートしない

                        str = ""; // "6,0,21,0" をリセット

                    }

                }

            }

            str.match(/(\d+),(\d+),(\d+),(\d+)/); // バージョン抽出

        }

        var version = {major:0, minor:0, revision:0, debug:0};

        if (str) {

            // 数値変換

            version.major = parseInt(RegExp.$1) || 0;

            version.minor = parseInt(RegExp.$2) || 0;

            version.revision = parseInt(RegExp.$3) || 0;

            version.debug    = parseInt(RegExp.$4) || 0;

        }

        return version;

    })(),



    // Plugin 有効/無効 (有効: true, 無効: false)

    enable : (function() {

        try {

            return !!navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin;

        } catch (e) {

            try {

                return !!(new ActiveXObject("ShockwaveFlash.ShockwaveFlash"));

            } catch (e) {

            }

        }

        return false;

    })(),



    // バージョン比較

    compVersion : function(major, minor, revision, debug) {

        // 小数点以下切捨

        major    = parseInt(major);

        minor    = parseInt(minor);

        revision = parseInt(revision);

        debug    = parseInt(debug);

        // プラグインバージョン < 比較バージョン ->  1

        // プラグインバージョン = 比較バージョン ->  0

        // プラグインバージョン > 比較バージョン -> -1

        return ((this.version.major < major)       ?  1 :

                (this.version.major > major)       ? -1 :

                isNaN(minor)                       ?  0 :

                (this.version.minor < minor)       ?  1 :

                (this.version.minor > minor)       ? -1 :

                isNaN(revision)                    ?  0 :

                (this.version.revision < revision) ?  1 :

                (this.version.revision > revision) ? -1 :

                isNaN(debug)                       ?  0 :

                (this.version.debug < debug)       ?  1 :

                (this.version.debug > debug)       ? -1 : 0);

    },



    // オプション設定

    setOptions : function(node, optional) {

        if (arguments.length < 1) { return; }

        if (1 != node.nodeType || "object" != node.nodeName.toLowerCase()) {

            // object 要素でなければ不正

            throw Error("invalid 'object' element node: "+elem);

        }

        this.node = node;

        this.validCreated = false;

        if (null !== optional && "object" == typeof optional) {

            for (var o in this.options) { // オプション抽出

                if (undefined === optional[o]) { continue; }

                this.options[o] = optional[o];

            }

        }

        this._setParams();

        this._setOptionsByParams(); // optional 引数に優先する

    },



    // 要素生成

    create : function(/* arguments */) {

        this.setOptions.apply(this, arguments);

        this._switchNode();

        this._activate();

        return this.node;

    },



    // ------------------------------------

    // Private

    // ------------------------------------

    // param 要素を抽出し、name/value 属性値を this.params に設定

    _setParams : function() {

        var params = MJL.getElementsByChildNodes(this.node, "param");

        var nparams = params.length;

        for (var p = 0; p < nparams; p++) {

            this.params[params[p].getAttribute("name")] =

                params[p].getAttribute("value");

        }

    },



    // param 要素 name/value からオプションを抽出

    _setOptionsByParams : function() {

        for (var o in this.options) {

            if (undefined === this.params[o]) { continue; }

            this.options[o] = this.params[o];

        }

    },



    // ノード切替

    _switchNode : function() {

        // 条件分岐による切替ノード分岐

        if (this.enable) {

            if (this.compVersion(this.options.version) <= 0) {

                this.validCreated = true; // 正常な object 要素を出力

                return;

            }

            // 代替コンテンツ表示ケース

            this.alt = (this.options.minVerMsg) ? this._createMinVerMsg()

                                                : this._createAlt();

            var alt = this.alt;

            var node = this.node;

            // ノード切替

            this.node = this.alt;

            // object 要素内の代替要素と object 要素自身を置換

            node.parentNode.replaceChild(alt, node);

        } // plugin 無効なら何もしない

    },



    // 特別ノード生成

    _createMinVerMsg : function() {

        // String -> DOM 文字列

        // その他 -> DOM ノード

        return ("string" == typeof this.options.minVerMsg) ?

            MJL.convNode(this.options.minVerMsg) : this.options.minVerMsg

    },



    // 代替コンテンツ取得

    _createAlt : (function() {

        if (MJL.ua.trident) { // Trident

            return function() {

                // BUG IE6,7: childNodes で代替コンテンツが取得不能

                // BUG IE7: 条件コメント <![endif]--> はテキストノードとして

                //          レンダリングされてしまう（未解決）

                // innerHTML を使うと代替コンテンツのみ取得可能

                return MJL.convNode(this.node.innerHTML);

            };

        } else {              // Others

            return function() {

                var df = document.createDocumentFragment();

                var elems = MJL.getElementsByChildNodes(this.node,

                                                        "param",

                                                        false);

                var nelems = elems.length;

                for (var e = 0; e < nelems; e++) {

                    df.appendChild(elems[e].cloneNode(true));

                }

                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 アクティブ化スイッチは classid 属性値

            // （ないし type 属性値）

            this.node.setAttribute("classid", this.classid);

       }

    },



    // IE ONLY: object 要素のコピーを設定

    _setCopyObject : function() {

        // clone ノードでは ActiveX アクティブ化ができない

        var obj = document.createElement("object");

        // 属性の全移植

        var attrs = this.node.attributes;

        var nattrs = attrs.length;

        for (var a = 0; a < nattrs; a++) {

            // 空文字列と "null" の場合は何もしない

            if ("" == attrs[a].value || "null" === attrs[a].value ||

                // ActiveX アクティブ化スイッチは入れないようにする

                "type" == attrs[a].name || "classid" == attrs[a].name) {

                continue;

            }

            obj.setAttribute(attrs[a].name, attrs[a].value);

        }

        // 子ノードの全移植

        var childs = this.node.childNodes;

        var nchilds = childs.length;

        for (var c = 0; c < nchilds; c++) {

            obj.appendChild(childs[c].cloneNode(true));

        }

        // DOM ツリー上にある object 要素と置換

        this.node.parentNode.replaceChild(obj, this.node);

        this.node = obj;

    }

}; // END MJL.Flash.prototype



// ユーティリティ

// コンストラクタ外にあるため new 時にコピーされない

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(/* arguments */) {

    this.parent = null;               // 起点要素 (親要素)

    this.targets = [                  // 対象要素群

        // {

        //     node : 対象要素,

        //     uri  : オープン URI,

        //     ref  : 開いたウインドウへのリファレンス

        // }

    ];

    var obj = this;

    this.options = {                  // オプション

        name    : "_blank",           // ウインドウ名

        collect : obj._collectDefault // 対象要素群 収集関数

    };

    this.params = {                   // ウインドウに渡すパラメタ

        // null: パラメタを渡さない

        // 状態

        left   : null, // 横位置

        top    : null, // 縦位置

        height : null, // 縦幅

        width  : null, // 横幅

        // 表示切替（非推奨）

        // "yes": 表示, "no": 非表示

        menubar  : "yes", // メニューバー

        toolbar  : "yes", // ツールバー

        location : "yes", // ロケーションバー

        status   : "yes"  // ステータスバー

    };



    this.setOptions.apply(this, arguments);

};



MJL.Window.prototype = {

    // ------------------------------------

    // Public

    // ------------------------------------

    // オプション設定

    setOptions : function(parent, optional) {

        if (arguments.length < 1) { return; }

        this.parent = parent;

        // 各種オプション値設定

        if (null !== optional && "object" == typeof optional) {

            for (var o in this.options) {

                if (undefined === optional[o]) { continue; }

                this.options[o] = optional[o];

            }

            for (var p in this.params) {

                if (undefined === optional[p]) { continue; }

                this.params[p] = this._normalizeParam(optional[p]);

            }

        }

    },



    // 生成

    create : function(/* arguments */) {

        this.setOptions.apply(this, arguments);

        this._setTargets();

        this._setEvents();

    },



    // ウインドウオープン

    open : function(id) {

        var ref = window.open(this.targets[id].uri,

                              this.options.name,

                              this._getParamStr());

        this.targets[id].ref = ref ? ref : null;

    },



    // ------------------------------------

    // Private

    // ------------------------------------

    // 変更不可パラメタ

    _IMMUTABLE_PARAMS : {

        resizable  : "yes",

        scrollbars : "yes"

    },



    // 要素名 - URI 取得関数対応

    _NODE2URI : {

        // 要素名 : URI 取得関数

        "a" : function(node) {

            return node.getAttribute("href");

        }

    },



    // 対象要素群 収集関数（既定）

    _collectDefault : function(parent) {

        // parent の子孫要素にある a 要素を収集

        // parent 自身が a 要素なら parent のみ収集

        return (("a" == parent.nodeName.toLowerCase()) ?

            [parent] : parent.getElementsByTagName("a")

        );

    },



    // 対象要素群 収集関数（外部リンク）

    _collectHTTP : function(parent) {

        var elems = [];

        if ("a" == parent.nodeName.toLowerCase()) { // parent 自身

            var href = parent.getAttribute("href");

            if (href && "http" == href.substring(0, 4)) {

                elems = [parent];

            }

        } else if (document.evaluate) {             // XPath が利用可能

            var query = document.evaluate(

                './/a[starts-with(@href, "http")]',

                parent,

                null,

                XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,

                null

            );

            var nquery = query.snapshotLength;

            elems.length = nquery;

            for (var i = 0; i < nquery; i++) {

                elems[i] = query.snapshotItem(i);

            }

        } else {                                    // 線形探索

            var anchors = parent.getElementsByTagName("a");

            var nanchors = anchors.length;

            for (var a = 0; a < nanchors; a++) {

                var href = anchors[a].getAttribute("href");

                if (href && "http" == href.substring(0, 4)) {

                    elems.push(anchors[a]);

                }

            }

        }

        return elems;

    },



    // 対象要素を設定

    _setTargets : function() {

        var targets = this.options.collect.call(this, this.parent);

        var ntargets = targets.length;

        for (var t = 0; t < ntargets; t++) {

            var nodeName = targets[t].nodeName.toLowerCase();

            this.targets[t] = {

                node : targets[t],

                uri  : this._NODE2URI[nodeName](targets[t]),

                ref  : null

            };

        }

    },



    // 対象要素のイベントに open メソッドを設定

    _setEvents : function() {

        var ntargets = this.targets.length;

        for (var t = 0; t < ntargets; t++) {

            MJL.event.add(this.targets[t].node,

                          "click",

                          this._getEventListener(t),

                          false);

        }

    },



    // イベントリスナ用クロージャを取得

    _getEventListener : function(id) {

        var obj = this;

        return function() {

            obj.open(id);

            return false;

        };

    },



    // 実際に window.open へ与えるパラメタ文字列取得

    _getParamStr : function() {

        var ret = [];

        for (var p in this.params) {

            if (null === this.params[p]) { continue; }

            ret.push(p+"="+this.params[p]);

        }

        for (var i in this._IMMUTABLE_PARAMS) {

            ret.push(i+"="+this._IMMUTABLE_PARAMS[i]);

        }

        return ret.join(",");

    },



    // 与パラメタ値を正規化

    _normalizeParam : function(value) {

        return ((true === value) ? "yes" : (false === value) ? "no" : value);

    }

};



// ユーティリティ

MJL.Window.collectDefault = MJL.Window.prototype._collectDefault;

MJL.Window.collectHTTP = MJL.Window.prototype._collectHTTP;





// ----------------------------------------------------------------------------

// Tab: タブインタフェイス生成

// ----------------------------------------------------------------------------

MJL.Tab = function(/* arguments */) {

    this.container = null;  // コンテンツコンテナノード

    this.content   = null;  // パネルを包含するノード (メインドキュメント)

    this.list      = null;  // タブリストノード (タブになる部分)

    this.id        = "";    // this.content ノードに付与された id 属性値

    this.activeId  = "";    // 現在アクティブなパネルの ID

    this.stat      = false; // 静的モード是非 (是: true, 非: false)

    // class 属性値対応

    this.classes = {

        container : "tabContainer", // コンテンツコンテナ

        list      : "tabList",      // タブリスト

        panel     : "tabPanel",     // パネル

        title     : "tabTitle",     // タイトル

        active    : "active",       // アクティブ状態

        stat      : "static"        // 静的モード

    };

    // オプション

    var obj = this;

    this.options = {

        // Cookie による状態保存有無 & Cookie オプション

        //   保存なし: null, 保存あり: 連想配列（MJL.Cookie 第2引数）

        cookie  : {},

        collect : obj._collectDefault // 内容要素群 収集関数

    };

    // id - アイテム集合対応

    this.items = {

        // id : { // 内容要素 id 属性値

        //    panel : パネルノード

        //    title   : タイトルノード (dynamic ONLY)

        //    list    : タブリストの該当ノード

        //    event   : イベントのトリガにするノード

        // }, ...

    };

    this.nitems      = 0;         // 内容要素数

    this._cookie     = null;      // MJL.Cookie オブジェクト

    this._cookieName = "MJL.Tab"; // Cookie 項目名



    this.setOptions.apply(this, arguments);

};



MJL.Tab.prototype = {

    // ------------------------------------

    // Public

    // ------------------------------------

    // オプション設定

    setOptions : function(content, optional) {

        if (arguments.length < 1) { return; }

        this.content = content;

        this.id = content.getAttribute("id");

        // 各種オプション値設定

        if (null !== optional && "object" == typeof optional) {

            for (var o in this.options) {

                if (undefined === optional[o]) { continue; }

                this.options[o] = optional[o];

            }

        }

        // id がない場合は cookie 制御ができないため強制 false

        if (!this.id) {

            this.options.cookie = null;

        }

    },



    // DOM ノード生成

    create : function(/* arguments */) {

        this.setOptions.apply(this, arguments);

        this._distStatic();

        this._getContents();

        this._createContainer();

        this._createList();

        this._setEvents();

        this._createCookie();

        // 対象要素 (this.content) との置換はまだ行わない

    },



    // 内容ノードとコンテナノードを置換

    replace : function() {

        var pivot = document.createComment(""); // ノード位置保存用ピボット

        var root  = this.content.parentNode;    // replaceChild の基点

        // 描画回数を2回にするため、一度 DOM ツリーから外す

        root.replaceChild(pivot, this.content);

        // パネルに class 属性値を付与

        for (var i in this.items) {

            MJL.addClassName(this.items[i].panel, this.classes.panel);

        }

        if (this.stat) {

            // 静的モード：戻す

            root.replaceChild(this.content, pivot);

        } else {

            // 動的モード：生成ノードを追加し、コンテンツコンテナノードを戻す

            this.container.appendChild(this.list);

            this.container.appendChild(this.content);

            root.replaceChild(this.container, pivot);

        }

        this.active();

    },



    // アクティブタブ＆コンテンツ切替

    active : function(id) {

        var aid = this._getActiveId();

        var c = this.classes.active;

        if (id == aid) {                   // アクティブ ID と同一

            return;   // 何もしない

        } else if (!this._isValidId(id)) { // 対象 ID が不正

            id = aid; // 現在のアクティブ ID を使用

        }

        // アクティブ ID のタブとパネルを非アクティブに

        MJL.removeClassName(this.items[aid].list, c);

        MJL.removeClassName(this.items[aid].panel, c);

        // 対象 ID のタブとパネルをアクティブに

        MJL.addClassName(this.items[id].list, c);

        MJL.addClassName(this.items[id].panel, c);

        // アクティブ ID を対象 ID に変更

        this._setActiveId(id);

        // Cookie 値設定

        this._setCookie();

    },



    // ------------------------------------

    // Private

    // ------------------------------------

    // 自動生成 id 属性値接頭語

    _ID_PREFIX : "MJL_TAB_ITEM_",



    // URI 文字列から id 属性値を抽出する際の置換用正規表現

    _URI2ID : /^[^#]*#/,



    // 内容要素群 収集関数（既定）

    _collectDefault : function(parent) {

        // parent の子要素を収集

        return MJL.getElementsByChildNodes(parent);

    },



    // 静的モード判定

    _distStatic : function() {

        this.stat = MJL.hasClassName(this.content, this.classes.stat);

        if (this.stat) {

            // 静的モードなら class 属性値削除

            // CSS ON, JS OFF の時、情報は伝達するように

            // ex) .tabContainer .tabs.static {position:static;}

            MJL.removeClassName(this.content, this.classes.stat);

        }

    },



    // 適正な ID 値の是非 (是: true, 非: false)

    _isValidId : function(id) {

        return ("" != id && undefined !== this.items[id]);

    },



    // アクティブ ID 取得

    _getActiveId : function() {

        //

        // 以降の処理は初回のみ実行される

        // 2回目以降は、末尾で上書きする2回目以降用関数が利用される

        //

        // マークアップから取得したアクティブ ID

        var activeIdByMarkup = this._getActiveIdByMarkup();

        //

        // アクティブ ID 優先順位

        //   Cookie > URI > 静的マークアップ > 先頭アイテム

        //

        // Cookie から取得

        var activeId = this._getCookie();

        if (!this._isValidId(activeId)) {         // Cookie からの取得値が不正

            // URI から取得

            var hash = window.location.hash;

            activeId = hash ? hash.replace(this._URI2ID, "") : "";

            if (!this._isValidId(activeId)) {     // URI からの取得値が不正

                // 静的マークアップから取得

                activeId = activeIdByMarkup;

                if (!this._isValidId(activeId)) { // 静的マークアップからの

                                                  // 取得値が不正

                    // 先頭アイテムの ID を取得

                    for (var id in this.items) {

                        activeId = id;

                        break;

                    }

                }

            }

        }

        this._setActiveId(activeId);

        // 自己を2回目以降用メソッドで上書き

        this._getActiveId = this._getActiveIdBySelf;

        return activeId;

    },



    // マークアップからアクティブ ID を取得

    _getActiveIdByMarkup : function() {

        var activeId = "";

        for (var id in this.items) {

            if (MJL.hasClassName(this.items[id].panel,

                                 this.classes.active)) {

                activeId = id;

                break;

            }

        }

        // アクティブ ID がある場合：アクティブアイテム クラス名削除

        if (activeId) {

            MJL.removeClassName(this.items[id].panel, this.classes.active);

            if (this.stat) {

                // 静的モードの時だけリストの該当要素からクラス名削除

                MJL.removeClassName(this.items[id].list, this.classes.active);

            }

        }

        return activeId;

    },



    // _getActiveId 2回目以降の実行用

    _getActiveIdBySelf : function() {

        return this.activeId;

    },



    // アクティブ 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 = "";

            // 静的コンテンツ: id 属性値取得

            if (this.stat) {

                id = elem.getAttribute("id");

                if (!id) {

                    // id 属性値の不在はありえない

                    throw Error("invalid id attribute value '"+id+"'");

                }

            // 動的コンテンツ: 生成

            } else {

                id = this._ID_PREFIX+idIndex;

                idIndex++;

            }

            return id;

        };

    })(),



    // a 要素 href 属性値から ID を取得

    _getIdByHref : function(elem) {

        var href = elem.getAttribute("href");

        if (!href) {

            // href の値がないのはありえない

            throw Error("invalid href attribure value '"+href+"'");

        }

        var id = href.replace(this._URI2ID, "");

        if (!this._isValidId(id)) {

            // ID の不正はありえない

            throw Error("invalid reference ID '"+id+"' in '"+href+"'");

        }

        return id;

    },



    // 内容ノードから id - アイテム集合を取得

    _getContents : function() {

        var targets = this.options.collect.call(this, this.content);

        var ntargets = targets.length;

        for (var t = 0; t < ntargets; t++) {

            // アイテム生成

            var id = this._getId(targets[t]);

            if (undefined === this.items[id]) {

                this.items[id] = {

                    panel : targets[t],

                    title : this._getTitle(targets[t]),

                    list  : null,

                    event : null

                };

            } else {

                throw Error("overlapping id value '"+id+"'");

            }

        }

        this.nitems = ntargets;      // 個数も保存

    },



    // タブタイトル要素取得

    _getTitle : function(elem) {

        var title = "";

        if (this.stat) {

            // 何もしない

        } else {

            var titles = MJL.getElementsByClassName(elem, this.classes.title);

            if (titles.length < 1) {

                // タブタイトルの不在はありえない

                throw Error("not found title-use element");

            }

            title = titles[0];

        }

        return title;

    },



    // タブタイトル要素の子孫ノードを全クローン

    _cloneTitle : function(id) {

        var df = document.createDocumentFragment();

        var childs = this.items[id].title.childNodes;

        var nchilds = childs.length;

        for (var c = 0; c < nchilds; c++) {

            df.appendChild(childs[c].cloneNode(true));

        }

        return df; // Document Fragment をそのまま使う

    },



    // コンテナノード生成

    _createContainer : function() {

        var cont = null;

        if (this.stat) {

            cont = this.content.parentNode;

            // 最大で root まで辿る

            while (cont && !MJL.hasClassName(cont, this.classes.container)) {

                cont = cont.parentNode;

            }

            if (!cont) {

                // コンテナノードの不在はありえない

                throw Error("not found tab container element");

            }

        } else {

            cont = document.createElement("div");

            MJL.addClassName(cont, this.classes.container);

        }

        this.container = cont;

    },



    // タブリストノード生成

    _createList : function() {

        var list = null;

        if (this.stat) {

            list = MJL.getElementsByClassName(this.container,

                                              this.classes.list)[0];

            if (!list) {

                // タブリストノードの不在はありえない

                throw Error("not found tab list element");

            }

            var childs = MJL.getElementsByChildNodes(list);

            var nchilds = childs.length;

            if (nchilds != this.nitems) {

                // タブリストノード項目数と内容ノード項目数が異なってはならない

                throw Error("not equal tab list items ("+this.nitems+") and contents ("+nchilds+")");

            }

            for (var c = 0; c < nchilds; c++) {

                var a = this._getEventElement(childs[c]);

                // href 属性値から ID を抽出し

                // 対応する id - アイテム集合対応のアイテムに値を与える

                var id = this._getIdByHref(a);

                this.items[id].list = childs[c];

                this.items[id].event = a;

            }

        } else {

            list = document.createElement("ul");

            MJL.addClassName(list, this.classes.list);

            for (var id in this.items) {

                var li = document.createElement("li");

                var a = document.createElement("a");

                a.setAttribute("href", '#'+id); // ページ内リンク生成

                list.appendChild(li)

                    .appendChild(a)

                    .appendChild(this._cloneTitle(id));

                this.items[id].list = li;

                this.items[id].event = a;

            }

        }

        this.list = list;

    },



    // イベントトリガ用要素取得 (static ONLY)

    _getEventElement : function(parent) {

        var elem = parent.getElementsByTagName("a")[0];

        if (!elem) {

            // イベント設定要素の不在はありえない

            throw Error("not found valid event element");

        }

        return elem;

    },



    // 全イベントトリガ用要素にイベントを設定

    _setEvents : function() {

        for (var i in this.items) {

            MJL.event.add(this.items[i].event,

                          "click",

                          this._getEventListener(i),

                          false);

        }

    },



    // イベントリスナ用クロージャを取得

    _getEventListener : function(id) {

        var obj = this;

        return function() {

            obj.active(id);

            return false; // ページ内遷移防止

        };

    },



    //

    // Cookie

    //

    // Cookie 生成

    _createCookie : function() {

        if (null === this.options.cookie || null !== this._cookie) { return; }

        this._cookie = new MJL.Cookie(this._cookieName, this.options.cookie);

    },



    // Cookie 値設定

    _setCookie : function() {

        if (null === this.options.cookie) { return; }

        this._cookie.set(this.id, this._getActiveId());

    },



    // Cookie 値取得

    _getCookie : function() {

        return (

            (null === this.options.cookie) ? "" : this._cookie.get(this.id)

        );

    }

}; // END MJL.Tab.prototype





// ----------------------------------------------------------------------------

// HeightEqualizer: 指定要素等高

// ----------------------------------------------------------------------------

MJL.HeightEqualizer = function(/* arguments */) {

    this.parent  = null;               // 起点要素 (親要素)

    this.targets = [];                 // 対象要素群

    var obj = this;

    this.options = {                   // オプション

        groupBy : 0,                   // グルーピング要素数

        collect : obj._collectDefault, // 対象要素群 収集関数

        resize  : true                 // 自動リサイズ (ON: true, OFF: false)

    };

    this._listeners = {                // イベントリスナ

        // イベント名 : MJL.event.add 返値

        resize     : null,

        fontresize : null

    };



    this.setOptions.apply(this, arguments);

};



MJL.HeightEqualizer.prototype = {

    // ------------------------------------

    // Public

    // ------------------------------------

    // オプション設定

    setOptions : function(parent, optional) {

        if (arguments.length < 1) { return; }

        this.parent = parent;

        if (null !== optional && "object" == typeof optional) {

            for (var o in this.options) {

                if (undefined === optional[o]) { continue; }

                this.options[o] = optional[o];

            }

        }

    },



    // 生成

    create : function(/* arguments */) {

        this.setOptions.apply(this, arguments);

        this.targets = this.options.collect.call(this, this.parent);

        this.set();

        this._setAutoResize();

    },



    // スタイル設定

    set : function() {

        // 一度スタイル開放しないと前回の設定値により通常フロー時の値がとれない

        this.release();

        var heights = this._getHeights();

        var ntargets = this.targets.length;

        for (var t = 0; t < ntargets; t++) {

            this.targets[t].style.height = heights[t];

        }

    },



    // スタイル開放

    release : function() {

        var ntargets = this.targets.length;

        for (var t = 0; t < ntargets; t++) {

            this.targets[t].style.height = "";

        }

    },



    // ------------------------------------

    // Private

    // ------------------------------------

    // 設定する単位

    _UNIT : "px",



    // 対象要素群 収集関数（既定）

    _collectDefault : function(parent) {

        // parent の子要素を収集

        return MJL.getElementsByChildNodes(parent);

    },



    // 各要素のレンダリング後 height 最大値取得

    _getHeights : function(elem) {

        var ntargets = this.targets.length;

        var heights = new Array(ntargets); // 対象要素の height 集合

        // あらかじめ height を全計算させる

        for (var t = 0; t < ntargets; t++) {

            // レンダリング後 height 値を取得 (単位 px, 数値のみ)

            heights[t] = parseInt(

                MJL.style.getComputed(this.targets[t], "height")

            );

        }

        var groupBy = this.options.groupBy;

        var max = 0; // height 最大値

        // groupBy 無効範囲

        // groupBy == 1 の場合は意味喪失するため groupBy 無効と判断

        if (groupBy < 2 || ntargets <= groupBy) {

            // 全対象要素に対する height 最大値

            max = Math.max.apply(Math, heights) + this._UNIT;

            for (var t = 0; t < ntargets; t++) {

                heights[t] = max;

            }

        // groupBy 有効範囲

        // 2 <= groupBy && groupBy < ntargets

        } else {

            for (var 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 obj = this;

            for (var l in this._listeners) {

                this._listeners[l] = MJL.event.add(window, l, function() {

                    obj.set();

                }, false);

            }

        }

    }

}; // END MJL.HeightEqualizer.prototype





// ----------------------------------------------------------------------------

// enable: 機能許可インタフェイス (簡易実行ラッパー)

// ----------------------------------------------------------------------------

MJL.enable = {

    // MJL.Rollover

    rollover : function(enable, optional) {

        var ret = new MJL.Rollover(enable, optional);

        ret.create();

        return ret;

    },



    // MJL.Flash

    flash : function(className, optional) {

        var elems = MJL.getElementsByClassName(document, className);

        var nelems = elems.length;

        var ret = new Array(nelems);

        for (var e = 0; e < nelems; e++) {

            var obj = new MJL.Flash(elems[e], optional);

            obj.create();

            ret.push(obj);

        }

        return ret;

    },



    // MJL.Window

    window : function(className, optional) {

        var elems = MJL.getElementsByClassName(document, className);

        var nelems = elems.length;

        var ret = new Array(nelems);

        for (var e = 0; e < nelems; e++) {

            var obj = new MJL.Window(elems[e], optional);

            obj.create();

            ret[e] = obj;

        }

        return ret;

    },



    // MJL.Tab

    tab : function(className, optional) {

        var elems = MJL.getElementsByClassName(document, className);

        var nelems = elems.length;

        var ret = new Array(nelems);

        for (var e = 0; e < nelems; e++) {

            var obj = new MJL.Tab(elems[e], optional);

            obj.create();

            obj.replace();

            ret[e] = obj;

        }

        return ret;

    },



    // MJL.style.Switcher

    styleSwitcher : function(className, optional) {

        var elems = MJL.getElementsByClassName(document, className);

        var nelems = elems.length;

        var ret = new Array(nelems);

        for (var e = 0; e < nelems; e++) {

            var obj = new MJL.style.Switcher(elems[e], optional);

            obj.create();

            ret[e] = obj;

        }

        return ret;

    },



    // MJL.HeightEqualizer

    heightEqualizer : function(className, optional) {

        var elems = MJL.getElementsByClassName(document, className);

        var nelems = elems.length;

        var ret = new Array(nelems);

        for (var e = 0; e < nelems; e++) {

            var obj = new MJL.HeightEqualizer(elems[e], optional);

            obj.create();

            ret[e] = obj;

        }

        return ret;

    }

};