コンテンツにスキップ

利用者:NA sounds/na lib.js

お知らせ: 保存した後、ブラウザのキャッシュをクリアしてページを再読み込みする必要があります。

多くの WindowsLinux のブラウザ

  • Ctrl を押しながら F5 を押す。

Mac における Safari

  • Shift を押しながら、更新ボタン をクリックする。

Mac における ChromeFirefox

  • Cmd Shift を押しながら R を押す。

詳細についてはWikipedia:キャッシュを消すをご覧ください。

/*
Copyright (C) 2007 by N/A sounds <[email protected]>

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library 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
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/



//<pre>
//*************************na_lib start
function na_lib(){


//************** この部分はglobalな名前に対する代入とprototypeの追加なので、na_lib名前空間とは関係ない
//************** na_libが使っているので定義しているが、他にも影響する。
//グローバルの名前衝突防止。
__global_objs = {
	global_obj_prefix: "__na_lib_global__",
	global_obj_count: 0,
	global_objs: {},
	set: function( o ){
		if( o ){
			var s = this.find(o);
			if( s )	return s;

			s = this.global_obj_prefix + this.global_obj_count;
			this.global_objs[s] = o;
			this.global_obj_count+=1;
			return s;
		}
	},
	find: function( o ){
		for( var k in this.global_objs )
			if( this.global_objs[k] == o )
				return k;
	},
	get: function( s ){
		if( s ){ return this.global_objs[s]; };
		return function(){};
	},
	remove: function( s ){
		var ret;
		if( s in this.global_objs ){
			ret = this.global_objs[s];
			delete this.global_objs[s];
		}
		return ret;
	},
	access_code: function( k ){
		return "(__global_objs.get('"+k+"'))";
	}
}

//toSource()やtoString()と組み合わせて使う。これで返ったfunctionのtoSource()は何処ででもevalが有効。
Function.prototype.global_func = function(){
	var global_name = __global_objs.set(this);
	return eval( "function(){ return __global_objs.get('"+global_name+"').apply(this, arguments); }" );
}

//function(val){ return function(){ any(val); } }(val)	//の代わり。
//function(val){ any(val); }.preset_args(val)		//こんな感じ。
Function.prototype.preset_args = function(){
	var args = [];
	for( var i=0; i<arguments.length; i++ ){
        	args.push( arguments[i] );
	}

	var f = function(){
		var tmp=[];
		for( var i=0; i<arguments.length; i++ ){
			tmp.push(arguments[i]);
		}
		return f.__self.apply(this, f.__preset_args.concat(tmp));
	}

	if( this.__self ){
		f.__self = this.__self;
		if( args.length > 0 ) f.__preset_args = this.__preset_args.concat(args);
		else f.__preset_args = this.__preset_args; //これは意味がないか...?
	}
	else{
		f.__self = this;
		f.__preset_args = args;
	}

	function args_to_str( args ){
		var arg_str = "[ ";
		for( var i=0; i<args.length; i++ ){
			var v = args[i];
			var type = (typeof v);
			if( type == "number" )		arg_str += v;
			else if( type == "string" )	arg_str += "'"+v+"'";
			else if( type == undefined )	arg_str += "undefined";
			else if( (v=__global_objs.find(v)) ) arg_str += __global_objs.access_code(v);
			else return;
			arg_str += ",";
		}
		return (arg_str.substring(0,arg_str.length-1) + "]");
	}

	f.global_func = function(){ //個別に__global_objs.setした方が良いかも...
		var global_name = __global_objs.set(f.__self);
		var arg_str = args_to_str(f.__preset_args);
		if( arg_str ){
			eval( "var ret = function(){ \
				var tmp="+arg_str+"; \
				for( var i=0; i<arguments.length; i++ ){ tmp.push(arguments[i]); }; \
				return __global_objs.get('"+global_name+"').apply(this, tmp); \
			}" );
			return ret;
		}
		else {
			var args_name = __global_objs.set(f.__preset_args);
			eval( "var ret = function(){ \
				var tmp=[]; \
				for( var i=0; i<arguments.length; i++ ){ tmp.push(arguments[i]); }; \
				return __global_objs.get('"+global_name+"').apply(this, __global_objs.get('"+args_name+"').concat(tmp)); \
			}" );
			return ret;
		}
	}

	return f;
}


//%02dみたいなことをする。from python??
Number.prototype.zfill = function(len){
	var s = new String(this); //this.toString()の方が良いのか?
	while( s.length < len )
		s = "0" + s;
	return s;
}



//************** 
with( na_lib ){


function checkBrowser(){ //とりあえず。いい加減だが。
	var results = { code_base:"unknown" };

	//this idea from http://nanto.asablo.jp/blog/2005/10/29/123294
	if( document.documentElement.getAttribute("style") == document.documentElement.style ){
		results.code_base = "ie";
		results.setAttr_class = false;
	}

	results.with_namespace = with_ns_test();
	results.hasAddEventListener = ( document.documentElement.addEventListener != undefined );
	results.hasRemoveEventListener = ( document.documentElement.removeEventListener != undefined );
	results.setAttr_class = setAttr_class_test(); //もっと良い名前...
	results.elementNS = ( document.createElementNS != undefined );
	results.hasNodeType = hasNodeType_test();

	//for( var k in results ) alert( k+" "+results[k] );

	return results;

	function hasNodeType_test(){
		try{
			Node.TEXT_NODE;
			results.Node = Node;
			return true;
		}
		catch(e){
			results.Node = { ELEMENT_NODE:1, ATTRIBUTE_NODE:2, TEXT_NODE:3, CDATA_SECTION_NODE:4, COMMENT_NODE:8 };
		}
		return false;
	}

	function with_ns_test(){
		var v = "a";
		function test_ns(){
			with(test_ns){
				var v = "b";
				function f(){ return v; }
			}
			test_ns.v = "c";
			test_ns.f = f;
		}
		test_ns();
		with( test_ns ){
			if( f && f() == test_ns.v ) return true;
			else return false;
		}
	}

	function setAttr_class_test(){
		var e = document.createElement("div");
		var test_code = "test class";
		e.setAttribute("class", test_code);
		if( e.getAttribute("class") != test_code ) return false;
		else if( test_code != e.className ) return false;
		else return true;
	}
}

var browser = checkBrowser();


if( !Array.indexOf ){
	Array.prototype.indexOf = function( obj ){
		for( var i=0; i<this.length; i++ )
			if( this[i] == obj ) return i;
		return -1;
	}
}


if( !document.importNode ){
    document.importNode = function(node, r){
	if( !node ) return;
	var root = document.createElement( node.nodeName );
	root.nodeValue = node.nodeValue;
	deepcopy(root, node.childNodes);
	deepcopy(root, node.attributes);
	return root;

	function deepcopy( root, nodes ){
	    for( var i=0; i<nodes.length; i++ ){
		var elm = nodes[i];
		var e = undefined, a = undefined;
		switch( elm.nodeType ){
			case browser.Node.ELEMENT_NODE:
				if(r) e = document.importNode(elm, r);
				break;
			case browser.Node.ATTRIBUTE_NODE:
				if( browser.code_base == "ie" && elm.name == "style" ) //if ie
					root.style.cssText = elm.value;
				else{
					if( (elm.name == "id" || elm.name == "ID") && document.getElementById(elm.value) ){
						message( "remove conflicted id("+elm.value+")", 0 );
						break;
					}
					a = document.createAttribute( elm.name );
					a.value = elm.value;
				}
				break;
			case browser.Node.TEXT_NODE:
				e = document.createTextNode(elm.data);
				break;
			case browser.Node.COMMENT_NODE:
				e = document.createComment(elm.data);
				break;
			case browser.Node.CDATA_SECTION_NODE:
				break;
			default:
				message( elm.nodeType, 2 );
		}
		if(e)
			if( browser.code_base != "ie" || root.nodeName != "SCRIPT" ) //ie6?
				root.appendChild(e); 
		if(a)	root.setAttributeNode(a);
	    }
	}
    }
}



//************** 
var _namespaceURI = undefined;
function createElement_defaultNS( tag ){
	if( !browser.elementNS ){ return document.createElement(tag); } //ie

	if( _namespaceURI )
		return document.createElementNS( _namespaceURI, tag );
	else
		return document.createElement( tag );
}

//名前変えた方良いかなぁ...
function createElement( tag, type, classes, type_is_not_class ){
	if( !browser.code_base == "ie" )
		var e = createElement_defaultNS( "<"+tag+" type="+type+">"); //...さらにradioのname対策が必要
	else{
		var e = createElement_defaultNS(tag);
		//textareaはtype属性がread only。
		if( type ) if( e.type != undefined ) e.type = type; //eがtypeを持ってない場合はどうなるんだっけ?
	}

	if( !type_is_not_class && type )
		class_change( e, type, true );
	if( classes )
		for( var i=0; i<classes.length; i++ )
			class_change( e, classes[i], true );
	return e;
}


//************** message
var _msg_element = undefined;
var _debug_level = 1;
var _message_num=0;

function init_message_element( elm, level ){ //ie
    _msg_element = elm;
    debug_level( level );
}

function message_clear(){
      _message_num = 0;
	if( _msg_element ){
	      _msg_element.innerHTML = "";
	}
}

function debug_level( level ){
	if( level >= 0 ){ _debug_level = level; message( "debug level => "+level, 2 ); }
	return _debug_level;
}


function message( s, level ){
	if( level < _debug_level || !s ){
		return;
	}
	if( window.hasOwnProperty && window.hasOwnProperty("console") && console.log ){ //ie
		console.log(""+s);
	}
	else{
	   if( _msg_element ){
	      s = s.replace( "&", "&amp;" );
	      s = s.replace( "<", "&lt;" );
	      s = s.replace( ">", "&gt;" );
	      if( level >= 2 ){
		var e = createElement_defaultNS( "span" );
		e.style.color = "red";
		e.appendChild( document.createTextNode(s) );
		insertFirstChild( _msg_element, createElement_defaultNS("br") );
		insertFirstChild( _msg_element, e );
		insertFirstChild( _msg_element, document.createTextNode( _message_num+": " ) );
	      }
	      else if( level >= _debug_level ){
		insertFirstChild( _msg_element, createElement_defaultNS("br") );
		insertFirstChild( _msg_element, document.createTextNode( _message_num+":"+ s ) );
	      }
	   }
	   else{
		//alert( s );
	   }
	}
        _message_num += 1;
}


//************** class
var class_delimiter = " ";
function class_check( element, val ){
        if( browser.setAttr_class ) var s = element.getAttribute("class");
        else var s = element.className;
	if( s ){
	      var classes = s.split(class_delimiter);
     	      for( var i=0; i<classes.length; i++ )
                if( classes[i] == val ) return true;
	}
      return false;
}

function class_change( element, val, onoff, optional ){
      if( !element ){
        return undefined;
      }
      if( browser.setAttr_class ) var s = element.getAttribute("class");
      else var s = element.className;
      if( !s ){
	message( "class is null", 0 );
	if( onoff && !browser.setAttr_class ){ element.className = val; return val; }
	if( onoff ){ element.setAttribute( "class", val ); return val; }
	else{ return ""; }
      }

      if( onoff ){
        if( !class_check(element, val) ){
  	    s = s + class_delimiter + val;
        }
        else {
          message( "class_change: already set.", 1 );
	  return s;
        }
      }
      else{
        var classes = s.split(class_delimiter);
	s = "";
        for( var i=0; i<classes.length; i++ )
          if( classes[i] != val )
            s += classes[i] + class_delimiter;
	if( s.length > 0 ) s = s.substring(0,s.length-1);
      }
      if( browser.setAttr_class )
		element.setAttribute( "class", s );
      else
		element.className = s;
      message( "class_change: " + s, 0 );
      return s;
}



//************ node
function insertAfter( parent, new_e, old_e ){
	if( old_e.nextSibling )
		parent.insertBefore( new_e, old_e.nextSibling );
	else
		parent.appendChild( new_e );
	return parent;
}

function insertFirstChild( parent, new_e ){
	if( parent.hasChildNodes() )
		return parent.insertBefore( new_e, parent.firstChild );
	else
		return parent.appendChild( new_e );
	return parent;
}


function foreachElementsByNodeType( root, type, func, ret ){
	if( !ret ) ret = [];
	if( !root.childNodes ) return;
	for( var i=0; i<root.childNodes.length; i++ ){
		if( root.childNodes[i].nodeType == type ){
			var r = func( root.childNodes[i], ret.length );
			if(r) ret.push(r);
		}
		foreachElementsByNodeType( root.childNodes[i], type, func, ret );
	}
	return ret;
}

function foreachElementsByTagName( root, tag, func ){
	var elements = root.getElementsByTagName( tag );
	var ret = [];
	for( var i=0; i<elements.length; i++ ){
		var r = func( elements[i], ret.length )
		if(r) ret.push(r);
	}
        return ret;
}


//Nodesではなく、配列が返ることに注意。
function getElementsBy( doc, tag, func ){
	var ret = [];
	var elements = doc.getElementsByTagName( tag );
	for( var i=0; i<elements.length; i++ )
		if( func(elements[i]) )
			ret.push( elements[i] );
        return ret;
}

//ie対応は? とりあえず、classNameに対応する必要あり?
//byClassesを使う方がよい。
function getElementsByTagAttrValue( doc, tag, attr, val ){
	var func = function(attr, val, element){ return (element.getAttribute(attr) == val); };
	return getElementsBy( doc, tag, func.preset_args(attr, val) );
}

function getElementByTagAttrValue( doc, tag, attr, val ){
      return getElementsByTagAttrValue( doc, tag, attr, val )[0];
}

function getElementsByTextNodeValue( doc, tag, val ){
	var func = function(val, element){
		return (get_text(element).replace(/\s*$/,"") == val.replace(/\s*$/,"")); //ie
	};
	return getElementsBy( doc, tag, func.preset_args(val) );
}

function getElementsByClasses( doc, tag, classes ){
	function func(classes, element){
		var fail = false;
		for( var i=0; i<classes.length; i++ ){
			var c = classes[i];
			if( !class_check(element, c) ){
				fail = true;
				break;
			}
		}
		return !fail;
	}
	return getElementsBy( doc, tag, func.preset_args(classes) );
}

//docそれ自体は含まない。arrayはundefinedでよい。
function getElementsByNodeType( doc, type, array ){
	if( !array ) array = [];
	for( var i=0; i<doc.childNodes.length; i++ ){
		if( doc.childNodes[i].nodeType == type ){
			array.push(doc.childNodes[i]);
		}
		getElementsByNodeType( doc.childNodes[i], type, array );
	}
	return array;
}


function getElementsByClassName(root, tag, c){
	return getElementsByClasses( doc, tag, [c] );
}

function getSequentialElements( groups, root, tag, class_name ){
	var elms = getElementsByClassName(root, tag, class_name);
	var g = [];
	var re = new RegExp(/\s*/);
	var re2 = new RegExp( class_name );
	for( var i=0; i<elms.length; i++ ){
		var next = elms[i].nextSibling;
		while( next && ( next.nodeType==8 || (next.nodeType==3 && !(next.data.replace(re,"").length > 0)) ) )
			next = next.nextSibling;
		if( next && next.className && next.className.search( re2 ) != -1 )
			g.push( elms[i] );
		else if( g.length > 0 ){
			g.push(elms[i]);
			groups.push(g);
			g = [];
		}
	}
	return groups;
}


function get_text( element ){
	if( element.textContent ) return element.textContent;
	var elms = getElementsByNodeType( element, browser.Node.TEXT_NODE );
	var s = "";
	for( var i=0; i<elms.length; i++ )
		s += elms[i].nodeValue;
	return s;
}


//最初に見つかった#textに値を入れる。
//見つからなかった場合は?
function set_text( element, val, create_text ){
	for( var i=0; i<element.childNodes.length; i++ ){
		if( element.childNodes[i].nodeName == "#text" ){
			element.childNodes[i].nodeValue = val;
			return true;
		}
		else{
			if( set_text(element.childNodes[i], val) ){
				return true;
			}
		}
	}
	if( create_text ){
		var text_element = document.createTextNode(val);
		element.appendChild( text_element );
	}
	return false;
}


function remove_LF( s ){
	return s.replace(/\r\n/g, "").replace(/\n/g,"");
}


/*
特殊イベントとして、_initが設定できる。発見時に実行される。function(element){}
[ {id:"id", tag:"tag", classes:["class", "class2"]}, { "event":[function(element, event){}, usecatch] } ], 

今のところ、idとtag&classesは排他。
*/
function setupChildrenByEventMap( root, event_map ){
	for( var k in event_map ){
		var conf = event_map[k];
		var elements = [];
		if( conf[0].id ){
			var element = document.getElementById(conf[0].id);
			if( element ){
				var n = element;
				do{
					if( n == root ){ elements.push( element ); break; }
					n = n.parentNode;
				}while( n );
			}
		}
		else if( conf[0].tag && conf[0].classes ){
			elements = elements.concat( getElementsByClasses(root, conf[0].tag, conf[0].classes) );
		}
		else if( conf[0].tag ){
			var tmp = root.getElementsByTagName(conf[0].tag)
			for( var i=0; i<tmp.length; i++ ){
				elements.push( tmp[i] );
			}
		}

		for( var i=0; i<elements.length; i++ ){
			element = elements[i];
			for( var key in conf[1] ){
				if( key == "_init" ){
					conf[1][key][0]( element );
				}
				else{
					addEventListener( element, key, conf[1][key][0].preset_args(element), conf[1][key][1] );
				}
			}
		}
	}
}


//array[name]と、input.valueを連動させる。numはstring<->numberの変換を行うか否か。
//今のところ対応しているのはtype=textのみ。
//nameは省略でき、その場合はinput.nameが使用される。
function attach_input( array, name, num, input ){
	if( !name ) name = input.name;
	if( !name ) return false;

	if( num )
		array[name] = Number(input.value);
	else
		array[name] = input.value;

	if( array.watch ){
		var watch_callback = update_input.preset_args(num, input);
		array.watch( name, watch_callback );
	}

	addEventListener( input, "change", update_array.preset_args(num,array,name,watch_callback), true );

	function update_input( num, target, i, o, n ){
		if( target.value != n ){
			if( num )
				target.value = String(n);
			else
				target.value = n;
		}
		return n;
	}
	function update_array( num, array, name, watch_callback, e ){
		if( array[name] != e.target.value ){ //newValue;
			if( array.unwatch ) array.unwatch( name );
			if( num )
				array[name] = Number(e.target.value);
			else
				array[name] = e.target.value;
			if( array.unwatch ) array.watch( name, watch_callback );
		}
	}
}



function addEventListener( elm, event_code, func, usecatch ){
	if( browser.hasAddEventListener ){
		return elm.addEventListener( event_code, func, usecatch );
	}
	else{
		function event(target){ this.target = target; }
		event.prototype = {
			target: undefined,
			preventDefault: function(){ this.ret = false; },
			stopPropagation: function(){},
			ret: true
		}

		function on_( func, event, elm ){
			delete event.ret;
			func(event);
			return event.ret;
		}
		var f = on_.preset_args(func, new event(elm));
		eval( "elm.on"+event_code+" = f;" );
	}
}



//************ tab
function create_tab( name, title, id ){
	var dt = createElement_defaultNS("dt");
	if(id) dt.setAttribute( "id", id+"_tip" );
	class_change( dt, "tip", true );

	if( !title ){title = name;}
	dt.appendChild( document.createTextNode(title) );

	var dd = createElement_defaultNS("dd");
	if(id) dd.setAttribute( "id", id );
	class_change( dd, "frame", true );

	return { 'tip':dt, 'frame':dd, 'name':name, 'parent':undefined };
}

function create_iframe_tab( name, title, frame_id ){
	var tab = create_tab( name, title, frame_id+"_tab" );
	class_change( tab['frame'], "iframe", true );
	var iframe = createElement_defaultNS("iframe");
	iframe.setAttribute( "id", frame_id );
	iframe.setAttribute( "name", frame_id );
	iframe.location = "about:blank";
	tab['frame'].appendChild( iframe );
	tab['iframe'] = iframe;
	return tab;
}



//************ tabset
var _tabsets = []; //タブセットの配列。内部で使う。
function get_tabset( tabset_name ){
	return _tabsets[tabset_name];
}

//newで呼び出すこと。
function tabset_Constructor( tabset_name, root ) {
	message( "tabset_constructor(). "+tabset_name, 1 );
	this.tabs = {};
	this.name = tabset_name;
	this.root = root;  //最初は作らない? rootがundefinedなら、get_node()で呼び出すべき。
	_tabsets[tabset_name] = this;

}
tabset_Constructor.prototype = {
	tip_onclick: function(tab){ //tip_onclick();
		if( tab && tab.onclick ){ tab.onclick(); }
	},
	tab_onchange: function( old_tab, new_tab ){
		if( old_tab && old_tab.onchange ){ old_tab.onchange(false); }
		if( new_tab && new_tab.onchange ){ new_tab.onchange(true); }
	},

	find_tabs : function( tabnames, append, _doc ){
		var doc = document;
		var import_flag = false;
		var found_tabs = [];
		if( _doc ){
			doc = _doc;
			import_flag = true; //importNodeの必要がある?
		}
		for( var i=0; i<tabnames.length; i++ ){
        		message( "find tab " + tabnames[i], 1 );
	        	var element = doc.getElementById(tabnames[i]);
			var tab = { 'name':tabnames[i] };
        		if( element ){
  		      		message( "found tabframe " + tabnames[i], 0 );
				if( import_flag ){ element = document.importNode(element, true); }
				tab['frame'] = element;
				class_change( element, "frame", true );
	        	}
 			element = doc.getElementById(tabnames[i]+"_tip");
        		if( element ){
        			message( "found tabtip " + tabnames[i], 0 );
				if( import_flag ){ element = document.importNode(element, true); }
				tab['tip'] = element;
			}

			if( tab['name'] && tab['frame'] && tab['tip'] ){
				message( "found tab " + tabnames[i], 1 );
				if( append ){
					this.append_tab( tab );
				}
				found_tabs.push( tab );
			}
			else {
				message( "find fail.", 1 );
			}
	      	}
	    	return found_tabs;
	},

	append_tab : function( tab, current_flag, rm_button ){
		if( !tab ){ return false; }
		if( tab["parent"] ){
			var old_tabset = tab["parent"];
			if( tab["parent"] == this ){
				message( "tab is aleady append. "+this.name, 2 );
				return false;
			}
			tab = old_tabset.remove_tab( tab["name"] );
		}
		tab["parent"] = this;

		//styleの整形のためにhiddenを経由した方が良いらしい?
		if(!current_flag){
			tab['frame'].style.visibility = "hidden";
	       		s = class_change( tab['tip'], "selected", false );
		}
		if( this.root ){
			if( this.tabs[tab['name']] ){
				this.root.replaceChild( tab['tip'], this.tabs[tab['name']]['tip'] );
				this.root.replaceChild( tab['frame'], this.tabs[tab['name']]['frame'] );
			}
			else{
				this.root.appendChild( tab['tip'] );
				this.root.appendChild( tab['frame'] );
			}
		}
		this.tabs[tab['name']] = tab;
		message( "append tab: " + this.name + " <- " + tab['name'], 1 );
		if( current_flag ){
			this.select_tab( tab.name );
		}

		//多重登録の心配は?
		function tip_onclick(self, name){
			if( self.tip_onclick ){ self.tip_onclick(self.tabs[name]); };
			self.select_tab(name);
			return false;
		}
		if( tab['callback'] ){
			if( browser.hasRemoveEventListener )
				tab['tip'].removeEventListener( "click", tab['callback'], false );
		}
		var f = tip_onclick.preset_args(this, tab['name']);
		addEventListener( tab['tip'], "click", f, false );
		tab['callback'] = f;

		if( rm_button ){
			var rm_b = createElement_defaultNS("span");
			class_change( rm_b, "switch", true );
			class_change( rm_b, "rm_button", true );
			rm_b.style.color = "red";
			addEventListener( rm_b, "click", function(self, name){
				self.remove_tab(name);
			}.preset_args(this, tab['name']), false );

			rm_b.appendChild( document.createTextNode("x") );
			tab['tip'].appendChild( rm_b );
		}
		return true;
	},

	get_tabnames : function(){
		var ret = [];
		for( key in this.tabs )	ret.push( key ); //他に方法は?
		return ret;
	},

	remove_tab : function( tab_name ){
		//tabsetから消す。のと、tabを消す。のの違いは?
		message( "remove_tab: "+ this.name + "/" + tab_name, 1 );
		var tab = this.tabs[tab_name];

		var target = {};
		if( class_check( tab['tip'], "selected" ) ){
			var tmp;
			for( var key in this.tabs ){
				if( tmp == tab_name ){
					target['post'] = key;
				}
				else if( key == tab_name ){
					target['pre'] = tmp;
				}
				tmp = key;
			}
		}

		delete this.tabs[tab['name']];
		tab['parent'] = undefined;
		if( this.root ){
			this.root.removeChild( tab['tip'] );
			this.root.removeChild( tab['frame'] );
		}

		if( class_check( tab['tip'], "selected" ) ){
			if( target['post'] ){
				this.select_tab( target['post'] );
			}
			else if( target['pre'] ){
				this.select_tab( target['pre'] );
			}
		}
		return tab;
	},

	//rootはundefinedでも良い。
	get_node : function( root, top_tab, relative ) {
		if( this.root ){ //繋ぎ換えをするべき?
			class_change( this.root, "tab", true );
			class_change( this.root, "relative", relative );
			return this.root;
		}
		else if( root ){
			this.root = root;
		}
		else {
			this.root = createElement_defaultNS("dl");
			class_change( this.root, "tab", true );
		}
		class_change( this.root, "relative", relative );
		this.select_tab( top_tab );
		for( key in this.tabs ){
			this.root.appendChild( this.tabs[key]['tip'] );
			this.root.appendChild( this.tabs[key]['frame'] );
		}
		return this.root;
	},

	//scrollHeight、offsetHeightは非標準。
	//scrollHeightは計算されていなければならない。従って、nodeはdocumentに繋がっていること。
	//最大の大きさのタブフレームとtipを足した高さを返す。
	set_tabframe_height : function(){
		var max_height = 0;
		var key;
		for( key in this.tabs ){
			var height = this.tabs[key]['frame'].scrollHeight + 1;  //umm.
			this.tabs[key]['frame'].style.height = height + "px";
			if( height > max_height ){
				max_height = this.tabs[key]['frame'].offsetHeight;
			}
		}
		var tip_height = 0;
		if( class_check( this.tabs[key]['tip'], "selected" ) ){
			class_change( this.tabs[key]['tip'], "selected", false ); //umm.
			tip_height = this.tabs[key]['tip'].offsetHeight;
			class_change( this.tabs[key]['tip'], "selected", true );
		}
		else {
			tip_height = this.tabs[key]['tip'].offsetHeight;
		}

		return max_height + tip_height;
	},

	select_tab : function( tabname ){
		if( ! this.tabs[tabname] ){
			return false;
		}
		var old_tab = undefined;
		var new_tab = undefined;
		for( key in this.tabs ){
		       	var frame = this.tabs[key]['frame'];
		       	var tip = this.tabs[key]['tip'];
		       	if( key == tabname ){
		       		class_change( tip, "selected", true );
				new_tab = this.tabs[key];
        		}
        		else {
				if( class_check( tip, "selected" ) ){
					old_tab = this.tabs[key];
				}
		       		class_change( tip, "selected", false );
		       	}
		}
		message( "sel: " + tabname, 0 );

		if( old_tab != new_tab ){
			if( old_tab ){
				old_tab['frame'].style.visibility = "hidden";
			}
			if( new_tab ){
				new_tab['frame'].style.visibility = "visible";
			}
			if( this.tab_onchange ){
				this.tab_onchange( old_tab, new_tab );
			}
		}
		return true;
	},

	get_tabs_num: function(){
		var count = 0;
		for( var key in this.tabs )
			count+=1; //何だっけ。数え方...keys()見たいのはないんだっけ?
		return count;
	}
}


//******** script loader
//obj = new script_loader( 全読み込み終了後のcallback = function(){} );
//obj.add( 検索する要素, 読み込みパス, 読み込み後のcallback = function(path){}, [scriptを追加する要素] );
//obj.start(); or any_set_callback_function(obj.start_func());
function script_loader( onload_func ){
	if( !script_loader.prototype._global_name ){
		try{script_loader.prototype._global_name = __global_objs.set(script_loader);}
		catch(e){}
	}
	script_loader.prototype._instance.push(this);	//クラスメンバのコード。になっているはず。
	this.id = script_loader.prototype._instance.length-1;

	this.script_onload = onload_func;
}
script_loader.prototype = {
	_instance: [],
	_global_name: undefined,

	script_onload : undefined,
	id: undefined,
	task: {},
	timer : undefined,
	counter : 0,

	check: function( name ){
		try{
			if( eval(name) != undefined ) return true;
			else return false;
		}
		catch(e){
			return undefined;
		}
	},

	add : function( find, path, callback, root ){
		if( this.check(find) != undefined )	return false;
		if( !root )	root = document.getElementsByTagName("head")[0];
		if( !root )	return false;
		var e = document.createElement("script");
		e.type = "text/javascript";
		e.src = path;
		root.appendChild( e );

		this.task[path] = [ find, callback ];
		return true;
	},

	start : function(){
		if( this.timer ) return;
		if( this._global_name )
			this.timer = setInterval( "__global_objs.get('"+this._global_name+"').prototype._instance["+this.id+"].script_loading();", 200 );
		else
			this.timer = setInterval( "script_loader.prototype._instance["+this.id+"].script_loading();", 200 );
	},

	start_func: function(){ var that=this; return function(){ that.start(); } },

	stop :function(flag){
		if( this.timer )
			clearInterval( this.timer );
		//if( flag ){alert( "error!" );}
	},

	script_loading : function(){
		var failed = false;
		for( var key in this.task ){
			var ret = this.check( this.task[key][0] );

			if( ret ){
				if( this.task[key][1] ){
					try{
						this.task[key][1](key);
					}
					catch(e){
						this.stop(true);
						failed = true;
					}
				}
				delete this.task[key];
			}
			else if( ret == false ){
				this.stop(true);
				failed = true;
			}
			else { //undefined
				if( this.counter > 100 )
					this.stop(true);
				failed = true;
				this.counter += 1;
			}
		}
		if( !failed ){
			this.stop();
			if( this.script_onload() ){
				try{ this.script_onload(); } //エラーが分かんなくなるか?
				catch(e){ }
			}
		}
	}
}


//******** download
//func = function(xmlhttp)
//elementは、downloadingのclassが追加される。
function download_xml( url, func, element ){
	var xmlhttp;
	if( window.ActiveXObject ){
		try {
			xmlhttp = new ActiveXObject("Msxml2.XMLHTTP");
		}
		catch(e){
			xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
		}
	}
	else if( window.XMLHttpRequest ){
		xmlhttp = new XMLHttpRequest();
	}

      if (xmlhttp) {
        class_change( element, "downloading", true );
        xmlhttp.open('GET', url, true);
        xmlhttp.onreadystatechange = function(xmlhttp, func, element, url) {
           if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
              message("download done. url = " + url, 1);
              func( xmlhttp );
              class_change( element, "downloading", false );
           }
      }.preset_args(xmlhttp, func, element, url);

        message("download start... url = " + url, 1);
        xmlhttp.send(null);
      }
}



//返値はdocumentだが、HTMLとして解釈されていることは期待してはいけない。
function parse_from_string( text, rm_LF ){
	if( rm_LF ){ text = remove_LF(text); }
	var doc;
	if( window.ActiveXObject ){
		doc = new ActiveXObject("Microsoft.XMLDOM");
		//Msxml2.DOMDocument, Msxml2.DOMDocument.3.0
		doc.async="false";
		doc.loadXML(text);
	}
	else if( window.XMLHttpRequest ){
		doc = (new DOMParser()).parseFromString(text, "text/xml");
	}
	if( !doc.getElementById ){
		//doc.getElementById = getElementsByTagAttrValue.preset_args( doc, "*", "id" );
	}
	return doc;
}


//
function get_processor( procs, name, url, post_func ){
        message("load_processor("+name+"): start", 1);

	procs[name] = undefined;
        var func = function(name, procs, post_func, xmlhttp){
          var p = new XSLTProcessor();
          var nodes = parse_from_string( xmlhttp.responseText );
          p.importStylesheet(nodes);
          message("load_processor(" + name + "): done.", 1);
          procs[name] = p;
          if( post_func ){
            post_func(name, p);
          }
	}
	download_xml( url, func.preset_args(name, procs, post_func) );
}


//******** timer
//呼びだしタイミングの累積回数と、誤差も採れるタイマー。
//負荷は高い。殆どの場合においてoverkill。単なる趣味。
//deltaは許可する誤差。最低でも10は有った方が良い。例え0にしても、保証されるわけではない。努力目標。
function Timer( delta ){
	this.code = __global_objs.set(this);
	if( delta != undefined ) this.delta = delta;
}
Timer.prototype = {
	timer: undefined,
	code: undefined,
	events: {},
	delta: 100,
	sub_timers: {},

	get: function( name ){
		if( this.events.hasOwnProperty(name) )
			return this.events[name];
		return this.sub_timers[name];
	},
	remove: function( name ){
		var ret;
		if( this.events.hasOwnProperty(name) ){
			ret = this.events[name];
			delete this.events[name];
		}
		else {
			ret = this.sub_timers[name];
			delete this.sub_timers[name];
		}
		return ret;
	},
	addTimerEvent: function( name, f, ms, repeat, paranoid, delta ){
		var e;
		if( (e=this.get(name)) ){
			e.f = f;
			if( repeat ) e.interval = ms;
			if( delta != undefined ) e.delta = delta;
			if( paranoid ){
				if( !e.paranoid ) e.latest_call = new Date();//Date.now();
				e.paranoid = true;
			}
			else	e.paranoid = false;
			return e;
		}
		e = { func:f, name:name, period:ms, count:0, delta:this.delta, paranoid:paranoid, retval:undefined };
		if( delta != undefined ) e.delta = delta;
		if( repeat ) e.interval = ms;
		if( paranoid ) e.latest_call = new Date();//Date.now();
		if( !this.timer ){
			this.events[name] = e;
			this.latest_call = new Date(); //Date.now();
			this.timer = setInterval( "__global_objs.get('"+this.code+"').run("+ms+");", ms );
			this.current_interval = ms;
		}
		else{
			this.sub_timers[name] = e;
			e.sub_timer = setInterval( "var self = __global_objs.get('"+this.code+"'); self.sub_run(self.sub_timers['"+name+"']);", ms );
		}
		return e;
	},
	sub_run: function( e ){
		e.count += 1;
		if( e.end_request || !e.interval ){
			message( "end rq", 0 );
			clearInterval( e.sub_timer );
			e.end_time = new Date();//Date.now();
			e.sub_timer = undefined;
		}
		var now = new Date(); //Date.now();
		e.func(e, e.period - (now - e.latest_call));
		e.latest_call = now;
		if( !this.timer ){
			this.addTimerEvent( 0, function(){}, "dummy", false );
		}
	},
	run: function( interval ){
		var now = new Date(); //Date.now();
		ms = (now - this.latest_call);
		this.latest_call = now;

		//サブタイマーの組み込み
		var min = Number.POSITIVE_INFINITY;
		for( var k in this.sub_timers ){
			var s = this.sub_timers[k];
			message("sub "+s.name, 0 );
			if( s.end_time ){
				delete this.sub_timers[s.name];
				if( s.interval ){
					this.events[s.name] = s;
					s.period = s.interval - (now - s.end_time) + ms;
				}
			}
			else{
				s.end_request = true;
				if( s.interval && min > s.interval ){
					min = s.interval;
					min_ev = s;
				}
			}
		}

		var timeouts = [];
		var min_ev;
		for( var k in this.events ){
			var e = this.events[k];
			message("events "+e.name, 0 );
			e.period -= ms;
			if( e.period <= e.delta ){
				timeouts.push( e );
				if( e.interval ){
					do {
						e.count += 1;
						e.period += e.interval;
					} while( e.period <= 0 );
				}
				else		e.period = undefined;
			}
			if( e.period && min > e.period + e.delta ){
				min = e.period + e.delta;
				min_ev = e;
			}
		}

		//タイマーの再設定
		if( min >= Number.POSITIVE_INFINITY ){
			message( "stop", 1 );
			if( this.timer ) clearInterval(this.timer);
			this.timer = undefined;
		}
		else if( min_ev.period+min_ev.delta <= interval || min_ev.period-min_ev.delta >= interval ){
			if( this.timer ) clearInterval(this.timer);
			var now2 = new Date(); //Date.now();
			var sleep_time = min_ev.period - (now2 - now);
	 		this.timer = setInterval( "__global_objs.get('"+this.code+"').run("+sleep_time+")", sleep_time );
			//message( "restart "+interval+"->"+ sleep_time + " sleep time="+ms+" "+min_ev.name+": period("+min_ev.period+"), delta("+min_ev.delta+")/ interval("+interval+")", 1 );
		}
		else{
			//message( "continue. min_ev.period("+min_ev.period+"), min_ev.delta("+min_ev.delta+"), interval("+interval+")", 1 );
		}

		//実行。速度を考えるとこの方が良いと思うのだが。
		//登録したfunctionからのinterval切り替えは、直ぐに反映されない。
		//periodをundefinedにする事で、消去は直ぐに出来る。
		for( var i=0; i<timeouts.length; i++ ){
			var diff = undefined;
			if( timeouts[i].paranoid ){
				var now2 = new Date(); //Date.now();
				diff = timeouts[i].interval - (now2 - timeouts[i].latest_call);
			}
			timeouts[i].retval = timeouts[i].func(timeouts[i], diff);
			if( !now && timeouts[i].paranoid ) timeouts[i].latest_call = new Date(); //Date.now();
			else if( timeouts[i].paranoid ) timeouts[i].latest_call = now2;
			if( !timeouts[i].period )
				this.remove(timeouts[i].name);
		}
	}
}



//******** 
function comment_visualization( root ){
	foreachElementsByNodeType( root, browser.Node.COMMENT_NODE, function(elm){
		var span = createElement_defaultNS("span");
		class_change( span, "hidden_comment", true );
		addEventListener( span, "click", comment_onclick, false );
		span.appendChild(document.createTextNode(elm.data));
		elm.parentNode.replaceChild( span, elm );
	});

	function comment_onclick(e){
		class_change( e.target, "visible", !class_check(e.target,"visible") );
	}
}


//******** 
//フォーマットの変更に対応できるようにする...?
//異常なデータを読んだときに、処理時間がかかりすぎる問題。
//他に逆読みする意味はあるのか?など。
function TreeDictParser(){
}
TreeDictParser.prototype = {
	re_end: /^(.*(?:[\s\{\},]|^))'(.*)'\s*:\s*\{\s*(.*),*\s*$/, 
	re_begin: /^(.*)\}.*/,
	re_test: /',?\s*$/,  	//速度のため?
	re_pair: /^(.*(?:[\s\{\},]|^))'(.*)'\s*:\s*'(.*)',*\s*$/, 

	//内部では使わないが、定義はある状態。
	encode: function(s){ return s.replace(/'/g, "\\'").replace(/\n/g, "\\n").replace(/\r/g, "\\r"); },
	decode: function(s){ return s.replace(/\\r/g, "\r").replace(/\\n/g, "\n").replace(/\\'/g, "'"); },

	parse: function(s){
		var array = {};
		this.parse_tree( "'r':" + s, array );
		if( array.r ) return array.r;
		return {};
	},

	parse_tree: function( s, array, parent ){
		s = this.parse_pairs( s, array );
		var block = s.match(this.re_end);
		if( !block ) return "";		//sを返すとループするかも。
		if( block[3].length > 0 ){	//処理すべき内容が残っているのに停止した。
			var m = s.match(this.re_begin);
			if( !m ) return "";
			s = this.parse_tree( m[1], {}, array );
			return this.parse_tree( s, array, parent );
		}
		else{
			parent[this.decode(block[2])] = array;
			return block[1];
		}
	},

	parse_pairs: function( s, array ){
		var pair;
		while( s.length > 0 ){
			if( s.search(this.re_test) != -1 )
				pair = s.match(this.re_pair);
			else
				break;
			if( !pair ) break;
			array[this.decode(pair[2])] = this.decode(pair[3]);
			s = pair[1];
		}
		return s;
	}
}


//********
//クッキーに格納する、/区切りのツリー構造を持った永続化辞書。
//大きすぎる場合は分割してデータを保存する。
function CookieBox( name, path, date, doc ){
	this.name = name;
	this.path = path;
	if( date ) this.date = date;
	if( doc ) this.doc = doc;
	else this.doc = document;

	this.parser = new TreeDictParser();

	var tmp = [];
	var cookies = this.get_cookies( new RegExp(this.encode(name)) );
	var name_re = new RegExp( name+"([0-9])" );
	for( var k in cookies ){
		var m = this.decode(k).match( name_re );
		if( m && m.length >= 2 )
			tmp[Number(m[1])] = cookies[k];
	}
	var s = "";
	for( var i=0; i<tmp.length; i++ ) s += tmp[i];

	try{
		this.data = this.string_to_dict( this.decode(s) );
	}
	catch(e){
		alert( "cookie error." );
		this.eat_cookies();
		this.data = {};
	}
}
CookieBox.prototype = {
	name: undefined,
	data: undefined,
	path: undefined,
	date: new Date(),
	parser: undefined,

	//*****************************
	//外部向け...クロージャっぽい書き方?
	access: function( name, func ){
		var re = /^(\/|)((?:\\\\|\\.|[^\\\/])*)\/?(.*)/;
		var p = name;
		var m = name.match(re);
		if( !m ) return undefined;
		if( !m[1] ){
			if( m[2] != this.name ) return undefined;
			p = m[3];
		}
		var o = this.data;
		while( o && p ){
			m = p.match(re);
			if( m[2] ) o = o[m[2]];
			p = m[3];
		}
		if( o ) return func(o);
	},

	set: function( name, value ){
		var m = name.match(/(.*(?:[^\\]|^)(?:\\\\)*\/)(.*)/);
		if( m )
			return this.access( m[1], function(o){ o[m[2]] = value; return true;} );
		else
			return false;
	},

	get: function( name ){
		return this.access( name, function(o){ return o; } );
	},

	eat: function( name ){
		var m = name.match(/(.*(?:[^\\]|^)(?:\\\\)*\/)(.*)/);
		if( m )
			return this.access( m[1], function(o){ return delete o[m[2]]; } );
		else
			return false;
	},

	keys: function(o, p, array){
		if(!o) o = this.data;
		if(p==undefined) p = "";
		if(!array) array = [];
		for( var k in o ){
			var c = p + "/" + k;
			if( typeof o[k] == "string" ) array.push( c );
			else{
				array.push( c );
				this.keys( o[k], c, array );
			}
		}
		return array;
	},

	find: function(re){
		var keys = this.keys();
		var ret = [];
		for( var i=0; i<keys.length; i++ )
			if( keys[i].search(re) != -1 )
				ret.push( keys[i] );
		return ret;
	},

	eat_cookies: function(){
		this.remove_cookies( this.encode(this.name) + "[0-9]" );
	},


	//*****************************
	//内部向け
	encode: function(s){
		return encodeURIComponent(s.replace(/'/g, "\\'"));
	},

	decode: function( s ){
		return decodeURIComponent( s ).replace(/\\'/g, "'");
	},

	string_to_dict: function( s ){
		if( !s ) return {};
		return this.parser.parse(s);
/*
		eval( "var d = " + s );
		decode_data.apply(this,[d]);
		function decode_data(d){
			for( var k in d ){
				if( (typeof d[k]) == "string" || d[k] instanceof String )
					d[this.decode(k)] = this.decode(d[k]);
				else
					decode_data.apply(this,[d[k]]);
			}
		}
*/
	},

	dict_to_string: function( d ){
		if( !d ) return "";
		var s = "{ ";
		for( var k in d ){
			if( !d[k] ) continue;
			else if( (typeof d[k]) == "string" || d[k] instanceof String
				|| d[k] instanceof MLString )
				s += "'"+this.parser.encode(k)+"':'"+this.parser.encode(d[k])+"',";
			else{
				s += "'"+this.parser.encode(k)+"':"+this.dict_to_string(d[k])+",";
			}
		}

		if( s.length > 0 )
			return s.substring(0,s.length-1) + "}";
		else
			return "{}";
	},

	//*****************************
	update: function(){
		var s = this.encode(this.dict_to_string(this.data));
		this.remove_cookies( this.encode(this.name)+"[0-9]" );
		var tmp = [];
		for( var o=0; o<s.length; o+=4000 )
			tmp.push( s.substring(o, o+4000) );
		for( var i=0; i<tmp.length; i++ ){
			var s = this.encode(this.name+i) + "=" + tmp[i] + "; ";
			s += "expires="+ this.date.toUTCString() +"; ";
			if( this.path )
				s += "path="+this.path+"; ";
			this.doc.cookie = s;
		}
	},

	//*****************************
	//低レベルの操作
	//name_reのcookieを見つけて、辞書配列で返す。encode、decodeは行わない
	get_cookies: function( name_re ){
		var chips = this.doc.cookie.split(";");
		var re = new RegExp(/^\s*(.*)\s*=\s*(.*)\s*/);
		var tmp = {};
		for( var i=0; i<chips.length; i++ ){
			var chip = chips[i].match( re );
			if( chip && chip.length >= 3 )
				if( chip[1].search( name_re ) != -1 )
					tmp[chip[1]] = chip[2];
		}
		return tmp;
	},

	remove_cookies: function( name_re ){
		var chips = this.get_cookies(name_re);
		for( var k in chips ){
			var s = k + "=x; ";
			if( this.path ) s += "path="+this.path+"; ";
			s += "expires=Tue, 1-Jan-1980 00:00:00; ";
			this.doc.cookie = s;
			if( this.path ) //ie???
				this.doc.cookie = k + "=x; expires=Tue, 1-Jan-1980 00:00:00; ";
			message( "I eat cookie. " + k, 1 );
		}
	}
}



//******** 
/*
MLString.prototype.lang = ["en", "ja"];
	s1 = new MLString( "a" );
or
	s2 = new MLString( { en:"b", ja:"は" } );

s1.toString()	//etc
s2.toString()	//b
s2.lang = "ja";
s2.toString()	//は


na_lib.MLString.prototype.lang = ["ja", "en"];
var s = new na_lib.MLString("test", "en");
console.log(s);		//test
var db = new na_lib.TranslateDB();
na_lib.MLString.prototype.translator = db;
db.add( "en", "ja", { "test":"試験" } );
console.log(s);		//試験	//この場合、support_langsが適当でない...まあいいか...

*/
function MLString( s, l, translator ){
        this.support_langs = [];
	if( translator ) this.translator = translator;

	if( s == undefined ); //?
	else if( (s instanceof String) || (typeof s) == "string" )
		this.ml_set(s, l);
	else if( s instanceof MLString ){
		for( var i=0; i<s.support_langs.length; i++ )
			this.ml_set( s[s.support_langs[i]], s.support_langs[i] );
		if( s.hasOwnProperty && s.hasOwnProperty("lang") ) this.lang = s.lang;
	}
	else if( s instanceof Object )
		for( var k in s )
			this.ml_set( s[k], k );
	else
		this.ml_set(s.toString(), l); //この振る舞いは正しい?
}
MLString.prototype = {
	lang: ["ja", "en"],		//default language
	support_langs: undefined,	//オブジェクト毎に持つ
	translator: undefined,

	ml_get: function( l ){
		if( !l ) l = this.lang[0];
		if( this[l] != undefined )
			return undefined;
		else
			return this[l];
	},

	ml_set: function( s, l, no_overwrite ){
		if( !l ) l = this.lang[0];
		if( this[l] != undefined ){ if( no_overwrite ) return false; else this[l] = s; }
		else{
			this[l] = s;
			this.support_langs.push(l);
		}
		return true;
	},

	ml_concat: function( mls ){
		var n = {};
		if( mls instanceof MLString ){
			for( var i=0; i<mls.support_langs.length; i++ ){
				var l = mls.support_langs[i];
				if( this[l] ) n[l] = this[l].concat(mls[l]);
				else n[l] = mls[l];
			}
			for( var i=0; i<this.support_langs.length; i++ ){
				var l = this.support_langs[i];
				if( !(l in n) )
					n[l] = this[l];
			}
		}
		return new MLString( n );
	},

	ml_merge: function( mls, overwrite ){
		var count = 0;
		for( var i=0; i<mls.support_langs.length; i++ ){
			var l = mls.support_langs[i];
			if( this.ml_set(mls[l], l, !overwrite) ) count += 1;
		}
		return count;
	}
}

function setupMLString(){
var funcs = ["charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "match", "replace", "search", "slice", "split", "substring", "toLowerCase", "toSource", "toString", "valueOf"];
var sample = new String("");
for( var i=0; i<funcs.length; i++ ){
	var k = funcs[i];
	if( (sample[k] instanceof Function) || (typeof sample[k]) == "function" ){
		function create_redirect_func(k){
			return function(){
				var l = this.lang[0];
				if( this[l] != undefined )
					return this[l][k].apply(this[l], arguments);
				else if( this.translator ){
					var mls = this.translator.translate(this, undefined, true);
					if( mls[l] != undefined )
						return mls[l][k].apply(mls[l], arguments);
				}
				else	var mls = this;

				for( var i=1; i<mls.lang.length; i++ ){
					l = mls.lang[i];
					if( mls[l] != undefined )
						return mls[l][k].apply(mls[l], arguments);
				}
				return undefined; //throwの方が良いのか...?
			}
		}
		MLString.prototype[k] = create_redirect_func(k);
	}
	else{
		//message( k + " is notfound?" + sample[k], 2 );
	}
}
}
setupMLString();



//翻訳データベース。相変わらずoverkill。
function TranslateDB(){
	this.data = {};
}
TranslateDB.prototype = {
	//ある文字列sを言語lであるとして、MLStringを返す。
	//deepオプションは変更点が無くなるまで繰り返し翻訳を繰り返すことを意味する。
	translate: function( s, l, deep ){
		if( s instanceof MLString ) return this.translate_ml( s, deep );
		if( s instanceof Object ) return this.translate_ml( new MLString(s,l), deep );
		if( !l ) l = MLString.prototype.lang[0];
		var ml = this.find_strings( s, l );
		if( deep && ml.support_langs.length > 0 )
			return this.translate_ml( ml, true );
		else
			return ml;
	},

	//MLStringを受け付けて、this.dbから翻訳可能な文字列をMLString形式で返す。
	//追加ではなく新たに作る。翻訳後文字列が常に優先。ぶつかる場合はsupport_langsに入っている順。
	translate_ml: function( from, deep ){
		var n = new MLString(from);
		var count = 0;
		for( var i=0; i<from.support_langs.length; i++ ){
			var l = from.support_langs[i];
			var ml = this.find_strings( from[l], l );
			count += n.ml_merge(ml, false);    //ml_mergeはno overwriteで呼び出される
		}
		if( deep && count > 0 ) //ので、真に新しい文が無くなった時点でcount == 0になる。
			return this.translate_ml( n, true );
		else
			return n;
	},

	//f語の文sの訳をMLStringで全て返す。検索語自体も含む。
	find_strings: function( s, l ){
		var n = new MLString( s, l );
		if( !this.data[l] ) return n;
		for( var tl in this.data[l] )
			if( this.data[l][tl][s] )
				n.ml_set( this.data[l][tl][s], tl );
		return n;
	},

	//データベースを追加する。
	//from言語をto言語に翻訳する(from言語のkeyとto言語のvalueを持つ)データを受け取る。
	add: function( from, to, dict, no_overwrite, no_create_reverse ){
		if( !this.data[from] ) this.data[from] = {};
		if( !this.data[from][to] ) this.data[from][to] = {};
		if( !no_create_reverse ){
			if( !this.data[to] ) this.data[to] = {};
			if( !this.data[to][from] ) this.data[to][from] = {};
		}
		for( var k in dict ){
			if( !this.data[from][to][k] || !no_overwrite )
				this.data[from][to][k] = dict[k];
			if( !no_create_reverse &&
			    ( !this.data[to][from][dict[k]] || !no_overwrite ) )
				this.data[to][from][dict[k]] = k;
		}
	}
}



} /*end of with(na_lib)*/

//***********exports
//for name in $(grep "^function" $file |sed -e "s/function //" -e "s/(.*$//" ); do echo "'$name'," ; done
	function exports( ns, array ){
		for( var i=0; i<array.length; i++ )
			eval( ns+"."+array[i]+" = "+array[i]+";" );
	}

	exports( "na_lib", [
	"_namespaceURI",		//var
	"_msg_element",			//var
	"_message_num",			//var
	"_debug_level",			//var

	"init_message_element",	/*ie*/
	"message_clear",
	"debug_level",
	"message",

	"createElement_defaultNS",
	"createElement",
	"addEventListener",
	"insertAfter",
	"insertFirstChild",

	"foreachElementsByTagName",
	"foreachElementsByNodeType",

	"getElementsByTagAttrValue",
	"getElementByTagAttrValue",
	"getElementsByTextNodeValue",
	"getElementsByClasses",
	"getElementsByNodeType",
	"getSequentialElements",
	"get_text",
	"set_text",
	"remove_LF",
	"setupChildrenByEventMap",
	"attach_input",

	"TreeDictParser",
	"CookieBox",

	"class_check",
	"class_change",

	"create_tab",
	"create_iframe_tab",
	"get_tabset",
	"tabset_Constructor",

	"script_loader",

	"parse_from_string",
	"download_xml",
	"get_processor",

	"Timer",

	"comment_visualization",
	"browser",

	"MLString",
	"TranslateDB"
	] );

} /**********************na_lib end*/
//</pre>