/*!
 * jQuery JavaScript Library v1.3.2
 * http://jquery.com/
 *
 * Copyright (c) 2009 John Resig
 * Dual licensed under the MIT and GPL licenses.
 * http://docs.jquery.com/License
 *
 * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009)
 * Revision: 6246
 */
(function(){

var 
	// Will speed up references to window, and allows munging its name.
	window = this,
	// Will speed up references to undefined, and allows munging its name.
	undefined,
	// Map over jQuery in case of overwrite
	_jQuery = window.jQuery,
	// Map over the $ in case of overwrite
	_$ = window.$,

	jQuery = window.jQuery = window.$ = function( selector, context ) {
		// The jQuery object is actually just the init constructor 'enhanced'
		return new jQuery.fn.init( selector, context );
	},

	// A simple way to check for HTML strings or ID strings
	// (both of which we optimize for)
	quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,
	// Is it a simple selector
	isSimple = /^.[^:#\[\.,]*$/;

jQuery.fn = jQuery.prototype = {
	init: function( selector, context ) {
		// Make sure that a selection was provided
		selector = selector || document;

		// Handle $(DOMElement)
		if ( selector.nodeType ) {
			this[0] = selector;
			this.length = 1;
			this.context = selector;
			return this;
		}
		// Handle HTML strings
		if ( typeof selector === "string" ) {
			// Are we dealing with HTML string or an ID?
			var match = quickExpr.exec( selector );

			// Verify a match, and that no context was specified for #id
			if ( match && (match[1] || !context) ) {

				// HANDLE: $(html) -> $(array)
				if ( match[1] )
					selector = jQuery.clean( [ match[1] ], context );

				// HANDLE: $("#id")
				else {
					var elem = document.getElementById( match[3] );

					// Handle the case where IE and Opera return items
					// by name instead of ID
					if ( elem && elem.id != match[3] )
						return jQuery().find( selector );

					// Otherwise, we inject the element directly into the jQuery object
					var ret = jQuery( elem || [] );
					ret.context = document;
					ret.selector = selector;
					return ret;
				}

			// HANDLE: $(expr, [context])
			// (which is just equivalent to: $(content).find(expr)
			} else
				return jQuery( context ).find( selector );

		// HANDLE: $(function)
		// Shortcut for document ready
		} else if ( jQuery.isFunction( selector ) )
			return jQuery( document ).ready( selector );

		// Make sure that old selector state is passed along
		if ( selector.selector && selector.context ) {
			this.selector = selector.selector;
			this.context = selector.context;
		}

		return this.setArray(jQuery.isArray( selector ) ?
			selector :
			jQuery.makeArray(selector));
	},

	// Start with an empty selector
	selector: "",

	// The current version of jQuery being used
	jquery: "1.3.2",

	// The number of elements contained in the matched element set
	size: function() {
		return this.length;
	},

	// Get the Nth element in the matched element set OR
	// Get the whole matched element set as a clean array
	get: function( num ) {
		return num === undefined ?

			// Return a 'clean' array
			Array.prototype.slice.call( this ) :

			// Return just the object
			this[ num ];
	},

	// Take an array of elements and push it onto the stack
	// (returning the new matched element set)
	pushStack: function( elems, name, selector ) {
		// Build a new jQuery matched element set
		var ret = jQuery( elems );

		// Add the old object onto the stack (as a reference)
		ret.prevObject = this;

		ret.context = this.context;

		if ( name === "find" )
			ret.selector = this.selector + (this.selector ? " " : "") + selector;
		else if ( name )
			ret.selector = this.selector + "." + name + "(" + selector + ")";

		// Return the newly-formed element set
		return ret;
	},

	// Force the current matched set of elements to become
	// the specified array of elements (destroying the stack in the process)
	// You should use pushStack() in order to do this, but maintain the stack
	setArray: function( elems ) {
		// Resetting the length to 0, then using the native Array push
		// is a super-fast way to populate an object with array-like properties
		this.length = 0;
		Array.prototype.push.apply( this, elems );

		return this;
	},

	// Execute a callback for every element in the matched set.
	// (You can seed the arguments with an array of args, but this is
	// only used internally.)
	each: function( callback, args ) {
		return jQuery.each( this, callback, args );
	},

	// Determine the position of an element within
	// the matched set of elements
	index: function( elem ) {
		// Locate the position of the desired element
		return jQuery.inArray(
			// If it receives a jQuery object, the first element is used
			elem && elem.jquery ? elem[0] : elem
		, this );
	},

	attr: function( name, value, type ) {
		var options = name;

		// Look for the case where we're accessing a style value
		if ( typeof name === "string" )
			if ( value === undefined )
				return this[0] && jQuery[ type || "attr" ]( this[0], name );

			else {
				options = {};
				options[ name ] = value;
			}

		// Check to see if we're setting style values
		return this.each(function(i){
			// Set all the styles
			for ( name in options )
				jQuery.attr(
					type ?
						this.style :
						this,
					name, jQuery.prop( this, options[ name ], type, i, name )
				);
		});
	},

	css: function( key, value ) {
		// ignore negative width and height values
		if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 )
			value = undefined;
		return this.attr( key, value, "curCSS" );
	},

	text: function( text ) {
		if ( typeof text !== "object" && text != null )
			return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );

		var ret = "";

		jQuery.each( text || this, function(){
			jQuery.each( this.childNodes, function(){
				if ( this.nodeType != 8 )
					ret += this.nodeType != 1 ?
						this.nodeValue :
						jQuery.fn.text( [ this ] );
			});
		});

		return ret;
	},

	wrapAll: function( html ) {
		if ( this[0] ) {
			// The elements to wrap the target around
			var wrap = jQuery( html, this[0].ownerDocument ).clone();

			if ( this[0].parentNode )
				wrap.insertBefore( this[0] );

			wrap.map(function(){
				var elem = this;

				while ( elem.firstChild )
					elem = elem.firstChild;

				return elem;
			}).append(this);
		}

		return this;
	},

	wrapInner: function( html ) {
		return this.each(function(){
			jQuery( this ).contents().wrapAll( html );
		});
	},

	wrap: function( html ) {
		return this.each(function(){
			jQuery( this ).wrapAll( html );
		});
	},

	append: function() {
		return this.domManip(arguments, true, function(elem){
			if (this.nodeType == 1)
				this.appendChild( elem );
		});
	},

	prepend: function() {
		return this.domManip(arguments, true, function(elem){
			if (this.nodeType == 1)
				this.insertBefore( elem, this.firstChild );
		});
	},

	before: function() {
		return this.domManip(arguments, false, function(elem){
			this.parentNode.insertBefore( elem, this );
		});
	},

	after: function() {
		return this.domManip(arguments, false, function(elem){
			this.parentNode.insertBefore( elem, this.nextSibling );
		});
	},

	end: function() {
		return this.prevObject || jQuery( [] );
	},

	// For internal use only.
	// Behaves like an Array's method, not like a jQuery method.
	push: [].push,
	sort: [].sort,
	splice: [].splice,

	find: function( selector ) {
		if ( this.length === 1 ) {
			var ret = this.pushStack( [], "find", selector );
			ret.length = 0;
			jQuery.find( selector, this[0], ret );
			return ret;
		} else {
			return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){
				return jQuery.find( selector, elem );
			})), "find", selector );
		}
	},

	clone: function( events ) {
		// Do the clone
		var ret = this.map(function(){
			if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) {
				// IE copies events bound via attachEvent when
				// using cloneNode. Calling detachEvent on the
				// clone will also remove the events from the orignal
				// In order to get around this, we use innerHTML.
				// Unfortunately, this means some modifications to
				// attributes in IE that are actually only stored
				// as properties will not be copied (such as the
				// the name attribute on an input).
				var html = this.outerHTML;
				if ( !html ) {
					var div = this.ownerDocument.createElement("div");
					div.appendChild( this.cloneNode(true) );
					html = div.innerHTML;
				}

				return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0];
			} else
				return this.cloneNode(true);
		});

		// Copy the events from the original to the clone
		if ( events === true ) {
			var orig = this.find("*").andSelf(), i = 0;

			ret.find("*").andSelf().each(function(){
				if ( this.nodeName !== orig[i].nodeName )
					return;

				var events = jQuery.data( orig[i], "events" );

				for ( var type in events ) {
					for ( var handler in events[ type ] ) {
						jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data );
					}
				}

				i++;
			});
		}

		// Return the cloned set
		return ret;
	},

	filter: function( selector ) {
		return this.pushStack(
			jQuery.isFunction( selector ) &&
			jQuery.grep(this, function(elem, i){
				return selector.call( elem, i );
			}) ||

			jQuery.multiFilter( selector, jQuery.grep(this, function(elem){
				return elem.nodeType === 1;
			}) ), "filter", selector );
	},

	closest: function( selector ) {
		var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null,
			closer = 0;

		return this.map(function(){
			var cur = this;
			while ( cur && cur.ownerDocument ) {
				if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) {
					jQuery.data(cur, "closest", closer);
					return cur;
				}
				cur = cur.parentNode;
				closer++;
			}
		});
	},

	not: function( selector ) {
		if ( typeof selector === "string" )
			// test special case where just one selector is passed in
			if ( isSimple.test( selector ) )
				return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector );
			else
				selector = jQuery.multiFilter( selector, this );

		var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
		return this.filter(function() {
			return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector;
		});
	},

	add: function( selector ) {
		return this.pushStack( jQuery.unique( jQuery.merge(
			this.get(),
			typeof selector === "string" ?
				jQuery( selector ) :
				jQuery.makeArray( selector )
		)));
	},

	is: function( selector ) {
		return !!selector && jQuery.multiFilter( selector, this ).length > 0;
	},

	hasClass: function( selector ) {
		return !!selector && this.is( "." + selector );
	},

	val: function( value ) {
		if ( value === undefined ) {			
			var elem = this[0];

			if ( elem ) {
				if( jQuery.nodeName( elem, 'option' ) )
					return (elem.attributes.value || {}).specified ? elem.value : elem.text;
				
				// We need to handle select boxes special
				if ( jQuery.nodeName( elem, "select" ) ) {
					var index = elem.selectedIndex,
						values = [],
						options = elem.options,
						one = elem.type == "select-one";

					// Nothing was selected
					if ( index < 0 )
						return null;

					// Loop through all the selected options
					for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
						var option = options[ i ];

						if ( option.selected ) {
							// Get the specifc value for the option
							value = jQuery(option).val();

							// We don't need an array for one selects
							if ( one )
								return value;

							// Multi-Selects return an array
							values.push( value );
						}
					}

					return values;				
				}

				// Everything else, we just grab the value
				return (elem.value || "").replace(/\r/g, "");

			}

			return undefined;
		}

		if ( typeof value === "number" )
			value += '';

		return this.each(function(){
			if ( this.nodeType != 1 )
				return;

			if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) )
				this.checked = (jQuery.inArray(this.value, value) >= 0 ||
					jQuery.inArray(this.name, value) >= 0);

			else if ( jQuery.nodeName( this, "select" ) ) {
				var values = jQuery.makeArray(value);

				jQuery( "option", this ).each(function(){
					this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
						jQuery.inArray( this.text, values ) >= 0);
				});

				if ( !values.length )
					this.selectedIndex = -1;

			} else
				this.value = value;
		});
	},

	html: function( value ) {
		return value === undefined ?
			(this[0] ?
				this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") :
				null) :
			this.empty().append( value );
	},

	replaceWith: function( value ) {
		return this.after( value ).remove();
	},

	eq: function( i ) {
		return this.slice( i, +i + 1 );
	},

	slice: function() {
		return this.pushStack( Array.prototype.slice.apply( this, arguments ),
			"slice", Array.prototype.slice.call(arguments).join(",") );
	},

	map: function( callback ) {
		return this.pushStack( jQuery.map(this, function(elem, i){
			return callback.call( elem, i, elem );
		}));
	},

	andSelf: function() {
		return this.add( this.prevObject );
	},

	domManip: function( args, table, callback ) {
		if ( this[0] ) {
			var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(),
				scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ),
				first = fragment.firstChild;

			if ( first )
				for ( var i = 0, l = this.length; i < l; i++ )
					callback.call( root(this[i], first), this.length > 1 || i > 0 ?
							fragment.cloneNode(true) : fragment );
		
			if ( scripts )
				jQuery.each( scripts, evalScript );
		}

		return this;
		
		function root( elem, cur ) {
			return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ?
				(elem.getElementsByTagName("tbody")[0] ||
				elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
				elem;
		}
	}
};

// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;

function evalScript( i, elem ) {
	if ( elem.src )
		jQuery.ajax({
			url: elem.src,
			async: false,
			dataType: "script"
		});

	else
		jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );

	if ( elem.parentNode )
		elem.parentNode.removeChild( elem );
}

function now(){
	return +new Date;
}

jQuery.extend = jQuery.fn.extend = function() {
	// copy reference to target object
	var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;

	// Handle a deep copy situation
	if ( typeof target === "boolean" ) {
		deep = target;
		target = arguments[1] || {};
		// skip the boolean and the target
		i = 2;
	}

	// Handle case when target is a string or something (possible in deep copy)
	if ( typeof target !== "object" && !jQuery.isFunction(target) )
		target = {};

	// extend jQuery itself if only one argument is passed
	if ( length == i ) {
		target = this;
		--i;
	}

	for ( ; i < length; i++ )
		// Only deal with non-null/undefined values
		if ( (options = arguments[ i ]) != null )
			// Extend the base object
			for ( var name in options ) {
				var src = target[ name ], copy = options[ name ];

				// Prevent never-ending loop
				if ( target === copy )
					continue;

				// Recurse if we're merging object values
				if ( deep && copy && typeof copy === "object" && !copy.nodeType )
					target[ name ] = jQuery.extend( deep, 
						// Never move original objects, clone them
						src || ( copy.length != null ? [ ] : { } )
					, copy );

				// Don't bring in undefined values
				else if ( copy !== undefined )
					target[ name ] = copy;

			}

	// Return the modified object
	return target;
};

// exclude the following css properties to add px
var	exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i,
	// cache defaultView
	defaultView = document.defaultView || {},
	toString = Object.prototype.toString;

jQuery.extend({
	noConflict: function( deep ) {
		window.$ = _$;

		if ( deep )
			window.jQuery = _jQuery;

		return jQuery;
	},

	// See test/unit/core.js for details concerning isFunction.
	// Since version 1.3, DOM methods and functions like alert
	// aren't supported. They return false on IE (#2968).
	isFunction: function( obj ) {
		return toString.call(obj) === "[object Function]";
	},

	isArray: function( obj ) {
		return toString.call(obj) === "[object Array]";
	},

	// check if an element is in a (or is an) XML document
	isXMLDoc: function( elem ) {
		return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
			!!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument );
	},

	// Evalulates a script in a global context
	globalEval: function( data ) {
		if ( data && /\S/.test(data) ) {
			// Inspired by code by Andrea Giammarchi
			// http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
			var head = document.getElementsByTagName("head")[0] || document.documentElement,
				script = document.createElement("script");

			script.type = "text/javascript";
			if ( jQuery.support.scriptEval )
				script.appendChild( document.createTextNode( data ) );
			else
				script.text = data;

			// Use insertBefore instead of appendChild  to circumvent an IE6 bug.
			// This arises when a base node is used (#2709).
			head.insertBefore( script, head.firstChild );
			head.removeChild( script );
		}
	},

	nodeName: function( elem, name ) {
		return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
	},

	// args is for internal usage only
	each: function( object, callback, args ) {
		var name, i = 0, length = object.length;

		if ( args ) {
			if ( length === undefined ) {
				for ( name in object )
					if ( callback.apply( object[ name ], args ) === false )
						break;
			} else
				for ( ; i < length; )
					if ( callback.apply( object[ i++ ], args ) === false )
						break;

		// A special, fast, case for the most common use of each
		} else {
			if ( length === undefined ) {
				for ( name in object )
					if ( callback.call( object[ name ], name, object[ name ] ) === false )
						break;
			} else
				for ( var value = object[0];
					i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
		}

		return object;
	},

	prop: function( elem, value, type, i, name ) {
		// Handle executable functions
		if ( jQuery.isFunction( value ) )
			value = value.call( elem, i );

		// Handle passing in a number to a CSS property
		return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ?
			value + "px" :
			value;
	},

	className: {
		// internal only, use addClass("class")
		add: function( elem, classNames ) {
			jQuery.each((classNames || "").split(/\s+/), function(i, className){
				if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
					elem.className += (elem.className ? " " : "") + className;
			});
		},

		// internal only, use removeClass("class")
		remove: function( elem, classNames ) {
			if (elem.nodeType == 1)
				elem.className = classNames !== undefined ?
					jQuery.grep(elem.className.split(/\s+/), function(className){
						return !jQuery.className.has( classNames, className );
					}).join(" ") :
					"";
		},

		// internal only, use hasClass("class")
		has: function( elem, className ) {
			return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
		}
	},

	// A method for quickly swapping in/out CSS properties to get correct calculations
	swap: function( elem, options, callback ) {
		var old = {};
		// Remember the old values, and insert the new ones
		for ( var name in options ) {
			old[ name ] = elem.style[ name ];
			elem.style[ name ] = options[ name ];
		}

		callback.call( elem );

		// Revert the old values
		for ( var name in options )
			elem.style[ name ] = old[ name ];
	},

	css: function( elem, name, force, extra ) {
		if ( name == "width" || name == "height" ) {
			var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];

			function getWH() {
				val = name == "width" ? elem.offsetWidth : elem.offsetHeight;

				if ( extra === "border" )
					return;

				jQuery.each( which, function() {
					if ( !extra )
						val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
					if ( extra === "margin" )
						val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0;
					else
						val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
				});
			}

			if ( elem.offsetWidth !== 0 )
				getWH();
			else
				jQuery.swap( elem, props, getWH );

			return Math.max(0, Math.round(val));
		}

		return jQuery.curCSS( elem, name, force );
	},

	curCSS: function( elem, name, force ) {
		var ret, style = elem.style;

		// We need to handle opacity special in IE
		if ( name == "opacity" && !jQuery.support.opacity ) {
			ret = jQuery.attr( style, "opacity" );

			return ret == "" ?
				"1" :
				ret;
		}

		// Make sure we're using the right name for getting the float value
		if ( name.match( /float/i ) )
			name = styleFloat;

		if ( !force && style && style[ name ] )
			ret = style[ name ];

		else if ( defaultView.getComputedStyle ) {

			// Only "float" is needed here
			if ( name.match( /float/i ) )
				name = "float";

			name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();

			var computedStyle = defaultView.getComputedStyle( elem, null );

			if ( computedStyle )
				ret = computedStyle.getPropertyValue( name );

			// We should always get a number back from opacity
			if ( name == "opacity" && ret == "" )
				ret = "1";

		} else if ( elem.currentStyle ) {
			var camelCase = name.replace(/\-(\w)/g, function(all, letter){
				return letter.toUpperCase();
			});

			ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];

			// From the awesome hack by Dean Edwards
			// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291

			// If we're not dealing with a regular pixel number
			// but a number that has a weird ending, we need to convert it to pixels
			if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
				// Remember the original values
				var left = style.left, rsLeft = elem.runtimeStyle.left;

				// Put in the new values to get a computed value out
				elem.runtimeStyle.left = elem.currentStyle.left;
				style.left = ret || 0;
				ret = style.pixelLeft + "px";

				// Revert the changed values
				style.left = left;
				elem.runtimeStyle.left = rsLeft;
			}
		}

		return ret;
	},

	clean: function( elems, context, fragment ) {
		context = context || document;

		// !context.createElement fails in IE with an error but returns typeof 'object'
		if ( typeof context.createElement === "undefined" )
			context = context.ownerDocument || context[0] && context[0].ownerDocument || document;

		// If a single string is passed in and it's a single tag
		// just do a createElement and skip the rest
		if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) {
			var match = /^<(\w+)\s*\/?>$/.exec(elems[0]);
			if ( match )
				return [ context.createElement( match[1] ) ];
		}

		var ret = [], scripts = [], div = context.createElement("div");

		jQuery.each(elems, function(i, elem){
			if ( typeof elem === "number" )
				elem += '';

			if ( !elem )
				return;

			// Convert html string into DOM nodes
			if ( typeof elem === "string" ) {
				// Fix "XHTML"-style tags in all browsers
				elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
					return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
						all :
						front + "></" + tag + ">";
				});

				// Trim whitespace, otherwise indexOf won't work as expected
				var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase();

				var wrap =
					// option or optgroup
					!tags.indexOf("<opt") &&
					[ 1, "<select multiple='multiple'>", "</select>" ] ||

					!tags.indexOf("<leg") &&
					[ 1, "<fieldset>", "</fieldset>" ] ||

					tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
					[ 1, "<table>", "</table>" ] ||

					!tags.indexOf("<tr") &&
					[ 2, "<table><tbody>", "</tbody></table>" ] ||

				 	// <thead> matched above
					(!tags.indexOf("<td") || !tags.indexOf("<th")) &&
					[ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] ||

					!tags.indexOf("<col") &&
					[ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] ||

					// IE can't serialize <link> and <script> tags normally
					!jQuery.support.htmlSerialize &&
					[ 1, "div<div>", "</div>" ] ||

					[ 0, "", "" ];

				// Go to html and back, then peel off extra wrappers
				div.innerHTML = wrap[1] + elem + wrap[2];

				// Move to the right depth
				while ( wrap[0]-- )
					div = div.lastChild;

				// Remove IE's autoinserted <tbody> from table fragments
				if ( !jQuery.support.tbody ) {

					// String was a <table>, *may* have spurious <tbody>
					var hasBody = /<tbody/i.test(elem),
						tbody = !tags.indexOf("<table") && !hasBody ?
							div.firstChild && div.firstChild.childNodes :

						// String was a bare <thead> or <tfoot>
						wrap[1] == "<table>" && !hasBody ?
							div.childNodes :
							[];

					for ( var j = tbody.length - 1; j >= 0 ; --j )
						if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length )
							tbody[ j ].parentNode.removeChild( tbody[ j ] );

					}

				// IE completely kills leading whitespace when innerHTML is used
				if ( !jQuery.support.leadingWhitespace && /^\s/.test( elem ) )
					div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );
				
				elem = jQuery.makeArray( div.childNodes );
			}

			if ( elem.nodeType )
				ret.push( elem );
			else
				ret = jQuery.merge( ret, elem );

		});

		if ( fragment ) {
			for ( var i = 0; ret[i]; i++ ) {
				if ( jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
					scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
				} else {
					if ( ret[i].nodeType === 1 )
						ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
					fragment.appendChild( ret[i] );
				}
			}
			
			return scripts;
		}

		return ret;
	},

	attr: function( elem, name, value ) {
		// don't set attributes on text and comment nodes
		if (!elem || elem.nodeType == 3 || elem.nodeType == 8)
			return undefined;

		var notxml = !jQuery.isXMLDoc( elem ),
			// Whether we are setting (or getting)
			set = value !== undefined;

		// Try to normalize/fix the name
		name = notxml && jQuery.props[ name ] || name;

		// Only do all the following if this is a node (faster for style)
		// IE elem.getAttribute passes even for style
		if ( elem.tagName ) {

			// These attributes require special treatment
			var special = /href|src|style/.test( name );

			// Safari mis-reports the default selected property of a hidden option
			// Accessing the parent's selectedIndex property fixes it
			if ( name == "selected" && elem.parentNode )
				elem.parentNode.selectedIndex;

			// If applicable, access the attribute via the DOM 0 way
			if ( name in elem && notxml && !special ) {
				if ( set ){
					// We can't allow the type property to be changed (since it causes problems in IE)
					if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode )
						throw "type property can't be changed";

					elem[ name ] = value;
				}

				// browsers index elements by id/name on forms, give priority to attributes.
				if( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) )
					return elem.getAttributeNode( name ).nodeValue;

				// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
				// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
				if ( name == "tabIndex" ) {
					var attributeNode = elem.getAttributeNode( "tabIndex" );
					return attributeNode && attributeNode.specified
						? attributeNode.value
						: elem.nodeName.match(/(button|input|object|select|textarea)/i)
							? 0
							: elem.nodeName.match(/^(a|area)$/i) && elem.href
								? 0
								: undefined;
				}

				return elem[ name ];
			}

			if ( !jQuery.support.style && notxml &&  name == "style" )
				return jQuery.attr( elem.style, "cssText", value );

			if ( set )
				// convert the value to a string (all browsers do this but IE) see #1070
				elem.setAttribute( name, "" + value );

			var attr = !jQuery.support.hrefNormalized && notxml && special
					// Some attributes require a special call on IE
					? elem.getAttribute( name, 2 )
					: elem.getAttribute( name );

			// Non-existent attributes return null, we normalize to undefined
			return attr === null ? undefined : attr;
		}

		// elem is actually elem.style ... set the style

		// IE uses filters for opacity
		if ( !jQuery.support.opacity && name == "opacity" ) {
			if ( set ) {
				// IE has trouble with opacity if it does not have layout
				// Force it by setting the zoom level
				elem.zoom = 1;

				// Set the alpha filter to set the opacity
				elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) +
					(parseInt( value ) + '' == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
			}

			return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
				(parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100) + '':
				"";
		}

		name = name.replace(/-([a-z])/ig, function(all, letter){
			return letter.toUpperCase();
		});

		if ( set )
			elem[ name ] = value;

		return elem[ name ];
	},

	trim: function( text ) {
		return (text || "").replace( /^\s+|\s+$/g, "" );
	},

	makeArray: function( array ) {
		var ret = [];

		if( array != null ){
			var i = array.length;
			// The window, strings (and functions) also have 'length'
			if( i == null || typeof array === "string" || jQuery.isFunction(array) || array.setInterval )
				ret[0] = array;
			else
				while( i )
					ret[--i] = array[i];
		}

		return ret;
	},

	inArray: function( elem, array ) {
		for ( var i = 0, length = array.length; i < length; i++ )
		// Use === because on IE, window == document
			if ( array[ i ] === elem )
				return i;

		return -1;
	},

	merge: function( first, second ) {
		// We have to loop this way because IE & Opera overwrite the length
		// expando of getElementsByTagName
		var i = 0, elem, pos = first.length;
		// Also, we need to make sure that the correct elements are being returned
		// (IE returns comment nodes in a '*' query)
		if ( !jQuery.support.getAll ) {
			while ( (elem = second[ i++ ]) != null )
				if ( elem.nodeType != 8 )
					first[ pos++ ] = elem;

		} else
			while ( (elem = second[ i++ ]) != null )
				first[ pos++ ] = elem;

		return first;
	},

	unique: function( array ) {
		var ret = [], done = {};

		try {

			for ( var i = 0, length = array.length; i < length; i++ ) {
				var id = jQuery.data( array[ i ] );

				if ( !done[ id ] ) {
					done[ id ] = true;
					ret.push( array[ i ] );
				}
			}

		} catch( e ) {
			ret = array;
		}

		return ret;
	},

	grep: function( elems, callback, inv ) {
		var ret = [];

		// Go through the array, only saving the items
		// that pass the validator function
		for ( var i = 0, length = elems.length; i < length; i++ )
			if ( !inv != !callback( elems[ i ], i ) )
				ret.push( elems[ i ] );

		return ret;
	},

	map: function( elems, callback ) {
		var ret = [];

		// Go through the array, translating each of the items to their
		// new value (or values).
		for ( var i = 0, length = elems.length; i < length; i++ ) {
			var value = callback( elems[ i ], i );

			if ( value != null )
				ret[ ret.length ] = value;
		}

		return ret.concat.apply( [], ret );
	}
});

// Use of jQuery.browser is deprecated.
// It's included for backwards compatibility and plugins,
// although they should work to migrate away.

var userAgent = navigator.userAgent.toLowerCase();

// Figure out what browser is being used
jQuery.browser = {
	version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1],
	safari: /webkit/.test( userAgent ),
	opera: /opera/.test( userAgent ),
	msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
	mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
};

jQuery.each({
	parent: function(elem){return elem.parentNode;},
	parents: function(elem){return jQuery.dir(elem,"parentNode");},
	next: function(elem){return jQuery.nth(elem,2,"nextSibling");},
	prev: function(elem){return jQuery.nth(elem,2,"previousSibling");},
	nextAll: function(elem){return jQuery.dir(elem,"nextSibling");},
	prevAll: function(elem){return jQuery.dir(elem,"previousSibling");},
	siblings: function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},
	children: function(elem){return jQuery.sibling(elem.firstChild);},
	contents: function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}
}, function(name, fn){
	jQuery.fn[ name ] = function( selector ) {
		var ret = jQuery.map( this, fn );

		if ( selector && typeof selector == "string" )
			ret = jQuery.multiFilter( selector, ret );

		return this.pushStack( jQuery.unique( ret ), name, selector );
	};
});

jQuery.each({
	appendTo: "append",
	prependTo: "prepend",
	insertBefore: "before",
	insertAfter: "after",
	replaceAll: "replaceWith"
}, function(name, original){
	jQuery.fn[ name ] = function( selector ) {
		var ret = [], insert = jQuery( selector );

		for ( var i = 0, l = insert.length; i < l; i++ ) {
			var elems = (i > 0 ? this.clone(true) : this).get();
			jQuery.fn[ original ].apply( jQuery(insert[i]), elems );
			ret = ret.concat( elems );
		}

		return this.pushStack( ret, name, selector );
	};
});

jQuery.each({
	removeAttr: function( name ) {
		jQuery.attr( this, name, "" );
		if (this.nodeType == 1)
			this.removeAttribute( name );
	},

	addClass: function( classNames ) {
		jQuery.className.add( this, classNames );
	},

	removeClass: function( classNames ) {
		jQuery.className.remove( this, classNames );
	},

	toggleClass: function( classNames, state ) {
		if( typeof state !== "boolean" )
			state = !jQuery.className.has( this, classNames );
		jQuery.className[ state ? "add" : "remove" ]( this, classNames );
	},

	remove: function( selector ) {
		if ( !selector || jQuery.filter( selector, [ this ] ).length ) {
			// Prevent memory leaks
			jQuery( "*", this ).add([this]).each(function(){
				jQuery.event.remove(this);
				jQuery.removeData(this);
			});
			if (this.parentNode)
				this.parentNode.removeChild( this );
		}
	},

	empty: function() {
		// Remove element nodes and prevent memory leaks
		jQuery(this).children().remove();

		// Remove any remaining nodes
		while ( this.firstChild )
			this.removeChild( this.firstChild );
	}
}, function(name, fn){
	jQuery.fn[ name ] = function(){
		return this.each( fn, arguments );
	};
});

// Helper function used by the dimensions and offset modules
function num(elem, prop) {
	return elem[0] && parseInt( jQuery.curCSS(elem[0], prop, true), 10 ) || 0;
}
var expando = "jQuery" + now(), uuid = 0, windowData = {};

jQuery.extend({
	cache: {},

	data: function( elem, name, data ) {
		elem = elem == window ?
			windowData :
			elem;

		var id = elem[ expando ];

		// Compute a unique ID for the element
		if ( !id )
			id = elem[ expando ] = ++uuid;

		// Only generate the data cache if we're
		// trying to access or manipulate it
		if ( name && !jQuery.cache[ id ] )
			jQuery.cache[ id ] = {};

		// Prevent overriding the named cache with undefined values
		if ( data !== undefined )
			jQuery.cache[ id ][ name ] = data;

		// Return the named cache data, or the ID for the element
		return name ?
			jQuery.cache[ id ][ name ] :
			id;
	},

	removeData: function( elem, name ) {
		elem = elem == window ?
			windowData :
			elem;

		var id = elem[ expando ];

		// If we want to remove a specific section of the element's data
		if ( name ) {
			if ( jQuery.cache[ id ] ) {
				// Remove the section of cache data
				delete jQuery.cache[ id ][ name ];

				// If we've removed all the data, remove the element's cache
				name = "";

				for ( name in jQuery.cache[ id ] )
					break;

				if ( !name )
					jQuery.removeData( elem );
			}

		// Otherwise, we want to remove all of the element's data
		} else {
			// Clean up the element expando
			try {
				delete elem[ expando ];
			} catch(e){
				// IE has trouble directly removing the expando
				// but it's ok with using removeAttribute
				if ( elem.removeAttribute )
					elem.removeAttribute( expando );
			}

			// Completely remove the data cache
			delete jQuery.cache[ id ];
		}
	},
	queue: function( elem, type, data ) {
		if ( elem ){
	
			type = (type || "fx") + "queue";
	
			var q = jQuery.data( elem, type );
	
			if ( !q || jQuery.isArray(data) )
				q = jQuery.data( elem, type, jQuery.makeArray(data) );
			else if( data )
				q.push( data );
	
		}
		return q;
	},

	dequeue: function( elem, type ){
		var queue = jQuery.queue( elem, type ),
			fn = queue.shift();
		
		if( !type || type === "fx" )
			fn = queue[0];
			
		if( fn !== undefined )
			fn.call(elem);
	}
});

jQuery.fn.extend({
	data: function( key, value ){
		var parts = key.split(".");
		parts[1] = parts[1] ? "." + parts[1] : "";

		if ( value === undefined ) {
			var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);

			if ( data === undefined && this.length )
				data = jQuery.data( this[0], key );

			return data === undefined && parts[1] ?
				this.data( parts[0] ) :
				data;
		} else
			return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){
				jQuery.data( this, key, value );
			});
	},

	removeData: function( key ){
		return this.each(function(){
			jQuery.removeData( this, key );
		});
	},
	queue: function(type, data){
		if ( typeof type !== "string" ) {
			data = type;
			type = "fx";
		}

		if ( data === undefined )
			return jQuery.queue( this[0], type );

		return this.each(function(){
			var queue = jQuery.queue( this, type, data );
			
			 if( type == "fx" && queue.length == 1 )
				queue[0].call(this);
		});
	},
	dequeue: function(type){
		return this.each(function(){
			jQuery.dequeue( this, type );
		});
	}
});/*!
 * Sizzle CSS Selector Engine - v0.9.3
 *  Copyright 2009, The Dojo Foundation
 *  Released under the MIT, BSD, and GPL Licenses.
 *  More information: http://sizzlejs.com/
 */
(function(){

var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,
	done = 0,
	toString = Object.prototype.toString;

var Sizzle = function(selector, context, results, seed) {
	results = results || [];
	context = context || document;

	if ( context.nodeType !== 1 && context.nodeType !== 9 )
		return [];
	
	if ( !selector || typeof selector !== "string" ) {
		return results;
	}

	var parts = [], m, set, checkSet, check, mode, extra, prune = true;
	
	// Reset the position of the chunker regexp (start from head)
	chunker.lastIndex = 0;
	
	while ( (m = chunker.exec(selector)) !== null ) {
		parts.push( m[1] );
		
		if ( m[2] ) {
			extra = RegExp.rightContext;
			break;
		}
	}

	if ( parts.length > 1 && origPOS.exec( selector ) ) {
		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
			set = posProcess( parts[0] + parts[1], context );
		} else {
			set = Expr.relative[ parts[0] ] ?
				[ context ] :
				Sizzle( parts.shift(), context );

			while ( parts.length ) {
				selector = parts.shift();

				if ( Expr.relative[ selector ] )
					selector += parts.shift();

				set = posProcess( selector, set );
			}
		}
	} else {
		var ret = seed ?
			{ expr: parts.pop(), set: makeArray(seed) } :
			Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context, isXML(context) );
		set = Sizzle.filter( ret.expr, ret.set );

		if ( parts.length > 0 ) {
			checkSet = makeArray(set);
		} else {
			prune = false;
		}

		while ( parts.length ) {
			var cur = parts.pop(), pop = cur;

			if ( !Expr.relative[ cur ] ) {
				cur = "";
			} else {
				pop = parts.pop();
			}

			if ( pop == null ) {
				pop = context;
			}

			Expr.relative[ cur ]( checkSet, pop, isXML(context) );
		}
	}

	if ( !checkSet ) {
		checkSet = set;
	}

	if ( !checkSet ) {
		throw "Syntax error, unrecognized expression: " + (cur || selector);
	}

	if ( toString.call(checkSet) === "[object Array]" ) {
		if ( !prune ) {
			results.push.apply( results, checkSet );
		} else if ( context.nodeType === 1 ) {
			for ( var i = 0; checkSet[i] != null; i++ ) {
				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
					results.push( set[i] );
				}
			}
		} else {
			for ( var i = 0; checkSet[i] != null; i++ ) {
				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
					results.push( set[i] );
				}
			}
		}
	} else {
		makeArray( checkSet, results );
	}

	if ( extra ) {
		Sizzle( extra, context, results, seed );

		if ( sortOrder ) {
			hasDuplicate = false;
			results.sort(sortOrder);

			if ( hasDuplicate ) {
				for ( var i = 1; i < results.length; i++ ) {
					if ( results[i] === results[i-1] ) {
						results.splice(i--, 1);
					}
				}
			}
		}
	}

	return results;
};

Sizzle.matches = function(expr, set){
	return Sizzle(expr, null, null, set);
};

Sizzle.find = function(expr, context, isXML){
	var set, match;

	if ( !expr ) {
		return [];
	}

	for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
		var type = Expr.order[i], match;
		
		if ( (match = Expr.match[ type ].exec( expr )) ) {
			var left = RegExp.leftContext;

			if ( left.substr( left.length - 1 ) !== "\\" ) {
				match[1] = (match[1] || "").replace(/\\/g, "");
				set = Expr.find[ type ]( match, context, isXML );
				if ( set != null ) {
					expr = expr.replace( Expr.match[ type ], "" );
					break;
				}
			}
		}
	}

	if ( !set ) {
		set = context.getElementsByTagName("*");
	}

	return {set: set, expr: expr};
};

Sizzle.filter = function(expr, set, inplace, not){
	var old = expr, result = [], curLoop = set, match, anyFound,
		isXMLFilter = set && set[0] && isXML(set[0]);

	while ( expr && set.length ) {
		for ( var type in Expr.filter ) {
			if ( (match = Expr.match[ type ].exec( expr )) != null ) {
				var filter = Expr.filter[ type ], found, item;
				anyFound = false;

				if ( curLoop == result ) {
					result = [];
				}

				if ( Expr.preFilter[ type ] ) {
					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );

					if ( !match ) {
						anyFound = found = true;
					} else if ( match === true ) {
						continue;
					}
				}

				if ( match ) {
					for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
						if ( item ) {
							found = filter( item, match, i, curLoop );
							var pass = not ^ !!found;

							if ( inplace && found != null ) {
								if ( pass ) {
									anyFound = true;
								} else {
									curLoop[i] = false;
								}
							} else if ( pass ) {
								result.push( item );
								anyFound = true;
							}
						}
					}
				}

				if ( found !== undefined ) {
					if ( !inplace ) {
						curLoop = result;
					}

					expr = expr.replace( Expr.match[ type ], "" );

					if ( !anyFound ) {
						return [];
					}

					break;
				}
			}
		}

		// Improper expression
		if ( expr == old ) {
			if ( anyFound == null ) {
				throw "Syntax error, unrecognized expression: " + expr;
			} else {
				break;
			}
		}

		old = expr;
	}

	return curLoop;
};

var Expr = Sizzle.selectors = {
	order: [ "ID", "NAME", "TAG" ],
	match: {
		ID: /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
		CLASS: /\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,
		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
		TAG: /^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,
		CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
		PSEUDO: /:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
	},
	attrMap: {
		"class": "className",
		"for": "htmlFor"
	},
	attrHandle: {
		href: function(elem){
			return elem.getAttribute("href");
		}
	},
	relative: {
		"+": function(checkSet, part, isXML){
			var isPartStr = typeof part === "string",
				isTag = isPartStr && !/\W/.test(part),
				isPartStrNotTag = isPartStr && !isTag;

			if ( isTag && !isXML ) {
				part = part.toUpperCase();
			}

			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
				if ( (elem = checkSet[i]) ) {
					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}

					checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
						elem || false :
						elem === part;
				}
			}

			if ( isPartStrNotTag ) {
				Sizzle.filter( part, checkSet, true );
			}
		},
		">": function(checkSet, part, isXML){
			var isPartStr = typeof part === "string";

			if ( isPartStr && !/\W/.test(part) ) {
				part = isXML ? part : part.toUpperCase();

				for ( var i = 0, l = checkSet.length; i < l; i++ ) {
					var elem = checkSet[i];
					if ( elem ) {
						var parent = elem.parentNode;
						checkSet[i] = parent.nodeName === part ? parent : false;
					}
				}
			} else {
				for ( var i = 0, l = checkSet.length; i < l; i++ ) {
					var elem = checkSet[i];
					if ( elem ) {
						checkSet[i] = isPartStr ?
							elem.parentNode :
							elem.parentNode === part;
					}
				}

				if ( isPartStr ) {
					Sizzle.filter( part, checkSet, true );
				}
			}
		},
		"": function(checkSet, part, isXML){
			var doneName = done++, checkFn = dirCheck;

			if ( !part.match(/\W/) ) {
				var nodeCheck = part = isXML ? part : part.toUpperCase();
				checkFn = dirNodeCheck;
			}

			checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
		},
		"~": function(checkSet, part, isXML){
			var doneName = done++, checkFn = dirCheck;

			if ( typeof part === "string" && !part.match(/\W/) ) {
				var nodeCheck = part = isXML ? part : part.toUpperCase();
				checkFn = dirNodeCheck;
			}

			checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
		}
	},
	find: {
		ID: function(match, context, isXML){
			if ( typeof context.getElementById !== "undefined" && !isXML ) {
				var m = context.getElementById(match[1]);
				return m ? [m] : [];
			}
		},
		NAME: function(match, context, isXML){
			if ( typeof context.getElementsByName !== "undefined" ) {
				var ret = [], results = context.getElementsByName(match[1]);

				for ( var i = 0, l = results.length; i < l; i++ ) {
					if ( results[i].getAttribute("name") === match[1] ) {
						ret.push( results[i] );
					}
				}

				return ret.length === 0 ? null : ret;
			}
		},
		TAG: function(match, context){
			return context.getElementsByTagName(match[1]);
		}
	},
	preFilter: {
		CLASS: function(match, curLoop, inplace, result, not, isXML){
			match = " " + match[1].replace(/\\/g, "") + " ";

			if ( isXML ) {
				return match;
			}

			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
				if ( elem ) {
					if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
						if ( !inplace )
							result.push( elem );
					} else if ( inplace ) {
						curLoop[i] = false;
					}
				}
			}

			return false;
		},
		ID: function(match){
			return match[1].replace(/\\/g, "");
		},
		TAG: function(match, curLoop){
			for ( var i = 0; curLoop[i] === false; i++ ){}
			return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
		},
		CHILD: function(match){
			if ( match[1] == "nth" ) {
				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
				var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
					match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" ||
					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);

				// calculate the numbers (first)n+(last) including if they are negative
				match[2] = (test[1] + (test[2] || 1)) - 0;
				match[3] = test[3] - 0;
			}

			// TODO: Move to normal caching system
			match[0] = done++;

			return match;
		},
		ATTR: function(match, curLoop, inplace, result, not, isXML){
			var name = match[1].replace(/\\/g, "");
			
			if ( !isXML && Expr.attrMap[name] ) {
				match[1] = Expr.attrMap[name];
			}

			if ( match[2] === "~=" ) {
				match[4] = " " + match[4] + " ";
			}

			return match;
		},
		PSEUDO: function(match, curLoop, inplace, result, not){
			if ( match[1] === "not" ) {
				// If we're dealing with a complex expression, or a simple one
				if ( match[3].match(chunker).length > 1 || /^\w/.test(match[3]) ) {
					match[3] = Sizzle(match[3], null, null, curLoop);
				} else {
					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
					if ( !inplace ) {
						result.push.apply( result, ret );
					}
					return false;
				}
			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
				return true;
			}
			
			return match;
		},
		POS: function(match){
			match.unshift( true );
			return match;
		}
	},
	filters: {
		enabled: function(elem){
			return elem.disabled === false && elem.type !== "hidden";
		},
		disabled: function(elem){
			return elem.disabled === true;
		},
		checked: function(elem){
			return elem.checked === true;
		},
		selected: function(elem){
			// Accessing this property makes selected-by-default
			// options in Safari work properly
			elem.parentNode.selectedIndex;
			return elem.selected === true;
		},
		parent: function(elem){
			return !!elem.firstChild;
		},
		empty: function(elem){
			return !elem.firstChild;
		},
		has: function(elem, i, match){
			return !!Sizzle( match[3], elem ).length;
		},
		header: function(elem){
			return /h\d/i.test( elem.nodeName );
		},
		text: function(elem){
			return "text" === elem.type;
		},
		radio: function(elem){
			return "radio" === elem.type;
		},
		checkbox: function(elem){
			return "checkbox" === elem.type;
		},
		file: function(elem){
			return "file" === elem.type;
		},
		password: function(elem){
			return "password" === elem.type;
		},
		submit: function(elem){
			return "submit" === elem.type;
		},
		image: function(elem){
			return "image" === elem.type;
		},
		reset: function(elem){
			return "reset" === elem.type;
		},
		button: function(elem){
			return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON";
		},
		input: function(elem){
			return /input|select|textarea|button/i.test(elem.nodeName);
		}
	},
	setFilters: {
		first: function(elem, i){
			return i === 0;
		},
		last: function(elem, i, match, array){
			return i === array.length - 1;
		},
		even: function(elem, i){
			return i % 2 === 0;
		},
		odd: function(elem, i){
			return i % 2 === 1;
		},
		lt: function(elem, i, match){
			return i < match[3] - 0;
		},
		gt: function(elem, i, match){
			return i > match[3] - 0;
		},
		nth: function(elem, i, match){
			return match[3] - 0 == i;
		},
		eq: function(elem, i, match){
			return match[3] - 0 == i;
		}
	},
	filter: {
		PSEUDO: function(elem, match, i, array){
			var name = match[1], filter = Expr.filters[ name ];

			if ( filter ) {
				return filter( elem, i, match, array );
			} else if ( name === "contains" ) {
				return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0;
			} else if ( name === "not" ) {
				var not = match[3];

				for ( var i = 0, l = not.length; i < l; i++ ) {
					if ( not[i] === elem ) {
						return false;
					}
				}

				return true;
			}
		},
		CHILD: function(elem, match){
			var type = match[1], node = elem;
			switch (type) {
				case 'only':
				case 'first':
					while (node = node.previousSibling)  {
						if ( node.nodeType === 1 ) return false;
					}
					if ( type == 'first') return true;
					node = elem;
				case 'last':
					while (node = node.nextSibling)  {
						if ( node.nodeType === 1 ) return false;
					}
					return true;
				case 'nth':
					var first = match[2], last = match[3];

					if ( first == 1 && last == 0 ) {
						return true;
					}
					
					var doneName = match[0],
						parent = elem.parentNode;
	
					if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
						var count = 0;
						for ( node = parent.firstChild; node; node = node.nextSibling ) {
							if ( node.nodeType === 1 ) {
								node.nodeIndex = ++count;
							}
						} 
						parent.sizcache = doneName;
					}
					
					var diff = elem.nodeIndex - last;
					if ( first == 0 ) {
						return diff == 0;
					} else {
						return ( diff % first == 0 && diff / first >= 0 );
					}
			}
		},
		ID: function(elem, match){
			return elem.nodeType === 1 && elem.getAttribute("id") === match;
		},
		TAG: function(elem, match){
			return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
		},
		CLASS: function(elem, match){
			return (" " + (elem.className || elem.getAttribute("class")) + " ")
				.indexOf( match ) > -1;
		},
		ATTR: function(elem, match){
			var name = match[1],
				result = Expr.attrHandle[ name ] ?
					Expr.attrHandle[ name ]( elem ) :
					elem[ name ] != null ?
						elem[ name ] :
						elem.getAttribute( name ),
				value = result + "",
				type = match[2],
				check = match[4];

			return result == null ?
				type === "!=" :
				type === "=" ?
				value === check :
				type === "*=" ?
				value.indexOf(check) >= 0 :
				type === "~=" ?
				(" " + value + " ").indexOf(check) >= 0 :
				!check ?
				value && result !== false :
				type === "!=" ?
				value != check :
				type === "^=" ?
				value.indexOf(check) === 0 :
				type === "$=" ?
				value.substr(value.length - check.length) === check :
				type === "|=" ?
				value === check || value.substr(0, check.length + 1) === check + "-" :
				false;
		},
		POS: function(elem, match, i, array){
			var name = match[2], filter = Expr.setFilters[ name ];

			if ( filter ) {
				return filter( elem, i, match, array );
			}
		}
	}
};

var origPOS = Expr.match.POS;

for ( var type in Expr.match ) {
	Expr.match[ type ] = RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
}

var makeArray = function(array, results) {
	array = Array.prototype.slice.call( array );

	if ( results ) {
		results.push.apply( results, array );
		return results;
	}
	
	return array;
};

// Perform a simple check to determine if the browser is capable of
// converting a NodeList to an array using builtin methods.
try {
	Array.prototype.slice.call( document.documentElement.childNodes );

// Provide a fallback method if it does not work
} catch(e){
	makeArray = function(array, results) {
		var ret = results || [];

		if ( toString.call(array) === "[object Array]" ) {
			Array.prototype.push.apply( ret, array );
		} else {
			if ( typeof array.length === "number" ) {
				for ( var i = 0, l = array.length; i < l; i++ ) {
					ret.push( array[i] );
				}
			} else {
				for ( var i = 0; array[i]; i++ ) {
					ret.push( array[i] );
				}
			}
		}

		return ret;
	};
}

var sortOrder;

if ( document.documentElement.compareDocumentPosition ) {
	sortOrder = function( a, b ) {
		var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
		if ( ret === 0 ) {
			hasDuplicate = true;
		}
		return ret;
	};
} else if ( "sourceIndex" in document.documentElement ) {
	sortOrder = function( a, b ) {
		var ret = a.sourceIndex - b.sourceIndex;
		if ( ret === 0 ) {
			hasDuplicate = true;
		}
		return ret;
	};
} else if ( document.createRange ) {
	sortOrder = function( a, b ) {
		var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
		aRange.selectNode(a);
		aRange.collapse(true);
		bRange.selectNode(b);
		bRange.collapse(true);
		var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
		if ( ret === 0 ) {
			hasDuplicate = true;
		}
		return ret;
	};
}

// Check to see if the browser returns elements by name when
// querying by getElementById (and provide a workaround)
(function(){
	// We're going to inject a fake input element with a specified name
	var form = document.createElement("form"),
		id = "script" + (new Date).getTime();
	form.innerHTML = "<input name='" + id + "'/>";

	// Inject it into the root element, check its status, and remove it quickly
	var root = document.documentElement;
	root.insertBefore( form, root.firstChild );

	// The workaround has to do additional checks after a getElementById
	// Which slows things down for other browsers (hence the branching)
	if ( !!document.getElementById( id ) ) {
		Expr.find.ID = function(match, context, isXML){
			if ( typeof context.getElementById !== "undefined" && !isXML ) {
				var m = context.getElementById(match[1]);
				return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
			}
		};

		Expr.filter.ID = function(elem, match){
			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
			return elem.nodeType === 1 && node && node.nodeValue === match;
		};
	}

	root.removeChild( form );
})();

(function(){
	// Check to see if the browser returns only elements
	// when doing getElementsByTagName("*")

	// Create a fake element
	var div = document.createElement("div");
	div.appendChild( document.createComment("") );

	// Make sure no comments are found
	if ( div.getElementsByTagName("*").length > 0 ) {
		Expr.find.TAG = function(match, context){
			var results = context.getElementsByTagName(match[1]);

			// Filter out possible comments
			if ( match[1] === "*" ) {
				var tmp = [];

				for ( var i = 0; results[i]; i++ ) {
					if ( results[i].nodeType === 1 ) {
						tmp.push( results[i] );
					}
				}

				results = tmp;
			}

			return results;
		};
	}

	// Check to see if an attribute returns normalized href attributes
	div.innerHTML = "<a href='#'></a>";
	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
			div.firstChild.getAttribute("href") !== "#" ) {
		Expr.attrHandle.href = function(elem){
			return elem.getAttribute("href", 2);
		};
	}
})();

if ( document.querySelectorAll ) (function(){
	var oldSizzle = Sizzle, div = document.createElement("div");
	div.innerHTML = "<p class='TEST'></p>";

	// Safari can't handle uppercase or unicode characters when
	// in quirks mode.
	if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
		return;
	}
	
	Sizzle = function(query, context, extra, seed){
		context = context || document;

		// Only use querySelectorAll on non-XML documents
		// (ID selectors don't work in non-HTML documents)
		if ( !seed && context.nodeType === 9 && !isXML(context) ) {
			try {
				return makeArray( context.querySelectorAll(query), extra );
			} catch(e){}
		}
		
		return oldSizzle(query, context, extra, seed);
	};

	Sizzle.find = oldSizzle.find;
	Sizzle.filter = oldSizzle.filter;
	Sizzle.selectors = oldSizzle.selectors;
	Sizzle.matches = oldSizzle.matches;
})();

if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
	var div = document.createElement("div");
	div.innerHTML = "<div class='test e'></div><div class='test'></div>";

	// Opera can't find a second classname (in 9.6)
	if ( div.getElementsByClassName("e").length === 0 )
		return;

	// Safari caches class attributes, doesn't catch changes (in 3.2)
	div.lastChild.className = "e";

	if ( div.getElementsByClassName("e").length === 1 )
		return;

	Expr.order.splice(1, 0, "CLASS");
	Expr.find.CLASS = function(match, context, isXML) {
		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
			return context.getElementsByClassName(match[1]);
		}
	};
})();

function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
	var sibDir = dir == "previousSibling" && !isXML;
	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
		var elem = checkSet[i];
		if ( elem ) {
			if ( sibDir && elem.nodeType === 1 ){
				elem.sizcache = doneName;
				elem.sizset = i;
			}
			elem = elem[dir];
			var match = false;

			while ( elem ) {
				if ( elem.sizcache === doneName ) {
					match = checkSet[elem.sizset];
					break;
				}

				if ( elem.nodeType === 1 && !isXML ){
					elem.sizcache = doneName;
					elem.sizset = i;
				}

				if ( elem.nodeName === cur ) {
					match = elem;
					break;
				}

				elem = elem[dir];
			}

			checkSet[i] = match;
		}
	}
}

function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
	var sibDir = dir == "previousSibling" && !isXML;
	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
		var elem = checkSet[i];
		if ( elem ) {
			if ( sibDir && elem.nodeType === 1 ) {
				elem.sizcache = doneName;
				elem.sizset = i;
			}
			elem = elem[dir];
			var match = false;

			while ( elem ) {
				if ( elem.sizcache === doneName ) {
					match = checkSet[elem.sizset];
					break;
				}

				if ( elem.nodeType === 1 ) {
					if ( !isXML ) {
						elem.sizcache = doneName;
						elem.sizset = i;
					}
					if ( typeof cur !== "string" ) {
						if ( elem === cur ) {
							match = true;
							break;
						}

					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
						match = elem;
						break;
					}
				}

				elem = elem[dir];
			}

			checkSet[i] = match;
		}
	}
}

var contains = document.compareDocumentPosition ?  function(a, b){
	return a.compareDocumentPosition(b) & 16;
} : function(a, b){
	return a !== b && (a.contains ? a.contains(b) : true);
};

var isXML = function(elem){
	return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
		!!elem.ownerDocument && isXML( elem.ownerDocument );
};

var posProcess = function(selector, context){
	var tmpSet = [], later = "", match,
		root = context.nodeType ? [context] : context;

	// Position selectors must be done after the filter
	// And so must :not(positional) so we move all PSEUDOs to the end
	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
		later += match[0];
		selector = selector.replace( Expr.match.PSEUDO, "" );
	}

	selector = Expr.relative[selector] ? selector + "*" : selector;

	for ( var i = 0, l = root.length; i < l; i++ ) {
		Sizzle( selector, root[i], tmpSet );
	}

	return Sizzle.filter( later, tmpSet );
};

// EXPOSE
jQuery.find = Sizzle;
jQuery.filter = Sizzle.filter;
jQuery.expr = Sizzle.selectors;
jQuery.expr[":"] = jQuery.expr.filters;

Sizzle.selectors.filters.hidden = function(elem){
	return elem.offsetWidth === 0 || elem.offsetHeight === 0;
};

Sizzle.selectors.filters.visible = function(elem){
	return elem.offsetWidth > 0 || elem.offsetHeight > 0;
};

Sizzle.selectors.filters.animated = function(elem){
	return jQuery.grep(jQuery.timers, function(fn){
		return elem === fn.elem;
	}).length;
};

jQuery.multiFilter = function( expr, elems, not ) {
	if ( not ) {
		expr = ":not(" + expr + ")";
	}

	return Sizzle.matches(expr, elems);
};

jQuery.dir = function( elem, dir ){
	var matched = [], cur = elem[dir];
	while ( cur && cur != document ) {
		if ( cur.nodeType == 1 )
			matched.push( cur );
		cur = cur[dir];
	}
	return matched;
};

jQuery.nth = function(cur, result, dir, elem){
	result = result || 1;
	var num = 0;

	for ( ; cur; cur = cur[dir] )
		if ( cur.nodeType == 1 && ++num == result )
			break;

	return cur;
};

jQuery.sibling = function(n, elem){
	var r = [];

	for ( ; n; n = n.nextSibling ) {
		if ( n.nodeType == 1 && n != elem )
			r.push( n );
	}

	return r;
};

return;

window.Sizzle = Sizzle;

})();
/*
 * A number of helper functions used for managing events.
 * Many of the ideas behind this code originated from
 * Dean Edwards' addEvent library.
 */
jQuery.event = {

	// Bind an event to an element
	// Original by Dean Edwards
	add: function(elem, types, handler, data) {
		if ( elem.nodeType == 3 || elem.nodeType == 8 )
			return;

		// For whatever reason, IE has trouble passing the window object
		// around, causing it to be cloned in the process
		if ( elem.setInterval && elem != window )
			elem = window;

		// Make sure that the function being executed has a unique ID
		if ( !handler.guid )
			handler.guid = this.guid++;

		// if data is passed, bind to handler
		if ( data !== undefined ) {
			// Create temporary function pointer to original handler
			var fn = handler;

			// Create unique handler function, wrapped around original handler
			handler = this.proxy( fn );

			// Store data in unique handler
			handler.data = data;
		}

		// Init the element's event structure
		var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
			handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
				// Handle the second event of a trigger and when
				// an event is called after a page has unloaded
				return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
					jQuery.event.handle.apply(arguments.callee.elem, arguments) :
					undefined;
			});
		// Add elem as a property of the handle function
		// This is to prevent a memory leak with non-native
		// event in IE.
		handle.elem = elem;

		// Handle multiple events separated by a space
		// jQuery(...).bind("mouseover mouseout", fn);
		jQuery.each(types.split(/\s+/), function(index, type) {
			// Namespaced event handlers
			var namespaces = type.split(".");
			type = namespaces.shift();
			handler.type = namespaces.slice().sort().join(".");

			// Get the current list of functions bound to this event
			var handlers = events[type];
			
			if ( jQuery.event.specialAll[type] )
				jQuery.event.specialAll[type].setup.call(elem, data, namespaces);

			// Init the event handler queue
			if (!handlers) {
				handlers = events[type] = {};

				// Check for a special event handler
				// Only use addEventListener/attachEvent if the special
				// events handler returns false
				if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem, data, namespaces) === false ) {
					// Bind the global event handler to the element
					if (elem.addEventListener)
						elem.addEventListener(type, handle, false);
					else if (elem.attachEvent)
						elem.attachEvent("on" + type, handle);
				}
			}

			// Add the function to the element's handler list
			handlers[handler.guid] = handler;

			// Keep track of which events have been used, for global triggering
			jQuery.event.global[type] = true;
		});

		// Nullify elem to prevent memory leaks in IE
		elem = null;
	},

	guid: 1,
	global: {},

	// Detach an event or set of events from an element
	remove: function(elem, types, handler) {
		// don't do events on text and comment nodes
		if ( elem.nodeType == 3 || elem.nodeType == 8 )
			return;

		var events = jQuery.data(elem, "events"), ret, index;

		if ( events ) {
			// Unbind all events for the element
			if ( types === undefined || (typeof types === "string" && types.charAt(0) == ".") )
				for ( var type in events )
					this.remove( elem, type + (types || "") );
			else {
				// types is actually an event object here
				if ( types.type ) {
					handler = types.handler;
					types = types.type;
				}

				// Handle multiple events seperated by a space
				// jQuery(...).unbind("mouseover mouseout", fn);
				jQuery.each(types.split(/\s+/), function(index, type){
					// Namespaced event handlers
					var namespaces = type.split(".");
					type = namespaces.shift();
					var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");

					if ( events[type] ) {
						// remove the given handler for the given type
						if ( handler )
							delete events[type][handler.guid];

						// remove all handlers for the given type
						else
							for ( var handle in events[type] )
								// Handle the removal of namespaced events
								if ( namespace.test(events[type][handle].type) )
									delete events[type][handle];
									
						if ( jQuery.event.specialAll[type] )
							jQuery.event.specialAll[type].teardown.call(elem, namespaces);

						// remove generic event handler if no more handlers exist
						for ( ret in events[type] ) break;
						if ( !ret ) {
							if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem, namespaces) === false ) {
								if (elem.removeEventListener)
									elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
								else if (elem.detachEvent)
									elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
							}
							ret = null;
							delete events[type];
						}
					}
				});
			}

			// Remove the expando if it's no longer used
			for ( ret in events ) break;
			if ( !ret ) {
				var handle = jQuery.data( elem, "handle" );
				if ( handle ) handle.elem = null;
				jQuery.removeData( elem, "events" );
				jQuery.removeData( elem, "handle" );
			}
		}
	},

	// bubbling is internal
	trigger: function( event, data, elem, bubbling ) {
		// Event object or event type
		var type = event.type || event;

		if( !bubbling ){
			event = typeof event === "object" ?
				// jQuery.Event object
				event[expando] ? event :
				// Object literal
				jQuery.extend( jQuery.Event(type), event ) :
				// Just the event type (string)
				jQuery.Event(type);

			if ( type.indexOf("!") >= 0 ) {
				event.type = type = type.slice(0, -1);
				event.exclusive = true;
			}

			// Handle a global trigger
			if ( !elem ) {
				// Don't bubble custom events when global (to avoid too much overhead)
				event.stopPropagation();
				// Only trigger if we've ever bound an event for it
				if ( this.global[type] )
					jQuery.each( jQuery.cache, function(){
						if ( this.events && this.events[type] )
							jQuery.event.trigger( event, data, this.handle.elem );
					});
			}

			// Handle triggering a single element

			// don't do events on text and comment nodes
			if ( !elem || elem.nodeType == 3 || elem.nodeType == 8 )
				return undefined;
			
			// Clean up in case it is reused
			event.result = undefined;
			event.target = elem;
			
			// Clone the incoming data, if any
			data = jQuery.makeArray(data);
			data.unshift( event );
		}

		event.currentTarget = elem;

		// Trigger the event, it is assumed that "handle" is a function
		var handle = jQuery.data(elem, "handle");
		if ( handle )
			handle.apply( elem, data );

		// Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
		if ( (!elem[type] || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
			event.result = false;

		// Trigger the native events (except for clicks on links)
		if ( !bubbling && elem[type] && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
			this.triggered = true;
			try {
				elem[ type ]();
			// prevent IE from throwing an error for some hidden elements
			} catch (e) {}
		}

		this.triggered = false;

		if ( !event.isPropagationStopped() ) {
			var parent = elem.parentNode || elem.ownerDocument;
			if ( parent )
				jQuery.event.trigger(event, data, parent, true);
		}
	},

	handle: function(event) {
		// returned undefined or false
		var all, handlers;

		event = arguments[0] = jQuery.event.fix( event || window.event );
		event.currentTarget = this;
		
		// Namespaced event handlers
		var namespaces = event.type.split(".");
		event.type = namespaces.shift();

		// Cache this now, all = true means, any handler
		all = !namespaces.length && !event.exclusive;
		
		var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");

		handlers = ( jQuery.data(this, "events") || {} )[event.type];

		for ( var j in handlers ) {
			var handler = handlers[j];

			// Filter the functions by class
			if ( all || namespace.test(handler.type) ) {
				// Pass in a reference to the handler function itself
				// So that we can later remove it
				event.handler = handler;
				event.data = handler.data;

				var ret = handler.apply(this, arguments);

				if( ret !== undefined ){
					event.result = ret;
					if ( ret === false ) {
						event.preventDefault();
						event.stopPropagation();
					}
				}

				if( event.isImmediatePropagationStopped() )
					break;

			}
		}
	},

	props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),

	fix: function(event) {
		if ( event[expando] )
			return event;

		// store a copy of the original event object
		// and "clone" to set read-only properties
		var originalEvent = event;
		event = jQuery.Event( originalEvent );

		for ( var i = this.props.length, prop; i; ){
			prop = this.props[ --i ];
			event[ prop ] = originalEvent[ prop ];
		}

		// Fix target property, if necessary
		if ( !event.target )
			event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either

		// check if target is a textnode (safari)
		if ( event.target.nodeType == 3 )
			event.target = event.target.parentNode;

		// Add relatedTarget, if necessary
		if ( !event.relatedTarget && event.fromElement )
			event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;

		// Calculate pageX/Y if missing and clientX/Y available
		if ( event.pageX == null && event.clientX != null ) {
			var doc = document.documentElement, body = document.body;
			event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
			event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
		}

		// Add which for key events
		if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
			event.which = event.charCode || event.keyCode;

		// Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
		if ( !event.metaKey && event.ctrlKey )
			event.metaKey = event.ctrlKey;

		// Add which for click: 1 == left; 2 == middle; 3 == right
		// Note: button is not normalized, so don't use it
		if ( !event.which && event.button )
			event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));

		return event;
	},

	proxy: function( fn, proxy ){
		proxy = proxy || function(){ return fn.apply(this, arguments); };
		// Set the guid of unique handler to the same of original handler, so it can be removed
		proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
		// So proxy can be declared as an argument
		return proxy;
	},

	special: {
		ready: {
			// Make sure the ready event is setup
			setup: bindReady,
			teardown: function() {}
		}
	},
	
	specialAll: {
		live: {
			setup: function( selector, namespaces ){
				jQuery.event.add( this, namespaces[0], liveHandler );
			},
			teardown:  function( namespaces ){
				if ( namespaces.length ) {
					var remove = 0, name = RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
					
					jQuery.each( (jQuery.data(this, "events").live || {}), function(){
						if ( name.test(this.type) )
							remove++;
					});
					
					if ( remove < 1 )
						jQuery.event.remove( this, namespaces[0], liveHandler );
				}
			}
		}
	}
};

jQuery.Event = function( src ){
	// Allow instantiation without the 'new' keyword
	if( !this.preventDefault )
		return new jQuery.Event(src);
	
	// Event object
	if( src && src.type ){
		this.originalEvent = src;
		this.type = src.type;
	// Event type
	}else
		this.type = src;

	// timeStamp is buggy for some events on Firefox(#3843)
	// So we won't rely on the native value
	this.timeStamp = now();
	
	// Mark it as fixed
	this[expando] = true;
};

function returnFalse(){
	return false;
}
function returnTrue(){
	return true;
}

// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jQuery.Event.prototype = {
	preventDefault: function() {
		this.isDefaultPrevented = returnTrue;

		var e = this.originalEvent;
		if( !e )
			return;
		// if preventDefault exists run it on the original event
		if (e.preventDefault)
			e.preventDefault();
		// otherwise set the returnValue property of the original event to false (IE)
		e.returnValue = false;
	},
	stopPropagation: function() {
		this.isPropagationStopped = returnTrue;

		var e = this.originalEvent;
		if( !e )
			return;
		// if stopPropagation exists run it on the original event
		if (e.stopPropagation)
			e.stopPropagation();
		// otherwise set the cancelBubble property of the original event to true (IE)
		e.cancelBubble = true;
	},
	stopImmediatePropagation:function(){
		this.isImmediatePropagationStopped = returnTrue;
		this.stopPropagation();
	},
	isDefaultPrevented: returnFalse,
	isPropagationStopped: returnFalse,
	isImmediatePropagationStopped: returnFalse
};
// Checks if an event happened on an element within another element
// Used in jQuery.event.special.mouseenter and mouseleave handlers
var withinElement = function(event) {
	// Check if mouse(over|out) are still within the same parent element
	var parent = event.relatedTarget;
	// Traverse up the tree
	while ( parent && parent != this )
		try { parent = parent.parentNode; }
		catch(e) { parent = this; }
	
	if( parent != this ){
		// set the correct event type
		event.type = event.data;
		// handle event if we actually just moused on to a non sub-element
		jQuery.event.handle.apply( this, arguments );
	}
};
	
jQuery.each({ 
	mouseover: 'mouseenter', 
	mouseout: 'mouseleave'
}, function( orig, fix ){
	jQuery.event.special[ fix ] = {
		setup: function(){
			jQuery.event.add( this, orig, withinElement, fix );
		},
		teardown: function(){
			jQuery.event.remove( this, orig, withinElement );
		}
	};			   
});

jQuery.fn.extend({
	bind: function( type, data, fn ) {
		return type == "unload" ? this.one(type, data, fn) : this.each(function(){
			jQuery.event.add( this, type, fn || data, fn && data );
		});
	},

	one: function( type, data, fn ) {
		var one = jQuery.event.proxy( fn || data, function(event) {
			jQuery(this).unbind(event, one);
			return (fn || data).apply( this, arguments );
		});
		return this.each(function(){
			jQuery.event.add( this, type, one, fn && data);
		});
	},

	unbind: function( type, fn ) {
		return this.each(function(){
			jQuery.event.remove( this, type, fn );
		});
	},

	trigger: function( type, data ) {
		return this.each(function(){
			jQuery.event.trigger( type, data, this );
		});
	},

	triggerHandler: function( type, data ) {
		if( this[0] ){
			var event = jQuery.Event(type);
			event.preventDefault();
			event.stopPropagation();
			jQuery.event.trigger( event, data, this[0] );
			return event.result;
		}		
	},

	toggle: function( fn ) {
		// Save reference to arguments for access in closure
		var args = arguments, i = 1;

		// link all the functions, so any of them can unbind this click handler
		while( i < args.length )
			jQuery.event.proxy( fn, args[i++] );

		return this.click( jQuery.event.proxy( fn, function(event) {
			// Figure out which function to execute
			this.lastToggle = ( this.lastToggle || 0 ) % i;

			// Make sure that clicks stop
			event.preventDefault();

			// and execute the function
			return args[ this.lastToggle++ ].apply( this, arguments ) || false;
		}));
	},

	hover: function(fnOver, fnOut) {
		return this.mouseenter(fnOver).mouseleave(fnOut);
	},

	ready: function(fn) {
		// Attach the listeners
		bindReady();

		// If the DOM is already ready
		if ( jQuery.isReady )
			// Execute the function immediately
			fn.call( document, jQuery );

		// Otherwise, remember the function for later
		else
			// Add the function to the wait list
			jQuery.readyList.push( fn );

		return this;
	},
	
	live: function( type, fn ){
		var proxy = jQuery.event.proxy( fn );
		proxy.guid += this.selector + type;

		jQuery(document).bind( liveConvert(type, this.selector), this.selector, proxy );

		return this;
	},
	
	die: function( type, fn ){
		jQuery(document).unbind( liveConvert(type, this.selector), fn ? { guid: fn.guid + this.selector + type } : null );
		return this;
	}
});

function liveHandler( event ){
	var check = RegExp("(^|\\.)" + event.type + "(\\.|$)"),
		stop = true,
		elems = [];

	jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){
		if ( check.test(fn.type) ) {
			var elem = jQuery(event.target).closest(fn.data)[0];
			if ( elem )
				elems.push({ elem: elem, fn: fn });
		}
	});

	elems.sort(function(a,b) {
		return jQuery.data(a.elem, "closest") - jQuery.data(b.elem, "closest");
	});
	
	jQuery.each(elems, function(){
		if ( this.fn.call(this.elem, event, this.fn.data) === false )
			return (stop = false);
	});

	return stop;
}

function liveConvert(type, selector){
	return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join(".");
}

jQuery.extend({
	isReady: false,
	readyList: [],
	// Handle when the DOM is ready
	ready: function() {
		// Make sure that the DOM is not already loaded
		if ( !jQuery.isReady ) {
			// Remember that the DOM is ready
			jQuery.isReady = true;

			// If there are functions bound, to execute
			if ( jQuery.readyList ) {
				// Execute all of them
				jQuery.each( jQuery.readyList, function(){
					this.call( document, jQuery );
				});

				// Reset the list of functions
				jQuery.readyList = null;
			}

			// Trigger any bound ready events
			jQuery(document).triggerHandler("ready");
		}
	}
});

var readyBound = false;

function bindReady(){
	if ( readyBound ) return;
	readyBound = true;

	// Mozilla, Opera and webkit nightlies currently support this event
	if ( document.addEventListener ) {
		// Use the handy event callback
		document.addEventListener( "DOMContentLoaded", function(){
			document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
			jQuery.ready();
		}, false );

	// If IE event model is used
	} else if ( document.attachEvent ) {
		// ensure firing before onload,
		// maybe late but safe also for iframes
		document.attachEvent("onreadystatechange", function(){
			if ( document.readyState === "complete" ) {
				document.detachEvent( "onreadystatechange", arguments.callee );
				jQuery.ready();
			}
		});

		// If IE and not an iframe
		// continually check to see if the document is ready
		if ( document.documentElement.doScroll && window == window.top ) (function(){
			if ( jQuery.isReady ) return;

			try {
				// If IE is used, use the trick by Diego Perini
				// http://javascript.nwbox.com/IEContentLoaded/
				document.documentElement.doScroll("left");
			} catch( error ) {
				setTimeout( arguments.callee, 0 );
				return;
			}

			// and execute any waiting functions
			jQuery.ready();
		})();
	}

	// A fallback to window.onload, that will always work
	jQuery.event.add( window, "load", jQuery.ready );
}

jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
	"mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave," +
	"change,select,submit,keydown,keypress,keyup,error").split(","), function(i, name){

	// Handle event binding
	jQuery.fn[name] = function(fn){
		return fn ? this.bind(name, fn) : this.trigger(name);
	};
});

// Prevent memory leaks in IE
// And prevent errors on refresh with events like mouseover in other browsers
// Window isn't included so as not to unbind existing unload events
jQuery( window ).bind( 'unload', function(){ 
	for ( var id in jQuery.cache )
		// Skip the window
		if ( id != 1 && jQuery.cache[ id ].handle )
			jQuery.event.remove( jQuery.cache[ id ].handle.elem );
}); 
(function(){

	jQuery.support = {};

	var root = document.documentElement,
		script = document.createElement("script"),
		div = document.createElement("div"),
		id = "script" + (new Date).getTime();

	div.style.display = "none";
	div.innerHTML = '   <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a</a><select><option>text</option></select><object><param/></object>';

	var all = div.getElementsByTagName("*"),
		a = div.getElementsByTagName("a")[0];

	// Can't get basic test support
	if ( !all || !all.length || !a ) {
		return;
	}

	jQuery.support = {
		// IE strips leading whitespace when .innerHTML is used
		leadingWhitespace: div.firstChild.nodeType == 3,
		
		// Make sure that tbody elements aren't automatically inserted
		// IE will insert them into empty tables
		tbody: !div.getElementsByTagName("tbody").length,
		
		// Make sure that you can get all elements in an <object> element
		// IE 7 always returns no results
		objectAll: !!div.getElementsByTagName("object")[0]
			.getElementsByTagName("*").length,
		
		// Make sure that link elements get serialized correctly by innerHTML
		// This requires a wrapper element in IE
		htmlSerialize: !!div.getElementsByTagName("link").length,
		
		// Get the style information from getAttribute
		// (IE uses .cssText insted)
		style: /red/.test( a.getAttribute("style") ),
		
		// Make sure that URLs aren't manipulated
		// (IE normalizes it by default)
		hrefNormalized: a.getAttribute("href") === "/a",
		
		// Make sure that element opacity exists
		// (IE uses filter instead)
		opacity: a.style.opacity === "0.5",
		
		// Verify style float existence
		// (IE uses styleFloat instead of cssFloat)
		cssFloat: !!a.style.cssFloat,

		// Will be defined later
		scriptEval: false,
		noCloneEvent: true,
		boxModel: null
	};
	
	script.type = "text/javascript";
	try {
		script.appendChild( document.createTextNode( "window." + id + "=1;" ) );
	} catch(e){}

	root.insertBefore( script, root.firstChild );
	
	// Make sure that the execution of code works by injecting a script
	// tag with appendChild/createTextNode
	// (IE doesn't support this, fails, and uses .text instead)
	if ( window[ id ] ) {
		jQuery.support.scriptEval = true;
		delete window[ id ];
	}

	root.removeChild( script );

	if ( div.attachEvent && div.fireEvent ) {
		div.attachEvent("onclick", function(){
			// Cloning a node shouldn't copy over any
			// bound event handlers (IE does this)
			jQuery.support.noCloneEvent = false;
			div.detachEvent("onclick", arguments.callee);
		});
		div.cloneNode(true).fireEvent("onclick");
	}

	// Figure out if the W3C box model works as expected
	// document.body must exist before we can do this
	jQuery(function(){
		var div = document.createElement("div");
		div.style.width = div.style.paddingLeft = "1px";

		document.body.appendChild( div );
		jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2;
		document.body.removeChild( div ).style.display = 'none';
	});
})();

var styleFloat = jQuery.support.cssFloat ? "cssFloat" : "styleFloat";

jQuery.props = {
	"for": "htmlFor",
	"class": "className",
	"float": styleFloat,
	cssFloat: styleFloat,
	styleFloat: styleFloat,
	readonly: "readOnly",
	maxlength: "maxLength",
	cellspacing: "cellSpacing",
	rowspan: "rowSpan",
	tabindex: "tabIndex"
};
jQuery.fn.extend({
	// Keep a copy of the old load
	_load: jQuery.fn.load,

	load: function( url, params, callback ) {
		if ( typeof url !== "string" )
			return this._load( url );

		var off = url.indexOf(" ");
		if ( off >= 0 ) {
			var selector = url.slice(off, url.length);
			url = url.slice(0, off);
		}

		// Default to a GET request
		var type = "GET";

		// If the second parameter was provided
		if ( params )
			// If it's a function
			if ( jQuery.isFunction( params ) ) {
				// We assume that it's the callback
				callback = params;
				params = null;

			// Otherwise, build a param string
			} else if( typeof params === "object" ) {
				params = jQuery.param( params );
				type = "POST";
			}

		var self = this;

		// Request the remote document
		jQuery.ajax({
			url: url,
			type: type,
			dataType: "html",
			data: params,
			complete: function(res, status){
				// If successful, inject the HTML into all the matched elements
				if ( status == "success" || status == "notmodified" )
					// See if a selector was specified
					self.html( selector ?
						// Create a dummy div to hold the results
						jQuery("<div/>")
							// inject the contents of the document in, removing the scripts
							// to avoid any 'Permission Denied' errors in IE
							.append(res.responseText.replace(/<script(.|\s)*?\/script>/g, ""))

							// Locate the specified elements
							.find(selector) :

						// If not, just inject the full result
						res.responseText );

				if( callback )
					self.each( callback, [res.responseText, status, res] );
			}
		});
		return this;
	},

	serialize: function() {
		return jQuery.param(this.serializeArray());
	},
	serializeArray: function() {
		return this.map(function(){
			return this.elements ? jQuery.makeArray(this.elements) : this;
		})
		.filter(function(){
			return this.name && !this.disabled &&
				(this.checked || /select|textarea/i.test(this.nodeName) ||
					/text|hidden|password|search/i.test(this.type));
		})
		.map(function(i, elem){
			var val = jQuery(this).val();
			return val == null ? null :
				jQuery.isArray(val) ?
					jQuery.map( val, function(val, i){
						return {name: elem.name, value: val};
					}) :
					{name: elem.name, value: val};
		}).get();
	}
});

// Attach a bunch of functions for handling common AJAX events
jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){
	jQuery.fn[o] = function(f){
		return this.bind(o, f);
	};
});

var jsc = now();

jQuery.extend({
  
	get: function( url, data, callback, type ) {
		// shift arguments if data argument was ommited
		if ( jQuery.isFunction( data ) ) {
			callback = data;
			data = null;
		}

		return jQuery.ajax({
			type: "GET",
			url: url,
			data: data,
			success: callback,
			dataType: type
		});
	},

	getScript: function( url, callback ) {
		return jQuery.get(url, null, callback, "script");
	},

	getJSON: function( url, data, callback ) {
		return jQuery.get(url, data, callback, "json");
	},

	post: function( url, data, callback, type ) {
		if ( jQuery.isFunction( data ) ) {
			callback = data;
			data = {};
		}

		return jQuery.ajax({
			type: "POST",
			url: url,
			data: data,
			success: callback,
			dataType: type
		});
	},

	ajaxSetup: function( settings ) {
		jQuery.extend( jQuery.ajaxSettings, settings );
	},

	ajaxSettings: {
		url: location.href,
		global: true,
		type: "GET",
		contentType: "application/x-www-form-urlencoded",
		processData: true,
		async: true,
		/*
		timeout: 0,
		data: null,
		username: null,
		password: null,
		*/
		// Create the request object; Microsoft failed to properly
		// implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available
		// This function can be overriden by calling jQuery.ajaxSetup
		xhr:function(){
			return window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
		},
		accepts: {
			xml: "application/xml, text/xml",
			html: "text/html",
			script: "text/javascript, application/javascript",
			json: "application/json, text/javascript",
			text: "text/plain",
			_default: "*/*"
		}
	},

	// Last-Modified header cache for next request
	lastModified: {},

	ajax: function( s ) {
		// Extend the settings, but re-extend 's' so that it can be
		// checked again later (in the test suite, specifically)
		s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));

		var jsonp, jsre = /=\?(&|$)/g, status, data,
			type = s.type.toUpperCase();

		// convert data if not already a string
		if ( s.data && s.processData && typeof s.data !== "string" )
			s.data = jQuery.param(s.data);

		// Handle JSONP Parameter Callbacks
		if ( s.dataType == "jsonp" ) {
			if ( type == "GET" ) {
				if ( !s.url.match(jsre) )
					s.url += (s.url.match(/\?/) ? "&" : "?") + (s.jsonp || "callback") + "=?";
			} else if ( !s.data || !s.data.match(jsre) )
				s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
			s.dataType = "json";
		}

		// Build temporary JSONP function
		if ( s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre)) ) {
			jsonp = "jsonp" + jsc++;

			// Replace the =? sequence both in the query string and the data
			if ( s.data )
				s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
			s.url = s.url.replace(jsre, "=" + jsonp + "$1");

			// We need to make sure
			// that a JSONP style response is executed properly
			s.dataType = "script";

			// Handle JSONP-style loading
			window[ jsonp ] = function(tmp){
				data = tmp;
				success();
				complete();
				// Garbage collect
				window[ jsonp ] = undefined;
				try{ delete window[ jsonp ]; } catch(e){}
				if ( head )
					head.removeChild( script );
			};
		}

		if ( s.dataType == "script" && s.cache == null )
			s.cache = false;

		if ( s.cache === false && type == "GET" ) {
			var ts = now();
			// try replacing _= if it is there
			var ret = s.url.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2");
			// if nothing was replaced, add timestamp to the end
			s.url = ret + ((ret == s.url) ? (s.url.match(/\?/) ? "&" : "?") + "_=" + ts : "");
		}

		// If data is available, append data to url for get requests
		if ( s.data && type == "GET" ) {
			s.url += (s.url.match(/\?/) ? "&" : "?") + s.data;

			// IE likes to send both get and post data, prevent this
			s.data = null;
		}

		// Watch for a new set of requests
		if ( s.global && ! jQuery.active++ )
			jQuery.event.trigger( "ajaxStart" );

		// Matches an absolute URL, and saves the domain
		var parts = /^(\w+:)?\/\/([^\/?#]+)/.exec( s.url );

		// If we're requesting a remote document
		// and trying to load JSON or Script with a GET
		if ( s.dataType == "script" && type == "GET" && parts
			&& ( parts[1] && parts[1] != location.protocol || parts[2] != location.host )){

			var head = document.getElementsByTagName("head")[0];
			var script = document.createElement("script");
			script.src = s.url;
			if (s.scriptCharset)
				script.charset = s.scriptCharset;

			// Handle Script loading
			if ( !jsonp ) {
				var done = false;

				// Attach handlers for all browsers
				script.onload = script.onreadystatechange = function(){
					if ( !done && (!this.readyState ||
							this.readyState == "loaded" || this.readyState == "complete") ) {
						done = true;
						success();
						complete();

						// Handle memory leak in IE
						script.onload = script.onreadystatechange = null;
						head.removeChild( script );
					}
				};
			}

			head.appendChild(script);

			// We handle everything using the script element injection
			return undefined;
		}

		var requestDone = false;

		// Create the request object
		var xhr = s.xhr();

		// Open the socket
		// Passing null username, generates a login popup on Opera (#2865)
		if( s.username )
			xhr.open(type, s.url, s.async, s.username, s.password);
		else
			xhr.open(type, s.url, s.async);

		// Need an extra try/catch for cross domain requests in Firefox 3
		try {
			// Set the correct header, if data is being sent
			if ( s.data )
				xhr.setRequestHeader("Content-Type", s.contentType);

			// Set the If-Modified-Since header, if ifModified mode.
			if ( s.ifModified )
				xhr.setRequestHeader("If-Modified-Since",
					jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" );

			// Set header so the called script knows that it's an XMLHttpRequest
			xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");

			// Set the Accepts header for the server, depending on the dataType
			xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ?
				s.accepts[ s.dataType ] + ", */*" :
				s.accepts._default );
		} catch(e){}

		// Allow custom headers/mimetypes and early abort
		if ( s.beforeSend && s.beforeSend(xhr, s) === false ) {
			// Handle the global AJAX counter
			if ( s.global && ! --jQuery.active )
				jQuery.event.trigger( "ajaxStop" );
			// close opended socket
			xhr.abort();
			return false;
		}

		if ( s.global )
			jQuery.event.trigger("ajaxSend", [xhr, s]);

		// Wait for a response to come back
		var onreadystatechange = function(isTimeout){
			// The request was aborted, clear the interval and decrement jQuery.active
			if (xhr.readyState == 0) {
				if (ival) {
					// clear poll interval
					clearInterval(ival);
					ival = null;
					// Handle the global AJAX counter
					if ( s.global && ! --jQuery.active )
						jQuery.event.trigger( "ajaxStop" );
				}
			// The transfer is complete and the data is available, or the request timed out
			} else if ( !requestDone && xhr && (xhr.readyState == 4 || isTimeout == "timeout") ) {
				requestDone = true;

				// clear poll interval
				if (ival) {
					clearInterval(ival);
					ival = null;
				}

				status = isTimeout == "timeout" ? "timeout" :
					!jQuery.httpSuccess( xhr ) ? "error" :
					s.ifModified && jQuery.httpNotModified( xhr, s.url ) ? "notmodified" :
					"success";

				if ( status == "success" ) {
					// Watch for, and catch, XML document parse errors
					try {
						// process the data (runs the xml through httpData regardless of callback)
						data = jQuery.httpData( xhr, s.dataType, s );
					} catch(e) {
						status = "parsererror";
					}
				}

				// Make sure that the request was successful or notmodified
				if ( status == "success" ) {
					// Cache Last-Modified header, if ifModified mode.
					var modRes;
					try {
						modRes = xhr.getResponseHeader("Last-Modified");
					} catch(e) {} // swallow exception thrown by FF if header is not available

					if ( s.ifModified && modRes )
						jQuery.lastModified[s.url] = modRes;

					// JSONP handles its own success callback
					if ( !jsonp )
						success();
				} else
					jQuery.handleError(s, xhr, status);

				// Fire the complete handlers
				complete();

				if ( isTimeout )
					xhr.abort();

				// Stop memory leaks
				if ( s.async )
					xhr = null;
			}
		};

		if ( s.async ) {
			// don't attach the handler to the request, just poll it instead
			var ival = setInterval(onreadystatechange, 13);

			// Timeout checker
			if ( s.timeout > 0 )
				setTimeout(function(){
					// Check to see if the request is still happening
					if ( xhr && !requestDone )
						onreadystatechange( "timeout" );
				}, s.timeout);
		}

		// Send the data
		try {
			xhr.send(s.data);
		} catch(e) {
			jQuery.handleError(s, xhr, null, e);
		}

		// firefox 1.5 doesn't fire statechange for sync requests
		if ( !s.async )
			onreadystatechange();

		function success(){
			// If a local callback was specified, fire it and pass it the data
			if ( s.success )
				s.success( data, status );

			// Fire the global callback
			if ( s.global )
				jQuery.event.trigger( "ajaxSuccess", [xhr, s] );
		}

		function complete(){
			// Process result
			if ( s.complete )
				s.complete(xhr, status);

			// The request was completed
			if ( s.global )
				jQuery.event.trigger( "ajaxComplete", [xhr, s] );

			// Handle the global AJAX counter
			if ( s.global && ! --jQuery.active )
				jQuery.event.trigger( "ajaxStop" );
		}

		// return XMLHttpRequest to allow aborting the request etc.
		return xhr;
	},

	handleError: function( s, xhr, status, e ) {
		// If a local callback was specified, fire it
		if ( s.error ) s.error( xhr, status, e );

		// Fire the global callback
		if ( s.global )
			jQuery.event.trigger( "ajaxError", [xhr, s, e] );
	},

	// Counter for holding the number of active queries
	active: 0,

	// Determines if an XMLHttpRequest was successful or not
	httpSuccess: function( xhr ) {
		try {
			// IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
			return !xhr.status && location.protocol == "file:" ||
				( xhr.status >= 200 && xhr.status < 300 ) || xhr.status == 304 || xhr.status == 1223;
		} catch(e){}
		return false;
	},

	// Determines if an XMLHttpRequest returns NotModified
	httpNotModified: function( xhr, url ) {
		try {
			var xhrRes = xhr.getResponseHeader("Last-Modified");

			// Firefox always returns 200. check Last-Modified date
			return xhr.status == 304 || xhrRes == jQuery.lastModified[url];
		} catch(e){}
		return false;
	},

	httpData: function( xhr, type, s ) {
		var ct = xhr.getResponseHeader("content-type"),
			xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0,
			data = xml ? xhr.responseXML : xhr.responseText;

		if ( xml && data.documentElement.tagName == "parsererror" )
			throw "parsererror";
			
		// Allow a pre-filtering function to sanitize the response
		// s != null is checked to keep backwards compatibility
		if( s && s.dataFilter )
			data = s.dataFilter( data, type );

		// The filter can actually parse the response
		if( typeof data === "string" ){

			// If the type is "script", eval it in global context
			if ( type == "script" )
				jQuery.globalEval( data );

			// Get the JavaScript object, if JSON is used.
			if ( type == "json" )
				data = window["eval"]("(" + data + ")");
		}
		
		return data;
	},

	// Serialize an array of form elements or a set of
	// key/values into a query string
	param: function( a ) {
		var s = [ ];

		function add( key, value ){
			s[ s.length ] = encodeURIComponent(key) + '=' + encodeURIComponent(value);
		};

		// If an array was passed in, assume that it is an array
		// of form elements
		if ( jQuery.isArray(a) || a.jquery )
			// Serialize the form elements
			jQuery.each( a, function(){
				add( this.name, this.value );
			});

		// Otherwise, assume that it's an object of key/value pairs
		else
			// Serialize the key/values
			for ( var j in a )
				// If the value is an array then the key names need to be repeated
				if ( jQuery.isArray(a[j]) )
					jQuery.each( a[j], function(){
						add( j, this );
					});
				else
					add( j, jQuery.isFunction(a[j]) ? a[j]() : a[j] );

		// Return the resulting serialization
		return s.join("&").replace(/%20/g, "+");
	}

});
var elemdisplay = {},
	timerId,
	fxAttrs = [
		// height animations
		[ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
		// width animations
		[ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
		// opacity animations
		[ "opacity" ]
	];

function genFx( type, num ){
	var obj = {};
	jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function(){
		obj[ this ] = type;
	});
	return obj;
}

jQuery.fn.extend({
	show: function(speed,callback){
		if ( speed ) {
			return this.animate( genFx("show", 3), speed, callback);
		} else {
			for ( var i = 0, l = this.length; i < l; i++ ){
				var old = jQuery.data(this[i], "olddisplay");
				
				this[i].style.display = old || "";
				
				if ( jQuery.css(this[i], "display") === "none" ) {
					var tagName = this[i].tagName, display;
					
					if ( elemdisplay[ tagName ] ) {
						display = elemdisplay[ tagName ];
					} else {
						var elem = jQuery("<" + tagName + " />").appendTo("body");
						
						display = elem.css("display");
						if ( display === "none" )
							display = "block";
						
						elem.remove();
						
						elemdisplay[ tagName ] = display;
					}
					
					jQuery.data(this[i], "olddisplay", display);
				}
			}

			// Set the display of the elements in a second loop
			// to avoid the constant reflow
			for ( var i = 0, l = this.length; i < l; i++ ){
				this[i].style.display = jQuery.data(this[i], "olddisplay") || "";
			}
			
			return this;
		}
	},

	hide: function(speed,callback){
		if ( speed ) {
			return this.animate( genFx("hide", 3), speed, callback);
		} else {
			for ( var i = 0, l = this.length; i < l; i++ ){
				var old = jQuery.data(this[i], "olddisplay");
				if ( !old && old !== "none" )
					jQuery.data(this[i], "olddisplay", jQuery.css(this[i], "display"));
			}

			// Set the display of the elements in a second loop
			// to avoid the constant reflow
			for ( var i = 0, l = this.length; i < l; i++ ){
				this[i].style.display = "none";
			}

			return this;
		}
	},

	// Save the old toggle function
	_toggle: jQuery.fn.toggle,

	toggle: function( fn, fn2 ){
		var bool = typeof fn === "boolean";

		return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ?
			this._toggle.apply( this, arguments ) :
			fn == null || bool ?
				this.each(function(){
					var state = bool ? fn : jQuery(this).is(":hidden");
					jQuery(this)[ state ? "show" : "hide" ]();
				}) :
				this.animate(genFx("toggle", 3), fn, fn2);
	},

	fadeTo: function(speed,to,callback){
		return this.animate({opacity: to}, speed, callback);
	},

	animate: function( prop, speed, easing, callback ) {
		var optall = jQuery.speed(speed, easing, callback);

		return this[ optall.queue === false ? "each" : "queue" ](function(){
		
			var opt = jQuery.extend({}, optall), p,
				hidden = this.nodeType == 1 && jQuery(this).is(":hidden"),
				self = this;
	
			for ( p in prop ) {
				if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden )
					return opt.complete.call(this);

				if ( ( p == "height" || p == "width" ) && this.style ) {
					// Store display property
					opt.display = jQuery.css(this, "display");

					// Make sure that nothing sneaks out
					opt.overflow = this.style.overflow;
				}
			}

			if ( opt.overflow != null )
				this.style.overflow = "hidden";

			opt.curAnim = jQuery.extend({}, prop);

			jQuery.each( prop, function(name, val){
				var e = new jQuery.fx( self, opt, name );

				if ( /toggle|show|hide/.test(val) )
					e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop );
				else {
					var parts = val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),
						start = e.cur(true) || 0;

					if ( parts ) {
						var end = parseFloat(parts[2]),
							unit = parts[3] || "px";

						// We need to compute starting value
						if ( unit != "px" ) {
							self.style[ name ] = (end || 1) + unit;
							start = ((end || 1) / e.cur(true)) * start;
							self.style[ name ] = start + unit;
						}

						// If a +=/-= token was provided, we're doing a relative animation
						if ( parts[1] )
							end = ((parts[1] == "-=" ? -1 : 1) * end) + start;

						e.custom( start, end, unit );
					} else
						e.custom( start, val, "" );
				}
			});

			// For JS strict compliance
			return true;
		});
	},

	stop: function(clearQueue, gotoEnd){
		var timers = jQuery.timers;

		if (clearQueue)
			this.queue([]);

		this.each(function(){
			// go in reverse order so anything added to the queue during the loop is ignored
			for ( var i = timers.length - 1; i >= 0; i-- )
				if ( timers[i].elem == this ) {
					if (gotoEnd)
						// force the next step to be the last
						timers[i](true);
					timers.splice(i, 1);
				}
		});

		// start the next in the queue if the last step wasn't forced
		if (!gotoEnd)
			this.dequeue();

		return this;
	}

});

// Generate shortcuts for custom animations
jQuery.each({
	slideDown: genFx("show", 1),
	slideUp: genFx("hide", 1),
	slideToggle: genFx("toggle", 1),
	fadeIn: { opacity: "show" },
	fadeOut: { opacity: "hide" }
}, function( name, props ){
	jQuery.fn[ name ] = function( speed, callback ){
		return this.animate( props, speed, callback );
	};
});

jQuery.extend({

	speed: function(speed, easing, fn) {
		var opt = typeof speed === "object" ? speed : {
			complete: fn || !fn && easing ||
				jQuery.isFunction( speed ) && speed,
			duration: speed,
			easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
		};

		opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
			jQuery.fx.speeds[opt.duration] || jQuery.fx.speeds._default;

		// Queueing
		opt.old = opt.complete;
		opt.complete = function(){
			if ( opt.queue !== false )
				jQuery(this).dequeue();
			if ( jQuery.isFunction( opt.old ) )
				opt.old.call( this );
		};

		return opt;
	},

	easing: {
		linear: function( p, n, firstNum, diff ) {
			return firstNum + diff * p;
		},
		swing: function( p, n, firstNum, diff ) {
			return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
		}
	},

	timers: [],

	fx: function( elem, options, prop ){
		this.options = options;
		this.elem = elem;
		this.prop = prop;

		if ( !options.orig )
			options.orig = {};
	}

});

jQuery.fx.prototype = {

	// Simple function for setting a style value
	update: function(){
		if ( this.options.step )
			this.options.step.call( this.elem, this.now, this );

		(jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );

		// Set display property to block for height/width animations
		if ( ( this.prop == "height" || this.prop == "width" ) && this.elem.style )
			this.elem.style.display = "block";
	},

	// Get the current size
	cur: function(force){
		if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) )
			return this.elem[ this.prop ];

		var r = parseFloat(jQuery.css(this.elem, this.prop, force));
		return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0;
	},

	// Start an animation from one number to another
	custom: function(from, to, unit){
		this.startTime = now();
		this.start = from;
		this.end = to;
		this.unit = unit || this.unit || "px";
		this.now = this.start;
		this.pos = this.state = 0;

		var self = this;
		function t(gotoEnd){
			return self.step(gotoEnd);
		}

		t.elem = this.elem;

		if ( t() && jQuery.timers.push(t) && !timerId ) {
			timerId = setInterval(function(){
				var timers = jQuery.timers;

				for ( var i = 0; i < timers.length; i++ )
					if ( !timers[i]() )
						timers.splice(i--, 1);

				if ( !timers.length ) {
					clearInterval( timerId );
					timerId = undefined;
				}
			}, 13);
		}
	},

	// Simple 'show' function
	show: function(){
		// Remember where we started, so that we can go back to it later
		this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
		this.options.show = true;

		// Begin the animation
		// Make sure that we start at a small width/height to avoid any
		// flash of content
		this.custom(this.prop == "width" || this.prop == "height" ? 1 : 0, this.cur());

		// Start by showing the element
		jQuery(this.elem).show();
	},

	// Simple 'hide' function
	hide: function(){
		// Remember where we started, so that we can go back to it later
		this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
		this.options.hide = true;

		// Begin the animation
		this.custom(this.cur(), 0);
	},

	// Each step of an animation
	step: function(gotoEnd){
		var t = now();

		if ( gotoEnd || t >= this.options.duration + this.startTime ) {
			this.now = this.end;
			this.pos = this.state = 1;
			this.update();

			this.options.curAnim[ this.prop ] = true;

			var done = true;
			for ( var i in this.options.curAnim )
				if ( this.options.curAnim[i] !== true )
					done = false;

			if ( done ) {
				if ( this.options.display != null ) {
					// Reset the overflow
					this.elem.style.overflow = this.options.overflow;

					// Reset the display
					this.elem.style.display = this.options.display;
					if ( jQuery.css(this.elem, "display") == "none" )
						this.elem.style.display = "block";
				}

				// Hide the element if the "hide" operation was done
				if ( this.options.hide )
					jQuery(this.elem).hide();

				// Reset the properties, if the item has been hidden or shown
				if ( this.options.hide || this.options.show )
					for ( var p in this.options.curAnim )
						jQuery.attr(this.elem.style, p, this.options.orig[p]);
					
				// Execute the complete function
				this.options.complete.call( this.elem );
			}

			return false;
		} else {
			var n = t - this.startTime;
			this.state = n / this.options.duration;

			// Perform the easing function, defaults to swing
			this.pos = jQuery.easing[this.options.easing || (jQuery.easing.swing ? "swing" : "linear")](this.state, n, 0, 1, this.options.duration);
			this.now = this.start + ((this.end - this.start) * this.pos);

			// Perform the next step of the animation
			this.update();
		}

		return true;
	}

};

jQuery.extend( jQuery.fx, {
	speeds:{
		slow: 600,
 		fast: 200,
 		// Default speed
 		_default: 400
	},
	step: {

		opacity: function(fx){
			jQuery.attr(fx.elem.style, "opacity", fx.now);
		},

		_default: function(fx){
			if ( fx.elem.style && fx.elem.style[ fx.prop ] != null )
				fx.elem.style[ fx.prop ] = fx.now + fx.unit;
			else
				fx.elem[ fx.prop ] = fx.now;
		}
	}
});
if ( document.documentElement["getBoundingClientRect"] )
	jQuery.fn.offset = function() {
		if ( !this[0] ) return { top: 0, left: 0 };
		if ( this[0] === this[0].ownerDocument.body ) return jQuery.offset.bodyOffset( this[0] );
		var box  = this[0].getBoundingClientRect(), doc = this[0].ownerDocument, body = doc.body, docElem = doc.documentElement,
			clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
			top  = box.top  + (self.pageYOffset || jQuery.boxModel && docElem.scrollTop  || body.scrollTop ) - clientTop,
			left = box.left + (self.pageXOffset || jQuery.boxModel && docElem.scrollLeft || body.scrollLeft) - clientLeft;
		return { top: top, left: left };
	};
else 
	jQuery.fn.offset = function() {
		if ( !this[0] ) return { top: 0, left: 0 };
		if ( this[0] === this[0].ownerDocument.body ) return jQuery.offset.bodyOffset( this[0] );
		jQuery.offset.initialized || jQuery.offset.initialize();

		var elem = this[0], offsetParent = elem.offsetParent, prevOffsetParent = elem,
			doc = elem.ownerDocument, computedStyle, docElem = doc.documentElement,
			body = doc.body, defaultView = doc.defaultView,
			prevComputedStyle = defaultView.getComputedStyle(elem, null),
			top = elem.offsetTop, left = elem.offsetLeft;

		while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
			computedStyle = defaultView.getComputedStyle(elem, null);
			top -= elem.scrollTop, left -= elem.scrollLeft;
			if ( elem === offsetParent ) {
				top += elem.offsetTop, left += elem.offsetLeft;
				if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && /^t(able|d|h)$/i.test(elem.tagName)) )
					top  += parseInt( computedStyle.borderTopWidth,  10) || 0,
					left += parseInt( computedStyle.borderLeftWidth, 10) || 0;
				prevOffsetParent = offsetParent, offsetParent = elem.offsetParent;
			}
			if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" )
				top  += parseInt( computedStyle.borderTopWidth,  10) || 0,
				left += parseInt( computedStyle.borderLeftWidth, 10) || 0;
			prevComputedStyle = computedStyle;
		}

		if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" )
			top  += body.offsetTop,
			left += body.offsetLeft;

		if ( prevComputedStyle.position === "fixed" )
			top  += Math.max(docElem.scrollTop, body.scrollTop),
			left += Math.max(docElem.scrollLeft, body.scrollLeft);

		return { top: top, left: left };
	};

jQuery.offset = {
	initialize: function() {
		if ( this.initialized ) return;
		var body = document.body, container = document.createElement('div'), innerDiv, checkDiv, table, td, rules, prop, bodyMarginTop = body.style.marginTop,
			html = '<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';

		rules = { position: 'absolute', top: 0, left: 0, margin: 0, border: 0, width: '1px', height: '1px', visibility: 'hidden' };
		for ( prop in rules ) container.style[prop] = rules[prop];

		container.innerHTML = html;
		body.insertBefore(container, body.firstChild);
		innerDiv = container.firstChild, checkDiv = innerDiv.firstChild, td = innerDiv.nextSibling.firstChild.firstChild;

		this.doesNotAddBorder = (checkDiv.offsetTop !== 5);
		this.doesAddBorderForTableAndCells = (td.offsetTop === 5);

		innerDiv.style.overflow = 'hidden', innerDiv.style.position = 'relative';
		this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);

		body.style.marginTop = '1px';
		this.doesNotIncludeMarginInBodyOffset = (body.offsetTop === 0);
		body.style.marginTop = bodyMarginTop;

		body.removeChild(container);
		this.initialized = true;
	},

	bodyOffset: function(body) {
		jQuery.offset.initialized || jQuery.offset.initialize();
		var top = body.offsetTop, left = body.offsetLeft;
		if ( jQuery.offset.doesNotIncludeMarginInBodyOffset )
			top  += parseInt( jQuery.curCSS(body, 'marginTop',  true), 10 ) || 0,
			left += parseInt( jQuery.curCSS(body, 'marginLeft', true), 10 ) || 0;
		return { top: top, left: left };
	}
};


jQuery.fn.extend({
	position: function() {
		var left = 0, top = 0, results;

		if ( this[0] ) {
			// Get *real* offsetParent
			var offsetParent = this.offsetParent(),

			// Get correct offsets
			offset       = this.offset(),
			parentOffset = /^body|html$/i.test(offsetParent[0].tagName) ? { top: 0, left: 0 } : offsetParent.offset();

			// Subtract element margins
			// note: when an element has margin: auto the offsetLeft and marginLeft 
			// are the same in Safari causing offset.left to incorrectly be 0
			offset.top  -= num( this, 'marginTop'  );
			offset.left -= num( this, 'marginLeft' );

			// Add offsetParent borders
			parentOffset.top  += num( offsetParent, 'borderTopWidth'  );
			parentOffset.left += num( offsetParent, 'borderLeftWidth' );

			// Subtract the two offsets
			results = {
				top:  offset.top  - parentOffset.top,
				left: offset.left - parentOffset.left
			};
		}

		return results;
	},

	offsetParent: function() {
		var offsetParent = this[0].offsetParent || document.body;
		while ( offsetParent && (!/^body|html$/i.test(offsetParent.tagName) && jQuery.css(offsetParent, 'position') == 'static') )
			offsetParent = offsetParent.offsetParent;
		return jQuery(offsetParent);
	}
});


// Create scrollLeft and scrollTop methods
jQuery.each( ['Left', 'Top'], function(i, name) {
	var method = 'scroll' + name;
	
	jQuery.fn[ method ] = function(val) {
		if (!this[0]) return null;

		return val !== undefined ?

			// Set the scroll offset
			this.each(function() {
				this == window || this == document ?
					window.scrollTo(
						!i ? val : jQuery(window).scrollLeft(),
						 i ? val : jQuery(window).scrollTop()
					) :
					this[ method ] = val;
			}) :

			// Return the scroll offset
			this[0] == window || this[0] == document ?
				self[ i ? 'pageYOffset' : 'pageXOffset' ] ||
					jQuery.boxModel && document.documentElement[ method ] ||
					document.body[ method ] :
				this[0][ method ];
	};
});
// Create innerHeight, innerWidth, outerHeight and outerWidth methods
jQuery.each([ "Height", "Width" ], function(i, name){

	var tl = i ? "Left"  : "Top",  // top or left
		br = i ? "Right" : "Bottom", // bottom or right
		lower = name.toLowerCase();

	// innerHeight and innerWidth
	jQuery.fn["inner" + name] = function(){
		return this[0] ?
			jQuery.css( this[0], lower, false, "padding" ) :
			null;
	};

	// outerHeight and outerWidth
	jQuery.fn["outer" + name] = function(margin) {
		return this[0] ?
			jQuery.css( this[0], lower, false, margin ? "margin" : "border" ) :
			null;
	};
	
	var type = name.toLowerCase();

	jQuery.fn[ type ] = function( size ) {
		// Get window width or height
		return this[0] == window ?
			// Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
			document.compatMode == "CSS1Compat" && document.documentElement[ "client" + name ] ||
			document.body[ "client" + name ] :

			// Get document width or height
			this[0] == document ?
				// Either scroll[Width/Height] or offset[Width/Height], whichever is greater
				Math.max(
					document.documentElement["client" + name],
					document.body["scroll" + name], document.documentElement["scroll" + name],
					document.body["offset" + name], document.documentElement["offset" + name]
				) :

				// Get or set width or height on the element
				size === undefined ?
					// Get width or height on the element
					(this.length ? jQuery.css( this[0], type ) : null) :

					// Set the width or height on the element (default to pixels if value is unitless)
					this.css( type, typeof size === "string" ? size : size + "px" );
	};

});
})();
/** 
 * x.js compiled from X 4.0 with XC 0.27b. 
 * Distributed by GNU LGPL. For copyrights, license, documentation and more visit Cross-Browser.com 
 * Copyright 2001-2005 Michael Foster (Cross-Browser.com)
 **/

var xOp7Up,xOp6Dn,xIE4Up,xIE4,xIE5,xIE6,xNN4,xUA=navigator.userAgent.toLowerCase();
if(window.opera){
  var i=xUA.indexOf('opera');
  if(i!=-1){
    var v=parseInt(xUA.charAt(i+6));
    xOp7Up=v>=7;
    xOp6Dn=v<7;
  }
}
else if(navigator.vendor!='KDE' && document.all && xUA.indexOf('msie')!=-1){
  xIE4Up=parseFloat(navigator.appVersion)>=4;
  xIE4=xUA.indexOf('msie 4')!=-1;
  xIE5=xUA.indexOf('msie 5')!=-1;
  xIE6=xUA.indexOf('msie 6')!=-1;
}
else if(document.layers){xNN4=true;}
var xMac=xUA.indexOf('mac')!=-1;
var xFF=xUA.indexOf('firefox')!=-1;

// (element, event(without 'on'), event listener(function name)[, caption])
function xAddEventListener(e,eT,eL,cap)
{
  if(!(e=xGetElementById(e))) return;
  eT=eT.toLowerCase();
  if((!xIE4Up && !xOp7Up) && e==window) {
    if(eT=='resize') { window.xPCW=xClientWidth(); window.xPCH=xClientHeight(); window.xREL=eL; xResizeEvent(); return; }
    if(eT=='scroll') { window.xPSL=xScrollLeft(); window.xPST=xScrollTop(); window.xSEL=eL; xScrollEvent(); return; }
  }
  var eh='e.on'+eT+'=eL';
  if(e.addEventListener) e.addEventListener(eT,eL,cap);
  else if(e.attachEvent) e.attachEvent('on'+eT,eL);
  else eval(eh);
}
// called only from the above
function xResizeEvent()
{
  if (window.xREL) setTimeout('xResizeEvent()', 250);
  var cw = xClientWidth(), ch = xClientHeight();
  if (window.xPCW != cw || window.xPCH != ch) { window.xPCW = cw; window.xPCH = ch; if (window.xREL) window.xREL(); }
}

function xScrollEvent()
{
  if (window.xSEL) setTimeout('xScrollEvent()', 250);
  var sl = xScrollLeft(), st = xScrollTop();
  if (window.xPSL != sl || window.xPST != st) { window.xPSL = sl; window.xPST = st; if (window.xSEL) window.xSEL(); }
}

function xAppendChild(oParent, oChild)
{
  if (oParent.appendChild) return oParent.appendChild(oChild);
  else return null;
}

function xClientHeight()
{
  var h=0;
  if(xOp6Dn) h=window.innerHeight;
  else if(document.compatMode == 'CSS1Compat' && !window.opera && document.documentElement && document.documentElement.clientHeight)
    h=document.documentElement.clientHeight;
  else if(document.body && document.body.clientHeight)
    h=document.body.clientHeight;
  else if(xDef(window.innerWidth,window.innerHeight,document.width)) {
    h=window.innerHeight;
    if(document.width>window.innerWidth) h-=16;
  }
  return h;
}

function xClientWidth()
{
  var w=0;
  if(xOp6Dn) w=window.innerWidth;
  else if(document.compatMode == 'CSS1Compat' && !window.opera && document.documentElement && document.documentElement.clientWidth)
    w=document.documentElement.clientWidth;
  else if(document.body && document.body.clientWidth)
    w=document.body.clientWidth;
  else if(xDef(window.innerWidth,window.innerHeight,document.height)) {
    w=window.innerWidth;
    if(document.height>window.innerHeight) w-=16;
  }
  return w;
}

function xCreateElement(sTag)
{
  if (document.createElement) return document.createElement(sTag);
  else return null;
}

function xDef()
{
  for(var i=0; i<arguments.length; ++i){if(typeof(arguments[i])=='undefined') return false;}
  return true;
}

function xDeleteCookie(name, path)
{
  if (xGetCookie(name)) {
    document.cookie = name + "=" +
                    "; path=" + ((!path) ? "/" : path) +
                    "; expires=" + new Date(0).toGMTString();
  }
}

function xDisplay(e,s)
{
  if(!(e=xGetElementById(e))) return null;
  if(e.style && xDef(e.style.display)) {
    if (xStr(s)) e.style.display = s;
    return e.style.display;
  }
  return null;
}

function xEvent(evt) // object prototype
{
  var e = evt || window.event;
  if(!e) return;
  if(e.type) this.type = e.type;
  if(e.target) this.target = e.target;
  else if(e.srcElement) this.target = e.srcElement;

  // Section B
  if (e.relatedTarget) this.relatedTarget = e.relatedTarget;
  else if (e.type == 'mouseover' && e.fromElement) this.relatedTarget = e.fromElement;
  else if (e.type == 'mouseout') this.relatedTarget = e.toElement;
  // End Section B

  if(xOp6Dn) { this.pageX = e.clientX; this.pageY = e.clientY; }
  else if(xDef(e.pageX,e.pageY)) { this.pageX = e.pageX; this.pageY = e.pageY; }
  else if(xDef(e.clientX,e.clientY)) { this.pageX = e.clientX + xScrollLeft(); this.pageY = e.clientY + xScrollTop(); }

  // Section A
  if (xDef(e.offsetX,e.offsetY)) {
    this.offsetX = e.offsetX;
    this.offsetY = e.offsetY;
  }
  else if (xDef(e.layerX,e.layerY)) {
    this.offsetX = e.layerX;
    this.offsetY = e.layerY;
  }
  else {
    this.offsetX = this.pageX - xPageX(this.target);
    this.offsetY = this.pageY - xPageY(this.target);
  }
  // End Section A
  
  if (e.keyCode) { this.keyCode = e.keyCode; } // for moz/fb, if keyCode==0 use which
  else if (xDef(e.which) && e.type.indexOf('key')!=-1) { this.keyCode = e.which; }

  this.shiftKey = e.shiftKey;
  this.ctrlKey = e.ctrlKey;
  this.altKey = e.altKey;
}

function xFirstChild(e, t)
{
  var c = e ? e.firstChild : null;
  if (t) while (c && c.nodeName != t) { c = c.nextSibling; }
  else while (c && c.nodeType != 1) { c = c.nextSibling; }
  return c;
}

function xGetBodyWidth() {
  var cw = xClientWidth();
  var sw = window.document.body.scrollWidth;
  return cw>sw?cw:sw;
}

function xGetBodyHeight() {
  var cw = xClientHeight();
  var sw = window.document.body.scrollHeight;
  return cw>sw?cw:sw;
}

function xGetComputedStyle(oEle, sProp, bInt)
{
  var s, p = 'undefined';
  var dv = document.defaultView;
  if(dv && dv.getComputedStyle){
    s = dv.getComputedStyle(oEle,'');
    if (s) p = s.getPropertyValue(sProp);
  }
  else if(oEle.currentStyle) {
    // convert css property name to object property name for IE
    var a = sProp.split('-');
    sProp = a[0];
    for (var i=1; i<a.length; ++i) {
      c = a[i].charAt(0);
      sProp += a[i].replace(c, c.toUpperCase());
    }   
    p = oEle.currentStyle[sProp];
  }
  else return null;
  return bInt ? (parseInt(p) || 0) : p;
}

function xGetCookie(name)
{
  var value=null, search=name+"=";
  if (document.cookie.length > 0) {
    var offset = document.cookie.indexOf(search);
    if (offset != -1) {
      offset += search.length;
      var end = document.cookie.indexOf(";", offset);
      if (end == -1) end = document.cookie.length;
      value = unescape(document.cookie.substring(offset, end));
    }
  }
  return value;
}

function xGetElementById(e)
{
  if(typeof(e)!='string') return e;
  if(document.getElementById) e=document.getElementById(e);
  else if(document.all) e=document.all[e];
  else e=null;
  return e;
}

function xGetElementsByAttribute(sTag, sAtt, sRE, fn)
{
  var a, list, found = new Array(), re = new RegExp(sRE, 'i');
  list = xGetElementsByTagName(sTag);
  for (var i = 0; i < list.length; ++i) {
    a = list[i].getAttribute(sAtt);
    if (!a) {a = list[i][sAtt];}
    if (typeof(a)=='string' && a.search(re) != -1) {
      found[found.length] = list[i];
      if (fn) fn(list[i]);
    }
  }
  return found;
}

function xGetElementsByClassName(c,p,t,f)
{
  var found = new Array();
  var re = new RegExp('\\b'+c+'\\b', 'i');
  var list = xGetElementsByTagName(t, p);
  for (var i = 0; i < list.length; ++i) {
    if (list[i].className && list[i].className.search(re) != -1) {
      found[found.length] = list[i];
      if (f) f(list[i]);
    }
  }
  return found;
}

function xGetElementsByTagName(t,p)
{
  var list = null;
  t = t || '*';
  p = p || document;
  if (xIE4 || xIE5) {
    if (t == '*') list = p.all;
    else list = p.all.tags(t);
  }
  else if (p.getElementsByTagName) list = p.getElementsByTagName(t);
  return list || new Array();
}

function xGetURLArguments()
{
  var idx = location.href.indexOf('?');
  var params = new Array();
  if (idx != -1) {
    var pairs = location.href.substring(idx+1, location.href.length).split('&');
    for (var i=0; i<pairs.length; i++) {
      nameVal = pairs[i].split('=');
      params[i] = nameVal[1];
      params[nameVal[0]] = nameVal[1];
    }
  }
  return params;
}

function xHeight(e,h)
{
  if(!(e=xGetElementById(e))) return 0;
  if (xNum(h)) {
    if (h<0) h = 0;
    else h=Math.round(h);
  }
  else h=-1;
  var css=xDef(e.style);
  if (e == document || e.tagName.toLowerCase() == 'html' || e.tagName.toLowerCase() == 'body') {
    h = xClientHeight();
  }
  else if(css && xDef(e.offsetHeight) && xStr(e.style.height)) {
    if(h>=0) {
      var pt=0,pb=0,bt=0,bb=0;
      if (document.compatMode=='CSS1Compat') {
        var gcs = xGetComputedStyle;
        pt=gcs(e,'padding-top',1);
        if (pt !== null) {
          pb=gcs(e,'padding-bottom',1);
          bt=gcs(e,'border-top-width',1);
          bb=gcs(e,'border-bottom-width',1);
        }
        // Should we try this as a last resort?
        // At this point getComputedStyle and currentStyle do not exist.
        else if(xDef(e.offsetHeight,e.style.height)){
          e.style.height=h+'px';
          pt=e.offsetHeight-h;
        }
      }
      h-=(pt+pb+bt+bb);
      if(isNaN(h)||h<0) return null;
      else e.style.height=h+'px';
    }
    h=e.offsetHeight;
  }
  else if(css && xDef(e.style.pixelHeight)) {
    if(h>=0) e.style.pixelHeight=h;
    h=e.style.pixelHeight;
  }
  return h;
}

function xHex(sn, digits, prefix)
{
  var p = '';
  var n = Math.ceil(sn);
  if (prefix) p = prefix;
  n = n.toString(16);
  for (var i=0; i < digits - n.length; ++i) {
    p += '0';
  }
  return p + n;
}

function xHide(e){return xVisibility(e,0);}

function xInnerHtml(e,h)
{
  if(!(e=xGetElementById(e)) || !xStr(e.innerHTML)) return null;
  var s = e.innerHTML;
  if (xStr(h)) {e.innerHTML = h;}
  return s;
}

function xLeft(e, iX)
{
  if(!(e=xGetElementById(e))) return 0;
  var css=xDef(e.style);
  if (css && xStr(e.style.left)) {
    if(xNum(iX)) e.style.left=iX+'px';
    else {
      iX=parseInt(e.style.left);
      if(isNaN(iX)) iX=0;
    }
  }
  else if(css && xDef(e.style.pixelLeft)) {
    if(xNum(iX)) e.style.pixelLeft=iX;
    else iX=e.style.pixelLeft;
  }
  return iX;
}

function xMoveTo(e,x,y)
{
  xLeft(e,x);
  xTop(e,y);
}

function xName(e)
{
  if (!e) return e;
  else if (e.id && e.id != "") return e.id;
  else if (e.name && e.name != "") return e.name;
  else if (e.nodeName && e.nodeName != "") return e.nodeName;
  else if (e.tagName && e.tagName != "") return e.tagName;
  else return e;
}

function xNextSib(e,t)
{
  var s = e ? e.nextSibling : null;
  if (t) while (s && s.nodeName != t) { s = s.nextSibling; }
  else while (s && s.nodeType != 1) { s = s.nextSibling; }
  return s;
}

function xNum()
{
  for(var i=0; i<arguments.length; ++i){if(isNaN(arguments[i]) || typeof(arguments[i])!='number') return false;}
  return true;
}

function xOffsetLeft(e)
{
  if (!(e=xGetElementById(e))) return 0;
  if (xDef(e.offsetLeft)) return e.offsetLeft;
  else return 0;
}

function xOffsetTop(e)
{
  if (!(e=xGetElementById(e))) return 0;
  if (xDef(e.offsetTop)) return e.offsetTop;
  else return 0;
}

function xPad(s,len,c,left)
{
  if(typeof s != 'string') s=s+'';
  if(left) {for(var i=s.length; i<len; ++i) s=c+s;}
  else {for (i=s.length; i<len; ++i) s+=c;}
  return s;
}

function xPageX(e)
{
  if (!(e=xGetElementById(e))) return 0;
  var x = 0;
  while (e) {
    if (xDef(e.offsetLeft)) x += e.offsetLeft;
    e = xDef(e.offsetParent) ? e.offsetParent : null;
  }
  return x;
}

function xPageY(e)
{
  if (!(e=xGetElementById(e))) return 0;
  var y = 0;
  while (e) {
    if (xDef(e.offsetTop)) y += e.offsetTop;
    e = xDef(e.offsetParent) ? e.offsetParent : null;
  }
//  if (xOp7Up) return y - document.body.offsetTop; // v3.14, temporary hack for opera bug 130324 (reported 1nov03)
  return y;
}

function xParent(e, bNode)
{
  if (!(e=xGetElementById(e))) return null;
  var p=null;
  if (!bNode && xDef(e.offsetParent)) p=e.offsetParent;
  else if (xDef(e.parentNode)) p=e.parentNode;
  else if (xDef(e.parentElement)) p=e.parentElement;
  return p;
}

function xPreventDefault(e)
{
  if (e && e.preventDefault) e.preventDefault()
  else if (window.event) window.event.returnValue = false;
}

function xPrevSib(e,t)
{
  var s = e ? e.previousSibling : null;
  if (t) while(s && s.nodeName != t) {s=s.previousSibling;}
  else while(s && s.nodeType != 1) {s=s.previousSibling;}
  return s;
}

function xRemoveEventListener(e,eT,eL,cap)
{
  if(!(e=xGetElementById(e))) return;
  eT=eT.toLowerCase();
  if((!xIE4Up && !xOp7Up) && e==window) {
    if(eT=='resize') { window.xREL=null; return; }
    if(eT=='scroll') { window.xSEL=null; return; }
  }
  var eh='e.on'+eT+'=null';
  if(e.removeEventListener) e.removeEventListener(eT,eL,cap);
  else if(e.detachEvent) e.detachEvent('on'+eT,eL);
  else eval(eh);
}

function xResizeTo(e,w,h)
{
  xWidth(e,w);
  xHeight(e,h);
}

function xScrollLeft(e, bWin)
{
  var offset=0;
  if (!xDef(e) || bWin || e == document || e.tagName.toLowerCase() == 'html' || e.tagName.toLowerCase() == 'body') {
    var w = window;
    if (bWin && e) w = e;
    if(w.document.documentElement && w.document.documentElement.scrollLeft) offset=w.document.documentElement.scrollLeft;
    else if(w.document.body && xDef(w.document.body.scrollLeft)) offset=w.document.body.scrollLeft;
  }
  else {
    e = xGetElementById(e);
    if (e && xNum(e.scrollLeft)) offset = e.scrollLeft;
  }
  return offset;
}

function xScrollTop(e, bWin)
{
  var offset=0;
  if (!xDef(e) || bWin || e == document || e.tagName.toLowerCase() == 'html' || e.tagName.toLowerCase() == 'body') {
    var w = window;
    if (bWin && e) w = e;
    if(w.document.documentElement && w.document.documentElement.scrollTop) offset=w.document.documentElement.scrollTop;
    else if(w.document.body && xDef(w.document.body.scrollTop)) offset=w.document.body.scrollTop;
  }
  else {
    e = xGetElementById(e);
    if (e && xNum(e.scrollTop)) offset = e.scrollTop;
  }
  return offset;
}

function xSetCookie(name, value, expire, path)
{
  document.cookie = name + "=" + escape(value) +
                    ((!expire) ? "" : ("; expires=" + expire.toGMTString())) +
                    "; path=" + ((!path) ? "/" : path);
}

function xShow(e) {return xVisibility(e,1);}


function xStr(s)
{
  for(var i=0; i<arguments.length; ++i){if(typeof(arguments[i])!='string') return false;}
  return true;
}

function xTop(e, iY)
{
  if(!(e=xGetElementById(e))) return 0;
  var css=xDef(e.style);
  if(css && xStr(e.style.top)) {
    if(xNum(iY)) e.style.top=iY+'px';
    else {
      iY=parseInt(e.style.top);
      if(isNaN(iY)) iY=0;
    }
  }
  else if(css && xDef(e.style.pixelTop)) {
    if(xNum(iY)) e.style.pixelTop=iY;
    else iY=e.style.pixelTop;
  }
  return iY;
}

function xVisibility(e, bShow)
{
  if(!(e=xGetElementById(e))) return null;
  if(e.style && xDef(e.style.visibility)) {
    if (xDef(bShow)) e.style.visibility = bShow ? 'visible' : 'hidden';
    return e.style.visibility;
  }
  return null;
}

function xWidth(e,w)
{
  if(!(e=xGetElementById(e))) return 0;
  if (xNum(w)) {
    if (w<0) w = 0;
    else w=Math.round(w);
  }
  else w=-1;
  var css=xDef(e.style);
  if (e == document || e.tagName.toLowerCase() == 'html' || e.tagName.toLowerCase() == 'body') {
    w = xClientWidth();
  }
  else if(css && xDef(e.offsetWidth) && xStr(e.style.width)) {
    if(w>=0) {
      var pl=0,pr=0,bl=0,br=0;
      if (document.compatMode=='CSS1Compat') {
        var gcs = xGetComputedStyle;
        pl=gcs(e,'padding-left',1);
        if (pl !== null) {
          pr=gcs(e,'padding-right',1);
          bl=gcs(e,'border-left-width',1);
          br=gcs(e,'border-right-width',1);
        }
        // Should we try this as a last resort?
        // At this point getComputedStyle and currentStyle do not exist.
        else if(xDef(e.offsetWidth,e.style.width)){
          e.style.width=w+'px';
          pl=e.offsetWidth-w;
        }
      }
      w-=(pl+pr+bl+br);
      if(isNaN(w)||w<0) return null;
      else e.style.width=w+'px';
    }
    w=e.offsetWidth;
  }
  else if(css && xDef(e.style.pixelWidth)) {
    if(w>=0) e.style.pixelWidth=w;
    w=e.style.pixelWidth;
  }
  return w;
}

function xZIndex(e,uZ)
{
  if(!(e=xGetElementById(e))) return 0;
  if(e.style && xDef(e.style.zIndex)) {
    if(xNum(uZ)) e.style.zIndex=uZ;
    uZ=parseInt(e.style.zIndex);
  }
  return uZ;
}

function xStopPropagation(evt)
{
  if (evt && evt.stopPropagation) evt.stopPropagation();
  else if (window.event) window.event.cancelBubble = true;
}
/**
 * @file common.js
 * @author zero (zero@nzeo.com)
 * @brief 몇가지 유용한 & 기본적으로 자주 사용되는 자바스크립트 함수들 모음
 **/

/* jQuery 참조변수($) 제거 */
if(jQuery) jQuery.noConflict();

(function($) {
    /* OS check */
    var UA = navigator.userAgent.toLowerCase();
    $.os = {
        Linux: /linux/.test(UA),
        Unix: /x11/.test(UA),
        Mac: /mac/.test(UA),
        Windows: /win/.test(UA)
    };
    $.os.name = ($.os.Windows) ? 'Windows' :
        ($.os.Linux) ? 'Linux' :
        ($.os.Unix) ? 'Unix' :
        ($.os.Mac) ? 'Mac' : '';

    /**
     * @brief XE 공용 유틸리티 함수
     * @namespace XE
     */
    window.XE = {
        loaded_popup_menus : new Array(),
        addedDocument : new Array(),
        /**
         * @brief 특정 name을 가진 체크박스들의 checked 속성 변경
         * @param [itemName='cart',][options={}]
         */
        checkboxToggleAll : function() {
            var itemName='cart';
            var options = {
                wrap : null,
                checked : 'toggle',
                doClick : false
            };

            switch(arguments.length) {
                case 1:
                    if(typeof(arguments[0]) == "string") {
                        itemName = arguments[0];
                    } else {
                        $.extend(options, arguments[0] || {});
                    }
                    break;
                case 2:
                    itemName = arguments[0];
                    $.extend(options, arguments[1] || {});
            }

            if(options.doClick == true) options.checked = null;
            if(typeof(options.wrap) == "string") options.wrap ='#'+options.wrap;

            if(options.wrap) {
                var obj = $(options.wrap).find('input[name='+itemName+']:checkbox');
            } else {
                var obj = $('input[name='+itemName+']:checkbox');
            }

            if(options.checked == 'toggle') {
                obj.each(function() {
                    $(this).attr('checked', ($(this).attr('checked')) ? false : true);
                });
            } else {
                (options.doClick == true) ? obj.click() : obj.attr('checked', options.checked);
            }
        },

        /**
         * @brief 문서/회원 등 팝업 메뉴 출력
         */
        displayPopupMenu : function(ret_obj, response_tags, params) {
            var target_srl = params["target_srl"];
            var menu_id = params["menu_id"];
            var menus = ret_obj['menus'];
            var html = "";

            if(this.loaded_popup_menus[menu_id]) {
                html = this.loaded_popup_menus[menu_id];

            } else {
                if(menus) {
                    var item = menus['item'];
                    if(typeof(item.length)=='undefined' || item.length<1) item = new Array(item);
                    if(item.length) {
                        for(var i=0;i<item.length;i++) {
                            var url = item[i].url;
                            var str = item[i].str;
                            var icon = item[i].icon;
                            var target = item[i].target;

                            var styleText = "";
                            var click_str = "";
                            if(icon) styleText = " style=\"background-image:url('"+icon+"')\" ";
                            switch(target) {
                                case "popup" :
                                        click_str = " onclick=\"popopen(this.href,'"+target+"'); return false;\"";
                                    break;
                                case "self" :
                                        //click_str = " onclick=\"location.href='"+url+"' return false;\"";
                                    break;
                                case "javascript" :
                                        click_str = " onclick=\""+url+"; return false; \"";
                                        url="#";
                                    break;
                                default :
                                        click_str = " onclick=\"window.open(this.href); return false;\"";
                                    break;
                            }

                            html += '<li '+styleText+'><a href="'+url+'"'+click_str+'>'+str+'</a></li> ';
                        }
                    }
                }
                this.loaded_popup_menus[menu_id] =  html;
            }

            /* 레이어 출력 */
            if(html) {
                var area = $('#popup_menu_area').html('<ul>'+html+'</ul>');
                var areaOffset = {top:params['page_y'], left:params['page_x']};

                if(area.outerHeight()+areaOffset.top > $(window).height()+$(window).scrollTop())
                    areaOffset.top = $(window).height() - area.outerHeight() + $(window).scrollTop();
                if(area.outerWidth()+areaOffset.left > $(window).width()+$(window).scrollLeft())
                    areaOffset.left = $(window).width() - area.outerWidth() + $(window).scrollLeft();

                area.css({ top:areaOffset.top, left:areaOffset.left }).show();
            }
        }
    }

}) (jQuery);



/* jQuery(document).ready() */
jQuery(function($) {
    /* 팝업메뉴 레이어 생성 */
    if(!$('#popup_menu_area').length) {
        var menuObj = $('<div>')
            .attr('id', 'popup_menu_area')
            .css({display:'none', zIndex:9999});
        $(document.body).append(menuObj);
    }

    $(document).click(function(evt) {
        var area = $('#popup_menu_area');
        if(!area.length) return;

        // 이전에 호출되었을지 모르는 팝업메뉴 숨김
        area.hide();

        var targetObj = $(evt.target);
        if(!targetObj.length) return;

        // obj의 nodeName이 div나 span이 아니면 나올대까지 상위를 찾음
        if(targetObj.length && $.inArray(targetObj.attr('nodeName'), ['DIV', 'SPAN', 'A']) == -1) targetObj = targetObj.parent();
        if(!targetObj.length || $.inArray(targetObj.attr('nodeName'), ['DIV', 'SPAN', 'A']) == -1) return;

        // 객체의 className값을 구함
        var class_name = targetObj.attr('className');
        if(class_name.indexOf('_') <= 0) return;
        // className을 분리
        var class_name_list = class_name.split(' ');

        var menu_id = '';
        var menu_id_regx = /^([a-zA-Z]+)_([0-9]+)$/;


        for(var i = 0, c = class_name_list.length; i < c; i++) {
            if(menu_id_regx.test(class_name_list[i])) {
                menu_id = class_name_list[i];
            }
        }

        if(!menu_id) return;

        // module명과 대상 번호가 없으면 return
        var tmp_arr = menu_id.split('_');
        var module_name = tmp_arr[0];
        var target_srl = tmp_arr[1];
        if(!module_name || !target_srl || target_srl < 1) return;

        // action이름을 규칙에 맞게 작성
        var action_name = "get" + module_name.substr(0,1).toUpperCase() + module_name.substr(1,module_name.length-1) + "Menu";

        // 서버에 메뉴를 요청
        var params = new Array();
        params["target_srl"] = target_srl;
        params["mid"] = params["cur_mid"] = current_mid;
        params["cur_act"] = current_url.getQuery('act');
        params["menu_id"] = menu_id;
        params["page_x"] = evt.pageX;
        params["page_y"] = evt.pageY;
        if(typeof(xeVid)!='undefined') params["vid"] = xeVid;

        var response_tags = new Array("error","message","menus");

        if(typeof(XE.loaded_popup_menus[menu_id]) != 'undefined') {
            XE.displayPopupMenu(params, response_tags, params);
            return;
        }
        show_waiting_message = false;
        exec_xml(module_name, action_name, params, XE.displayPopupMenu, response_tags, params);
        show_waiting_message = true;
    });

    /* select - option의 disabled=disabled 속성을 IE에서도 체크하기 위한 함수 */
    if($.browser.msie) {
        $('select').each(function(i, sels) {
            var disabled_exists = false;
            var first_enable = new Array();

            for(var j=0; j < sels.options.length; j++) {
                if(sels.options[j].disabled) {
                    sels.options[j].style.color = '#CCCCCC';
                    disabled_exists = true;
                }else{
                    first_enable[i] = (first_enable[i] > -1) ? first_enable[i] : j;
                }
            }

            if(!disabled_exists) return;

            sels.oldonchange = sels.onchange;
            sels.onchange = function() {
                if(this.options[this.selectedIndex].disabled) {

                    this.selectedIndex = first_enable[i];
                    /*
                    if(this.options.length<=1) this.selectedIndex = -1;
                    else if(this.selectedIndex < this.options.length - 1) this.selectedIndex++;
                    else this.selectedIndex--;
                    */

                } else {
                    if(this.oldonchange) this.oldonchange();
                }
            };

            if(sels.selectedIndex >= 0 && sels.options[ sels.selectedIndex ].disabled) sels.onchange();

        });
    }
});


/**
 * @brief location.href에서 특정 key의 값을 return
 **/
String.prototype.getQuery = function(key) {
    var idx = this.indexOf('?');
    if(idx == -1) return null;
    var query_string = this.substr(idx+1, this.length);
    var args = {};
    query_string.replace(/([^=]+)=([^&]*)(&|$)/g, function() { args[arguments[1]] = arguments[2]; });

    var q = args[key];
    if(typeof(q)=="undefined") q = "";

    return q;
}

/**
 * @brief location.href에서 특정 key의 값을 return
 **/
String.prototype.setQuery = function(key, val) {
    var idx = this.indexOf('?');
    var uri = this;
    uri = uri.replace(/#$/,'');

    if(idx != -1) {
        uri = this.substr(0, idx);
        var query_string = this.substr(idx+1, this.length);
        var args = new Array();
        query_string.replace(/([^=]+)=([^&]*)(&|$)/g, function() { args[arguments[1]] = arguments[2]; });

        args[key] = val;

        var q_list = new Array();
        for(var i in args) {
        if( !args.hasOwnProperty(i) ) continue;
            var arg = args[i];
            if(!arg.toString().trim()) continue;
            arg = decodeURI(arg);
            q_list[q_list.length] = i+'='+arg;
        }
        uri = uri+"?"+q_list.join("&");
    } else {
        if(val.toString().trim()) uri = uri+"?"+key+"="+val;
    }

    var re = /https:\/\/([^:\/]+)(:\d+|)/i;
    var check = re.exec(uri);
    if(check)
    {
        var toReplace = "http://"+check[1];
        if(typeof(http_port)!='undefined' && http_port != 80)
        {
            toReplace += ":" + http_port;
        }
        uri = uri.replace(re,toReplace);
    }
    var bUseSSL = false;
    if(typeof(enforce_ssl)!='undefined' && enforce_ssl)
    {
        bUseSSL = true;
    }
    else if(typeof(ssl_actions)!='undefined' && typeof(ssl_actions.length)!='undefined' && uri.getQuery('act')) {
        var act = uri.getQuery('act');
        for(i=0;i<ssl_actions.length;i++) {
            if(ssl_actions[i]==act) {
                bUseSSL = true;
                break;
            }
        }
    }

    if(bUseSSL)
    {
        var re = /http:\/\/([^:\/]+)(:\d+|)/i;
        var check = re.exec(uri);
        if(check)
        {
            var toReplace = "https://"+check[1];
            if(typeof(https_port)!='undefined' && https_port != 443)
            {
                toReplace += ":" + https_port;
            }
            uri = uri.replace(re,toReplace);
        }
    }

    return encodeURI(uri);
}

/**
 * @brief string prototype으로 trim 함수 추가
 **/
String.prototype.trim = function() {
    return this.replace(/(^\s*)|(\s*$)/g, "");
}

/**
 * @brief xSleep(micro time)
 **/
function xSleep(sec) {
    sec = sec / 1000;
    var now = new Date();
    var sleep = new Date();
    while( sleep.getTime() - now.getTime() < sec) {
        sleep = new Date();
    }
}

/**
 * @brief 주어진 인자가 하나라도 defined되어 있지 않으면 false return
 **/
function isDef() {
    for(var i=0; i < arguments.length; ++i) {
        if(typeof(arguments[i]) == "undefined") return false;
    }
    return true;
}

/**
 * @brief 윈도우 오픈
 * 열려진 윈도우의 관리를 통해 window.focus()등을 FF에서도 비슷하게 구현함
 **/
var winopen_list = new Array();
function winopen(url, target, attribute) {
    if(typeof(xeVid)!='undefined' && url.indexOf(request_uri)>-1 && !url.getQuery('vid')) url = url.setQuery('vid',xeVid);
    try {
        if(target != "_blank" && winopen_list[target]) {
            winopen_list[target].close();
            winopen_list[target] = null;
        }
    } catch(e) {
    }

    if(typeof(target) == 'undefined') target = '_blank';
    if(typeof(attribute) == 'undefined') attribute = '';
    var win = window.open(url, target, attribute);
    win.focus();
    if(target != "_blank") winopen_list[target] = win;
}

/**
 * @brief 팝업으로만 띄우기
 * common/tpl/popup_layout.html이 요청되는 XE내의 팝업일 경우에 사용
 **/
function popopen(url, target) {
    if(typeof(target) == "undefined") target = "_blank";
    if(typeof(xeVid)!='undefined' && url.indexOf(request_uri)>-1 && !url.getQuery('vid')) url = url.setQuery('vid',xeVid);
    winopen(url, target, "left=10,top=10,width=10,height=10,scrollbars=no,resizable=yes,toolbars=no");
}

/**
 * @brief 메일 보내기용
 **/
function sendMailTo(to) {
    location.href="mailto:"+to;
}

/**
 * @brief url이동 (open_window 값이 N 가 아니면 새창으로 띄움)
 **/
function move_url(url, open_wnidow) {
    if(!url) return false;
    if(typeof(open_wnidow) == 'undefined') open_wnidow = 'N';
    if(open_wnidow=='N') {
        open_wnidow = false;
    } else {
        open_wnidow = true;
    }

    if(/^\./.test(url)) url = request_uri+url;

    if(open_wnidow) {
        winopen(url);
    } else {
        location.href=url;
    }

    return false;
}

/**
 * @brief 멀티미디어 출력용 (IE에서 플래쉬/동영상 주변에 점선 생김 방지용)
 **/
function displayMultimedia(src, width, height, options) {
    var html = _displayMultimedia(src, width, height, options);
    if(html) document.writeln(html);
}
function _displayMultimedia(src, width, height, options) {
    if(src.indexOf('files') == 0) src = request_uri + src;

    var defaults = {
        wmode : 'transparent',
        allowScriptAccess : 'sameDomain',
        quality : 'high',
        flashvars : '',
        autostart : false
    };

    var params = jQuery.extend(defaults, options || {});
	var autostart = (params.autostart && params.autostart != 'false') ? 'true' : 'false';
	delete(params.autostart);

    var clsid = "";
    var codebase = "";
    var html = "";

    if(/\.(gif|jpg|jpeg|bmp|png)$/i.test(src)){
        html = '<img src="'+src+'" width="'+width+'" height="'+height+'" />';
    } else if(/\.flv$/i.test(src) || /\.mov$/i.test(src) || /\.moov$/i.test(src) || /\.m4v$/i.test(src)) {
        html = '<embed src="'+request_uri+'common/tpl/images/flvplayer.swf" allowfullscreen="true" autostart="'+autostart+'" width="'+width+'" height="'+height+'" flashvars="&file='+src+'&width='+width+'&height='+height+'&autostart='+autostart+'" wmode="'+params.wmode+'" />';
    } else if(/\.swf/i.test(src)) {
        clsid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000';

        if(typeof(enforce_ssl)!='undefined' && enforce_ssl){ codebase = "https://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,28,0"; }
        else { codebase = "http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=9,0,28,0"; }
        html = '<object classid="'+clsid+'" codebase="'+codebase+'" width="'+width+'" height="'+height+'" flashvars="'+params.flashvars+'">';
        html += '<param name="movie" value="'+src+'" />';
        for(var name in params) {
            if(params[name] != 'undefined' && params[name] != '') {
                html += '<param name="'+name+'" value="'+params[name]+'" />';
            }
        }
        html += ''
            + '<embed src="'+src+'" autostart="'+autostart+'"  width="'+width+'" height="'+height+'" flashvars="'+params.flashvars+'" wmode="'+params.wmode+'"></embed>'
            + '</object>';
    }  else {
		if (jQuery.browser.mozilla || jQuery.browser.opera) {
			// firefox and opera uses 0 or 1 for autostart parameter.
			autostart = (params.autostart && params.autostart != 'false') ? '1' : '0';
		}

        html = '<embed src="'+src+'" autostart="'+autostart+'" width="'+width+'" height="'+height+'"';
        if(params.wmode == 'transparent') {
            html += ' windowlessvideo="1"';
        }
        html += '></embed>';
    }
    return html;
}

/**
 * @brief 에디터에서 사용되는 내용 여닫는 코드 (고정, zbxe용)
 **/
function zbxe_folder_open(id) {
    jQuery("#folder_open_"+id).hide();
    jQuery("#folder_close_"+id).show();
    jQuery("#folder_"+id).show();
}
function zbxe_folder_close(id) {
    jQuery("#folder_open_"+id).show();
    jQuery("#folder_close_"+id).hide();
    jQuery("#folder_"+id).hide();
}

/**
 * @brief 팝업의 경우 내용에 맞춰 현 윈도우의 크기를 조절해줌
 * 팝업의 내용에 맞게 크기를 늘리는 것은... 쉽게 되지는 않음.. ㅡ.ㅜ
 * popup_layout 에서 window.onload 시 자동 요청됨.
 **/
var _popupHeight = 0;
function setFixedPopupSize() {
    var headerObj = jQuery('#popHeader');
    var bodyObj = jQuery('#popBody');

    if(bodyObj.length) {
        if(bodyObj.height() > 400) {
            bodyObj.css({ overflowY:'scroll', overflowX:'hidden', height:400 });
        }
    }

    bodyObj.css({paddingRight:30});

    var w = jQuery("#popup_content").width();
    w = w< 600 ? 600 : w;
    var h = jQuery("#popup_content").height();

    if(h != _popupHeight)  {
        _popupHeight = h;

        jQuery('div').each(function() { var ww = jQuery(this).width(); if(jQuery.inArray(this.id, ['waitingforserverresponse', 'fororiginalimagearea', 'fororiginalimageareabg']) == -1) { if(ww > w) w = ww; } });
        jQuery('table').each(function() { var ww = jQuery(this).width(); if(ww > w) w = ww; });
        jQuery('form').each(function() { var ww = jQuery(this).width(); if(ww > w) w = ww; });

        jQuery("#popup_content").width(w);
        jQuery("#popHeader").width(w);
        jQuery("#popFooter").width(w);

        window.resizeTo(w, h);

        // 윈도우 OS에서는 브라우저별로 미세 조절이 필요
        var moreW = 0;
        if(navigator.userAgent.toLowerCase().indexOf('windows') > 0) {
            if(jQuery.browser.opera) moreW += 9;
            else if(jQuery.browser.msie) moreW += 10;
            else if(jQuery.browser.mozilla) moreW += 8;
            else if(jQuery.browser.safari) {
                moreW += 4;
                h -= 12;
            }
        }
        var h1 = jQuery(window).height();
        if(!/chrome/.test(navigator.userAgent.toLowerCase())) {
            window.resizeBy(moreW, h-h1+5);
        } else {
            window.resizeBy(10,60);
        }
        window.scrollTo(0,0);
    }

    setTimeout(setFixedPopupSize, 300);
}

/**
 * @brief 추천/비추천,스크랩,신고기능등 특정 srl에 대한 특정 module/action을 호출하는 함수
 **/
function doCallModuleAction(module, action, target_srl) {
    var params = new Array();
    params['target_srl'] = target_srl;
    params['cur_mid'] = current_mid;
    exec_xml(module, action, params, completeCallModuleAction);
}

function completeCallModuleAction(ret_obj, response_tags) {
    if(ret_obj['message']!='success') alert(ret_obj['message']);
    location.reload();
}

function completeMessage(ret_obj) {
    alert(ret_obj['message']);
    location.reload();
}



/* 언어코드 (lang_type) 쿠키값 변경 */
function doChangeLangType(obj) {
    if(typeof(obj) == "string") {
        setLangType(obj);
    } else {
        var val = obj.options[obj.selectedIndex].value;
        setLangType(val);
    }
    location.reload();
}
function setLangType(lang_type) {
    var expire = new Date();
    expire.setTime(expire.getTime()+ (7000 * 24 * 3600000));
    xSetCookie('lang_type', lang_type, expire, '/');
}

/* 미리보기 */
function doDocumentPreview(obj) {
    var fo_obj = obj;
    while(fo_obj.nodeName != "FORM") {
        fo_obj = fo_obj.parentNode;
    }
    if(fo_obj.nodeName != "FORM") return;
    var editor_sequence = fo_obj.getAttribute('editor_sequence');

    var content = editorGetContent(editor_sequence);

    var win = window.open("", "previewDocument","toolbars=no,width=700px;height=800px,scrollbars=yes,resizable=yes");

    var dummy_obj = jQuery("#previewDocument");

    if(!dummy_obj.length) {
        jQuery(
            '<form id="previewDocument" target="previewDocument" method="post" action="'+request_uri+'">'+
            '<input type="hidden" name="module" value="document" />'+
            '<input type="hidden" name="act" value="dispDocumentPreview" />'+
            '<input type="hidden" name="content" />'+
            '</form>'
        ).appendTo(document.body);

        dummy_obj = jQuery("#previewDocument")[0];
    }

    if(dummy_obj) {
        dummy_obj.content.value = content;
        dummy_obj.submit();
    }
}

/* 게시글 저장 */
function doDocumentSave(obj) {
    var editor_sequence = obj.form.getAttribute('editor_sequence');
    var prev_content = editorRelKeys[editor_sequence]['content'].value;
    if(typeof(editor_sequence)!='undefined' && editor_sequence && typeof(editorRelKeys)!='undefined' && typeof(editorGetContent)=='function') {
        var content = editorGetContent(editor_sequence);
        editorRelKeys[editor_sequence]['content'].value = content;
    }

	var params={}, responses=['error','message','document_srl'], elms=obj.form.elements, data=jQuery(obj.form).serializeArray();;
	jQuery.each(data, function(i, field){
		var val = jQuery.trim(field.value);
		if(!val) return true;
		if(/\[\]$/.test(field.name)) field.name = field.name.replace(/\[\]$/, '');
		if(params[field.name]) params[field.name] += '|@|'+val;
		else params[field.name] = field.value;
	});

	exec_xml('member','procMemberSaveDocument', params, completeDocumentSave, responses, params, obj.form);

    editorRelKeys[editor_sequence]['content'].value = prev_content;
    return false;
}

function completeDocumentSave(ret_obj) {
    jQuery('input[name=document_srl]').eq(0).val(ret_obj['document_srl']);
    alert(ret_obj['message']);
}

/* 저장된 게시글 불러오기 */
var objForSavedDoc = null;
function doDocumentLoad(obj) {
    // 저장된 게시글 목록 불러오기
    objForSavedDoc = obj.form;
    popopen(request_uri.setQuery('module','member').setQuery('act','dispSavedDocumentList'));
}

/* 저장된 게시글의 선택 */
function doDocumentSelect(document_srl) {
    if(!opener || !opener.objForSavedDoc) {
        window.close();
        return;
    }

    // 게시글을 가져와서 등록하기
    opener.location.href = opener.current_url.setQuery('document_srl', document_srl).setQuery('act', 'dispBoardWrite');
    window.close();
}


/* 스킨 정보 */
function viewSkinInfo(module, skin) {
    popopen("./?module=module&act=dispModuleSkinInfo&selected_module="+module+"&skin="+skin, 'SkinInfo');
}


/* 관리자가 문서를 관리하기 위해서 선택시 세션에 넣음 */
var addedDocument = new Array();
function doAddDocumentCart(obj) {
    var srl = obj.value;
    addedDocument[addedDocument.length] = srl;
    setTimeout(function() { callAddDocumentCart(addedDocument.length); }, 100);
}

function callAddDocumentCart(document_length) {
    if(addedDocument.length<1 || document_length != addedDocument.length) return;
    var params = new Array();
    params["srls"] = addedDocument.join(",");
    exec_xml("document","procDocumentAddCart", params, null);
    addedDocument = new Array();
}

/* ff의 rgb(a,b,c)를 #... 로 변경 */
function transRGB2Hex(value) {
    if(!value) return value;
    if(value.indexOf('#') > -1) return value.replace(/^#/, '');

    if(value.toLowerCase().indexOf('rgb') < 0) return value;
    value = value.replace(/^rgb\(/i, '').replace(/\)$/, '');
    value_list = value.split(',');

    var hex = '';
    for(var i = 0; i < value_list.length; i++) {
        var color = parseInt(value_list[i], 10).toString(16);
        if(color.length == 1) color = '0'+color;
        hex += color;
    }
    return hex;
}

/* 보안 로그인 모드로 전환 */
function toggleSecuritySignIn() {
    var href = location.href;
    if(/https:\/\//i.test(href)) location.href = href.replace(/^https/i,'http');
    else location.href = href.replace(/^http/i,'https');
}

function reloadDocument() {
    location.reload();
}


/**
*
* Base64 encode / decode
* http://www.webtoolkit.info/
*
**/

var Base64 = {

    // private property
    _keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",

    // public method for encoding
    encode : function (input) {
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;

        input = Base64._utf8_encode(input);

        while (i < input.length) {

            chr1 = input.charCodeAt(i++);
            chr2 = input.charCodeAt(i++);
            chr3 = input.charCodeAt(i++);

            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;

            if (isNaN(chr2)) {
                enc3 = enc4 = 64;
            } else if (isNaN(chr3)) {
                enc4 = 64;
            }

            output = output +
            this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
            this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);

        }

        return output;
    },

    // public method for decoding
    decode : function (input) {
        var output = "";
        var chr1, chr2, chr3;
        var enc1, enc2, enc3, enc4;
        var i = 0;

        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");

        while (i < input.length) {

            enc1 = this._keyStr.indexOf(input.charAt(i++));
            enc2 = this._keyStr.indexOf(input.charAt(i++));
            enc3 = this._keyStr.indexOf(input.charAt(i++));
            enc4 = this._keyStr.indexOf(input.charAt(i++));

            chr1 = (enc1 << 2) | (enc2 >> 4);
            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            chr3 = ((enc3 & 3) << 6) | enc4;

            output = output + String.fromCharCode(chr1);

            if (enc3 != 64) {
                output = output + String.fromCharCode(chr2);
            }
            if (enc4 != 64) {
                output = output + String.fromCharCode(chr3);
            }

        }

        output = Base64._utf8_decode(output);

        return output;

    },

    // private method for UTF-8 encoding
    _utf8_encode : function (string) {
        string = string.replace(/\r\n/g,"\n");
        var utftext = "";

        for (var n = 0; n < string.length; n++) {

            var c = string.charCodeAt(n);

            if (c < 128) {
                utftext += String.fromCharCode(c);
            }
            else if((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            }
            else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }

        }

        return utftext;
    },

    // private method for UTF-8 decoding
    _utf8_decode : function (utftext) {
        var string = "";
        var i = 0;
        var c = c1 = c2 = 0;

        while ( i < utftext.length ) {

            c = utftext.charCodeAt(i);

            if (c < 128) {
                string += String.fromCharCode(c);
                i++;
            }
            else if((c > 191) && (c < 224)) {
                c2 = utftext.charCodeAt(i+1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            }
            else {
                c2 = utftext.charCodeAt(i+1);
                c3 = utftext.charCodeAt(i+2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }

        }

        return string;
    }

}






/* ----------------------------------------------
 * DEPRECATED
 * 하위호환용으로 남겨 놓음
 * ------------------------------------------- */

if(typeof(resizeImageContents) == 'undefined') {
    function resizeImageContents() {}
}

if(typeof(activateOptionDisabled) == 'undefined') {
    function activateOptionDisabled() {}
}

objectExtend = jQuery.extend;

/**
 * @brief 특정 Element의 display 옵션 토글
 **/
function toggleDisplay(objId) {
    jQuery('#'+objId).toggle();
}

/* 체크박스 선택 */
function checkboxSelectAll(formObj, name, checked) {
    var itemName = name;
    var option = {};
    if(typeof(formObj) != "undefined") option.wrap = formObj;
    if(typeof(checked) != "undefined") option.checked = checked;

    XE.checkboxToggleAll(itemName, option);
}

/* 체크박스를 실행 */
function clickCheckBoxAll(formObj, name) {
    var itemName = name;
    var option = { doClick:true };
    if(typeof(formObj) != "undefined") option.wrap = formObj;

    XE.checkboxToggleAll(itemName, option);
}

/**
 * @brief 에디터에서 사용하되 내용 여닫는 코드 (zb5beta beta 호환용으로 남겨 놓음)
 **/
function svc_folder_open(id) {
    jQuery("#_folder_open_"+id).hide();
    jQuery("#_folder_close_"+id).show();
    jQuery("#_folder_"+id).show();
}
function svc_folder_close(id) {
    jQuery("#_folder_open_"+id).show();
    jQuery("#_folder_close_"+id).hide();
    jQuery("#_folder_"+id).hide();
}

/**
 * @brief 날짜 선택 (달력 열기)
 **/
function open_calendar(fo_id, day_str, callback_func) {
    if(typeof(day_str)=="undefined") day_str = "";

    var url = "./common/tpl/calendar.php?";
    if(fo_id) url+="fo_id="+fo_id;
    if(day_str) url+="&day_str="+day_str;
    if(callback_func) url+="&callback_func="+callback_func;

    popopen(url, 'Calendar');
}

var loaded_popup_menus = XE.loaded_popup_menus;
function createPopupMenu() {}
function chkPopupMenu() {}
function displayPopupMenu(ret_obj, response_tags, params) {
    XE.displayPopupMenu(ret_obj, response_tags, params);
}

function GetObjLeft(obj) {
    return jQuery(obj).offset().left;
}
function GetObjTop(obj) {
    return jQuery(obj).offset().top;
}

function replaceOuterHTML(obj, html) {
    jQuery(obj).replaceWith(html);
}

function getOuterHTML(obj) {
    return jQuery(obj).html().trim();
}

jQuery(function(){
    jQuery(".lang_code").each(
    function() 
    {
        var objText = jQuery(this);
        var targetName = objText.attr("name");
        objText.after("<a href='"+request_uri.setQuery('module','module').setQuery('act','dispModuleAdminLangcode').setQuery('target',targetName)+"' class='buttonSet buttonSetting' onclick='popopen(this.href);return false;'><span>find_langcode</span></a>"); 
    }
    );
});
/**
 * @file js_app.js
 * @author zero (zero@nzeo.com)
 * @brief XE JavaScript Application Framework (JAF)
 * @namespace xe
 * @update 20091120
 */
(function($){

var _xe_base, _app_base, _plugin_base;
var _apps = [];

_xe_base = {
	/**
	 * @brief return the name of Core module
	 */
	getName : function() {
		return 'Core';
	},

	/**
	 * @brief Create an application class
	 */
	createApp : function(sName, oDef) {
		var _base = getTypeBase();

		$.extend(_base.prototype, _app_base, oDef);

		_base.prototype.getName = function() {
			return sName;
		};

		return _base;
	},

	/**
	 * @brief Create a plugin class
	 */
	createPlugin : function(sName, oDef) {
		var _base = getTypeBase();

		$.extend(_base.prototype, _plugin_base, oDef);

		_base.prototype.getName = function() {
			return sName;
		};

		return _base;
	},

	/**
	 * @brief Get the array of applications
	 */
	getApps : function() {
		return $.makeArray(_apps);
	},

	/**
	 * @brief Get one application
	 */
	getApp : function(indexOrName) {
		indexOrName = (indexOrName||'').toLowerCase();
		if (typeof _apps[indexOrName] != 'undefined') {
			return _apps[indexOrName];
		} else {
			return null;
		}
	},

	/**
	 * @brief Register an application instance
	 */
	registerApp : function(oApp) {
		var sName = oApp.getName().toLowerCase();

		_apps.push(oApp);
		if (!$.isArray(_apps[sName])) {
			_apps[sName] = [];
		}
		_apps[sName].push(oApp);

		oApp.parent = this;
	},

	/**
	 * @brief Unregister an application instance
	 */
	unregisterApp : function(oApp) {
		var sName  = oPlugin.getName().toLowerCase();
		var nIndex = $.inArray(oApp, _apps);

		if (nIndex >= 0) _apps.splice(nIndex, 1);

		if ($.isArray(_apps[sName])) {
			nIndex = $.inArray(oApp, _apps[sName]);
			if (nIndex >= 0) _apps[sName].splice(nIndex, 1);
		}
	},

	/**
	 * @brief overrides broadcast method
	 */
	broadcast : function(sender, msg, params) {
		for(var i=0; i < _apps.length; i++) {
			_apps[i]._cast(sender, msg, params);
		}

		// cast to child plugins
		this._cast(sender, msg, params);
	}
}

_app_base = {
	_plugins  : [],
	_messages : [],

	_fn_level : [],

	/**
	 * @brief register a plugin instance
	 */
	registerPlugin : function(oPlugin) {
		var sName = oPlugin.getName().toLowerCase();

		// check if the plugin is already registered
		if ($.inArray(oPlugin, this._plugins) >= 0) return false;

		// push the plugin into the _plugins array
		this._plugins.push(oPlugin);

		if (!$.isArray(this._plugins[sName])) {
			this._plugins[sName] = [];
		}
		this._plugins[sName].push(oPlugin);

		// register method pool
		var msgs = this._messages;
		$.each(oPlugin, function(key, val){
			if (!$.isFunction(val)) return true;
			if (!/^API_((BEFORE_|AFTER_)?[A-Z0-9_]+)$/.test(key)) return true;
			var fn = function(s,p){ return oPlugin[key](s,p) };
			fn._fn = val;

			if (!$.isArray(msgs[RegExp.$1])) msgs[RegExp.$1] = [];
			msgs[RegExp.$1].push(fn);
		});

		// set the application
		oPlugin.oApp = this;

		// binding
		oPlugin.cast = function(msg, params) {
			return oPlugin._cast(msg, params);
		};

		oPlugin.broadcast = function(msg, params) {
			oPlugin._broadcast(msg, params);
		};

		return true;
	},

	/**
	 * @brief unregister a plugin  instance
	 */
	unregisterPlugin : function(oPlugin) {
		var sName = oPlugin.getName().toLowerCase();

		// remove from _plugins array
		var nIndex = $.inArray(oPlugin, this._plugins);
		if (nIndex >= 0) this._plugins.splice(nIndex, 1);

		if ($.isArray(this._plugins[sName])) {
			nIndex = $.inArray(oPlugin, this._plugins);
			if (nIndex >= 0) this._plugins[sName].splice(nIndex, 1);
		}

		// unregister method pool
		var msgs = this._messages;
		$.each(oPlugin, function(key, val){
			if (!$.isFunction(val)) return true;
			if (!/^API_([A-Z0-9_]+)$/.test(key)) return true;
			if (typeof msgs[RegExp.$1] == 'undefined') return true;

			if ($.isArray(msgs[RegExp.$1])) {
				msgs[RegExp.$1] = $.grep(msgs[RegExp.$1], function(fn,i){ return (fn._fn != val); });
				if (!msgs[RegExp.$1].length) {
					delete msgs[RegExp.$1];
				}
			} else {
				if (msgs[RegExp.$1]._fn == val) {
					delete msgs[RegExp.$1];
				}

			}
		});

		// unset the application
		oPlugin.oApp = null;
	},

	cast : function(msg, params) {
		return this._cast(this, msg, params || []);
	},

	broadcast : function(sender, msg, params) {
		if (this.parent && this.parent.broadcast) {
			this.parent.broadcast(sender, msg, params);
		}
	},

	_cast : function(sender, msg, params) {
		var i, len;
		var aMsg = this._messages;

		msg = msg.toUpperCase();

		// BEFORE hooker
		if (aMsg['BEFORE_'+msg] || this['API_BEFORE_'+msg]) {
			var bContinue = this._cast(sender, 'BEFORE_'+msg, params);
			if (!bContinue) return;
		}

		// main api function
		var vRet = [], sFn = 'API_'+msg;
		if ($.isFunction(this[sFn])) vRet.push( this[sFn](sender, params) );
		if ($.isFunction(aMsg[msg])) vRet.push( aMsg[msg](sender, params) );
		else if ($.isArray(aMsg[msg])) {
			for(i=0; i < aMsg[msg].length; i++) {
				vRet.push( aMsg[msg][i](sender, params) );
			}
		}
		if (vRet.length < 2) vRet = vRet[0];

		// AFTER hooker
		if (aMsg['AFTER_'+msg] || this['API_AFTER_'+msg]) {
			this._cast(sender, 'AFTER_'+msg, params);
		}

		if (!/^(?:AFTER_|BEFORE_)/.test(msg)) { // top level function
			return vRet;
		} else {
			return $.isArray(vRet)?($.inArray(false, vRet)<0):((typeof vRet=='undefined')?true:!!vRet);
		}
	}
};

_plugin_base = {
	oApp : null,
	_binded_fn : [],

	_cast : function(msg, params) {
		if (this.oApp && this.oApp._cast) {
			return this.oApp._cast(this, msg, params || []);
		}
	},
	_broadcast : function(msg, params) {
		if (this.oApp && this.oApp.broadcast) {
			this.oApp.broadcast(this, mag, params || []);
		}
	}

	/**
	 * Event handler prototype
	 *
	 * function (oSender, params)
	 */
};

function getTypeBase() {
	var _base = function() {
		if ($.isArray(this._plugins))   this._plugins   = [];
		if ($.isArray(this._messages))  this._messages  = [];
		if ($.isArray(this._binded_fn)) this._binded_fn = [];

		if ($.isFunction(this.init)) {
			this.init.apply(this, arguments);
		}
	};

	return _base;
}

window.xe = $.extend(_app_base, _xe_base);
window.xe.lang = {}; // language repository

// domready event
$(function(){ xe.broadcast(xe, 'ONREADY'); });

// load event
$(window).load(function(){ xe.broadcast(xe, 'ONLOAD'); });

})(jQuery);
/**
 * @file   common/js/xml_handler.js
 * @author zero <zero@nzeo.com>
 * @brief  zbxe내에서 ajax기능을 이용함에 있어 module, act를 잘 사용하기 위한 자바스크립트
 **/

// xml handler을 이용하는 user function
var show_waiting_message = true;
function exec_xml(module, act, params, callback_func, response_tags, callback_func_arg, fo_obj) {
    var oXml = new xml_handler();
    oXml.reset();
    if(typeof(params)!='undefined') {
        for(var key in params) {
            if(!params.hasOwnProperty(key)) continue;
            var val = params[key];
            oXml.addParam(key, val);
        }
    }
    oXml.addParam("module", module);
    oXml.addParam("act", act);
    if(typeof(xeVid)!='undefined') oXml.addParam('vid', xeVid);

    if(typeof(response_tags)=="undefined" || response_tags.length<1) response_tags = new Array('error','message');

    oXml.request(xml_response_filter, oXml, callback_func, response_tags, callback_func_arg, fo_obj);
}

// 결과 처리 후 callback_func에 넘겨줌
function xml_response_filter(oXml, callback_func, response_tags, callback_func_arg, fo_obj) {
    var text = oXml.getResponseText();
    if(oXml.objXmlHttp.readyState!=4) return;
    if(text && !/<\/response>$/i.test(text)) {
        var waiting_obj = xGetElementById("waitingforserverresponse");
        if(waiting_obj) waiting_obj.style.visibility = "hidden";
        alert(text);
        return null;
    }

    var xmlDoc = oXml.getResponseXml();
    if(!xmlDoc) return null;

    var waiting_obj = xGetElementById("waitingforserverresponse");
    if(waiting_obj) waiting_obj.style.visibility = "hidden";

    var ret_obj = oXml.toZMsgObject(xmlDoc, response_tags);
    if(ret_obj["error"]!=0) {
        alert(ret_obj["message"]);
        return null;
    }

    if(ret_obj["redirect_url"]) {
        location.href=ret_obj["redirect_url"].replace(/&amp;/g,'&');
        return null;
    }

    if(!callback_func) return null;

    callback_func(ret_obj, response_tags, callback_func_arg, fo_obj);

    return null;
}

// xml handler
function xml_handler() {
    this.objXmlHttp = null;
    this.method_name = null;
    this.xml_path = request_uri+"index.php";

    this.params = new Array();

    this.reset = xml_handlerReset;
    this.getXmlHttp = zGetXmlHttp;
    this.request = xml_handlerRequest;
    this.setPath = xml_handlerSetPath;
    this.addParam = xml_handlerAddParam;
    this.getResponseXml = xml_handlerGetResponseXML;
    this.getResponseText = xml_handlerGetResponseText;
    this.toZMsgObject = xml_handlerToZMsgObject;
    this.parseXMLDoc = xml_parseXmlDoc;

    this.objXmlHttp = this.getXmlHttp();
}

function zGetXmlHttp() {
    if (window.XMLHttpRequest) return new XMLHttpRequest();
    else if (window.ActiveXObject) {
        try {
            return new ActiveXObject("Msxml2.XMLHTTP");
        } catch (e) {
            return new ActiveXObject("Microsoft.XMLHTTP");
        }
    }
    return null;
}

function xml_handlerRequest(callBackFunc, xmlObj, callBackFunc2, response_tags, callback_func_arg, fo_obj) {
    // ssl action
    if(typeof(ssl_actions)!='undefined' && typeof(ssl_actions.length)!='undefined' && typeof(this.params['act'])!='undefined') {
        var action = this.params['act'];
        for(i=0;i<ssl_actions.length;i++) {
            if(ssl_actions[i]==action) {
                var url = request_uri;
                if(typeof(default_url)!='undefined' && default_url) url = default_url;
                var port = 443;
                if(typeof(https_port)!='undefined' && https_port != 443) port = https_port;
                var _u1 = xCreateElement('a');
                _u1.href = url;
                var targetUrl = 'https://';
                targetUrl += _u1.hostname.replace(/:([0-9]+)$/,'');
                if(port != 443) targetUrl += ':'+port;
                if(_u1.pathname[0] != "/") targetUrl += "/";
                targetUrl += _u1.pathname;  
                targetUrl = targetUrl.replace(/\/$/,'');
                this.xml_path = targetUrl + '/index.php';
            }
        }
    }

    var _u1 = xCreateElement('a');
    _u1.href = location.href;
    var _u2 = xCreateElement('a');
    _u2.href = this.xml_path;

    // 현 url과 ajax call 대상 url의 schema 또는 port가 다르면 직접 form 전송
    if(_u1.protocol != _u2.protocol || _u1.port != _u2.port) {
        var fr = xGetElementById('xeTmpIframe');
        if(!fr) {
            fr = xCreateElement('iframe');
            fr.style.position = 'absolute';
            fr.style.left = '-1px';
            fr.style.top = '1px';
            fr.style.width = '1px';
            fr.style.height = '1px';
            fr.name = fr.id = 'xeTmpIframe';
            document.body.appendChild(fr);
        }

        var fo = xGetElementById('xeVirtualForm');
        if(fo) document.body.removeChild(fo);

        fo = xCreateElement('form');
        fo.id = 'xeVirtualForm';
        fo.action = this.xml_path;
        fo.method = 'post';
        fo.target = 'xeTmpIframe';

        var i = xCreateElement('input');
        i.type = 'hidden';
        i.name = 'xeVirtualRequestMethod';
        i.value = 'xml';
        fo.appendChild(i);

        var j = xCreateElement('input');
        j.type = 'hidden';
        j.name = 'xeRequestURI';
        j.value = location.href.replace(/#(.*)$/i,'');
        fo.appendChild(j);

        var k = xCreateElement('input');
        k.type = 'hidden';
        k.name = 'xeVirtualRequestUrl';
        k.value = request_uri;
        fo.appendChild(k);

        for (var key in this.params) {
            if(!this.params.hasOwnProperty(key)) continue;
            var i = xCreateElement('input');
            i.type = 'hidden';
            i.name = key;
            i.value = this.params[key];
            fo.appendChild(i);
        }
        document.body.appendChild(fo);
        fo.submit();

        return;
    }

    var rd = "";
    rd += "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n"
    +  "<methodCall>\n"
    +  "<params>\n"

    for (var key in this.params) {
        if(!this.params.hasOwnProperty(key)) continue;
        var val = this.params[key];
        rd += "<"+key+"><![CDATA["+val+"]]></"+key+">\n";
    }

    rd += "</params>\n"
    +  "</methodCall>\n";


    if(this.objXmlHttp.readyState!=0) {
        this.objXmlHttp.abort();
        this.objXmlHttp = this.getXmlHttp();
    }
    this.objXmlHttp.onreadystatechange = function () {callBackFunc(xmlObj, callBackFunc2, response_tags, callback_func_arg, fo_obj)};

    // 모든 xml데이터는 POST방식으로 전송. try-catch문으로 오류 발생시 대처
    try {
        this.objXmlHttp.open("POST", this.xml_path, true);
    } catch(e) {
        alert(e);
        return;
    }

    // ajax 통신중 대기 메세지 출력 (show_waiting_message값을 false로 세팅시 보이지 않음)
    var waiting_obj = xGetElementById("waitingforserverresponse");
    if(show_waiting_message && waiting_obj) {
        xInnerHtml(waiting_obj, waiting_message);

        xTop(waiting_obj, xScrollTop()+20);
        xLeft(waiting_obj, xScrollLeft()+20);
        waiting_obj.style.visibility = "visible";
    }

    this.objXmlHttp.send(rd);
}

function xml_handlerSetPath(path) {
    this.xml_path = "./"+path;
}


function xml_handlerReset() {
    this.objXmlHttp = this.getXmlHttp();
    this.params = new Array();
}

function xml_handlerAddParam(key, val) {
    this.params[key] = val;
}

function xml_handlerGetResponseXML() {
    if(this.objXmlHttp && this.objXmlHttp.readyState == 4 && isDef(this.objXmlHttp.responseXML)) {
        var xmlDoc = this.objXmlHttp.responseXML;
        this.reset();
        return xmlDoc;
    }
    return null;
}

function xml_handlerGetResponseText() {
    if(this.objXmlHttp && this.objXmlHttp.readyState == 4 && isDef(this.objXmlHttp.responseText)) {
        return this.objXmlHttp.responseText;
    }
    return null;
}


function xml_parseXmlDoc(dom) {

    if(!dom) return;

    var jsonStr = xml2json(dom,false,false);
    var jsonObj = eval("("+ jsonStr +");");
    return jsonObj.response;
/*

    var ret_obj = new Array();

    var obj = dom.firstChild;
    var preObj;
    if(!obj) return;

    while(obj) {
        if(obj.nodeType == 1) {

            var name = obj.nodeName;
            var value = null;

            if(obj.childNodes.length==1 && obj.firstChild.nodeType != 1) {

                value = obj.firstChild.nodeValue;
            } else {
                value = this.parseXMLDoc(obj);
            }
            if(typeof(ret_obj[name])=='undefined') {
                ret_obj[name] = value;
            } else {
                if(ret_obj[name].length>0) {
                    ret_obj[name][ret_obj[name].length] = value;
                } else {
                    var tmp_value = ret_obj[name];
                    ret_obj[name] = new Array();
                    ret_obj[name][ret_obj[name].length] = tmp_value;
                    ret_obj[name][ret_obj[name].length] = value;
                }
            }

        }
        obj = obj.nextSibling;

    }
    return ret_obj;
*/
}

function xml_handlerToZMsgObject(xmlDoc, tags) {
    if(!xmlDoc) return null;
    if(!tags) tags = new Array("error","message");
    tags[tags.length] = "redirect_url";
    tags[tags.length] = "act";

    var parsed_array = this.parseXMLDoc(xmlDoc.getElementsByTagName('response')[0]);

    if(typeof(parsed_array)=='undefined') {
        var ret = new Array();
        ret['error'] = -1;
        ret['message'] = "Unexpected error occured.";
        try{
            if(typeof(xmlDoc.childNodes[0].firstChild.data)!='undefined') ret['message']+="\r\n"+xmlDoc.childNodes[0].firstChild.data;
        } catch(e) {
        }
        return ret;
    }

    var obj_ret = new Array();
    for(var i=0; i<tags.length; i++) {
        var key = tags[i];
        if(parsed_array[key]) obj_ret[key] = parsed_array[key];
        else obj_ret[key] = null;
    }
    return obj_ret;
}



/*  This work is licensed under Creative Commons GNU LGPL License.

    License: http://creativecommons.org/licenses/LGPL/2.1/
   Version: 0.9
    Author:  Stefan Goessner/2006
    Web:     http://goessner.net/
*/
function xml2json(xml, tab, ignoreAttrib) {
   var X = {
      toObj: function(xml) {
         var o = {};
         if (xml.nodeType==1) {   // element node ..
            if (ignoreAttrib && xml.attributes.length)   // element with attributes  ..
               for (var i=0; i<xml.attributes.length; i++)
                  o["@"+xml.attributes[i].nodeName] = (xml.attributes[i].nodeValue||"").toString();
            if (xml.firstChild) { // element has child nodes ..
               var textChild=0, cdataChild=0, hasElementChild=false;
               for (var n=xml.firstChild; n; n=n.nextSibling) {
                  if (n.nodeType==1) hasElementChild = true;
                  else if (n.nodeType==3 && n.nodeValue.match(/[^ \f\n\r\t\v]/)) textChild++; // non-whitespace text
                  else if (n.nodeType==4) cdataChild++; // cdata section node
               }
               if (hasElementChild) {
                  if (textChild < 2 && cdataChild < 2) { // structured element with evtl. a single text or/and cdata node ..
                     X.removeWhite(xml);
                     for (var n=xml.firstChild; n; n=n.nextSibling) {
                        if (n.nodeType == 3)  // text node
                           o = X.escape(n.nodeValue);
                        else if (n.nodeType == 4)  // cdata node
//                           o["#cdata"] = X.escape(n.nodeValue);
                            o = X.escape(n.nodeValue);
                        else if (o[n.nodeName]) {  // multiple occurence of element ..
                           if (o[n.nodeName] instanceof Array)
                              o[n.nodeName][o[n.nodeName].length] = X.toObj(n);
                           else
                              o[n.nodeName] = [o[n.nodeName], X.toObj(n)];
                        }
                        else  // first occurence of element..
                           o[n.nodeName] = X.toObj(n);
                     }
                  }
                  else { // mixed content
                     if (!xml.attributes.length)
                        o = X.escape(X.innerXml(xml));
                     else
                        o["#text"] = X.escape(X.innerXml(xml));
                  }
               }
               else if (textChild) { // pure text
                  if (!xml.attributes.length)
                     o = X.escape(X.innerXml(xml));
                  else
                     o["#text"] = X.escape(X.innerXml(xml));
               }
               else if (cdataChild) { // cdata
                  if (cdataChild > 1)
                     o = X.escape(X.innerXml(xml));
                  else
                     for (var n=xml.firstChild; n; n=n.nextSibling){
                        //o["#cdata"] = X.escape(n.nodeValue);
                        o = X.escape(n.nodeValue);
                  }
               }
            }
            if (!xml.attributes.length && !xml.firstChild) o = null;
         }
         else if (xml.nodeType==9) { // document.node
            o = X.toObj(xml.documentElement);
         }
         else
            alert("unhandled node type: " + xml.nodeType);
         return o;
      },
      toJson: function(o, name, ind) {
         var json = name ? ("\""+name+"\"") : "";
         if (o instanceof Array) {
            for (var i=0,n=o.length; i<n; i++)
               o[i] = X.toJson(o[i], "", ind+"\t");
            json += (name?":[":"[") + (o.length > 1 ? ("\n"+ind+"\t"+o.join(",\n"+ind+"\t")+"\n"+ind) : o.join("")) + "]";
         }
         else if (o == null)
            json += (name&&":") + "null";
         else if (typeof(o) == "object") {
            var arr = [];
            for (var m in o)
               arr[arr.length] = X.toJson(o[m], m, ind+"\t");
            json += (name?":{":"{") + (arr.length > 1 ? ("\n"+ind+"\t"+arr.join(",\n"+ind+"\t")+"\n"+ind) : arr.join("")) + "}";
         }
         else if (typeof(o) == "string")
            json += (name&&":") + "\"" + o.toString() + "\"";
         else
            json += (name&&":") + o.toString();
         return json;
      },
      innerXml: function(node) {
         var s = ""
         if ("innerHTML" in node)
            s = node.innerHTML;
         else {
            var asXml = function(n) {
               var s = "";
               if (n.nodeType == 1) {
                  s += "<" + n.nodeName;
                  for (var i=0; i<n.attributes.length;i++)
                     s += " " + n.attributes[i].nodeName + "=\"" + (n.attributes[i].nodeValue||"").toString() + "\"";
                  if (n.firstChild) {
                     s += ">";
                     for (var c=n.firstChild; c; c=c.nextSibling)
                        s += asXml(c);
                     s += "</"+n.nodeName+">";
                  }
                  else
                     s += "/>";
               }
               else if (n.nodeType == 3)
                  s += n.nodeValue;
               else if (n.nodeType == 4)
                  s += "<![CDATA[" + n.nodeValue + "]]>";
               return s;
            };
            for (var c=node.firstChild; c; c=c.nextSibling)
               s += asXml(c);
         }
         return s;
      },
      escape: function(txt) {
         return txt.replace(/[\\]/g, "\\\\")
                   .replace(/[\"]/g, '\\"')
                   .replace(/[\n]/g, '\\n')
                   .replace(/[\r]/g, '\\r');
      },
      removeWhite: function(e) {
         e.normalize();
         for (var n = e.firstChild; n; ) {
            if (n.nodeType == 3) {  // text node
               if (!n.nodeValue.match(/[^ \f\n\r\t\v]/)) { // pure whitespace text node
                  var nxt = n.nextSibling;
                  e.removeChild(n);
                  n = nxt;
               }
               else
                  n = n.nextSibling;
            }
            else if (n.nodeType == 1) {  // element node
               X.removeWhite(n);
               n = n.nextSibling;
            }
            else                      // any other node
               n = n.nextSibling;
         }
         return e;
      }
   };
   if (xml.nodeType == 9) // document node
      xml = xml.documentElement;
   var json = X.toJson(X.toObj(X.removeWhite(xml)), xml.nodeName, "");
   return "{" + (tab ? json.replace(/\t/g, tab) : json.replace(/\t|\n/g, "")) + "}";
}


/**
 * @brief exec_json (exec_xml와 같은 용도)
 **/
(function($){
$.exec_json = function(action,data,func){
    if(typeof(data) == 'undefined') data = {};
    action = action.split(".");
    if(action.length == 2){

        if(show_waiting_message) {
            $("#waitingforserverresponse").html(waiting_message).css('top',$(document).scrollTop()+20).css('left',$(document).scrollLeft()+20).css('visibility','visible');
        }

        $.extend(data,{module:action[0],act:action[1]});
        if(typeof(xeVid)!='undefined') $.extend(data,{vid:xeVid});
        $.ajax({
            type:"POST"
            ,dataType:"json"
            ,url:request_uri
            ,contentType:"application/json"
            ,data:$.param(data)
            ,success : function(data){
                $("#waitingforserverresponse").css('visibility','hidden');
                if(data.error > 0) alert(data.message);
                if($.isFunction(func)) func(data);
            }
        });
    }
};

$.fn.exec_html = function(action,data,type,func,args){
    if(typeof(data) == 'undefined') data = {};
    if(!$.inArray(type, ['html','append','prepend'])) type = 'html';

    var self = $(this);
    action = action.split(".");
    if(action.length == 2){
        if(show_waiting_message) {
            $("#waitingforserverresponse").html(waiting_message).css('top',$(document).scrollTop()+20).css('left',$(document).scrollLeft()+20).css('visibility','visible');
        }

        $.extend(data,{module:action[0],act:action[1]});
        $.ajax({
            type:"POST"
            ,dataType:"html"
            ,url:request_uri
            ,data:$.param(data)
            ,success : function(html){
                $("#waitingforserverresponse").css('visibility','hidden');
                self[type](html);
                if($.isFunction(func)) func(args);
            }
        });
    }
};
})(jQuery);
/**
 * @file   common/js/xml_js_filter.js
 * @author taggon (taggon@gmail.com)
 * @brief  xml filter (validator) plugin
 * 
 * A rule is a method validate one field.
 * A filter is made up of one or more rules.
 **/
(function($){

var messages  = [];
var rules     = [];
var filters   = [];
var callbacks = [];
var extras    = {};

var Validator = xe.createApp('Validator', {
	init : function() {
		// {{{ add filters
		// email
		var regEmail = /^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)*$/;
		this.cast('ADD_RULE', ['email', regEmail]);
		this.cast('ADD_RULE', ['email_address', regEmail]);

		// userid
		var regUserid = /^[a-z]+[\w-]*[a-z0-9_]+$/i;
		this.cast('ADD_RULE', ['userid', regUserid]);
		this.cast('ADD_RULE', ['user_id', regUserid]);

		// url
		var regUrl = /^(https?|ftp|mms):\/\/[0-9a-z-]+(\.[_0-9a-z-\/\~]+)+(:[0-9]{2,4})*$/;
		this.cast('ADD_RULE', ['url', regUrl]);
		this.cast('ADD_RULE', ['homepage', regUrl]);

		// korean
		var regKor = /^[가-힣]*$/;
		this.cast('ADD_RULE', ['korean', regKor]);

		// korean_number
		var regKorNum = /^[가-힣0-9]*$/;
		this.cast('ADD_RULE', ['korean_number', regKorNum]);

		// alpha
		var regAlpha = /^[a-z]*$/i;
		this.cast('ADD_RULE', ['alpha', regAlpha]);

		// alpha_number
		var regAlphaNum = /^[a-z][a-z0-9_]*$/i;
		this.cast('ADD_RULE', ['alpha_number', regAlphaNum]);

		// number
		var regNum = /^[0-9]*$/;
		this.cast('ADD_RULE', ['number', regNum]);
		// }}} add filters
	},
	// run validator
	run : function(oForm) {
		var filter = '';

		if (oForm._filter) filter = oForm._filter.value;

		var params = [oForm, filter];
		var result = this.cast('VALIDATE', params);
		if (typeof result == 'undefined') result = false;

		return result;
	},
	API_ONREADY : function() {
		var self = this;

		// hook form submit event
		$('form')
			.each(function(){
				if (this.onsubmit) {
					this['xe:onsubmit'] = this.onsubmit;
					this.onsubmit = null;
				}
			})
			.submit(function(){
				var legacyFn = this['xe:onsubmit'];		
				var hasLegacyFn = $.isFunction(legacyFn);
				var bResult = hasLegacyFn?legacyFn.apply(this):self.run(this);

				return bResult;
			});
	},
	API_VALIDATE : function(sender, params) {
		var self = this, result = true, form = params[0], filter=null, callback=null;

		if (form.elements['_filter']) filter = form.elements['_filter'].value;
		if (!filter) return true;
		if ($.isFunction(callbacks[filter])) callback = callbacks[filter];
		filter = $.extend({}, filters[filter.toLowerCase()] || {}, extras);

		$.each(filter, function(name) {
			var _el = form.elements[name];

			if (!_el) return true;

			var el = $(_el), val = $.trim(get_value(el));
			var minlen = parseInt(this.minlen) || 0;
			var maxlen = parseInt(this.maxlen) || 0;
			var rule   = (this.rule || '').split(',');

			if (this.required && !val) return (result = (!!self.cast('ALERT', [form, name, 'isnull']) && false));
			if (!this.required && !val) return (result = true);
			if ((minlen && maxlen) && (val.length < minlen || val.length > maxlen)) return (result = (!!self.cast('ALERT', [form, name, 'outofrange', minlen, maxlen]) && false));
			
			if (this.equalto) {
				var eq_val = get_value($(form.elements[this.equalto]));
				if (eq_val != val) return (result = (!!self.cast('ALERT', [form, name, 'equalto']) && false));
			}

			$.each(rule, function() {
				var ret = self.cast('APPLY_RULE', [this, val]);
				if (!ret) {
					self.cast('ALERT', [form, name, 'invalid_'+this]);
					return (result = false);
				}
			});

			if (!result) return false;
		});

		if (!result) return false;
		if ($.isFunction(callback)) return callback(form);

		return true;
	},
	API_ADD_RULE : function(sender, params) {
		var name = params[0].toLowerCase();
		rules[name] = params[1];
	},
	API_DEL_RULE : function(sender, params) {
		var name = params[0].toLowerCase();
		delete rules[name];
	},
	API_GET_RULE : function(sender, params) {
		var name = params[0].toLowerCase();

		if (rules[name]) {
			return rules[name];
		} else {
			return null;
		}
	},
	API_ADD_FILTER : function(sender, params) {
		var name   = params[0].toLowerCase();
		var filter = params[1];

		filters[name] = filter;
	},
	API_DEL_FILTER : function(sender, params) {
		var name = params[0].toLowerCase();
		delete filters[name];
	},
	API_GET_FILTER : function(sender, params) {
		var name = params[0].toLowerCase();

		if (filters[name]) {
			return filters[name];
		} else {
			return null;
		}
	},
	API_ADD_EXTRA_FIELD : function(sender, params) {
		var name = params[0].toLowerCase();
		var prop = params[1];

		extras[name] = prop;
	},
	API_GET_EXTRA_FIELD : function(sender, params) {
		var name = params[0].toLowerCase();
		return extras[name];
	},
	API_DEL_EXTRA_FIELD : function(sender, params) {
		var name = params[0].toLowerCase();
		delete extras[name];
	},
	API_APPLY_RULE : function(sender, params) {
		var name  = params[0].toLowerCase();
		var value = params[1];

		if (typeof(rules[name]) == 'undefined') return true; // no filter
		if ($.isFunction(rules[name])) return rules[name](value);
		if (rules[name] instanceof RegExp) return rules[name].test(value);

		return true;
	},
	API_ALERT : function(sender, params) {
		var form = params[0];
		var field_name = params[1];
		var msg_code = params[2];
		var minlen   = params[3];
		var maxlen   = params[4];

		var field_msg = this.cast('GET_MESSAGE', [field_name]);
		var msg = this.cast('GET_MESSAGE', [msg_code]);

		if (msg != msg_code) msg = (msg.indexOf('%s')<0)?(field_msg+msg):(msg.replace('%s',field_msg));
		if (typeof(minlen)!='undefined' && typeof(maxlen)!='undefined') msg += '('+minlen+'~'+maxlen+')';

		this.cast('SHOW_ALERT', [msg]);

		// set focus
		$(form.elements[field_name]).focus();
	},
	API_SHOW_ALERT : function(sender, params) {
		alert(params[0]);
	},
	API_ADD_MESSAGE : function(sender, params) {
		var msg_code = params[0];
		var msg_str  = params[1];

		messages[msg_code] = msg_str;
	},
	API_GET_MESSAGE : function(sender, params) {
		var msg_code = params[0];

		return messages[msg_code] || msg_code;
	},
	API_ADD_CALLBACK : function(sender, params) {
		var name = params[0];
		var func = params[1];

		callbacks[name] = func;
	},
	API_REMOVE_CALLBACK : function(sender, params) {
		var name = params[0];

		delete callbacks[name];
	}
});

var oValidator = new Validator;

// register validator
xe.registerApp(oValidator);

// 호환성을 위해 추가한 플러그인 - 에디터에서 컨텐트를 가져와서 설정한다.
var EditorStub = xe.createPlugin('editor_stub', {
	API_BEFORE_VALIDATE : function(sender, params) {
		var form = params[0];
		var seq  = form.getAttribute('editor_sequence');

		if (seq) {
			try {
				editorRelKeys[seq].content.value = editorRelKeys[seq].func(seq) || '';
			} catch(e) { }
		}
	}
});
oValidator.registerPlugin(new EditorStub);

// functions
function get_value(elem) {
	var vals = [];
	if (elem.is(':radio')){
		return elem.filter(':checked').val();
	} else if (elem.is(':checkbox')) {
		elem.filter(':checked').each(function(){
			vals.push(this.value);
		});
		return vals.join('|@|');
	} else {
		return elem.val();
	}
}

})(jQuery);

/**
 * @function filterAlertMessage
 * @brief ajax로 서버에 요청후 결과를 처리할 callback_function을 지정하지 않았을 시 호출되는 기본 함수
 **/
function filterAlertMessage(ret_obj) {
	var error = ret_obj["error"];
	var message = ret_obj["message"];
	var act = ret_obj["act"];
	var redirect_url = ret_obj["redirect_url"];
	var url = location.href;

	if(typeof(message)!="undefined"&&message&&message!="success") alert(message);

	if(typeof(act)!="undefined" && act) url = current_url.setQuery("act", act);
	else if(typeof(redirect_url)!="undefined" && redirect_url) url = redirect_url;

	if(url == location.href) url = url.replace(/#(.*)$/,'');

	location.href = url;
}

/**
 * @brief Function to process filters
 * @deprecated
 */
function procFilter(fo_obj, filter_func)
{
	filter_func(fo_obj);
	return false;
}
/************************************************************************/
/* Rainbow Links Version 1.03 (2003.9.20)                               */
/* Script updated by Dynamicdrive.com for IE6                           */
/* Copyright (C) 1999-2001 TAKANASHI Mizuki                             */
/* takanasi@hamal.freemail.ne.jp                                        */
/*----------------------------------------------------------------------*/
/* Read it somehow even if my English text is a little wrong! ;-)       */
/*                                                                      */
/* Usage:                                                               */
/*  Insert '<script src="rainbow.js"></script>' into the BODY section,  */
/*  right after the BODY tag itself, before anything else.              */
/*  You don't need to add "onMouseover" and "onMouseout" attributes!!   */
/*                                                                      */
/*  If you'd like to add effect to other texts(not link texts), then    */
/*  add 'onmouseover="doRainbow(this);"' and                            */
/*  'onmouseout="stopRainbow();"' to the target tags.                   */
/*                                                                      */
/* This Script works with IE4,Netscape6,Mozilla browser and above only, */
/* but no error occurs on other browsers.                               */
/************************************************************************/


////////////////////////////////////////////////////////////////////
// Setting

var rate = 20;  // Increase amount(The degree of the transmutation)


////////////////////////////////////////////////////////////////////
// Main routine

/*
if (document.getElementById)
window.onerror=new Function("return true")
*/

var objActive;  // The object which event occured in
var act = 0;    // Flag during the action
var elmH = 0;   // Hue
var elmS = 128; // Saturation
var elmV = 255; // Value
var clrOrg;     // A color before the change
var TimerID;    // Timer ID


if(xIE4Up) {
    xAddEventListener(document, 'mouseover', doRainbowAnchor);
    xAddEventListener(document, 'mouseout', stopRainbowAnchor);
} else {
    xAddEventListener(document, 'mouseover', Mozilla_doRainbowAnchor);
    xAddEventListener(document, 'mouseout', Mozilla_stopRainbowAnchor);
}
/*
if (document.all) {
    document.onmouseover = doRainbowAnchor;
    document.onmouseout = stopRainbowAnchor;
}
else if (document.getElementById) {
    document.captureEvents(Event.MOUSEOVER | Event.MOUSEOUT);
    document.onmouseover = Mozilla_doRainbowAnchor;
    document.onmouseout = Mozilla_stopRainbowAnchor;
}
*/


//=============================================================================
// doRainbow
//  This function begins to change a color.
//=============================================================================
function doRainbow(obj)
{
    if (act == 0) {
        act = 1;
        if (obj)
            objActive = obj;
        else
            objActive = event.srcElement;
        clrOrg = objActive.style.color;
        TimerID = setInterval("ChangeColor()",100);
    }
}


//=============================================================================
// stopRainbow
//  This function stops to change a color.
//=============================================================================
function stopRainbow()
{
    if (act) {
        objActive.style.color = clrOrg;
        clearInterval(TimerID);
        act = 0;
    }
}


//=============================================================================
// doRainbowAnchor
//  This function begins to change a color. (of a anchor, automatically)
//=============================================================================
function doRainbowAnchor()
{
    try {
        if (act == 0) {
            var obj = event.srcElement;
            while (obj.tagName != 'A' && obj.tagName != 'BODY') {
                obj = obj.parentElement;
                if (obj.tagName == 'A' || obj.tagName == 'BODY')
                    break;
            }

            if (obj.tagName == 'A' && obj.href != '') {
                objActive = obj;
                act = 1;
                clrOrg = objActive.style.color;
                TimerID = setInterval("ChangeColor()",100);
            }
        }
    } catch(e) {
    }
}


//=============================================================================
// stopRainbowAnchor
//  This function stops to change a color. (of a anchor, automatically)
//=============================================================================
function stopRainbowAnchor()
{
    if (act) {
        if (objActive.tagName == 'A') {
            objActive.style.color = clrOrg;
            clearInterval(TimerID);
            act = 0;
        }
    }
}


//=============================================================================
// Mozilla_doRainbowAnchor(for Netscape6 and Mozilla browser)
//  This function begins to change a color. (of a anchor, automatically)
//=============================================================================
function Mozilla_doRainbowAnchor(evt)
{
    var e = new xEvent(evt);
    if (act == 0) {
        obj = e.target;
        while (obj.nodeName != 'A' && obj.nodeName != 'BODY') {
            obj = obj.parentNode;
            if(typeof(obj)=='undefined'||!obj) return;
            if (obj.nodeName == 'A' || obj.nodeName == 'BODY') break;
        }

        if (obj.nodeName == 'A' && obj.href != '') {
            objActive = obj;
            act = 1;
            clrOrg = obj.style.color;
            TimerID = setInterval("ChangeColor()",100);
        }
    }
}


//=============================================================================
// Mozilla_stopRainbowAnchor(for Netscape6 and Mozilla browser)
//  This function stops to change a color. (of a anchor, automatically)
//=============================================================================
function Mozilla_stopRainbowAnchor(e)
{
    if (act) {
        if (objActive.nodeName == 'A') {
            objActive.style.color = clrOrg;
            clearInterval(TimerID);
            act = 0;
        }
    }
}


//=============================================================================
// Change Color
//  This function changes a color actually.
//=============================================================================
function ChangeColor()
{
    objActive.style.color = makeColor();
}


//=============================================================================
// makeColor
//  This function makes rainbow colors.
//=============================================================================
function makeColor()
{
    // Don't you think Color Gamut to look like Rainbow?

    // HSVtoRGB
    if (elmS == 0) {
        elmR = elmV;    elmG = elmV;    elmB = elmV;
    }
    else {
        t1 = elmV;
        t2 = (255 - elmS) * elmV / 255;
        t3 = elmH % 60;
        t3 = (t1 - t2) * t3 / 60;

        if (elmH < 60) {
            elmR = t1;  elmB = t2;  elmG = t2 + t3;
        }
        else if (elmH < 120) {
            elmG = t1;  elmB = t2;  elmR = t1 - t3;
        }
        else if (elmH < 180) {
            elmG = t1;  elmR = t2;  elmB = t2 + t3;
        }
        else if (elmH < 240) {
            elmB = t1;  elmR = t2;  elmG = t1 - t3;
        }
        else if (elmH < 300) {
            elmB = t1;  elmG = t2;  elmR = t2 + t3;
        }
        else if (elmH < 360) {
            elmR = t1;  elmG = t2;  elmB = t1 - t3;
        }
        else {
            elmR = 0;   elmG = 0;   elmB = 0;
        }
    }

    elmR = Math.floor(elmR).toString(16);
    elmG = Math.floor(elmG).toString(16);
    elmB = Math.floor(elmB).toString(16);
    if (elmR.length == 1)    elmR = "0" + elmR;
    if (elmG.length == 1)    elmG = "0" + elmG;
    if (elmB.length == 1)    elmB = "0" + elmB;

    elmH = elmH + rate;
    if (elmH >= 360)
        elmH = 0;

    return '#' + elmR + elmG + elmB;
}
function input_password(fo_obj){
	var validator = xe.getApp('validator')[0];
	if(!validator) return false;
	if(!fo_obj.elements['_filter']) jQuery(fo_obj).prepend('<input type="hidden" name="_filter" value="" />');
	fo_obj.elements['_filter'].value = 'input_password';
	validator.cast('ADD_CALLBACK', ['input_password', function(form){
		var params={}, responses=[], elms=form.elements, data=jQuery(form).serializeArray();
		jQuery.each(data, function(i, field){
			var val = jQuery.trim(field.value);
			if(!val) return true;
			if(/\[\]$/.test(field.name)) field.name = field.name.replace(/\[\]$/, '');
			if(params[field.name]) params[field.name] += '|@|'+val;
			else params[field.name] = field.value;
		});
		responses = ['error','message'];
		exec_xml('board','procBoardVerificationPassword', params, filterAlertMessage, responses, params, form);
	}]);
	validator.cast('VALIDATE', [fo_obj,'input_password']);
	return false;
};

(function($){
	var validator = xe.getApp('Validator')[0];
	if(!validator) return false;
	validator.cast('ADD_FILTER', ['input_password', {
		'document_srl': {required:true},
		'password': {required:true}
	}]);
	validator.cast('ADD_MESSAGE', ['document_srl', '문서번호']);
	validator.cast('ADD_MESSAGE', ['password', '비밀번호']);
	validator.cast('ADD_MESSAGE', ['mid', '모듈 이름']);
	validator.cast('ADD_MESSAGE', ['comment_srl', 'comment_srl']);
	validator.cast('ADD_MESSAGE', ['isnull', '%s을 입력해주세요.']);
	validator.cast('ADD_MESSAGE', ['outofrange', '%s의 글자 수를 맞추어 주세요.']);
	validator.cast('ADD_MESSAGE', ['equalto', '%s이 잘못되었습니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_email', '%s의 형식이 잘못되었습니다. (예: xe@xpressengine.com)']);
	validator.cast('ADD_MESSAGE', ['invalid_userid', '%s의 형식이 잘못되었습니다.\n영문, 숫자와 _로 만드실 수 있으며, 첫 글자는 영문이어야 합니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_user_id', '%s의 형식이 잘못되었습니다.\n영문, 숫자와 _로 만드실 수 있으며, 첫 글자는 영문이어야 합니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_homepage', '%s의 형식이 잘못되었습니다. (예: http://www.xpressengine.com)']);
	validator.cast('ADD_MESSAGE', ['invalid_korean', '%s의 형식이 잘못되었습니다. 한글로만 입력하셔야 합니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_korean_number', '%s의 형식이 잘못되었습니다. 한글과 숫자로만 입력하셔야 합니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_alpha', '%s의 형식이 잘못되었습니다. 영문으로만 입력하셔야 합니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_alpha_number', '%s의 형식이 잘못되었습니다. 영문과 숫자로만 입력하셔야 합니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_number', '%s의 형식이 잘못되었습니다. 숫자로만 입력하셔야 합니다.']);
})(jQuery);
/**
 * @file   modules/board/js/board.js
 * @author zero (zero@nzeo.com)
 * @brief  board 모듈의 javascript
 **/

/* 글쓰기 작성후 */
function completeDocumentInserted(ret_obj) {
    var error = ret_obj['error'];
    var message = ret_obj['message'];
    var mid = ret_obj['mid'];
    var document_srl = ret_obj['document_srl'];
    var category_srl = ret_obj['category_srl'];

    //alert(message);

    var url;
    if(!document_srl)
    {
        url = current_url.setQuery('mid',mid).setQuery('act','');
    }
    else
    {
        url = current_url.setQuery('mid',mid).setQuery('document_srl',document_srl).setQuery('act','');
    }
    if(category_srl) url = url.setQuery('category',category_srl);
    location.href = url;
}

/* 글 삭제 */
function completeDeleteDocument(ret_obj) {
    var error = ret_obj['error'];
    var message = ret_obj['message'];
    var mid = ret_obj['mid'];
    var page = ret_obj['page'];

    var url = current_url.setQuery('mid',mid).setQuery('act','').setQuery('document_srl','');
    if(page) url = url.setQuery('page',page);

    //alert(message);

    location.href = url;
}

/* 검색 실행 */
function completeSearch(fo_obj, params) {
    fo_obj.submit();
}

function completeVote(ret_obj) {
    var error = ret_obj['error'];
    var message = ret_obj['message'];
    alert(message);
    location.href = location.href;
}

// 현재 페이지 reload
function completeReload(ret_obj) {
    var error = ret_obj['error'];
    var message = ret_obj['message'];

    location.href = location.href;
}

/* 댓글 글쓰기 작성후 */
function completeInsertComment(ret_obj) {
    var error = ret_obj['error'];
    var message = ret_obj['message'];
    var mid = ret_obj['mid'];
    var document_srl = ret_obj['document_srl'];
    var comment_srl = ret_obj['comment_srl'];

    var url = current_url.setQuery('mid',mid).setQuery('document_srl',document_srl).setQuery('act','');
    if(comment_srl) url = url.setQuery('rnd',comment_srl)+"#comment_"+comment_srl;

    //alert(message);

    location.href = url;
}

/* 댓글 삭제 */
function completeDeleteComment(ret_obj) {
    var error = ret_obj['error'];
    var message = ret_obj['message'];
    var mid = ret_obj['mid'];
    var document_srl = ret_obj['document_srl'];
    var page = ret_obj['page'];

    var url = current_url.setQuery('mid',mid).setQuery('document_srl',document_srl).setQuery('act','');
    if(page) url = url.setQuery('page',page);

    //alert(message);

    location.href = url;
}

/* 트랙백 삭제 */
function completeDeleteTrackback(ret_obj) {
    var error = ret_obj['error'];
    var message = ret_obj['message'];
    var mid = ret_obj['mid'];
    var document_srl = ret_obj['document_srl'];
    var page = ret_obj['page'];

    var url = current_url.setQuery('mid',mid).setQuery('document_srl',document_srl).setQuery('act','');
    if(page) url = url.setQuery('page',page);

    //alert(message);

    location.href = url;
}

/* 카테고리 이동 */
function doChangeCategory() {
    var category_srl = jQuery('#board_category option:selected').val();
    location.href = decodeURI(current_url).setQuery('category',category_srl);
}

/* 스크랩 */
function doScrap(document_srl) {
    var params = new Array();
    params["document_srl"] = document_srl;
    exec_xml("member","procMemberScrapDocument", params, null);
}
function insert_comment(fo_obj){
	var validator = xe.getApp('validator')[0];
	if(!validator) return false;
	if(!fo_obj.elements['_filter']) jQuery(fo_obj).prepend('<input type="hidden" name="_filter" value="" />');
	fo_obj.elements['_filter'].value = 'insert_comment';
	validator.cast('ADD_CALLBACK', ['insert_comment', function(form){
		var params={}, responses=[], elms=form.elements, data=jQuery(form).serializeArray();
		jQuery.each(data, function(i, field){
			var val = jQuery.trim(field.value);
			if(!val) return true;
			if(/\[\]$/.test(field.name)) field.name = field.name.replace(/\[\]$/, '');
			if(params[field.name]) params[field.name] += '|@|'+val;
			else params[field.name] = field.value;
		});
		responses = ['error','message','mid','document_srl','comment_srl'];
		exec_xml('board','procBoardInsertComment', params, completeInsertComment, responses, params, form);
	}]);
	validator.cast('VALIDATE', [fo_obj,'insert_comment']);
	return false;
};

(function($){
	var validator = xe.getApp('Validator')[0];
	if(!validator) return false;
	validator.cast('ADD_FILTER', ['insert_comment', {
		'document_srl': {required:true},
		'nick_name': {required:true,maxlength:20},
		'password': {required:true},
		'email_address': {maxlength:250},
		'homepage': {maxlength:250},
		'content': {required:true,minlength:1}
	}]);
	validator.cast('ADD_MESSAGE', ['document_srl', '문서번호']);
	validator.cast('ADD_MESSAGE', ['nick_name', '닉네임']);
	validator.cast('ADD_MESSAGE', ['password', '비밀번호']);
	validator.cast('ADD_MESSAGE', ['email_address', '이메일 주소']);
	validator.cast('ADD_MESSAGE', ['homepage', '홈페이지']);
	validator.cast('ADD_MESSAGE', ['content', '내용']);
	validator.cast('ADD_MESSAGE', ['mid', '모듈 이름']);
	validator.cast('ADD_MESSAGE', ['comment_srl', 'comment_srl']);
	validator.cast('ADD_MESSAGE', ['parent_srl', 'parent_srl']);
	validator.cast('ADD_MESSAGE', ['is_secret', 'is_secret']);
	validator.cast('ADD_MESSAGE', ['notify_message', 'notify_message']);
	validator.cast('ADD_MESSAGE', ['isnull', '%s을 입력해주세요.']);
	validator.cast('ADD_MESSAGE', ['outofrange', '%s의 글자 수를 맞추어 주세요.']);
	validator.cast('ADD_MESSAGE', ['equalto', '%s이 잘못되었습니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_email', '%s의 형식이 잘못되었습니다. (예: xe@xpressengine.com)']);
	validator.cast('ADD_MESSAGE', ['invalid_userid', '%s의 형식이 잘못되었습니다.\n영문, 숫자와 _로 만드실 수 있으며, 첫 글자는 영문이어야 합니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_user_id', '%s의 형식이 잘못되었습니다.\n영문, 숫자와 _로 만드실 수 있으며, 첫 글자는 영문이어야 합니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_homepage', '%s의 형식이 잘못되었습니다. (예: http://www.xpressengine.com)']);
	validator.cast('ADD_MESSAGE', ['invalid_korean', '%s의 형식이 잘못되었습니다. 한글로만 입력하셔야 합니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_korean_number', '%s의 형식이 잘못되었습니다. 한글과 숫자로만 입력하셔야 합니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_alpha', '%s의 형식이 잘못되었습니다. 영문으로만 입력하셔야 합니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_alpha_number', '%s의 형식이 잘못되었습니다. 영문과 숫자로만 입력하셔야 합니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_number', '%s의 형식이 잘못되었습니다. 숫자로만 입력하셔야 합니다.']);
})(jQuery);
function search(fo_obj){
	var validator = xe.getApp('validator')[0];
	if(!validator) return false;
	if(!fo_obj.elements['_filter']) jQuery(fo_obj).prepend('<input type="hidden" name="_filter" value="" />');
	fo_obj.elements['_filter'].value = 'search';
	validator.cast('ADD_CALLBACK', ['search', function(form){
		var params={}, responses=[], elms=form.elements, data=jQuery(form).serializeArray();
		jQuery.each(data, function(i, field){
			var val = jQuery.trim(field.value);
			if(!val) return true;
			if(/\[\]$/.test(field.name)) field.name = field.name.replace(/\[\]$/, '');
			if(params[field.name]) params[field.name] += '|@|'+val;
			else params[field.name] = field.value;
		});
		responses = ['error','message'];
		exec_xml('board','', params, completeSearch, responses, params, form);
	}]);
	validator.cast('VALIDATE', [fo_obj,'search']);
	return false;
};

(function($){
	var validator = xe.getApp('Validator')[0];
	if(!validator) return false;
	validator.cast('ADD_FILTER', ['search', {
		'search_target': {required:true},
		'search_keyword': {required:true,maxlength:40}
	}]);
	validator.cast('ADD_MESSAGE', ['search_target', '검색대상']);
	validator.cast('ADD_MESSAGE', ['search_keyword', '검색어']);
	validator.cast('ADD_MESSAGE', ['mid', '모듈 이름']);
	validator.cast('ADD_MESSAGE', ['isnull', '%s을 입력해주세요.']);
	validator.cast('ADD_MESSAGE', ['outofrange', '%s의 글자 수를 맞추어 주세요.']);
	validator.cast('ADD_MESSAGE', ['equalto', '%s이 잘못되었습니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_email', '%s의 형식이 잘못되었습니다. (예: xe@xpressengine.com)']);
	validator.cast('ADD_MESSAGE', ['invalid_userid', '%s의 형식이 잘못되었습니다.\n영문, 숫자와 _로 만드실 수 있으며, 첫 글자는 영문이어야 합니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_user_id', '%s의 형식이 잘못되었습니다.\n영문, 숫자와 _로 만드실 수 있으며, 첫 글자는 영문이어야 합니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_homepage', '%s의 형식이 잘못되었습니다. (예: http://www.xpressengine.com)']);
	validator.cast('ADD_MESSAGE', ['invalid_korean', '%s의 형식이 잘못되었습니다. 한글로만 입력하셔야 합니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_korean_number', '%s의 형식이 잘못되었습니다. 한글과 숫자로만 입력하셔야 합니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_alpha', '%s의 형식이 잘못되었습니다. 영문으로만 입력하셔야 합니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_alpha_number', '%s의 형식이 잘못되었습니다. 영문과 숫자로만 입력하셔야 합니다.']);
	validator.cast('ADD_MESSAGE', ['invalid_number', '%s의 형식이 잘못되었습니다. 숫자로만 입력하셔야 합니다.']);
})(jQuery);
/**
 * @file autolink.js
 * @brief javascript code for autolink addon
 * @author taggon (gonom9@gmail.com)
 */
(function($){
	var protocol_re = '(https?|ftp|news|telnet|irc|mms)://';
	var domain_re   = '(?:[\\w\\-]+\\.)+(?:[a-z]+)';
	var max_255_re  = '(?:1[0-9]{2}|2[0-4][0-9]|25[0-5]|[1-9]?[0-9])';
	var ip_re       = '(?:'+max_255_re+'\\.){3}'+max_255_re;
	var port_re     = '(?::([0-9]+))?';
	var user_re     = '(?:/~[\\w-]+)?';
	var path_re     = '((?:/[\\w!"$-/:-@]+)*)';
	var hash_re     = '(?:#([\\w!-@]+))?';

	var url_regex = new RegExp('('+protocol_re+'('+domain_re+'|'+ip_re+'|localhost'+')'+port_re+user_re+path_re+hash_re+')', 'ig');

	var AutoLink = xe.createPlugin("autolink", {
		targets : [],
		init : function() {
			this.targets = [];
		},
		API_ONREADY : function() {
			var thisPlugin = this;

			// extract target text nodes
			this.extractTargets($('.xe_content'));

			$(this.targets).each(function(){
				thisPlugin.cast('AUTOLINK', [this]);
			});
		},
		API_AUTOLINK : function(oSender, params) {
			var textNode = params[0];
			if(!$(textNode).parent().length || $(textNode).parent().get(0).nodeName.toLowerCase() == 'a') return;
			var content  = textNode.nodeValue;
			var dummy    = $('<span>');

			content = content.replace(/</g, '&lt;').replace(/>/g, '&gt;');
			content = content.replace(url_regex, '<a href="$1" target="_blank">$1</a>');

			$(textNode).before(dummy);
			$(textNode).replaceWith(content);
			params[0] = dummy.next('a');
			dummy.remove();
		},
		extractTargets : function(obj) {
			var thisPlugin = this;
			var wrap = $('.xe_content', obj);
			if(wrap.length) {
				this.extractTargets(wrap);
				return;
			}

			$(obj)
			.contents()
			.each(function(){
				var node_name = this.nodeName.toLowerCase();
				if($.inArray(node_name, ['a', 'pre', 'xml', 'textarea', 'input', 'select', 'option', 'code', 'script', 'style', 'iframe', 'button', 'img', 'embed', 'object', 'ins']) != -1) return;

				// FIX ME : When this meanless code wasn't executed, url_regex do not run correctly. why?
				url_regex.exec('');

				if(this.nodeType == 3) { // text node
					var content = this.nodeValue;

					if(content.length < 5) return;

					if(!/(http|https|ftp|news|telnet|irc|mms):\/\//i.test(content)) return;

					thisPlugin.targets.push(this);
				} else {
					thisPlugin.extractTargets(this);
				}
			});
		}
	});

	xe.registerPlugin(new AutoLink());
})(jQuery);
/**
 * 에디터에서 사용하기 위한 변수
 **/
var editorMode = new Array(); ///<< 에디터의 html편집 모드 flag 세팅 변수 (html or null)
var editorAutoSaveObj = {fo_obj:null, editor_sequence:0, title:'', content:'', locked:false} ///< 자동저장을 위한 정보를 가진 object
var editorRelKeys = new Array(); ///< 에디터와 각 모듈과의 연동을 위한 key 값을 보관하는 변수
var editorDragObj = {isDrag:false, y:0, obj:null, id:'', det:0, source_height:0}

/**
 * 에디터 사용시 사용되는 이벤트 연결 함수 호출
 **/
xAddEventListener(document, 'click', editorEventCheck);
xAddEventListener(document, 'mousedown', editorDragStart);
xAddEventListener(document, 'mouseup', editorDragStop);

function editorGetContent(editor_sequence) {
    // 입력된 내용을 받아옴
    var content = editorRelKeys[editor_sequence]["func"](editor_sequence);

    // 첨부파일 링크시 url을 변경
    var reg_pattern = new RegExp( request_uri.replace(/\//g,'\\/')+"(files|common|modules|layouts|widgets)", 'ig' );
    return content.replace(reg_pattern, "$1");
}

// 에디터에 포커스를 줌
function editorFocus(editor_sequence) {
	try {
		var iframe_obj = editorGetIFrame(editor_sequence);
		iframe_obj.contentWindow.focus();
	} catch(e){}
}

/**
 * 자동 저장 기능
 **/
// 자동 저장 활성화 시키는 함수 (50초마다 자동저장)
function editorEnableAutoSave(fo_obj, editor_sequence) {
    var title = fo_obj.title.value;
    var content = editorRelKeys[editor_sequence]['content'].value;
    editorAutoSaveObj = {"fo_obj":fo_obj, "editor_sequence":editor_sequence, "title":title, "content":content, locked:false};
    setTimeout('_editorAutoSave()', 50000);
}

// ajax를 이용하여 editor.procEditorSaveDoc 호출하여 자동 저장시킴 exe는 강제 코드
function _editorAutoSave(exe) {
    var fo_obj = editorAutoSaveObj.fo_obj;
    var editor_sequence = editorAutoSaveObj.editor_sequence;

    // 50초마다 동기화를 시킴 강제 실행은 제외
    if(!exe) setTimeout('_editorAutoSave()', 50000);

    // 현재 자동저장중이면 중지
    if(editorAutoSaveObj.locked == true) return;

    // 대상이 없으면 자동저장 시키는 기능 자체를 중지
    if(!fo_obj || typeof(fo_obj.title)=='undefined' || !editor_sequence) return;

    // 자동저장을 위한 준비
    var title = fo_obj.title.value;
	var content = '';
	try{
	   content = editorGetContent(editor_sequence);
	}catch(e){
	}

    // 내용이 이전에 저장하였던 것과 다르면 자동 저장을 함 또는 강제 저장 설정시 자동 저장
    if(title != editorAutoSaveObj.title || content != editorAutoSaveObj.content || exe) {
        var params = new Array();

        params["title"] = title;
        params["content"] = content;
        params["mid"] = current_mid;
        params["document_srl"] = editorRelKeys[editor_sequence]['primary'].value;

        editorAutoSaveObj.title = title;
        editorAutoSaveObj.content = content;

        var obj   = jQuery("#editor_autosaved_message_"+editor_sequence);
        var oDate = new Date();

        // 메시지 만들어서 보여줌
        obj.text(oDate.getHours()+':'+oDate.getMinutes()+' '+auto_saved_msg).show(300);

        // 현재 자동저장중임을 설정
        editorAutoSaveObj.locked = true;

        // 서버 호출 (서버와 교신중이라는 메세지를 보이지 않도록 함)
        show_waiting_message = false;
        exec_xml("editor","procEditorSaveDoc", params, function() { editorAutoSaveObj.locked = false; } );
        show_waiting_message = true;
    }
}

// 자동저장된 모든 메세지를 삭제하는 루틴
function editorRemoveSavedDoc() {
    var param = new Array();
    param['mid'] = current_mid;
    exec_xml("editor","procEditorRemoveSavedDoc", param);
}

/**
 * 에디터의 상태나 객체를 구하기 위한 함수
 **/

// editor_sequence값에 해당하는 iframe의 object를 return
function editorGetIFrame(editor_sequence) {
    if(editorRelKeys != undefined && editorRelKeys[editor_sequence] != undefined && editorRelKeys[editor_sequence]['editor'] != undefined)
    return editorRelKeys[editor_sequence]['editor'].getFrame();
    return xGetElementById( 'editor_iframe_'+ editor_sequence );
}
function editorGetTextarea(editor_sequence) {
    return xGetElementById( 'editor_textarea_'+ editor_sequence );
}
/**
 * iframe 세로 크기 조절 드래그 관련
 **/
function editorDragStart(evt) {
    var e = new xEvent(evt);
    var obj = e.target;
    if(typeof(obj.id)=='undefined'||!obj.id) return;

    var id = obj.id;
    if(id.indexOf('editor_drag_bar_')!=0) return;

    editorDragObj.isDrag = true;
    editorDragObj.y = e.pageY;
    editorDragObj.obj = e.target;
    editorDragObj.id = id.substr('editor_drag_bar_'.length);

    var iframe_obj = editorGetIFrame(editorDragObj.id);
    var textarea_obj = editorGetTextarea(editorDragObj.id);
    var preview_obj = xGetElementById('editor_preview_'+editorDragObj.id);
    editorDragObj.source_height = xHeight(iframe_obj) || xHeight(preview_obj);
    xGetElementById('xeEditorMask_' + editorDragObj.id).style.display='block';

    xAddEventListener(document, 'mousemove', editorDragMove, true);
//    xAddEventListener(editorDragObj.obj, 'mousemove', editorDragMove, false);
}

function editorDragMove(evt) {

    if(!editorDragObj.isDrag){
        if(editorDragObj.id) xGetElementById('xeEditorMask_' + editorDragObj.id).style.display='none';
        return;
    }

    var e = new xEvent(evt);
    var h = e.pageY - editorDragObj.y;

    editorDragObj.isDrag = true;
    editorDragObj.y = e.pageY;
    editorDragObj.obj = e.target;


    var iframe_obj = editorGetIFrame(editorDragObj.id);
    var textarea_obj = editorGetTextarea(editorDragObj.id);
    var preview_obj = xGetElementById('editor_preview_'+editorDragObj.id);
    var height = xHeight(iframe_obj) || xHeight(textarea_obj) || xHeight(preview_obj);
    height += h;
    xHeight(iframe_obj, height);
    xHeight(textarea_obj, height);
    xHeight(preview_obj, height);
//    xHeight(iframe_obj.parentNode, height+200);
}

function editorDragStop(evt) {
    if(editorDragObj.id) xGetElementById('xeEditorMask_'+editorDragObj.id).style.display='none';
    if(!editorDragObj.isDrag){
        return;
    }


    xRemoveEventListener(document, 'mousemove', editorDragMove, false);
//    xRemoveEventListener(editorDragObj.obj, 'mousemove', editorDragMove, false);

    var iframe_obj = editorGetIFrame(editorDragObj.id);
    var textarea_obj = editorGetTextarea(editorDragObj.id);
    if(typeof(fixAdminLayoutFooter)=='function') fixAdminLayoutFooter(xHeight(iframe_obj)-editorDragObj.source_height);


    editorDragObj.isDrag = false;
    editorDragObj.y = 0;
    editorDragObj.obj = null;
    editorDragObj.id = '';
}

// Editor Option Button
function eOptionOver(obj) {
    obj.style.marginTop='-21px';
    obj.style.zIndex='99';
}
function eOptionOut(obj) {
    obj.style.marginTop='0';
    obj.style.zIndex='1';
}
function eOptionClick(obj) {
    obj.style.marginTop='-42px';
    obj.style.zIndex='99';
}

/**
 * 에디터 컴포넌트 구현 부분
 **/

// 에디터 상단의 컴포넌트 버튼 클릭시 action 처리 (마우스다운 이벤트 발생시마다 요청이 됨)
var editorPrevSrl = null;
function editorEventCheck(evt) {
    editorPrevNode = null;

    // 이벤트가 발생한 object의 ID를 구함
    var e = new xEvent(evt);
    var target_id = e.target.id;
    if(!target_id) return;

    // editor_sequence와 component name을 구함 (id가 포맷과 다르면 return)
    var info = target_id.split('_');
    if(info[0]!="component") return;
    var editor_sequence = info[1];
    var component_name = target_id.replace(/^component_([0-9]+)_/,'');
    if(!editor_sequence || !component_name) return;

    if(editorMode[editor_sequence]=='html') return;

    switch(component_name) {

        // 기본 기능에 대한 동작 (바로 실행)
        case 'Bold' :
        case 'Italic' :
        case 'Underline' :
        case 'StrikeThrough' :
        case 'undo' :
        case 'redo' :
        case 'JustifyLeft' :
        case 'JustifyCenter' :
        case 'JustifyRight' :
        case 'JustifyFull' :
        case 'Indent' :
        case 'Outdent' :
        case 'InsertOrderedList' :
        case 'InsertUnorderedList' :
        case 'SaveAs' :
        case 'Print' :
        case 'Copy' :
        case 'Cut' :
        case 'Paste' :
        case 'RemoveFormat' :
        case 'Subscript' :
        case 'Superscript' :
                editorDo(component_name, '', editor_sequence);
            break;

        // 추가 컴포넌트의 경우 서버에 요청을 시도
        default :
                openComponent(component_name, editor_sequence);
            break;
    }

    return;
}

// 컴포넌트 팝업 열기
function openComponent(component_name, editor_sequence, manual_url) {
    editorPrevSrl = editor_sequence;
    if(editorMode[editor_sequence]=='html') return;

    var popup_url = request_uri+"?module=editor&act=dispEditorPopup&editor_sequence="+editor_sequence+"&component="+component_name;
    if(typeof(manual_url)!="undefined" && manual_url) popup_url += "&manual_url="+escape(manual_url);

    popopen(popup_url, 'editorComponent');
}

// 더블클릭 이벤트 발생시에 본문내에 포함된 컴포넌트를 찾는 함수
var editorPrevNode = null;
function editorSearchComponent(evt) {
    var e = new xEvent(evt);

    editorPrevNode = null;
    var obj = e.target;

    // 위젯인지 일단 체크
    if(obj.getAttribute("widget")) {
        // editor_sequence을 찾음
        var tobj = obj;
        while(tobj && tobj.nodeName != "BODY") {
            tobj = xParent(tobj);
        }
        if(!tobj || tobj.nodeName != "BODY" || !tobj.getAttribute("editor_sequence")) {
            editorPrevNode = null;
            return;
        }
        var editor_sequence = tobj.getAttribute("editor_sequence");
        var widget = obj.getAttribute("widget");
        editorPrevNode = obj;

        if(editorMode[editor_sequence]=='html') return;
        popopen(request_uri+"?module=widget&act=dispWidgetGenerateCodeInPage&selected_widget="+widget+"&module_srl="+editor_sequence,'GenerateCodeInPage');
        return;
    }

    // 선택되어진 object부터 상단으로 이동하면서 editor_component attribute가 있는지 검사
    if(!obj.getAttribute("editor_component")) {
        while(obj && !obj.getAttribute("editor_component")) {
            if(obj.parentElement) obj = obj.parentElement;
            else obj = xParent(obj);
        }
    }

    if(!obj) obj = e.target;

    var editor_component = obj.getAttribute("editor_component");

    // editor_component를 찾지 못했을 경우에 이미지/텍스트/링크의 경우 기본 컴포넌트와 연결
    if(!editor_component) {
        // 이미지일 경우
        if(obj.nodeName == "IMG") {
            editor_component = "image_link";
            editorPrevNode = obj;
        }
    } else {
        editorPrevNode = obj;
    }

    // 아무런 editor_component가 없다면 return
    if(!editor_component) {
        editorPrevNode = null;
        return;
    }

    // editor_sequence을 찾음
    var tobj = obj;
    while(tobj && tobj.nodeName != "BODY") {
        tobj = xParent(tobj);
    }
    if(!tobj || tobj.nodeName != "BODY" || !tobj.getAttribute("editor_sequence")) {
        editorPrevNode = null;
        return;
    }
    var editor_sequence = tobj.getAttribute("editor_sequence");

    // 해당 컴포넌트를 찾아서 실행
    openComponent(editor_component, editor_sequence);
}

// 에디터 내의 선택된 부분의 html코드를 변경
function editorReplaceHTML(iframe_obj, html) {
    // 이미지 경로 재지정 (rewrite mod)
    var srcPathRegx = /src=("|\'){1}(\.\/)?(files\/attach|files\/cache|files\/faceOff|files\/member_extra_info|modules|common|widgets|widgetstyle|layouts|addons)\/([^"\']+)\.(jpg|jpeg|png|gif)("|\'){1}/g;
    html = html.replace(srcPathRegx, 'src="'+request_uri+'$3/$4.$5"');

    // href 경로 재지정 (rewrite mod)
    var hrefPathRegx = /href=("|\'){1}(\.\/)?\?([^"\']+)("|\'){1}/g;
    html = html.replace(hrefPathRegx, 'href="'+request_uri+'?$3"');

    // 에디터가 활성화 되어 있는지 확인 후 비활성화시 활성화
    var editor_sequence = iframe_obj.contentWindow.document.body.getAttribute("editor_sequence");

    // iframe 에디터에 포커스를 둠
    iframe_obj.contentWindow.focus();

    if(xIE4Up) {
        var range = iframe_obj.contentWindow.document.selection.createRange();
        if(range.pasteHTML) {
            range.pasteHTML(html);
        } else if(editorPrevNode) {
            editorPrevNode.outerHTML = html;
        }

    } else {

        try {
            if(iframe_obj.contentWindow.getSelection().focusNode.tagName == "HTML") {
                var range = iframe_obj.contentDocument.createRange();
                range.setStart(iframe_obj.contentDocument.body,0);
                range.setEnd(iframe_obj.contentDocument.body,0);
                range.insertNode(range.createContextualFragment(html));
            } else {
                var range = iframe_obj.contentWindow.getSelection().getRangeAt(0);
                range.deleteContents();
                range.insertNode(range.createContextualFragment(html));
            }
        } catch(e) {
            xInnerHtml(iframe_obj.contentWindow.document.body, html+xInnerHtml(iframe_obj.contentWindow.document.body));
        }

    }
}

// 에디터 내의 선택된 부분의 html 코드를 return
function editorGetSelectedHtml(editor_sequence) {
    var iframe_obj = editorGetIFrame(editor_sequence);
    if(xIE4Up) {
        var range = iframe_obj.contentWindow.document.selection.createRange();
        var html = range.htmlText;
        return html;
    } else {
        var range = iframe_obj.contentWindow.getSelection().getRangeAt(0);
        var dummy = xCreateElement('div');
        dummy.appendChild(range.cloneContents());
        var html = xInnerHtml(dummy);
        return html;
    }
}
(function($){

// extends jQuery object
$.extend({
	Class : function(def) {
		function c(){
			if (typeof this.$super != 'undefined') this.$super.$this = this;
			if ($.isFunction(this.$init)) this.$init.apply(this, arguments);
		}
		c.prototype = def;
		c.constructor = c;
		c.extend = Class_extend;

		return c;
	},
	$ : function(id) {
		if(typeof id == 'string') {
			if (id.substring(0,1) == '<') return $(id).get(0);
			return $('#'+id).get(0);
		} else {
			return id;
		}
	},
	fnBind : function(fn, th/* , args... */) {
		var args = $.makeArray(arguments);
		args.shift(); args.shift();

		return function() {
			var a = args.concat($.makeArray(arguments));

			return fn.apply(th, a);
		};
	}
});

$.browser.nVersion = parseFloat($.browser.version);

function Class_extend(superDef) {
	var Super = superDef.prototype;

	this.prototype.$super = {};

	function bind(fn) {
		return function() {
			return fn.apply(this.$this, arguments);
		};
	}

	for(var x in Super) {
		if (!Super.propertyIsEnumerable(x)) continue;

		if (typeof this.prototype[x] == 'undefined') this.prototype[x] = Super[x];
		this.prototype.$super[x] = $.isFunction(Super[x])?bind(Super[x]):Super[x];
	}

	return this;
}

if (typeof window.xe == 'undefined') window.xe = {};

//{
 /**
 * @fileOverview This file contains Xpress framework core
 * @name XpressCore.js
 */
xe.XpressCore = $.Class({
	name : "XpressCore",

	$init : function(htOptions){
		htOptions = !htOptions?{}:$.Class({}).extend({
			oDebugger : null
		}).extend(htOptions);
		if(htOptions.oDebugger){
			this.oDebugger = htOptions.oDebugger;
			this.oDebugger.oApp = this;
		}

		// To prevent processing a Xpress command before all the plugins are registered and ready,
		// Queue up all the commands here until the application's status is changed to READY
		this.commandQueue = [];

		this.oCommandMap = {};
		this.oDisabledCommand = {};
		this.aPlugins = [];

		this.appStatus = xe.APP_STATUS["NOT_READY"];

		// Register the core as a plugin so it can receive messages
		this.registerPlugin(this);
	},

	exec : function(msg, args, oEvent){
		// If the application is not yet ready just queue the command
		if(this.appStatus == xe.APP_STATUS["NOT_READY"]){
			this.commandQueue[this.commandQueue.length] = {'msg':msg, 'args':args, 'event':oEvent};
			return true;
		}

		this.exec = this._exec;
		this.exec(msg, args, oEvent);
	},

	delayedExec : function(msg, args, nDelay, oEvent){
		var fExec = $.fnBind(this.exec, this, msg, args, oEvent);
		setTimeout(fExec, nDelay);
	},

	_exec : function(msg, args, oEvent){return (this._exec = this.oDebugger?this._execWithDebugger:this._execWithoutDebugger).call(this, msg, args, oEvent);},
	_execWithDebugger : function(msg, args, oEvent){this.oDebugger.log_MessageStart(msg, args);var bResult = this._doExec(msg, args, oEvent);this.oDebugger.log_MessageEnd(msg, args);return bResult;	},
	_execWithoutDebugger : function(msg, args, oEvent){return this._doExec(msg, args, oEvent);},
	_doExec : function(msg, args, oEvent){
		var bContinue = false;

		if(!this.oDisabledCommand[msg]){
			var allArgs = [];
			if(args && args.length){
				var iLen = args.length;
				for(var i=0; i<iLen; i++) allArgs[i] = args[i];
			}
			if(oEvent) allArgs[allArgs.length] = oEvent;

			var bContinue = true;
			bContinue = this._execMsgStep("BEFORE", msg, allArgs);
			if(bContinue) bContinue = this._execMsgStep("ON", msg, allArgs);
			if(bContinue) bContinue = this._execMsgStep("AFTER", msg, allArgs);
		}

		return bContinue;
	},

	registerPlugin : function(oPlugin){
		if(!oPlugin) throw("An error occured in registerPlugin(): invalid plug-in");

		oPlugin.nIdx = this.aPlugins.length;
		oPlugin.oApp = this;
		this.aPlugins[oPlugin.nIdx] = oPlugin;

		// If the plugin does not specify that it takes time to be ready, change the stauts to READY right away
		if(oPlugin.status != xe.PLUGIN_STATUS["NOT_READY"]) oPlugin.status = xe.PLUGIN_STATUS["READY"];

		this.exec("MSG_PLUGIN_REGISTERED", [oPlugin]);

		return oPlugin.nIdx;
	},

	disableCommand : function(sCommand, bDisable){this.oDisabledCommand[sCommand] = bDisable;},

	registerBrowserEvent : function(obj, sEvent, sCMD, aParams, nDelay){
		if(!obj) return;
		aParams = aParams || [];
		var func = (nDelay)?$.fnBind(this.delayedExec, this, sCMD, aParams, nDelay):$.fnBind(this.exec, this, sCMD, aParams);
		$(obj).bind(sEvent, func);
	},

	run : function(){
		// Change the status from NOT_READY to let exec to process all the way
		this._changeAppStatus(xe.APP_STATUS["WAITING_FOR_PLUGINS_READY"]);

		// Process all the commands in the queue
		var iQueueLength = this.commandQueue.length;
		for(i=0; i<iQueueLength; i++){
			var curMsgAndArgs = this.commandQueue[i];
			this.exec(curMsgAndArgs.msg, curMsgAndArgs.args, curMsgAndArgs.event);
		}

		this._waitForPluginReady();
	},

	// Use this also to update the mapping
	createCommandMap : function(sMsgHandler){
		this.oCommandMap[sMsgHandler] = [];

		var nLen = this.aPlugins.length;
		for(var i=0; i<nLen; i++) this._doAddToCommandMap(sMsgHandler, this.aPlugins[i]);
	},

	addToCommandMap : function(sMsgHandler, oPlugin){
		// cannot "ADD" unless the map is already created.
		// the message will be added automatically to the mapping when it is first passed anyways, so do not add now
		if(!this.oCommandMap[sMsgHandler]) return;

		this._addToCommandMap(sMsgHandler, oPlugin);
	},

	_changeAppStatus : function(appStatus){
		this.appStatus = appStatus;

		// Initiate MSG_APP_READY if the application's status is being switched to READY
		if(this.appStatus == xe.APP_STATUS["READY"]) this.exec("MSG_APP_READY");
	},

	_execMsgStep : function(sMsgStep, sMsg, args){return (this._execMsgStep = this.oDebugger?this._execMsgStepWithDebugger:this._execMsgStepWithoutDebugger).call(this, sMsgStep, sMsg, args);},
	_execMsgStepWithDebugger : function(sMsgStep, sMsg, args){this.oDebugger.log_MessageStepStart(sMsgStep, sMsg, args);var bStatus = this._execMsgHandler ("$"+sMsgStep+"_"+sMsg, args);this.oDebugger.log_MessageStepEnd(sMsgStep, sMsg, args);return bStatus;},
	_execMsgStepWithoutDebugger : function(sMsgStep, sMsg, args){return this._execMsgHandler ("$"+sMsgStep+"_"+sMsg, args);},
	_execMsgHandler : function(sMsgHandler, args){
		if(!this.oCommandMap[sMsgHandler]){
			this.createCommandMap(sMsgHandler);
		}

		var aPlugins = this.oCommandMap[sMsgHandler];
		var iNumOfPlugins = aPlugins.length;

		if(iNumOfPlugins == 0) return true;

		var tmpStatus, bResult = true;
		// two similar codes were written twice due to the performace.
		if(sMsgHandler.match(/^\$(BEFORE|ON|AFTER)_MSG_APP_READY$/)){
			for(var i=0; i<iNumOfPlugins; i++){
				tmpStatus = this._execHandler(aPlugins[i], sMsgHandler, args);
				if(tmpStatus === false){
					bResult = false;
					break;
				}
			}
		}else{
			for(var i=0; i<iNumOfPlugins; i++){
				if(typeof aPlugins[i]["$PRECONDITION"] == "function") if(!this._execHandler(aPlugins[i], "$PRECONDITION", [sMsgHandler, args])) continue;

				tmpStatus = this._execHandler(aPlugins[i], sMsgHandler, args);
				if(tmpStatus === false){
					bResult = false;
					break;
				}
			}
		}

		return bResult;
	},

	_execHandler : function(oPlugin, sHandler, args){return	(this._execHandler = this.oDebugger?this._execHandlerWithDebugger:this._execHandlerWithoutDebugger).call(this, oPlugin, sHandler, args);},
	_execHandlerWithDebugger : function(oPlugin, sHandler, args){this.oDebugger.log_CallHandlerStart(oPlugin, sHandler, args);var bResult = oPlugin[sHandler].apply(oPlugin, args);this.oDebugger.log_CallHandlerEnd(oPlugin, sHandler, args);return bResult;},
	_execHandlerWithoutDebugger : function(oPlugin, sHandler, args){return oPlugin[sHandler].apply(oPlugin, args);},

	_doAddToCommandMap : function(sMsgHandler, oPlugin){
		if(typeof oPlugin[sMsgHandler] != "function") return;
		this.oCommandMap[sMsgHandler][this.oCommandMap[sMsgHandler].length] = oPlugin;
	},

	_waitForPluginReady : function(){
		var bAllReady = true;
		for(var i=0; i<this.aPlugins.length; i++){
			if(this.aPlugins[i].status == xe.PLUGIN_STATUS["NOT_READY"]){
				bAllReady = false;
				break;
			}
		}
		if(bAllReady){
			this._changeAppStatus(xe.APP_STATUS["READY"]);
		}else{
			setTimeout($.fnBind(this._waitForPluginReady, this), 100);
		}
	}
});
//}

xe.APP_STATUS = {
	'NOT_READY' : 0,
	'WAITING_FOR_PLUGINS_READY' : 1,
	'READY' : 2
};

xe.PLUGIN_STATUS = {
	'NOT_READY' : 0,
	'READY' : 1
};
/**
 * @fileOverview This file contains a cross-browser implementation of W3C's DOM Range
 * @name W3CDOMRange.js
 */
xe.W3CDOMRange = $.Class({
	$init : function(doc){
		this._document = doc || document;

		this.collapsed = true;
		this.commonAncestorContainer = this._document.body;
		this.endContainer = this._document.body;
		this.endOffset = 0;
		this.startContainer = this._document.body;
		this.startOffset = 0;
	},

	cloneContents : function(){
		var oClonedContents = this._document.createDocumentFragment();
		var oTmpContainer = this._document.createDocumentFragment();

		var aNodes = this._getNodesInRange();

		if(aNodes.length < 1) return oClonedContents;

		var oClonedContainers = this._constructClonedTree(aNodes, oTmpContainer);

		// oTopContainer = aNodes[aNodes.length-1].parentNode and this is not part of the initial array and only those child nodes should be cloned
		var oTopContainer = oTmpContainer.firstChild;

		if(oTopContainer){
			var elCurNode = oTopContainer.firstChild;
			var elNextNode;

			while(elCurNode){
				elNextNode = elCurNode.nextSibling;
				oClonedContents.appendChild(elCurNode);
				elCurNode = elNextNode;
			}
		}

		oClonedContainers = this._splitTextEndNodes({oStartContainer: oClonedContainers.oStartContainer, iStartOffset: this.startOffset,
													oEndContainer: oClonedContainers.oEndContainer, iEndOffset: this.endOffset});

		if(oClonedContainers.oStartContainer && oClonedContainers.oStartContainer.previousSibling)
			xe.DOMFix.parentNode(oClonedContainers.oStartContainer).removeChild(oClonedContainers.oStartContainer.previousSibling);

		if(oClonedContainers.oEndContainer && oClonedContainers.oEndContainer.nextSibling)
			xe.DOMFix.parentNode(oClonedContainers.oEndContainer).removeChild(oClonedContainers.oEndContainer.nextSibling);

		return oClonedContents;
	},

	_constructClonedTree : function(aNodes, oClonedParentNode){
		var oClonedStartContainer = null;
		var oClonedEndContainer = null;

		var oStartContainer = this.startContainer;
		var oEndContainer = this.endContainer;

		_recurConstructClonedTree = function(aAllNodes, iCurIdx, oParentNode, oClonedParentNode){

			if(iCurIdx < 0) return iCurIdx;

			var iChildIdx = iCurIdx-1;

			var oCurNodeCloneWithChildren = aAllNodes[iCurIdx].cloneNode(false);

			if(aAllNodes[iCurIdx] == oStartContainer) oClonedStartContainer = oCurNodeCloneWithChildren;
			if(aAllNodes[iCurIdx] == oEndContainer) oClonedEndContainer = oCurNodeCloneWithChildren;

			while(iChildIdx >= 0 && xe.DOMFix.parentNode(aAllNodes[iChildIdx]) == aAllNodes[iCurIdx]){
				iChildIdx = this._recurConstructClonedTree(aAllNodes, iChildIdx, aAllNodes[iCurIdx], oCurNodeCloneWithChildren, oClonedStartContainer, oClonedEndContainer);
			}

			// this may trigger an error message in IE when an erroneous script is inserted
			oClonedParentNode.insertBefore(oCurNodeCloneWithChildren, oClonedParentNode.firstChild);

			return iChildIdx;
		};

		aNodes[aNodes.length] = xe.DOMFix.parentNode(aNodes[aNodes.length-1]);
		_recurConstructClonedTree(aNodes, aNodes.length-1, aNodes[aNodes.length-1], oClonedParentNode);

		return {oStartContainer: oClonedStartContainer, oEndContainer: oClonedEndContainer};
	},

	cloneRange : function(){
		return this._copyRange(new xe.W3CDOMRange(this._document));
	},

	_copyRange : function(oClonedRange){
		oClonedRange.collapsed = this.collapsed;
		oClonedRange.commonAncestorContainer = this.commonAncestorContainer;
		oClonedRange.endContainer = this.endContainer;
		oClonedRange.endOffset = this.endOffset;
		oClonedRange.startContainer = this.startContainer;
		oClonedRange.startOffset = this.startOffset;
		oClonedRange._document = this._document;

		return oClonedRange;
	},

	collapse : function(toStart){
		if(toStart){
			this.endContainer = this.startContainer;
			this.endOffset = this.startOffset;
		}else{
			this.startContainer = this.endContainer;
			this.startOffset = this.endOffset;
		}

		this._updateRangeInfo();
	},

	compareBoundaryPoints : function(how, sourceRange){
		switch(how){
			case xe.W3CDOMRange.START_TO_START:
				return this._compareEndPoint(this.startContainer, this.startOffset, sourceRange.startContainer, sourceRange.startOffset);
			case xe.W3CDOMRange.START_TO_END:
				return this._compareEndPoint(this.endContainer, this.endOffset, sourceRange.startContainer, sourceRange.startOffset);
			case xe.W3CDOMRange.END_TO_END:
				return this._compareEndPoint(this.endContainer, this.endOffset, sourceRange.endContainer, sourceRange.endOffset);
			case xe.W3CDOMRange.END_TO_START:
				return this._compareEndPoint(this.startContainer, this.startOffset, sourceRange.endContainer, sourceRange.endOffset);
		}
	},

	_findBody : function(oNode){
		if(!oNode) return null;
		while(oNode){
			if(oNode.tagName == "BODY") return oNode;
			oNode = xe.DOMFix.parentNode(oNode);
		}
		return null;
	},

	_compareEndPoint : function(oContainerA, iOffsetA, oContainerB, iOffsetB){
		var iIdxA, iIdxB;

		if(!oContainerA || this._findBody(oContainerA) != this._document.body){
			oContainerA = this._document.body;
			iOffsetA = 0;
		}

		if(!oContainerB || this._findBody(oContainerB) != this._document.body){
			oContainerB = this._document.body;
			iOffsetB = 0;
		}

		var compareIdx = function(iIdxA, iIdxB){
			// iIdxX == -1 when the node is the commonAncestorNode
			// if iIdxA == -1
			// -> [[<nodeA>...<nodeB></nodeB>]]...</nodeA>
			// if iIdxB == -1
			// -> <nodeB>...[[<nodeA></nodeA>...</nodeB>]]
			if(iIdxB == -1) iIdxB = iIdxA+1;
			if(iIdxA < iIdxB) return -1;
			if(iIdxA == iIdxB) return 0;
			return 1;
		};

		var oCommonAncestor = this._getCommonAncestorContainer(oContainerA, oContainerB);

		// ================================================================================================================================================
		//  Move up both containers so that both containers are direct child nodes of the common ancestor node. From there, just compare the offset
		// Add 0.5 for each contaienrs that has "moved up" since the actual node is wrapped by 1 or more parent nodes and therefore its position is somewhere between idx & idx+1
		// <COMMON_ANCESTOR>NODE1<P>NODE2</P>NODE3</COMMON_ANCESTOR>
		// The position of NODE2 in COMMON_ANCESTOR is somewhere between after NODE1(idx1) and before NODE3(idx2), so we let that be 1.5

		// container node A in common ancestor container
		var oNodeA = oContainerA;
		if(oNodeA != oCommonAncestor){
			while((oTmpNode = xe.DOMFix.parentNode(oNodeA)) != oCommonAncestor){oNodeA = oTmpNode;}

			iIdxA = this._getPosIdx(oNodeA)+0.5;
		}else iIdxA = iOffsetA;

		// container node B in common ancestor container
		var oNodeB = oContainerB;
		if(oNodeB != oCommonAncestor){
			while((oTmpNode = xe.DOMFix.parentNode(oNodeB)) != oCommonAncestor){oNodeB = oTmpNode;}

			iIdxB = this._getPosIdx(oNodeB)+0.5;
		}else iIdxB = iOffsetB;

		return compareIdx(iIdxA, iIdxB);
	},

	_getCommonAncestorContainer : function(oNode1, oNode2){
		var oComparingNode = oNode2;

		while(oNode1){
			while(oComparingNode){
				if(oNode1 == oComparingNode) return oNode1;
				oComparingNode = xe.DOMFix.parentNode(oComparingNode);
			}
			oComparingNode = oNode2;
			oNode1 = xe.DOMFix.parentNode(oNode1);
		}

		return this._document.body;
	},

	deleteContents : function(){
		if(this.collapsed) return;

		this._splitTextEndNodesOfTheRange();

		var aNodes = this._getNodesInRange();

		if(aNodes.length < 1) return;

		var oPrevNode = aNodes[0].previousSibling;
		while(oPrevNode && this._isBlankTextNode(oPrevNode)) oPrevNode = oPrevNode.previousSibling;

		var oNewStartContainer, iNewOffset;
		if(!oPrevNode){
			oNewStartContainer = xe.DOMFix.parentNode(aNodes[0]);
			iNewOffset = 0;
		}

		for(var i=0; i<aNodes.length; i++){
			var oNode = aNodes[i];
			if(!oNode.firstChild){
				if(oNewStartContainer == oNode){
					iNewOffset = this._getPosIdx(oNewStartContainer);
					oNewStartContainer = xe.DOMFix.parentNode(oNode);
				}
				xe.DOMFix.parentNode(oNode).removeChild(oNode);
			}
		}

		if(!oPrevNode){
			this.setStart(oNewStartContainer, iNewOffset);
		}else{
			if(oPrevNode.tagName == "BODY")
				this.setStartBefore(oPrevNode);
			else
				this.setStartAfter(oPrevNode);
		}

		this.collapse(true);
	},

	extractContents : function(){
		var oClonedContents = this.cloneContents();
		this.deleteContents();
		return oClonedContents;
	},

	insertNode : function(newNode){
		var oFirstNode = null;

		var oParentContainer;

		if(this.startContainer.nodeType == "3"){
			oParentContainer = xe.DOMFix.parentNode(this.startContainer);
			if(this.startContainer.nodeValue.length <= this.startOffset)
				oFirstNode = this.startContainer.nextSibling;
			else
				oFirstNode = this.startContainer.splitText(this.startOffset);
		}else{
			oParentContainer = this.startContainer;
			oFirstNode = xe.DOMFix.childNodes(this.startContainer)[this.startOffset];
		}

		if(!oFirstNode || !xe.DOMFix.parentNode(oFirstNode)) oFirstNode = null;

		oParentContainer.insertBefore(newNode, oFirstNode);

		this.setStartBefore(newNode);
	},

	selectNode : function(refNode){
		this.setStartBefore(refNode);
		this.setEndAfter(refNode);
	},

	selectNodeContents : function(refNode){
		this.setStart(refNode, 0);
		this.setEnd(refNode, xe.DOMFix.childNodes(refNode).length);
	},

	_endsNodeValidation : function(oNode, iOffset){
		if(!oNode || this._findBody(oNode) != this._document.body) throw new Error("INVALID_NODE_TYPE_ERR oNode is not part of current document");

		if(oNode.nodeType == 3){
			if(iOffset > oNode.nodeValue.length) iOffset = oNode.nodeValue.length;
		}else{
			if(iOffset > xe.DOMFix.childNodes(oNode).length) iOffset = xe.DOMFix.childNodes(oNode).length;
		}

		return iOffset;
	},


	setEnd : function(refNode, offset){
		offset = this._endsNodeValidation(refNode, offset);

		this.endContainer = refNode;
		this.endOffset = offset;
		if(!this.startContainer || this._compareEndPoint(this.startContainer, this.startOffset, this.endContainer, this.endOffset) != -1) this.collapse(false);

		this._updateRangeInfo();
	},

	setEndAfter : function(refNode){
		if(!refNode) throw new Error("INVALID_NODE_TYPE_ERR in setEndAfter");

		if(refNode.tagName == "BODY"){
			this.setEnd(refNode, xe.DOMFix.childNodes(refNode).length);
			return;
		}
		this.setEnd(xe.DOMFix.parentNode(refNode), this._getPosIdx(refNode)+1);
	},

	setEndBefore : function(refNode){
		if(!refNode) throw new Error("INVALID_NODE_TYPE_ERR in setEndBefore");

		if(refNode.tagName == "BODY"){
			this.setEnd(refNode, 0);
			return;
		}

		this.setEnd(xe.DOMFix.parentNode(refNode), this._getPosIdx(refNode));
	},

	setStart : function(refNode, offset){
		offset = this._endsNodeValidation(refNode, offset);

		this.startContainer = refNode;
		this.startOffset = offset;

		if(!this.endContainer || this._compareEndPoint(this.startContainer, this.startOffset, this.endContainer, this.endOffset) != -1) this.collapse(true);
		this._updateRangeInfo();
	},

	setStartAfter : function(refNode){
		if(!refNode) throw new Error("INVALID_NODE_TYPE_ERR in setStartAfter");

		if(refNode.tagName == "BODY"){
			this.setStart(refNode, xe.DOMFix.childNodes(refNode).length);
			return;
		}

		this.setStart(xe.DOMFix.parentNode(refNode), this._getPosIdx(refNode)+1);
	},

	setStartBefore : function(refNode){
		if(!refNode) throw new Error("INVALID_NODE_TYPE_ERR in setStartBefore");

		if(refNode.tagName == "BODY"){
			this.setStart(refNode, 0);
			return;
		}
		this.setStart(xe.DOMFix.parentNode(refNode), this._getPosIdx(refNode));
	},

	surroundContents : function(newParent){
		newParent.appendChild(this.extractContents());
		this.insertNode(newParent);
		this.selectNode(newParent);
	},

	toString : function(){
		var oTmpContainer = this._document.createElement("DIV");
		oTmpContainer.appendChild(this.cloneContents());

		return oTmpContainer.textContent || oTmpContainer.innerText || "";
	},

	_isBlankTextNode : function(oNode){
		if(oNode.nodeType == 3 && oNode.nodeValue == "") return true;
		return false;
	},

	_getPosIdx : function(refNode){
		var idx = 0;
		for(var node = refNode.previousSibling; node; node = node.previousSibling) idx++;

		return idx;
	},

	_updateRangeInfo : function(){
		if(!this.startContainer){
			this.init(this._document);
			return;
		}

		this.collapsed = this._isCollapsed(this.startContainer, this.startOffset, this.endContainer, this.endOffset);

		this.commonAncestorContainer = this._getCommonAncestorContainer(this.startContainer, this.endContainer);
	},

	_isCollapsed : function(oStartContainer, iStartOffset, oEndContainer, iEndOffset){
		var bCollapsed = false;

		if(oStartContainer == oEndContainer && iStartOffset == iEndOffset){
			bCollapsed = true;
		}else{
			var oActualStartNode = this._getActualStartNode(oStartContainer, iStartOffset);
			var oActualEndNode = this._getActualEndNode(oEndContainer, iEndOffset);

			// Take the parent nodes on the same level for easier comparison when they're next to each other
			// eg) From
			//	<A>
			//		<B>
			//			<C>
			//			</C>
			//		</B>
			//		<D>
			//			<E>
			//				<F>
			//				</F>
			//			</E>
			//		</D>
			//	</A>
			//	, it's easier to compare the position of B and D rather than C and F because they are siblings
			//
			// If the range were collapsed, oActualEndNode will precede oActualStartNode by doing this
			oActualStartNode = this._getNextNode(this._getPrevNode(oActualStartNode));
			oActualEndNode = this._getPrevNode(this._getNextNode(oActualEndNode));

			if(oActualStartNode && oActualEndNode && oActualEndNode.tagName != "BODY" &&
				(this._getNextNode(oActualEndNode) == oActualStartNode || (oActualEndNode == oActualStartNode && this._isBlankTextNode(oActualEndNode)))
			)
				bCollapsed = true;
		}

		return bCollapsed;
	},

	_splitTextEndNodesOfTheRange : function(){
		var oEndPoints = this._splitTextEndNodes({oStartContainer: this.startContainer, iStartOffset: this.startOffset,
													oEndContainer: this.endContainer, iEndOffset: this.endOffset});

		this.startContainer = oEndPoints.oStartContainer;
		this.startOffset = oEndPoints.iStartOffset;

		this.endContainer = oEndPoints.oEndContainer;
		this.endOffset = oEndPoints.iEndOffset;
	},

	_splitTextEndNodes : function(oEndPoints){
		oEndPoints = this._splitStartTextNode(oEndPoints);
		oEndPoints = this._splitEndTextNode(oEndPoints);

		return oEndPoints;
	},

	_splitStartTextNode : function(oEndPoints){
		var oStartContainer = oEndPoints.oStartContainer;
		var iStartOffset = oEndPoints.iStartOffset;

		var oEndContainer = oEndPoints.oEndContainer;
		var iEndOffset = oEndPoints.iEndOffset;

		if(!oStartContainer) return oEndPoints;
		if(oStartContainer.nodeType != 3) return oEndPoints;
		if(iStartOffset == 0) return oEndPoints;

		if(oStartContainer.nodeValue.length <= iStartOffset) return oEndPoints;

		var oLastPart = oStartContainer.splitText(iStartOffset);

		if(oStartContainer == oEndContainer){
			iEndOffset -= iStartOffset;
			oEndContainer = oLastPart;
		}
		oStartContainer = oLastPart;
		iStartOffset = 0;

		return {oStartContainer: oStartContainer, iStartOffset: iStartOffset, oEndContainer: oEndContainer, iEndOffset: iEndOffset};
	},

	_splitEndTextNode : function(oEndPoints){
		var oStartContainer = oEndPoints.oStartContainer;
		var iStartOffset = oEndPoints.iStartOffset;

		var oEndContainer = oEndPoints.oEndContainer;
		var iEndOffset = oEndPoints.iEndOffset;

		if(!oEndContainer) return oEndPoints;
		if(oEndContainer.nodeType != 3) return oEndPoints;

		if(iEndOffset >= oEndContainer.nodeValue.length) return oEndPoints;
		if(iEndOffset == 0) return oEndPoints;

		oEndContainer.splitText(iEndOffset);

		return {oStartContainer: oStartContainer, iStartOffset: iStartOffset, oEndContainer: oEndContainer, iEndOffset: iEndOffset};
	},

	_getNodesInRange : function(){
		if(this.collapsed) return [];

		var oStartNode = this._getActualStartNode(this.startContainer, this.startOffset);
		var oEndNode = this._getActualEndNode(this.endContainer, this.endOffset);

		return this._getNodesBetween(oStartNode, oEndNode);
	},

	_getActualStartNode : function(oStartContainer, iStartOffset){
		var oStartNode = oStartContainer;;

		if(oStartContainer.nodeType == 3){
			if(iStartOffset >= oStartContainer.nodeValue.length){
				oStartNode = this._getNextNode(oStartContainer);
				if(oStartNode.tagName == "BODY") oStartNode = null;
			}else{
				oStartNode = oStartContainer;
			}
		}else{
			if(iStartOffset < xe.DOMFix.childNodes(oStartContainer).length){
				oStartNode = xe.DOMFix.childNodes(oStartContainer)[iStartOffset];
			}else{
				oStartNode = this._getNextNode(oStartContainer);
				if(oStartNode.tagName == "BODY") oStartNode = null;
			}
		}

		return oStartNode;
	},

	_getActualEndNode : function(oEndContainer, iEndOffset){
		var oEndNode = oEndContainer;

		if(iEndOffset == 0){
			oEndNode = this._getPrevNode(oEndContainer);
			if(oEndNode.tagName == "BODY") oEndNode = null;
		}else if(oEndContainer.nodeType == 3){
			oEndNode = oEndContainer;
		}else{
			oEndNode = xe.DOMFix.childNodes(oEndContainer)[iEndOffset-1];
		}

		return oEndNode;
	},

	_getNextNode : function(oNode){
		if(!oNode || oNode.tagName == "BODY") return this._document.body;

		if(oNode.nextSibling) return oNode.nextSibling;

		return this._getNextNode(xe.DOMFix.parentNode(oNode));
	},

	_getPrevNode : function(oNode){
		if(!oNode || oNode.tagName == "BODY") return this._document.body;

		if(oNode.previousSibling) return oNode.previousSibling;

		return this._getPrevNode(xe.DOMFix.parentNode(oNode));
	},

	// includes partially selected
	// for <div id="a"><div id="b"></div></div><div id="c"></div>, _getNodesBetween(b, c) will yield to b, "a" and c
	_getNodesBetween : function(oStartNode, oEndNode){
		var aNodesBetween = [];

		if(!oStartNode || !oEndNode) return aNodesBetween;

		this._recurGetNextNodesUntil(oStartNode, oEndNode, aNodesBetween);
		return aNodesBetween;
	},

	_recurGetNextNodesUntil : function(oNode, oEndNode, aNodesBetween){
		if(!oNode) return false;

		if(!this._recurGetChildNodesUntil(oNode, oEndNode, aNodesBetween)) return false;

		var oNextToChk = oNode.nextSibling;

		while(!oNextToChk){
			if(!xe.DOMFix.parentNode(oNode)) return false;
			oNode = xe.DOMFix.parentNode(oNode);

			aNodesBetween[aNodesBetween.length] = oNode;

			if(oNode == oEndNode) return false;

			oNextToChk = oNode.nextSibling;
		}

		return this._recurGetNextNodesUntil(oNextToChk, oEndNode, aNodesBetween);
	},

	_recurGetChildNodesUntil : function(oNode, oEndNode, aNodesBetween){
		if(!oNode) return false;

		var bEndFound = false;
		var oCurNode = oNode;
		if(oCurNode.firstChild){
			oCurNode = oCurNode.firstChild;
			while(oCurNode){
				if(!this._recurGetChildNodesUntil(oCurNode, oEndNode, aNodesBetween)){
					bEndFound = true;
					break;
				}
				oCurNode = oCurNode.nextSibling;
			}
		}

		aNodesBetween[aNodesBetween.length] = oNode;

		if(bEndFound) return false;
		if(oNode == oEndNode) return false;

		return true;
	}
});

xe.W3CDOMRange.START_TO_START = 0;
xe.W3CDOMRange.START_TO_END = 1;
xe.W3CDOMRange.END_TO_END = 2;
xe.W3CDOMRange.END_TO_START = 3;


/**
 * @fileOverview This file contains a cross-browser function that implements all of the W3C's DOM Range specification and some more
 * @name XpressRange.js
 */
xe.XpressRange = $.Class({
	setWindow : function(win){
		this._window = win;
		this._document = win.document;
	},

	$init : function(win){
		this.HUSKY_BOOMARK_START_ID_PREFIX = "xpress_bookmark_start_";
		this.HUSKY_BOOMARK_END_ID_PREFIX = "xpress_bookmark_end_";

		this.sBlockElement = "P|DIV|LI|H[1-6]|PRE";
		this.sBlockContainer = "BODY|TABLE|TH|TR|TD|UL|OL|BLOCKQUOTE|FORM";

		this.rxBlockElement = new RegExp("^("+this.sBlockElement+")$");
		this.rxBlockContainer = new RegExp("^("+this.sBlockContainer+")$")
		this.rxLineBreaker = new RegExp("^("+this.sBlockElement+"|"+this.sBlockContainer+")$")

		this.setWindow(win);

		this.oSimpleSelection = new xe.SimpleSelection(this._window);
		this.selectionLoaded = this.oSimpleSelection.selectionLoaded;

		this.$super.$init(this._document);
	},

	select : function(){
		this.oSimpleSelection.selectRange(this);
	},

	setFromSelection : function(iNum){
		this.setRange(this.oSimpleSelection.getRangeAt(iNum));
	},

	setRange : function(oW3CRange){
		this.setStart(oW3CRange.startContainer, oW3CRange.startOffset);
		this.setEnd(oW3CRange.endContainer, oW3CRange.endOffset);
	},

	setEndNodes : function(oSNode, oENode){
		this.setEndAfter(oENode);
		this.setStartBefore(oSNode);
	},

	splitTextAtBothEnds : function(){
		this._splitTextEndNodesOfTheRange();
	},

	getStartNode : function(){
		if(this.collapsed){
			if(this.startContainer.nodeType == 3){
				if(this.startOffset == 0) return null;
				if(this.startContainer.nodeValue.length <= this.startOffset) return null;
				return this.startContainer;
			}
			return null;
		}

		if(this.startContainer.nodeType == 3){
			if(this.startOffset >= this.startContainer.nodeValue.length) return this._getNextNode(this.startContainer);
			return this.startContainer;
		}else{
			if(this.startOffset >= xe.DOMFix.childNodes(this.startContainer).length) return this._getNextNode(this.startContainer);
			return xe.DOMFix.childNodes(this.startContainer)[this.startOffset];
		}
	},

	getEndNode : function(){
		if(this.collapsed) return this.getStartNode();

		if(this.endContainer.nodeType == 3){
			if(this.endOffset == 0) return this._getPrevNode(this.endContainer);
			return this.endContainer;
		}else{
			if(this.endOffset == 0) return this._getPrevNode(this.endContainer);
			return xe.DOMFix.childNodes(this.endContainer)[this.endOffset-1];
		}
	},

	getNodeAroundRange : function(bBefore, bStrict){
		if(this.collapsed && this.startContainer && this.startContainer.nodeType == 3) return this.startContainer;
		if(!this.collapsed || (this.startContainer && this.startContainer.nodeType == 3)) return this.getStartNode();

		var oBeforeRange, oAfterRange, oResult;

		if(this.startOffset >= xe.DOMFix.childNodes(this.startContainer).length)
			oAfterRange = this._getNextNode(this.startContainer);
		else
			oAfterRange = xe.DOMFix.childNodes(this.startContainer)[this.startOffset];

		if(this.endOffset == 0)
			oBeforeRange = this._getPrevNode(this.endContainer);
		else
			oBeforeRange = xe.DOMFix.childNodes(this.endContainer)[this.endOffset-1];

		if(bBefore){
			oResult = oBeforeRange;
			if(!oResult && !bStrict) oResult = oAfterRange;
		}else{
			oResult = oAfterRange;
			if(!oResult && !bStrict) oResult = oBeforeRange;
		}

		return oResult;
	},

	_getXPath : function(elNode){
		var sXPath = "";

		while(elNode && elNode.nodeType == 1){
			sXPath = "/" + elNode.tagName+"["+this._getPosIdx4XPath(elNode)+"]" + sXPath;
			elNode = xe.DOMFix.parentNode(elNode);
		}

		return sXPath;
	},

	_getPosIdx4XPath : function(refNode){
		var idx = 0;
		for(var node = refNode.previousSibling; node; node = node.previousSibling)
			if(node.tagName == refNode.tagName) idx++;

		return idx;
	},

	// this was written specifically for XPath Bookmark and it may not perform correctly for general purposes
	_evaluateXPath : function(sXPath, oDoc){
		sXPath = sXPath.substring(1, sXPath.length-1);
		var aXPath = sXPath.split(/\//);
		var elNode = oDoc.body;

		for(var i=2; i<aXPath.length && elNode; i++){
			aXPath[i].match(/([^\[]+)\[(\d+)/i);
			var sTagName = RegExp.$1;
			var nIdx = RegExp.$2;

			var aAllNodes = xe.DOMFix.childNodes(elNode);
			var aNodes = [];
			var nLength = aAllNodes.length;
			var nCount = 0;
			for(var ii=0; ii<nLength; ii++){
				if(aAllNodes[ii].tagName == sTagName) aNodes[nCount++] = aAllNodes[ii];
			}

			if(aNodes.length < nIdx)
				elNode = null;
			else
				elNode = aNodes[nIdx];
		}

		return elNode;
	},

	_evaluateXPathBookmark : function(oBookmark){
		var sXPath = oBookmark["sXPath"];
		var nTextNodeIdx = oBookmark["nTextNodeIdx"];
		var nOffset = oBookmark["nOffset"];

		var elContainer = this._evaluateXPath(sXPath, this._document);

		if(nTextNodeIdx > -1 && elContainer){
			var aChildNodes = xe.DOMFix.childNodes(elContainer);
			var elNode = null;

			var nIdx = nTextNodeIdx;
			var nOffsetLeft = nOffset;

			while((elNode = aChildNodes[nIdx]) && elNode.nodeType == 3 && elNode.nodeValue.length < nOffsetLeft){
				nOffsetLeft -= elNode.nodeValue.length;
				nIdx++;
			}

			elContainer = xe.DOMFix.childNodes(elContainer)[nIdx];
			nOffset = nOffsetLeft;
		}

		if(!elContainer){
			elContainer = this._document.body;
			nOffset = 0;
		}
		return {elContainer: elContainer, nOffset: nOffset};
	},

	// this was written specifically for XPath Bookmark and it may not perform correctly for general purposes
	getXPathBookmark : function(){
		var nTextNodeIdx1 = -1;
		var htEndPt1 = {elContainer: this.startContainer, nOffset: this.startOffset};
		var elNode1 = this.startContainer;
		if(elNode1.nodeType == 3){
			htEndPt1 = this._getFixedStartTextNode();
			nTextNodeIdx1 = this._getPosIdx(htEndPt1.elContainer);
			elNode1 = xe.DOMFix.parentNode(elNode1);
		}
		var sXPathNode1 = this._getXPath(elNode1);
		var oBookmark1 = {sXPath:sXPathNode1, nTextNodeIdx:nTextNodeIdx1, nOffset: htEndPt1.nOffset};

		var nTextNodeIdx2 = -1;
		var htEndPt2 = {elContainer: this.endContainer, nOffset: this.endOffset};
		var elNode2 = this.endContainer;
		if(elNode2.nodeType == 3){
			htEndPt2 = this._getFixedEndTextNode();
			nTextNodeIdx2 = this._getPosIdx(htEndPt2.elContainer);
			elNode2 = xe.DOMFix.parentNode(elNode2);
		}
		var sXPathNode2 = this._getXPath(elNode2);
		var oBookmark2 = {sXPath:sXPathNode2, nTextNodeIdx:nTextNodeIdx2, nOffset: htEndPt2.nOffset};

		return [oBookmark1, oBookmark2];
	},

	moveToXPathBookmark : function(aBookmark){
		if(!aBookmark) return;

		var oBookmarkInfo1 = this._evaluateXPathBookmark(aBookmark[0]);
		var oBookmarkInfo2 = this._evaluateXPathBookmark(aBookmark[1]);

		if(!oBookmarkInfo1["elContainer"] || !oBookmarkInfo2["elContainer"]) return;

		this.startContainer = oBookmarkInfo1["elContainer"];
		this.startOffset = oBookmarkInfo1["nOffset"];

		this.endContainer = oBookmarkInfo2["elContainer"];
		this.endOffset = oBookmarkInfo2["nOffset"];
	},

	_getFixedTextContainer : function(elNode, nOffset){
		while(elNode && elNode.nodeType == 3 && elNode.previousSibling && elNode.previousSibling.nodeType == 3){
			nOffset += elNode.previousSibling.nodeValue.length;
			elNode = elNode.previousSibling;
		}

		return {elContainer:elNode, nOffset:nOffset};
	},

	_getFixedStartTextNode : function(){
		return this._getFixedTextContainer(this.startContainer, this.startOffset);
	},

	_getFixedEndTextNode : function(){
		return this._getFixedTextContainer(this.endContainer, this.endOffset);
	},

	placeStringBookmark : function(){
		var sTmpId = (new Date()).getTime();

		var oInsertionPoint = this.cloneRange();
		oInsertionPoint.collapseToEnd();
		var oEndMarker = this._document.createElement("A");
		oEndMarker.id = this.HUSKY_BOOMARK_END_ID_PREFIX+sTmpId;
		oInsertionPoint.insertNode(oEndMarker);

		var oInsertionPoint = this.cloneRange();
		oInsertionPoint.collapseToStart();
		var oStartMarker = this._document.createElement("A");
		oStartMarker.id = this.HUSKY_BOOMARK_START_ID_PREFIX+sTmpId;
		oInsertionPoint.insertNode(oStartMarker);

		this.moveToBookmark(sTmpId);

		return sTmpId;
	},

	cloneRange : function(){
		return this._copyRange(new xe.XpressRange(this._window));
	},

	moveToBookmark : function(vBookmark){
		if(typeof(vBookmark) != "object")
			this.moveToStringBookmark(vBookmark);
		else
			this.moveToXPathBookmark(vBookmark);
	},

	moveToStringBookmark : function(sBookmarkID){
		var oStartMarker = this._document.getElementById(this.HUSKY_BOOMARK_START_ID_PREFIX+sBookmarkID);
		var oEndMarker = this._document.getElementById(this.HUSKY_BOOMARK_END_ID_PREFIX+sBookmarkID);

		if(!oStartMarker || !oEndMarker) return;

		this.setEndBefore(oEndMarker);
		this.setStartAfter(oStartMarker);
	},

	removeStringBookmark : function(sBookmarkID){
		var oStartMarker = this._document.getElementById(this.HUSKY_BOOMARK_START_ID_PREFIX+sBookmarkID);
		var oEndMarker = this._document.getElementById(this.HUSKY_BOOMARK_END_ID_PREFIX+sBookmarkID);

		if(oStartMarker) xe.DOMFix.parentNode(oStartMarker).removeChild(oStartMarker);
		if(oEndMarker) xe.DOMFix.parentNode(oEndMarker).removeChild(oEndMarker);
	},

	collapseToStart : function(){
		this.collapse(true);
	},

	collapseToEnd : function(){
		this.collapse(false);
	},

	createAndInsertNode : function(sTagName){
		tmpNode = this._document.createElement(tagName);
		this.insertNode(tmpNode)
		return tmpNode
	},

	getNodes : function(bSplitTextEndNodes, fnFilter){
		if(bSplitTextEndNodes) this._splitTextEndNodesOfTheRange();

		var aAllNodes = this._getNodesInRange();
		var aFilteredNodes = [];

		if(!fnFilter) return aAllNodes;

		for(var i=0; i<aAllNodes.length; i++)
			if(fnFilter(aAllNodes[i])) aFilteredNodes[aFilteredNodes.length] = aAllNodes[i];

		return aFilteredNodes;
	},

	getTextNodes : function(bSplitTextEndNodes){
		var txtFilter = function(oNode){
			if (oNode.nodeType == 3 && oNode.nodeValue != "\n" && oNode.nodeValue != "")
				return true;
			else
				return false;
		}

		return this.getNodes(bSplitTextEndNodes, txtFilter);
	},

	surroundContentsWithNewNode : function(sTagName){
		var oNewParent = this._document.createElement(sTagName);
		this.surroundContents(oNewParent);
		return oNewParent;
	},

	isRangeinRange : function(oAnoterRange, bIncludePartlySelected){
		var startToStart = this.compareBoundaryPoints(this.START_TO_START, oAnoterRange);
		var startToEnd = this.compareBoundaryPoints(this.START_TO_END, oAnoterRange);
		var endToStart = this.compareBoundaryPoints(this.END_TO_START, oAnoterRange);
		var endToEnd = this.compareBoundaryPoints(this.END_TO_END, oAnoterRange);

		if(startToStart <= 0 && endToEnd >= 0) return true;

		if(bIncludePartlyIncluded){
			if(startToEnd == 1) return false;
			if(endToStart == -1) return false;
			return true;
		}

		return false;
	},

	isNodeInRange : function(oNode, bIncludePartlySelected, bContentOnly){
		var oTmpRange = new xe.XpressRange(this._window);

		if(bContentOnly && oNode.firstChild){
			oTmpRange.setStartBefore(oNode.firstChild);
			oTmpRange.setEndAfter(oNode.lastChild);
		}else{
			oTmpRange.selectNode(oNode);
		}

		return isRangeInRange(oTmpRange, bIncludePartlySelected);
	},

	pasteHTML : function(sHTML){
		if(sHTML == ""){
			this.deleteContents();
			return;
		}

		var oTmpDiv = this._document.createElement("DIV");
		oTmpDiv.innerHTML = sHTML;

		var oFirstNode = oTmpDiv.firstChild;
		var oLastNode = oTmpDiv.lastChild;

		var clone = this.cloneRange();
		var sBM = clone.placeStringBookmark();

		while(oTmpDiv.lastChild) this.insertNode(oTmpDiv.lastChild);

		this.setEndNodes(oFirstNode, oLastNode);

		// delete the content later as deleting it first may mass up the insertion point
		// eg) <p>[A]BCD</p> ---paste O---> O<p>BCD</p>
		clone.moveToBookmark(sBM);
		clone.deleteContents();
		clone.removeStringBookmark(sBM);
	},

	toString : function(){
		this.toString = xe.W3CDOMRange.prototype.toString;
		return this.toString();
	},

	toHTMLString : function(){
		var oTmpContainer = this._document.createElement("DIV");
		oTmpContainer.appendChild(this.cloneContents());

		return oTmpContainer.innerHTML;
	},

	findAncestorByTagName : function(sTagName){
		var oNode = this.commonAncestorContainer;
		while(oNode && oNode.tagName != sTagName) oNode = xe.DOMFix.parentNode(oNode);

		return oNode;
	},

	selectNodeContents : function(oNode){
		if(!oNode) return;

		var oFirstNode = oNode.firstChild?oNode.firstChild:oNode;
		var oLastNode = oNode.lastChild?oNode.lastChild:oNode;

		if(oFirstNode.nodeType == 3)
			this.setStart(oFirstNode, 0);
		else
			this.setStartBefore(oFirstNode);

		if(oLastNode.nodeType == 3)
			this.setEnd(oLastNode, oLastNode.nodeValue.length);
		else
			this.setEndAfter(oLastNode);
	},

	styleRange : function(oStyle, oAttribute, sNewSpanMarker){
		var aStyleParents = this._getStyleParentNodes(sNewSpanMarker);
		if(aStyleParents.length < 1) return;

		var sName, sValue;

		for(var i=0; i<aStyleParents.length; i++){
			for(var x in oStyle){
				sName = x;
				sValue = oStyle[sName];

				if(typeof sValue != "string") continue;

				aStyleParents[i].style[sName] = sValue;
			}

			if(!oAttribute) continue;

			for(var x in oAttribute){
				sName = x;
				sValue = oAttribute[sName];

				if(typeof sValue != "string") continue;

				if(sName == "class"){
					$(aStyleParents[i]).addClass(sValue);
				}else{
					aStyleParents[i].setAttribute(sName, sValue);
				}
			}
		}

		this.setStartBefore(aStyleParents[0]);
		this.setEndAfter(aStyleParents[aStyleParents.length-1]);
	},

	_getStyleParentNodes : function(sNewSpanMarker){
		this._splitTextEndNodesOfTheRange();

		var oSNode = this.getStartNode();
		var oENode = this.getEndNode();

		var aAllNodes = this._getNodesInRange();
		var aResult = [];

		var oNode, iStartRelPos, iEndRelPos, oSpan, iSIdx, iEIdx;
		var nInitialLength = aAllNodes.length;
		for(var i=0; i<nInitialLength; i++){
			oNode = aAllNodes[i];

			if(!oNode) continue;
			if(oNode.nodeType != 3) continue;
			if(oNode.nodeValue == "") continue;

			if(xe.DOMFix.parentNode(oNode).tagName == "SPAN"){
				// check if the SPAN element is fully contained
				iSIdx = $.inArray(this._getVeryFirstRealChild(xe.DOMFix.parentNode(oNode.parentNode)), aAllNodes);
				iEIdx = $.inArray(this._getVeryLastRealChild(xe.DOMFix.parentNode(oNode)), aAllNodes);

				if(iSIdx != -1 && iEIdx != -1){
					aResult[aResult.length] = xe.DOMFix.parentNode(oNode);
					continue;
				}
			}

			oSpan = this._document.createElement("SPAN");
			xe.DOMFix.parentNode(oNode).insertBefore(oSpan, oNode);
			oSpan.appendChild(oNode);
			aResult[aResult.length] = oSpan;
			aAllNodes[aAllNodes.length] = oSpan;

			if(sNewSpanMarker) oSpan.setAttribute(sNewSpanMarker, "true");
		}

		this.setStartBefore(oSNode);
		this.setEndAfter(oENode);

		return aResult;
	},

	_getVeryFirstChild : function(oNode){
		if(oNode.firstChild) return this._getVeryFirstChild(oNode.firstChild);
		return oNode;
	},

	_getVeryLastChild : function(oNode){
		if(oNode.lastChild) return this._getVeryLastChild(oNode.lastChild);
		return oNode;
	},

	_getFirstRealChild : function(oNode){
		var oFirstNode = oNode.firstChild;
		while(oFirstNode && oFirstNode.nodeType == 3 && oFirstNode.nodeValue == "") oFirstNode = oFirstNode.nextSibling;

		return oFirstNode;
	},

	_getLastRealChild : function(oNode){
		var oLastNode = oNode.lastChild;
		while(oLastNode && oLastNode.nodeType == 3 && oLastNode.nodeValue == "") oLastNode = oLastNode.previousSibling;

		return oLastNode;
	},

	_getVeryFirstRealChild : function(oNode){
		var oFirstNode = this._getFirstRealChild(oNode);
		if(oFirstNode) return this._getVeryFirstRealChild(oFirstNode);
		return oNode;
	},
	_getVeryLastRealChild : function(oNode){
		var oLastNode = this._getLastRealChild(oNode);
		if(oLastNode) return this._getVeryLastChild(oLastNode);
		return oNode;
	},

	_getLineStartInfo : function(node){
		var frontEndFinal = null;
		var frontEnd = node;
		var lineBreaker = node;
		var bParentBreak = true;

		var rxLineBreaker = this.rxLineBreaker;

		// vertical(parent) search
		function getLineStart(node){
			if(!node) return;
			if(frontEndFinal) return;

			if(rxLineBreaker.test(node.tagName)){
				lineBreaker = node;
				frontEndFinal = frontEnd;

				bParentBreak = true;

				return;
			}else{
				frontEnd = node;
			}

			getFrontEnd(node.previousSibling);

			if(frontEndFinal) return;
			getLineStart(xe.DOMFix.parentNode(node));
		}

		// horizontal(sibling) search
		function getFrontEnd(node){
			if(!node) return;
			if(frontEndFinal) return;

			if(rxLineBreaker.test(node.tagName)){
				lineBreaker = node;
				frontEndFinal = frontEnd;

				bParentBreak = false;
				return;
			}

			if(node.firstChild && node.tagName != "TABLE"){
				var curNode = node.lastChild;
				while(curNode && !frontEndFinal){
					getFrontEnd(curNode);

					curNode = curNode.previousSibling;
				}
			}else{
				frontEnd = node;
			}

			if(!frontEndFinal){
				getFrontEnd(node.previousSibling);
			}
		}

		getLineStart(node);

		return {oNode: frontEndFinal, oLineBreaker: lineBreaker, bParentBreak: bParentBreak};
	},

	_getLineEndInfo : function(node){
		var backEndFinal = null;
		var backEnd = node;
		var lineBreaker = node;
		var bParentBreak = true;

		var rxLineBreaker = this.rxLineBreaker;

		// vertical(parent) search
		function getLineEnd(node){
			if(!node) return;
			if(backEndFinal) return;

			if(rxLineBreaker.test(node.tagName)){
				lineBreaker = node;
				backEndFinal = backEnd;

				bParentBreak = true;

				return;
			}else{
				backEnd = node;
			}

			getBackEnd(node.nextSibling);
			if(backEndFinal) return;

			getLineEnd(xe.DOMFix.parentNode(node));
		}

		// horizontal(sibling) search
		function getBackEnd(node){
			if(!node) return;
			if(backEndFinal) return;

			if(rxLineBreaker.test(node.tagName)){
				lineBreaker = node;
				backEndFinal = backEnd;

				bParentBreak = false;

				return;
			}

			if(node.firstChild && node.tagName != "TABLE"){
				var curNode = node.firstChild;
				while(curNode && !backEndFinal){
					getBackEnd(curNode);

					curNode = curNode.nextSibling;
				}
			}else{
				backEnd = node;
			}

			if(!backEndFinal){
				getBackEnd(node.nextSibling);
			}
		}

		getLineEnd(node);

		return {oNode: backEndFinal, oLineBreaker: lineBreaker, bParentBreak: bParentBreak};
	},

	getLineInfo : function(){
		var oSNode = this.getStartNode();
		var oENode = this.getEndNode();

		// the range is currently collapsed
		if(!oSNode) oSNode = this.getNodeAroundRange(true, true);
		if(!oENode) oENode = this.getNodeAroundRange(true, true);

		var oStart = this._getLineStartInfo(oSNode);
		var oStartNode = oStart.oNode;
		var oEnd = this._getLineEndInfo(oENode);
		var oEndNode = oEnd.oNode;

		var iRelativeStartPos = this._compareEndPoint(xe.DOMFix.parentNode(oStartNode), this._getPosIdx(oStartNode), this.endContainer, this.endOffset);
		var iRelativeEndPos = this._compareEndPoint(xe.DOMFix.parentNode(oEndNode), this._getPosIdx(oEndNode)+1, this.startContainer, this.startOffset);

		if(!(iRelativeStartPos <= 0 && iRelativeEndPos >= 0)){
			oSNode = this.getNodeAroundRange(false, true);
			oENode = this.getNodeAroundRange(false, true);
			oStart = this._getLineStartInfo(oSNode);
			oEnd = this._getLineEndInfo(oENode);
		}

		return {oStart: oStart, oEnd: oEnd};
	}
}).extend(xe.W3CDOMRange);

/**
 * @fileOverview This file contains cross-browser selection function
 * @name SimpleSelection.js
 */
xe.SimpleSelection = function(win){
	this.init = function(win){
		this._window = win || window;
		this._document = this._window.document;
	};

	this.init(win);

	if($.browser.msie)
		xe.SimpleSelectionImpl_IE.apply(this);
	else
		xe.SimpleSelectionImpl_FF.apply(this);

	this.selectRange = function(oRng){
		this.selectNone();
		this.addRange(oRng);
	};

	this.selectionLoaded = true;
	if(!this._oSelection) this.selectionLoaded = false;
};

xe.SimpleSelectionImpl_FF = function(){
	this._oSelection = this._window.getSelection();

	this.getRangeAt = function(iNum){
		iNum = iNum || 0;

		try{
			var oFFRange = this._oSelection.getRangeAt(iNum);
		}catch(e){return new xe.W3CDOMRange(this._document);}

		return this._FFRange2W3CRange(oFFRange);
	};

	this.addRange = function(oW3CRange){
		var oFFRange = this._W3CRange2FFRange(oW3CRange);
		this._oSelection.addRange(oFFRange);
	};

	this.selectNone = function(){
		this._oSelection.removeAllRanges();
	};

	this._FFRange2W3CRange = function(oFFRange){
		var oW3CRange = new xe.W3CDOMRange(this._document);
		oW3CRange.setStart(oFFRange.startContainer, oFFRange.startOffset);
		oW3CRange.setEnd(oFFRange.endContainer, oFFRange.endOffset);
		return oW3CRange;
	};

	this._W3CRange2FFRange = function(oW3CRange){
		var oFFRange = this._document.createRange();
		oFFRange.setStart(oW3CRange.startContainer, oW3CRange.startOffset);
		oFFRange.setEnd(oW3CRange.endContainer, oW3CRange.endOffset);

		return oFFRange;
	};
};

xe.SimpleSelectionImpl_IE = function(){
	this._oSelection = this._document.selection;

	this.getRangeAt = function(iNum){
		iNum = iNum || 0;

		if(this._oSelection.type == "Control"){
			var oW3CRange = new xe.W3CDOMRange(this._document);

			var oSelectedNode = this._oSelection.createRange().item(iNum);

			// if the selction occurs in a different document, ignore
			if(!oSelectedNode || oSelectedNode.ownerDocument != this._document) return oW3CRange;

			oW3CRange.selectNode(oSelectedNode);

			return oW3CRange;
		}else{
			var oSelectedNode = this._oSelection.createRangeCollection().item(iNum).parentElement();

			// if the selction occurs in a different document, ignore
			if(!oSelectedNode || oSelectedNode.ownerDocument != this._document){
				var oW3CRange = new xe.W3CDOMRange(this._document);
				return oW3CRange;
			}
			return this._IERange2W3CRange(this._oSelection.createRangeCollection().item(iNum));
		}
	};

	this.addRange = function(oW3CRange){
		var oIERange = this._W3CRange2IERange(oW3CRange);
		oIERange.select();
	};

	this.selectNone = function(){
		this._oSelection.empty();
	};

	this._W3CRange2IERange = function(oW3CRange){
		var oStartIERange = this._getIERangeAt(oW3CRange.startContainer, oW3CRange.startOffset);
		var oEndIERange = this._getIERangeAt(oW3CRange.endContainer, oW3CRange.endOffset);
		oStartIERange.setEndPoint("EndToEnd", oEndIERange);

		return oStartIERange;
	};

	this._getIERangeAt = function(oW3CContainer, iW3COffset){
		var oIERange = this._document.body.createTextRange();

		var oEndPointInfoForIERange = this._getSelectableNodeAndOffsetForIE(oW3CContainer, iW3COffset);

		var oSelectableNode = oEndPointInfoForIERange.oSelectableNodeForIE;
		var iIEOffset = oEndPointInfoForIERange.iOffsetForIE;

		oIERange.moveToElementText(oSelectableNode);
		oIERange.collapse(oEndPointInfoForIERange.bCollapseToStart);
		oIERange.moveStart("character", iIEOffset);

		return oIERange;
	};

	this._getSelectableNodeAndOffsetForIE = function(oW3CContainer, iW3COffset){
		var oIERange = this._document.body.createTextRange();

		var oNonTextNode = null;
		var aChildNodes =  null;
		var iNumOfLeftNodesToCount = 0;

		if(oW3CContainer.nodeType == 3){
			oNonTextNode = xe.DOMFix.parentNode(oW3CContainer);
			aChildNodes = xe.DOMFix.childNodes(oNonTextNode);
			iNumOfLeftNodesToCount = aChildNodes.length;
		}else{
			oNonTextNode = oW3CContainer;
			aChildNodes = xe.DOMFix.childNodes(oNonTextNode);
			iNumOfLeftNodesToCount = iW3COffset;
		}

		var oNodeTester = null;

		var iResultOffset = 0;

		var bCollapseToStart = true;

		for(var i=0; i<iNumOfLeftNodesToCount; i++){
			oNodeTester = aChildNodes[i];

			if(oNodeTester.nodeType == 3){
				if(oNodeTester == oW3CContainer) break;

				iResultOffset += oNodeTester.nodeValue.length;
			}else{
				oIERange.moveToElementText(oNodeTester);
				oNonTextNode = oNodeTester;
				iResultOffset = 0;

				bCollapseToStart = false;
			}
		}

		if(oW3CContainer.nodeType == 3) iResultOffset += iW3COffset;

		return {oSelectableNodeForIE:oNonTextNode, iOffsetForIE: iResultOffset, bCollapseToStart: bCollapseToStart};
	};

	this._IERange2W3CRange = function(oIERange){
		var oW3CRange = new xe.W3CDOMRange(this._document);

		var oIEPointRange = null;
		var oPosition = null;

		oIEPointRange = oIERange.duplicate();
		oIEPointRange.collapse(true);

		oPosition = this._getW3CContainerAndOffset(oIEPointRange, true);

		oW3CRange.setStart(oPosition.oContainer, oPosition.iOffset);

		var oCollapsedChecker = oIERange.duplicate();
		oCollapsedChecker.collapse(true);
		if(oCollapsedChecker.isEqual(oIERange)){
			oW3CRange.collapse(true);
		}else{
			oIEPointRange = oIERange.duplicate();
			oIEPointRange.collapse(false);
			oPosition = this._getW3CContainerAndOffset(oIEPointRange);
			oW3CRange.setEnd(oPosition.oContainer, oPosition.iOffset);
		}

		return oW3CRange;
	};

	this._getW3CContainerAndOffset = function(oIEPointRange, bStartPt){
		var oRgOrigPoint = oIEPointRange;

		var oContainer = oRgOrigPoint.parentElement();
		var offset = -1;

		var oRgTester = this._document.body.createTextRange();
		var aChildNodes = xe.DOMFix.childNodes(oContainer);
		var oPrevNonTextNode = null;
		var pointRangeIdx = 0;

		for(var i=0;i<aChildNodes.length;i++){
			if(aChildNodes[i].nodeType == 3) continue;

			oRgTester.moveToElementText(aChildNodes[i]);

			if(oRgTester.compareEndPoints("StartToStart", oIEPointRange)>=0) break;

			oPrevNonTextNode = aChildNodes[i];
		}

		var pointRangeIdx = i;

		if(pointRangeIdx != 0 && aChildNodes[pointRangeIdx-1].nodeType == 3){
			var oRgTextStart = this._document.body.createTextRange();
			var oCurTextNode = null;
			if(oPrevNonTextNode){
				oRgTextStart.moveToElementText(oPrevNonTextNode);
				oRgTextStart.collapse(false);
				oCurTextNode = oPrevNonTextNode.nextSibling;
			}else{
				oRgTextStart.moveToElementText(oContainer);
				oRgTextStart.collapse(true);
				oCurTextNode = oContainer.firstChild;
			}

			var oRgTextsUpToThePoint = oRgOrigPoint.duplicate();
			oRgTextsUpToThePoint.setEndPoint("StartToStart", oRgTextStart);

			var textCount = oRgTextsUpToThePoint.text.length

			while(textCount > oCurTextNode.nodeValue.length && oCurTextNode.nextSibling){
				textCount -= oCurTextNode.nodeValue.length;
				oCurTextNode = oCurTextNode.nextSibling;
			}

			// this will enforce IE to re-reference oCurTextNode
			var oTmp = oCurTextNode.nodeValue;

			if(bStartPt && oCurTextNode.nextSibling && oCurTextNode.nextSibling.nodeType == 3 && textCount == oCurTextNode.nodeValue.length){
				textCount -= oCurTextNode.nodeValue.length;
				oCurTextNode = oCurTextNode.nextSibling;
			}

			oContainer = oCurTextNode;
			offset = textCount;
		}else{
			oContainer = oRgOrigPoint.parentElement();
			offset = pointRangeIdx;
		}

		return {"oContainer" : oContainer, "iOffset" : offset};
	};
}

xe.DOMFix = new ($.Class({
	$init : function(){
		if($.browser.msie || $.browser.opera){
			this.childNodes = this._childNodes_Fix;
			this.parentNode = this._parentNode_Fix;
		}else{
			this.childNodes = this._childNodes_Native;
			this.parentNode = this._parentNode_Native;
		}
	},

	_parentNode_Native : function(elNode){
		return elNode.parentNode;
	},

	_parentNode_Fix : function(elNode){
		if(!elNode) return elNode;

		while(elNode.previousSibling){elNode = elNode.previousSibling;}

		return elNode.parentNode;
	},

	_childNodes_Native : function(elNode){
		return elNode.childNodes;
	},

	_childNodes_Fix : function(elNode){
		var aResult = null;
		var nCount = 0;

		if(elNode){
			var aResult = [];
			elNode = elNode.firstChild;
			while(elNode){
				aResult[nCount++] = elNode;
				elNode=elNode.nextSibling;
			}
		}

		return aResult;
	}
}))();
/**
 * @fileOverview This file contains a function that takes care of various operations related to find and replace
 * @name N_FindReplace.js
 */
xe.FindReplace = $.Class({
	sKeyword : "",
	window : null,
	document : null,
	bBrowserSupported : false,

	// true if End Of Contents is reached during last execution of find
	bEOC : false,

	$init : function(win){
		this.window = win;
		this.document = this.window.document;

		if(this.document.domain != this.document.location.hostname){
			if($.browser.mozilla && $.browser.nVersion < 3){
				this.bBrowserSupported = false;
				this.find = function(){return 3};
				return;
			}
		}

		this.bBrowserSupported = true;
	},

	// 0: found
	// 1: not found
	// 2: keyword required
	// 3: browser not supported
	find : function(sKeyword, bCaseMatch, bBackwards, bWholeWord){
		var bSearchResult, bFreshSearch;

		this.window.focus();

		if(!sKeyword) return 2;

		// try find starting from current cursor position
		this.bEOC = false;
		bSearchResult = this.findNext(sKeyword, bCaseMatch, bBackwards, bWholeWord);
		if(bSearchResult) return 0;

		// end of the contents could have been reached so search again from the beginning
		this.bEOC = true;
		bSearchResult = this.findNew(sKeyword, bCaseMatch, bBackwards, bWholeWord);

		if(bSearchResult) return 0;

		return 1;
	},

	findNew : function (sKeyword, bCaseMatch, bBackwards, bWholeWord){
		this.findReset();
		return this.findNext(sKeyword, bCaseMatch, bBackwards, bWholeWord);
	},

	findNext : function(sKeyword, bCaseMatch, bBackwards, bWholeWord){
		var bSearchResult;
		bCaseMatch = bCaseMatch || false;
		bWholeWord = bWholeWord || false;
		bBackwards = bBackwards || false;

		if(this.window.find){
			var bWrapAround = false;
			return this.window.find(sKeyword, bCaseMatch, bBackwards, bWrapAround, bWholeWord);
		}

		// IE solution
		if(this.document.body.createTextRange){
			var iOption = 0;
			if(bBackwards) iOption += 1;
			if(bWholeWord) iOption += 2;
			if(bCaseMatch) iOption += 4;

			this.window.focus();
			this._range = this.document.selection.createRangeCollection().item(0);
			this._range.collapse(false);
			bSearchResult = this._range.findText(sKeyword, 1, iOption);

			this._range.select();
			return bSearchResult;
		}

		return false;
	},

	findReset : function() {
		if (this.window.find){
			this.window.getSelection().removeAllRanges();
			return;
		}

		// IE solution
		if(this.document.body.createTextRange){
			this._range = this.document.body.createTextRange();
			this._range.collapse(true);
			this._range.select();
		}
	},

	// 0: replaced & next word found
	// 1: replaced & next word not found
	// 2: not replaced & next word found
	// 3: not replaced & next word not found
	// 4: sOriginalWord required
	replace : function(sOriginalWord, Replacement, bCaseMatch, bBackwards, bWholeWord){
		if(!sOriginalWord) return 4;

		var oSelection = new xe.XpressRange(this.window);
		oSelection.setFromSelection();

		bCaseMatch = bCaseMatch || false;
		var bMatch, selectedText = oSelection.toString();
		if(bCaseMatch)
			bMatch = (selectedText == sOriginalWord);
		else
			bMatch = (selectedText.toLowerCase() == sOriginalWord.toLowerCase());

		if(!bMatch)
			return this.find(sOriginalWord, bCaseMatch, bBackwards, bWholeWord)+2;

		if(typeof Replacement == "function"){
			// the returned oSelection must contain the replacement
			oSelection = Replacement(oSelection);
		}else{
			oSelection.pasteHTML(Replacement);
		}

		// force it to find the NEXT occurance of sOriginalWord
		oSelection.select();

		return this.find(sOriginalWord, bCaseMatch, bBackwards, bWholeWord);
	},

	// returns number of replaced words
	// -1 : if original word is not given
	replaceAll : function(sOriginalWord, Replacement, bCaseMatch, bWholeWord){
		if(!sOriginalWord) return -1;

		var bBackwards = false;

		var iReplaceResult;
		var iResult = 0;
		var win = this.window;
		var oSelection = new xe.XpressRange(this.window);
		oSelection.setFromSelection();
		var sBookmark = oSelection.placeStringBookmark();

		this.bEOC = false;
		while(!this.bEOC){
			iReplaceResult = this.replace(sOriginalWord, Replacement, bCaseMatch, bBackwards, bWholeWord);
			if(iReplaceResult == 0 || iReplaceResult == 1) iResult++;
		}

		var startingPointReached = function(){
			var oCurSelection = new xe.XpressRange(win);
			oCurSelection.setFromSelection();

			oSelection.moveToBookmark(sBookmark);
			var pos = oSelection.compareBoundaryPoints(xe.W3CDOMRange.START_TO_END, oCurSelection);

			if(pos == 1) return false;
			return true;
		}

		iReplaceResult = 0;
		this.bEOC = false;
		while(!startingPointReached() && iReplaceResult == 0 && !this.bEOC){
			iReplaceResult = this.replace(sOriginalWord, Replacement, bCaseMatch, bBackwards, bWholeWord);
			if(iReplaceResult == 0 || iReplaceResult == 1) iResult++;
		}

		oSelection.moveToBookmark(sBookmark);
		oSelection.select();
		oSelection.removeStringBookmark(sBookmark);

		return iResult;
	}
});

/**
 * @fileOverview This file contains a function that takes care of the draggable layers
 * @name N_DraggableLayer.js
 */
xe.DraggableLayer = $.Class({
	$init : function(oLayer, oOptions){
		this.oOptions = $.extend({
			bModal : "false",
			oHandle : oLayer,
			iMinX : -999999,
			iMinY : -999999,
			iMaxX : 999999,
			iMaxY : 999999
		}, oOptions);

		this.oHandle = this.oOptions.oHandle;

		oLayer.style.display = "block";
		oLayer.style.position = "absolute";
		oLayer.style.zIndex = "9999";

		this.aBasePosition = this.getBaseOffset(oLayer);

		// "number-ize" the position and set it as inline style. (the position could've been set as "auto" or set  by css, not inline style)
		oLayer.style.top = (this.toInt($(oLayer).offset().top) - this.aBasePosition.top)+"px";
		oLayer.style.left = (this.toInt($(oLayer).offset().left) - this.aBasePosition.left)+"px";

		this.$FnMouseDown = $.fnBind(this._mousedown, this, oLayer);
		this.$FnMouseMove = $.fnBind(this._mousemove, this, oLayer);
		this.$FnMouseUp = $.fnBind(this._mouseup, this, oLayer);

		$(this.oHandle).bind("mousedown", this.$FnMouseDown);
	},

	_mousedown : function(oLayer, oEvent){
		if(oEvent.target.tagName == "INPUT") return;

		this.MouseOffsetY = (oEvent.pageY-this.toInt(oLayer.style.top)-this.aBasePosition['top']);
		this.MouseOffsetX = (oEvent.pageX-this.toInt(oLayer.style.left)-this.aBasePosition['left']);

		$(oLayer).bind("mousemove", this.$FnMouseMove);
		$(oLayer).bind("mouseup", this.$FnMouseUp);
	},

	_mousemove : function(oLayer, oEvent){
		var iTop = (oEvent.pageY-this.MouseOffsetY-this.aBasePosition['top']);
		var iLeft = (oEvent.pageX-this.MouseOffsetX-this.aBasePosition['left']);

		if(iTop<this.oOptions.iMinY) iTop = this.oOptions.iMinY;
		if(iTop>this.oOptions.iMaxY) iTop = this.oOptions.iMaxY;

		if(iLeft<this.oOptions.iMinX) iLeft = this.oOptions.iMinX;
		if(iLeft>this.oOptions.iMaxX) iLeft = this.oOptions.iMaxX;

		oLayer.style.top = iTop + "px";
		oLayer.style.left = iLeft + "px";
	},

	_mouseup : function(oLayer, oEvent){
		$(oLayer).unbind("mousemove", this.$FnMouseMove);
		$(oLayer).unbind("mouseup", this.$FnMouseUp);
	},

	toInt : function(num){
		var result = parseInt(num);
		return result || 0;
	},

	findNonStatic : function(oEl){
		if(!oEl) return null;
		if(oEl.tagName == "BODY") return oEl;

		if($(oEl).css("position").match(/absolute|relative/i)) return oEl;

		return this.findNonStatic(oEl.offsetParent);
	},

	getBaseOffset : function(oEl){
		var oBase = this.findNonStatic(oEl.offsetParent);
		var tmp = $(oBase).offset();

		return {top: tmp.top, left: tmp.left};
	}
});
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the messages related to core operations
 * @name hp_CorePlugin.js
 */
xe.CorePlugin = $.Class({
	name : "CorePlugin",

	$init : function(funcOnReady){
		this.funcOnReady = funcOnReady;
	},

	$AFTER_MSG_APP_READY : function(){
		this.oApp.exec("EXEC_ON_READY_FUNCTION", []);
	},

	$ON_ADD_APP_PROPERTY : function(sPropertyName, oProperty){
		this.oApp[sPropertyName] = oProperty;
	},

	$ON_REGISTER_BROWSER_EVENT : function(obj, sEvent, sCMD, aParams, nDelay){
		this.oApp.registerBrowserEvent(obj, sEvent, sCMD, aParams, nDelay);
	},

	$ON_DISABLE_COMMAND : function(sCommand){
		this.oApp.disableCommand(sCommand, true);
	},

	$ON_ENABLE_COMMAND : function(sCommand){
		this.oApp.disableCommand(sCommand, false);
	},

	$ON_EXEC_ON_READY_FUNCTION : function(){
		if(typeof this.funcOnReady == "function") this.funcOnReady();
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that helps various operations.
 * @name hp_Utils.js
 */
 xe.Utils = $.Class({
	name : "Utils",

	$init : function(){
		if($.browser.msie && $.browser.nVersion == 6){
			try{
				document.execCommand('BackgroundImageCache', false, true);
			}catch(e){}
		}
	},

	$ON_ATTACH_HOVER_EVENTS : function(aElms, sHoverClass){
		sHoverClass = sHoverClass || "hover";

		if(!aElms) return;

		$(aElms).hover(
			function(){$(this).addClass(sHoverClass)},
			function(){$(this).removeClass(sHoverClass)}
		);
	}
});
//}

//{
/**
 * @fileOverview This file contains Xpress plugin that bridges the XpressRange function
 * @name hp_XpressRangeManager.js
 */
xe.XpressRangeManager = $.Class({
	name : "XpressRangeManager",

	oWindow : null,

	$init : function(win){
		this.oWindow = win || window;
	},

	$BEFORE_MSG_APP_READY : function(){
		if(this.oWindow && this.oWindow.tagName == "IFRAME")
			this.oWindow = this.oWindow.contentWindow;

		this.oApp.exec("ADD_APP_PROPERTY", ["getSelection", $.fnBind(this.getSelection, this)]);
		this.oApp.exec("ADD_APP_PROPERTY", ["getEmptySelection", $.fnBind(this.getEmptySelection, this)]);
	},

	$ON_SET_EDITING_WINDOW : function(oWindow){
		this.oWindow = oWindow;
	},

	getEmptySelection : function(){
		var oXpressRange = new xe.XpressRange(this.oWindow);
		return oXpressRange;
	},

	getSelection : function(){
		this.oApp.exec("RESTORE_IE_SELECTION", []);

		var oXpressRange = this.getEmptySelection();

		// this may throw an exception if the selected is area is not yet shown
		try{
			oXpressRange.setFromSelection();
		}catch(e){}

		return oXpressRange;
	}
});
//}
xe.Hotkey = $.Class({
	name : "Hotkey",

	storage : {},
	keyhash : {},

	$init : function(){
		this.storage = {};

		this.keyhash = {
			backspace : 8,
			tab		  : 9,
			enter	  : 13,
			shift	 : 16,
			ctrl	  : 17,
			alt	   : 18,
			meta	  : 224,
			esc		  : 27,
			space	  : 32,
			pageup	  : 33,
			pagedown  : 34,
			end		  : 35,
			home	  : 36,
			left	  : 37,
			up		  : 38,
			right	  : 39,
			down	  : 40,
			del	  	  : 46,
			comma	  : 188,//(,)
			period	  : 190,//(.)
			slash	  : 191,//(/)
			hyphen	: 109,
			equal	 : 61
		};

		if ($.browser.msie || $.browser.safari) {
			this.keyhash.hyphen = 189; // (-)
			this.keyhash.equal = 187;  // (=)
			this.keyhash.meta  = 91;   // meta
		}


	},

	$ON_MSG_APP_READY : function(){
		$(this.oApp.getWYSIWYGDocument() || document).keydown($.fnBind(this.keydown, this));
	},

	$ON_REGISTER_HOTKEY : function(sHotkey, sCMD, sArgs){
		if(!sArgs) sArgs = [];
		var func = $.fnBind(this.oApp.exec, this.oApp, sCMD, sArgs);

		sHotkey = this.normalize(sHotkey);
		if (!sHotkey) return false;

		this.add(sHotkey, func);
	},

	add : function(sHotkey, func) {
		if (typeof this.storage[sHotkey] == 'undefined') {
			this.storage[sHotkey] = [func];
		} else {
			this.storage[sHotkey].push(func);
		}
	},

	keydown : function(event) {
		var key  = [], kh = this.keyhash;

		if ($.inArray(event.keyCode, [kh.shift, kh.ctrl, kh.alt, kh.meta]) >= 0) return;

		if (event.shiftKey) key.push('shift');
		if (event.altKey)   key.push('alt');
		if (event.ctrlKey)  key.push('ctrl');
		if (event.metaKey)  key.push('meta');
		if (!key.length) return;
		if (key.length == 1 && event.metaKey) key = ['ctrl', 'meta'];

		key.push(event.keyCode);

		key = key.join('+');

		if (!this.storage[key]) return;

		$.each(this.storage[key], function(){ this(); });

		return false;
	},

	normalize : function(sHotkey) {
		var shift, ctrl, alt, meta, key, keys = (sHotkey||"").toLowerCase().split('+');

		shift = ctrl = alt = meta = key = false;

		$.each(keys, function(){
			var s = ""+this;
			switch(s) {
				case 'shift': shift = true;
				case 'alt'  : alt   = true;
				case 'ctrl' : ctrl  = true;
				case 'meta' : meta  = true;
				default:
					key = s;
			}
		});

		if (!key) return '';

		keys = [];
		if (shift) keys.push('shift');
		if (alt) keys.push('alt');
		if (ctrl) keys.push('ctrl');
		if (meta || (ctrl && !shift && !alt)) keys.push('meta');

		keys.push(this.keyhash[key] || key.toUpperCase().charCodeAt(0));

		return keys.join('+');
	}
});

//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the draggable layers
 * @name hp_DialogLayerManager.js
 */
xe.DialogLayerManager = $.Class({
	name : "DialogLayerManager",
	aMadeDraggable : null,
	aOpenedLayers : null,

	$init : function(){
		this.aMadeDraggable = [];
		this.aOpenedLayers = [];
	},

	$ON_SHOW_DIALOG_LAYER : function(oLayer, bModal){
		oLayer = $.$(oLayer);
		bModal = $.$(bModal) || false;
		if(!oLayer) return;

		if($.inArray(oLayer, this.aOpenedLayers)) return;

		this.oApp.exec("POSITION_DIALOG_LAYER", [oLayer]);

		this.aOpenedLayers[this.aOpenedLayers.length] = oLayer;

		if(!$.inArray(oLayer, this.aMadeDraggable)){
			new xe.DraggableLayer(oLayer, {bModal: bModal, iMinY: 0});
			this.aMadeDraggable[this.aMadeDraggable.length] = oLayer;
		}else{
			oLayer.style.display = "block";
		}
	},

	$ON_HIDE_LAST_DIALOG_LAYER : function(){
		this.oApp.exec("HIDE_DIALOG_LAYER", [this.aOpenedLayers[this.aOpenedLayers.length-1]]);
	},

	$ON_HIDE_ALL_DIALOG_LAYER : function(){
		for(var i=this.aOpenedLayers.length-1; i>=0; i--)
			this.oApp.exec("HIDE_DIALOG_LAYER", [this.aOpenedLayers[i]]);
	},

	$ON_HIDE_DIALOG_LAYER : function(oLayer){
		oLayer = $.$(oLayer);

		if(oLayer) oLayer.style.display = "none";
		this.aOpenedLayers = $.grep(this.aOpenedLayers, function(a){return a!=oLayer});
	},

	$ON_SET_DIALOG_LAYER_POSITION : function(oLayer, iTop, iLeft){
		oLayer.style.top = iTop;
		oLayer.style.left = iLeft;
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the layers that should disappear when the focus is lost
 * @name hp_ActiveLayerManager.js
 */
xe.ActiveLayerManager = $.Class({
	name : "ActiveLayerManager",
	oCurrentLayer : null,

	$ON_TOGGLE_ACTIVE_LAYER : function(oLayer, sOnOpenCmd, aOnOpenParam, sOnCloseCmd, aOnCloseParam){
		if(oLayer == this.oCurrentLayer){
			this.oApp.exec("HIDE_ACTIVE_LAYER", []);
		}else{
			this.oApp.exec("SHOW_ACTIVE_LAYER", [oLayer, sOnCloseCmd, aOnCloseParam]);
			if(sOnOpenCmd) this.oApp.exec(sOnOpenCmd, aOnOpenParam);
		}
	},

	$ON_SHOW_ACTIVE_LAYER : function(oLayer, sOnCloseCmd, aOnCloseParam){
		oLayer = $.$(oLayer);
		this.sOnCloseCmd = sOnCloseCmd;
		this.aOnCloseParam = aOnCloseParam;

		var oPrevLayer = this.oCurrentLayer;

		if(oLayer == oPrevLayer) return;

		this.oApp.exec("HIDE_ACTIVE_LAYER", []);

		oLayer.style.display = "block";
		this.oCurrentLayer = oLayer;
	},

	$ON_HIDE_ACTIVE_LAYER : function(){
		var oLayer = this.oCurrentLayer;
		if(!oLayer) return;
		oLayer.style.display = "none";
		this.oCurrentLayer = null;

		if(this.sOnCloseCmd)
			this.oApp.exec(this.sOnCloseCmd, this.aOnCloseParam);
	},

	// for backward compatibility only.
	// use HIDE_ACTIVE_LAYER instead!
	$ON_HIDE_CURRENT_ACTIVE_LAYER : function(){
		this.oApp.exec("HIDE_ACTIVE_LAYER", []);
	},

	$ON_EVENT_EDITING_AREA_KEYDOWN : function(){
		this.oApp.exec("HIDE_ACTIVE_LAYER", []);
	},

	$ON_EVENT_EDITING_AREA_MOUSEDOWN : function(){
		this.oApp.exec("HIDE_ACTIVE_LAYER", []);
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations related to string conversion. Ususally used to convert the IR value.
 * @name hp_StringConverterManager.js
 */
xe.StringConverterManager = $.Class({
	name : "StringConverterManager",

	oConverters : null,

	$init : function(){
		this.oConverters = {};
	},

	$BEFORE_MSG_APP_READY : function(){
		this.oApp.exec("ADD_APP_PROPERTY", ["applyConverter", $.fnBind(this.applyConverter, this)]);
		this.oApp.exec("ADD_APP_PROPERTY", ["addConverter", $.fnBind(this.addConverter, this)]);
	},

	applyConverter : function(sRuleName, sContent){
		var aConverters = this.oConverters[sRuleName];
		if(!aConverters) return sContent;

		for(var i=0; i<aConverters.length; i++) sContent = aConverters[i](sContent);

		return sContent;
	},

	addConverter : function(sRuleName, funcConverter){
		var aConverters = this.oConverters[sRuleName];
		if(!aConverters) this.oConverters[sRuleName] = [];

		this.oConverters[sRuleName][this.oConverters[sRuleName].length] = funcConverter;
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that maps a message code to the actual message
 * @name hp_MessageManager.js
 */
xe.MessageManager = $.Class({
	name : "MessageManager",

	oMessageMap : null,

	$init : function(oMessageMap){
		this.oMessageMap = oMessageMap;
	},

	$BEFORE_MSG_APP_READY : function(){
		this.oApp.exec("ADD_APP_PROPERTY", ["$MSG", $.fnBind(this.getMessage, this)]);
	},

	getMessage : function(sMsg){
		if(this.oMessageMap[sMsg]) return unescape(this.oMessageMap[sMsg]);

		return sMsg;
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations related to the tool bar UI
 * @name hp_XE_Toolbar.js
 */
xe.XE_Toolbar = $.Class({
	name : "XE_Toolbar",
	toolbarArea : null,
	toolbarButton : null,
	uiNameTag : "uiName",

	sUIClassPrefix : "xpress_xeditor_ui_",

	aUICmdMap : null,

	$init : function(oAppContainer){
		this.htUIList = {};

		this.aUICmdMap = {};
		this._assignHTMLObjects(oAppContainer);
	},

	_assignHTMLObjects : function(oAppContainer){
		oAppContainer = $.$(oAppContainer) || document;
		this.toolbarArea = $(".tool", oAppContainer).get(0);
		this.welToolbarArea = $(this.toolbarArea);

		this.aAllButtons = $("BUTTON", this.toolbarArea).get();

		var aAllLi = this.toolbarArea.getElementsByTagName("LI");
		var nCount = aAllLi.length;
		var rxUI = new RegExp(this.sUIClassPrefix+"([^ ]+)");
		for(var i=0; i<nCount; i++){
			if(rxUI.test(aAllLi[i].className)){
				var sUIName = RegExp.$1;
				if(this.htUIList[sUIName] != null) continue;

				this.htUIList[sUIName] = $(">*:first-child", aAllLi[i]).get(0);
			}
		}
	},

	$ON_MSG_APP_READY : function(){
		this.oApp.registerBrowserEvent(this.toolbarArea, "mouseover", "EVENT_TOOLBAR_MOUSEOVER", []);
		this.oApp.registerBrowserEvent(this.toolbarArea, "mouseout", "EVENT_TOOLBAR_MOUSEOUT", []);

		this.oApp.exec("ADD_APP_PROPERTY", ["getToolbarButtonByUIName", $.fnBind(this.getToolbarButtonByUIName, this)]);
	},

	$ON_EVENT_TOOLBAR_MOUSEOVER : function(weEvent){
		if(weEvent.target.tagName == "BUTTON") $(weEvent.target).addClass("hover").parent("span").addClass("hover");
	},

	$ON_EVENT_TOOLBAR_MOUSEOUT : function(weEvent){
		if(weEvent.target.tagName == "BUTTON") $(weEvent.target).removeClass("hover").parent("span").removeClass("hover");
	},

	$ON_TOGGLE_TOOLBAR_ACTIVE_LAYER : function(oLayer, oBtn, sOpenCmd, aOpenArgs, sCloseCmd, aCloseArgs){
		this.oApp.exec("TOGGLE_ACTIVE_LAYER", [oLayer, "MSG_TOOLBAR_LAYER_SHOWN", [oLayer, oBtn, sOpenCmd, aOpenArgs], sCloseCmd, aCloseArgs]);
	},

	$ON_MSG_TOOLBAR_LAYER_SHOWN : function(oLayer, oBtn, aOpenCmd, aOpenArgs){
		this.oApp.exec("POSITION_TOOLBAR_LAYER", [oLayer, oBtn]);
		if(aOpenCmd) this.oApp.exec(aOpenCmd, aOpenArgs);
	},

	$ON_SHOW_TOOLBAR_ACTIVE_LAYER : function(oLayer, sCmd, aArgs, oBtn){
		this.oApp.exec("SHOW_ACTIVE_LAYER", [oLayer, sCmd, aArgs]);
		this.oApp.exec("POSITION_TOOLBAR_LAYER", [oLayer, oBtn]);
	},

	$ON_ENABLE_UI : function(sUIName){
		var elUI = this.htUIList[sUIName];
		if(!elUI) return;
		$(elUI).removeClass("off");
		elUI.disabled = false;

		// enable related commands
		var sCmd = "";
		if(this.aUICmdMap[sUIName]){
			for(var i=0; i<this.aUICmdMap[sUIName].length;i++){
				sCmd = this.aUICmdMap[sUIName][i];
				this.oApp.exec("ENABLE_COMMAND", [sCmd]);
			}
		}
	},

	$ON_DISABLE_UI : function(sUIName){
		var elUI = this.htUIList[sUIName];
		if(!elUI) return;
		$(elUI).addClass("off");
		$(elUI).removeClass("hover").parent("span").removeClass("hover");
		elUI.disabled = true;

		// disable related commands
		var sCmd = "";
		if(this.aUICmdMap[sUIName]){
			for(var i=0; i<this.aUICmdMap[sUIName].length;i++){
				sCmd = this.aUICmdMap[sUIName][i];
				this.oApp.exec("DISABLE_COMMAND", [sCmd]);
			}
		}
	},

	$ON_SELECT_UI : function(sUIName){
		var elUI = this.htUIList[sUIName];
		if(!elUI) return;
		$(elUI).addClass("active");
	},

	$ON_DESELECT_UI : function(sUIName){
		var elUI = this.htUIList[sUIName];
		if(!elUI) return;
		$(elUI).removeClass("active");
	},

	$ON_ENABLE_ALL_UI : function(){
		var sUIName, className;

		for(var sUIName in this.htUIList){
			if(sUIName) this.oApp.exec("ENABLE_UI", [sUIName]);
		}
		$(this.toolbarArea).removeClass("off");
	},

	$ON_DISABLE_ALL_UI : function(){
		var sUIName;

		for(var sUIName in this.htUIList){
			if(sUIName) this.oApp.exec("DISABLE_UI", [sUIName]);
		}
		$(this.toolbarArea).addClass("off");
		this.oApp.exec("HIDE_ACTIVE_LAYER",[]);
	},

	$ON_MSG_STYLE_CHANGED : function(sAttributeName, attributeValue){
		if(attributeValue == 1)
			this.oApp.exec("SELECT_UI", [sAttributeName]);
		else
			this.oApp.exec("DESELECT_UI", [sAttributeName]);
	},

	$ON_REGISTER_UI_EVENT : function(sUIName, sEvent, sCmd, aParams){
		// map cmd & ui
		if(!this.aUICmdMap[sUIName]){this.aUICmdMap[sUIName] = [];}
		this.aUICmdMap[sUIName][this.aUICmdMap[sUIName].length] = sCmd;
		var elUI = this.htUIList[sUIName];
		if(!elUI) return;
		this.oApp.registerBrowserEvent(elUI, sEvent, sCmd, aParams);
	},

	$ON_POSITION_TOOLBAR_LAYER : function(oLayer, oBtn){
		oLayer = $.$(oLayer);
		oBtn = $.$(oBtn);

		if(!oLayer) return;
		if(oBtn && oBtn.tagName && oBtn.tagName == "BUTTON") oBtn.parentNode.appendChild(oLayer);

		oLayer.style.left = "0";

		var welLayer = $(oLayer);
		var nLayerLeft = welLayer.offset().left;
		nLayerLeft += oLayer.offsetWidth;

		var nToolbarLeft = this.welToolbarArea.offset().left;
		nToolbarLeft += this.toolbarArea.offsetWidth;

		if(nLayerLeft > nToolbarLeft) oLayer.style.left = (nToolbarLeft-nLayerLeft-5)+"px";
	},

	getToolbarButtonByUIName : function(sUIName){
		return this.htUIList[sUIName];
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that manages multiple number editing area plugins and the IR value
 * @name hp_XE_EditingAreaManager.js
 */
xe.XE_EditingAreaManager = $.Class({
	name : "XE_EditingAreaManager",

	// Currently active plugin instance(XE_EditingArea_???)
	oActivePlugin : null,

	// Intermediate Representation of the content being edited.
	// This should be a textarea element.
	oIRField : null,

	bIsDirty : false,

	$init : function(sInitialMode, oIRField, oDimension, fOnBeforeUnload, oAppContainer){
		this.sInitialMode = sInitialMode;
		this.oIRField = $.$(oIRField);
		this._assignHTMLObjects(oAppContainer);
		this.fOnBeforeUnload = fOnBeforeUnload;

		this.oEditingMode = {};

		this.elEditingAreaContainer.style.height = parseInt(oDimension.nHeight || this.elEditingAreaContainer.offsetHeight)+"px";

		this.nMinHeight = oDimension.nMinHeight || 10;
		this.niMinWidth = oDimension.nMinWidth || 10;
	},

	_assignHTMLObjects : function(oAppContainer){
		oAppContainer = $.$(oAppContainer) || document;
		this.elEditingAreaContainer = $("DIV.xpress_xeditor_editing_area_container", oAppContainer).get(0);
		this.elEditingAreaSkipUI = $("A.skip", oAppContainer).get(0);
	},

	$BEFORE_MSG_APP_READY : function(msg){
		this.oApp.exec("ADD_APP_PROPERTY", ["elEditingAreaContainer", this.elEditingAreaContainer]);
		this.oApp.exec("ADD_APP_PROPERTY", ["getIR", $.fnBind(this.getIR, this)]);
		this.oApp.exec("ADD_APP_PROPERTY", ["setIR", this.setIR]);
		this.oApp.exec("ADD_APP_PROPERTY", ["getEditingMode", $.fnBind(this.getEditingMode, this)]);
	},

	$ON_MSG_APP_READY : function(){
		this.oApp.exec("CHANGE_EDITING_MODE", [this.sInitialMode, true]);
		this.oApp.exec("LOAD_IR_FIELD", [false]);

		this.oApp.registerBrowserEvent(this.elEditingAreaSkipUI, "focus", "MSG_EDITING_AREA_SIZE_CHANGED", [], 50);
		this.oApp.registerBrowserEvent(this.elEditingAreaSkipUI, "blur", "MSG_EDITING_AREA_SIZE_CHANGED", [], 50);

		var fOnBeforeUnload = this.fOnBeforeUnload||function(){if(this.getIR() != this.oIRField.value || this.bIsDirty) return this.oApp.$MSG("XE_EditingAreaManager.onExit")};
		$(window).bind("beforeunload", $.fnBind(fOnBeforeUnload, this));
	},

	$AFTER_MSG_APP_READY : function(){
		this.oApp.exec("UPDATE_IR_FIELD", []);
	},

	$ON_LOAD_IR_FIELD : function(bDontAddUndo){
		this.oApp.setIR(this.oIRField.value, bDontAddUndo);
	},

	$ON_UPDATE_IR_FIELD : function(){
		this.oIRField.value = this.oApp.getIR();
	},

	$BEFORE_CHANGE_EDITING_MODE : function(sMode){
		this._oPrevActivePlugin = this.oActivePlugin;
		this.oActivePlugin = this.oEditingMode[sMode];
	},

	$AFTER_CHANGE_EDITING_MODE : function(sMode, bNoFocus){
		if(this._oPrevActivePlugin){
			var sIR = this._oPrevActivePlugin.getIR();
			this.oApp.exec("SET_IR", [sIR]);

			this.oApp.exec("ENABLE_UI", [this._oPrevActivePlugin.sMode]);

			this._setEditingAreaDimension();
		}
		this.oApp.exec("DISABLE_UI", [this.oActivePlugin.sMode]);

		if(!bNoFocus){
			this.oApp.exec("FOCUS", []);
		}
	},

	$ON_SET_IS_DIRTY : function(bIsDirty){
		this.bIsDirty = bIsDirty;
	},

	$ON_FOCUS : function(){
		if(!this.oActivePlugin || typeof this.oActivePlugin.setIR != "function") return

		this.oActivePlugin.focus();
	},

	$BEFORE_SET_IR : function(sIR, bDontAddUndoHistory){
		bDontAddUndoHistory = bDontAddUndoHistory || false;
		if(!bDontAddUndoHistory) this.oApp.exec("RECORD_UNDO_ACTION", ["SET CONTENTS"]);
	},

	$ON_SET_IR : function(sIR){
		if(!this.oActivePlugin || typeof this.oActivePlugin.setIR != "function") return

		this.oActivePlugin.setIR(sIR);
	},

	$AFTER_SET_IR : function(sIR, bDontAddUndoHistory){
		bDontAddUndoHistory = bDontAddUndoHistory || false;
		if(!bDontAddUndoHistory) this.oApp.exec("RECORD_UNDO_ACTION", ["SET CONTENTS"]);
	},

	$ON_REGISTER_EDITING_AREA : function(oEditingAreaPlugin){
		this.oEditingMode[oEditingAreaPlugin.sMode] = oEditingAreaPlugin;
		this.attachDocumentEvents(oEditingAreaPlugin.oEditingArea);
	},

	$ON_MSG_EDITING_AREA_RESIZE_STARTED : function(){
		this.oActivePlugin.elEditingArea.style.display = "none";

		this.iStartingHeight = parseInt(this.elEditingAreaContainer.style.height);
	},

	$ON_RESIZE_EDITING_AREA: function(ipNewWidth, ipNewHeight){
		var iNewWidth = parseInt(ipNewWidth);
		var iNewHeight = parseInt(ipNewHeight);

		if(iNewWidth < this.niMinWidth) iNewWidth = this.niMinWidth;
		if(iNewHeight < this.nMinHeight) iNewHeight = this.nMinHeight;

		if(ipNewWidth) this.elEditingAreaContainer.style.width = iNewWidth + "px";
		if(ipNewHeight) this.elEditingAreaContainer.style.height = iNewHeight + "px";
	},

	$ON_RESIZE_EDITING_AREA_BY : function(ipWidthChange, ipHeightChange){
		var iWidthChange = parseInt(ipWidthChange);
		var iHeightChange = parseInt(ipHeightChange);

		var iWidth = this.elEditingAreaContainer.style.width?parseInt(this.elEditingAreaContainer.style.width)+iWidthChange:null;
		var iHeight = this.elEditingAreaContainer.style.height?this.iStartingHeight+iHeightChange:null;

		this.oApp.exec("RESIZE_EDITING_AREA", [iWidth, iHeight]);
	},

	$ON_MSG_EDITING_AREA_RESIZE_ENDED : function(FnMouseDown, FnMouseMove, FnMouseUp){
		this.oActivePlugin.elEditingArea.style.display = "block";
		this._setEditingAreaDimension();
	},

	_setEditingAreaDimension : function(){
		this.oActivePlugin.elEditingArea.style.height = this.elEditingAreaContainer.style.height;
		this.oActivePlugin.elEditingArea.style.width = this.elEditingAreaContainer.style.width;
	},

	attachDocumentEvents : function(doc){
		this.oApp.registerBrowserEvent(doc, "click", "EVENT_EDITING_AREA_CLICK");
		this.oApp.registerBrowserEvent(doc, "mousedown", "EVENT_EDITING_AREA_MOUSEDOWN");
		this.oApp.registerBrowserEvent(doc, "mousemove", "EVENT_EDITING_AREA_MOUSEMOVE");
		this.oApp.registerBrowserEvent(doc, "mouseup", "EVENT_EDITING_AREA_MOUSEUP");
		this.oApp.registerBrowserEvent(doc, "keydown", "EVENT_EDITING_AREA_KEYDOWN");
		this.oApp.registerBrowserEvent(doc, "keypress", "EVENT_EDITING_AREA_KEYPRESS");
		this.oApp.registerBrowserEvent(doc, "keyup", "EVENT_EDITING_AREA_KEYUP");
	},

	getIR : function(){
		return this.oActivePlugin.getIR();
	},

	setIR : function(sIR, bDontAddUndo){
		this.oApp.exec("SET_IR", [sIR, bDontAddUndo]);
	},

	getEditingMode : function(){
		return this.oActivePlugin.sMode;
	}
});
//}

//{
/**
  * @fileOverview This file contains Xpress plugin that takes care of the operations directly related to editing the HTML source code using Textarea element
 * @name hp_XE_EditingArea_HTMLSrc.js
 * @required XE_EditingAreaManager
 */
xe.XE_EditingArea_HTMLSrc = $.Class({
	name : "XE_EditingArea_HTMLSrc",

	sMode : "HTMLSrc",
	textarea : null,

	$init : function(textarea){
		this.textarea = $.$(textarea);
		this.elEditingArea = this.textarea;
	},

	$BEFORE_MSG_APP_READY : function(){
		this.oEditingArea = this.textarea;
		this.oApp.exec("REGISTER_EDITING_AREA", [this]);
	},

	$ON_CHANGE_EDITING_MODE : function(sMode, bNoFocus){
		if(sMode == this.sMode){
			this.textarea.style.display = "block";
		}else{
			this.textarea.style.display = "none";
		}
	},

	$ON_PASTE_HTML : function(sHTML, oPSelection){
		if(this.oApp.getEditingMode() != this.sMode) return;

		var o = new TextRange(this.textarea);
		o.paste(sHTML);
		this.textarea.focus();
	},

	getIR : function(){
		var sIR;
		var sContent = this.textarea.value;

		if(this.oApp.applyConverter)
			sIR = this.oApp.applyConverter(this.sMode+"_TO_IR", sContent);
		else
			sIR = sContent;

		return sIR;
	},

	setIR : function(sIR){
		var sContent;

		if(this.oApp.applyConverter)
			sContent = this.oApp.applyConverter("IR_TO_"+this.sMode, sIR);
		else
			sContent = sIR;

		this.textarea.value = sContent;
	},

	focus : function(){
		this.textarea.focus();
	}
});

var TextRange = function(oEl) {
	this._o = oEl;
};

/**
 * Selection for textfield
 *
 * @author hooriza
 */
TextRange.prototype.getSelection = function() {
	var obj = this._o;
	var ret = [ -1, -1 ];

	if (isNaN(this._o.selectionStart)) {
		obj.focus();

		// textarea support added by nagoon97
		var range = document.body.createTextRange();
		var rangeField = null;

		rangeField = document.selection.createRange().duplicate();
		range.moveToElementText(obj);
		rangeField.collapse(true);
		range.setEndPoint("EndToEnd", rangeField);
		ret[0] = range.text.length;

		rangeField = document.selection.createRange().duplicate();
		range.moveToElementText(obj);
		rangeField.collapse(false);
		range.setEndPoint("EndToEnd", rangeField);
		ret[1] = range.text.length;

		obj.blur();
	} else {
		ret[0] = obj.selectionStart;
		ret[1] = obj.selectionEnd;
	}

	return ret;
};

TextRange.prototype.setSelection = function(start, end) {

	var obj = this._o;
	if (typeof end == 'undefined') end = start;

	if (obj.setSelectionRange) {

		obj.setSelectionRange(start, end);

	} else if (obj.createTextRange) {

		var range = obj.createTextRange();

		range.collapse(true);
		range.moveStart("character", start);
		range.moveEnd("character", end - start);
		range.select();

		obj.blur();
	}

};

TextRange.prototype.copy = function() {

	var r = this.getSelection();
	return this._o.value.substring(r[0], r[1]);

};

TextRange.prototype.paste = function(sStr) {

	var obj = this._o;
	var sel = this.getSelection();

	var value = obj.value;

	var pre = value.substr(0, sel[0]);
	var post = value.substr(sel[1]);

	value = pre + sStr + post;
	obj.value = value;

	var n = 0;
	if ( typeof document.body.style.maxHeight == "undefined" ) {
		var a = pre.match( /\n/gi );
		n = ( a != null ? a.length : 0 );
	}
	this.setSelection(sel[0] + sStr.length - n );

};

TextRange.prototype.cut = function() {
	var r = this.copy();
	this.paste('');

	return r;
};
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations directly related to WYSIWYG iframe
 * @name hp_XE_EditingArea_WYSIWYG.js
 */
xe.XE_EditingArea_WYSIWYG = $.Class({
	name : "XE_EditingArea_WYSIWYG",
	status : xe.PLUGIN_STATUS["NOT_READY"],

	sMode : "WYSIWYG",
	iframe : null,
	doc : null,

	iLastUndoRecorded : 0,
	iMinUndoInterval : 3000,

	_nIFrameReadyCount : 50,

	$init : function(iframe){
		this.iframe = $.$(iframe);

		this.initIframe();

		this.elEditingArea = iframe;
	},

	$BEFORE_MSG_APP_READY : function(){
		this.oEditingArea = this.doc;
		this.oApp.exec("REGISTER_EDITING_AREA", [this]);
		this.oApp.exec("ADD_APP_PROPERTY", ["getWYSIWYGWindow", $.fnBind(this.getWindow, this)]);
		this.oApp.exec("ADD_APP_PROPERTY", ["getWYSIWYGDocument", $.fnBind(this.getDocument, this)]);
	},

	$ON_MSG_APP_READY : function(){
		// uncomment this line if you wish to use the IE-style cursor in FF
		// this.getDocument().body.style.cursor = "text";

		if($.browser.msie){
			$(this.doc).bind('keydown', $.fnBind(
				function(weEvent){
					if(this.doc.selection.type.toLowerCase() == 'control' && weEvent.keyCode == 8)  {
						this.oApp.exec("EXECCOMMAND", ['delete', false, false]);
						weEvent.preventDefault(); weEvent.stopPropagation();
					}
				}
			, this));
			$(this.doc.body).bind('mousedown', $.fnBind(
				function(weEvent){
					this._oIERange = null;
					this._bIERangeReset = true;
				}
			, this));
			$(this.doc.body).bind('beforedeactivate', $.fnBind(
				function(weEvent){
					// without this, cursor won't make it inside a table.
					// mousedown(_oIERange gets reset) -> beforedeactivate(gets fired for table) -> RESTORE_IE_SELECTION
					if(this._bIERangeReset) return;

					var tmpRange = this.getDocument().selection.createRange(0);
					// Control range does not have parentElement
					if(tmpRange.parentElement && tmpRange.parentElement() && tmpRange.parentElement().tagName == "INPUT"){
						this._oIERange = this._oPrevIERange;
					}else{
						this._oIERange = tmpRange;
					}
				}
			, this));
			$(this.doc.body).bind('mouseup', $.fnBind(
				function(weEvent){
					this._bIERangeReset = false;
				}
			, this));
		}
	},

	$ON_CHANGE_EDITING_MODE : function(sMode, bNoFocus){
		if(sMode == this.sMode){
			this.iframe.style.display = "block";

			this.oApp.exec("REFRESH_WYSIWYG", []);
			this.oApp.exec("SET_EDITING_WINDOW", [this.getWindow()]);
		}else{
			this.iframe.style.display = "none";
		}
	},

	$AFTER_CHANGE_EDITING_MODE : function(sMode, bNoFocus){
		this._oIERange = null;
	},

	$ON_REFRESH_WYSIWYG : function(){
		if(!$.browser.mozilla) return;

		this._disableWYSIWYG();
		this._enableWYSIWYG();
	},

	$ON_ENABLE_WYSIWYG : function(){
		this._enableWYSIWYG();
	},

	$ON_DISABLE_WYSIWYG : function(){
		this._disableWYSIWYG();
	},

	$ON_EVENT_EDITING_AREA_KEYDOWN : function(oEvent) {
		// ctrl-left/right add/remove indent
		if(!oEvent.ctrlKey) return;

		switch(oEvent.keyCode) {
			// outdent
			case 37 :
					this.oApp.exec("EXECCOMMAND",  ["outdent", false, false]);
				break;
			// indent
			case 39 :
					this.oApp.exec("EXECCOMMAND",  ["indent", false, false]);
				break;
			// h1 ~ h6, normal
			case 49 :
			case 50 :
			case 51 :
			case 52 :
			case 53 :
			case 54 :
					this.oApp.exec("EXECCOMMAND", ["FormatBlock", false, '<h'+(oEvent.keyCode-48)+'>']);
				break;
			default :
				return;
		}
		oEvent.preventDefault(); oEvent.stopPropagation();

	},

	$ON_EVENT_EDITING_AREA_KEYUP : function(oEvent){
		// 33, 34: page up/down, 35,36: end/home, 37,38,39,40: left, up, right, down
		if(oEvent.keyCode == 229 || oEvent.keyCode == 13 || oEvent.altKey || oEvent.ctrlKey || (oEvent.keyCode >= 33 && oEvent.keyCode <= 40) || oEvent.keyCode == 16) return;
		this._recordUndo(oEvent);
	},

	$ON_PASTE_HTML : function(sHTML, oPSelection){
		if(this.oApp.getEditingMode() != this.sMode) return;

		var oSelection = oPSelection || this.oApp.getSelection();
		oSelection.pasteHTML(sHTML);

		// every browser except for IE may modify the innerHTML when it is inserted
		if(!$.browser.msie){
			var sTmpBookmark = oSelection.placeStringBookmark();
			this.oApp.getWYSIWYGDocument().body.innerHTML = this.oApp.getWYSIWYGDocument().body.innerHTML;
			oSelection.moveToBookmark(sTmpBookmark);
			oSelection.collapseToEnd();
			oSelection.select();
			oSelection.removeStringBookmark(sTmpBookmark);
		}

		this.oApp.exec("RECORD_UNDO_ACTION", ["INSERT HTML"]);
	},

	$AFTER_MSG_EDITING_AREA_RESIZE_ENDED : function(FnMouseDown, FnMouseMove, FnMouseUp){
		this.oApp.exec("REFRESH_WYSIWYG", []);
	},

	$ON_RESTORE_IE_SELECTION : function(){
		if(this._oIERange){
			this._oIERange.select();
			this._oPrevIERange = this._oIERange;
			this._oIERange = null;
		}
	},

	initIframe : function(){
		try {
			this.doc = this.iframe.contentWindow.document;
			if (this.doc == null || this.doc.location.href == 'about:blank') {
				throw new Error('Access denied');
			}

			this._enableWYSIWYG();

			this.status = xe.PLUGIN_STATUS["READY"];
		} catch(e) {
			if(this._nIFrameReadyCount-- > 0){
				setTimeout($.fnBind(this.initIframe, this), 100);
			}else{
				throw("iframe for WYSIWYG editing mode can't be initialized. Please check if the iframe document exists and is also accessable(cross-domain issues). ");
			}
		}
	},

	getIR : function(){
		var sContent = this.doc.body.innerHTML;
		var sIR;

		if(this.oApp.applyConverter)
			sIR = this.oApp.applyConverter(this.sMode+"_TO_IR", sContent);
		else
			sIR = sContent;

		return sIR;
	},

	setIR : function(sIR){
		var sContent;
		if(this.oApp.applyConverter)
			sContent = this.oApp.applyConverter("IR_TO_"+this.sMode, sIR);
		else
			sContent = sIR;

		this.doc.body.innerHTML = sContent;

		if($.browser.mozilla){
			if(this.doc.body.innerHTML == "") this.doc.body.innerHTML = "<br>";
		}
	},

	getWindow : function(){
		return this.iframe.contentWindow;
	},

	getDocument : function(){
		return this.iframe.contentWindow.document;
	},

	focus : function(){
		//this.getWindow().focus();
		//this.oApp.exec("RESTORE_IE_SELECTION", []);
	},

	_recordUndo : function(oKeyInfo){
		var curTime = new Date();
		if(curTime-this.iLastUndoRecorded < this.iMinUndoInterval) return;
		this.oApp.exec("RECORD_UNDO_ACTION", ["KEYPRESS"]);

		this.iLastUndoRecorded = new Date();

		this.prevKeyCode = oKeyInfo.keyCode;
	},

	_enableWYSIWYG : function(){
		if ($.browser.msie){
			this.doc.body.disabled = true;
			this.doc.body.contentEditable = true;
			this.doc.body.removeAttribute('disabled');
		} else {
			this.doc.designMode = "on";
		}
	},

	_disableWYSIWYG : function(){
		if ($.browser.msie){
			this.doc.body.contentEditable = false;
		} else {
			this.doc.designMode = "off";
		}
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations related to resizing the editing area vertically
 * @name hp_XE_EditingAreaVerticalResizer.js
 */
xe.XE_EditingAreaVerticalResizer = $.Class({
	name : "XE_EditingAreaVerticalResizer",
	oResizeGrip : null,

	$init : function(oAppContainer){
		this._assignHTMLObjects(oAppContainer);

		this.$FnMouseDown = $.fnBind(this._mousedown, this);
		this.$FnMouseMove = $.fnBind(this._mousemove, this);
		this.$FnMouseUp = $.fnBind(this._mouseup, this);

		$(this.oResizeGrip).bind("mousedown", this.$FnMouseDown);
	},

	_assignHTMLObjects : function(oAppContainer){
		oAppContainer = $.$(oAppContainer) || document;

		this.oResizeGrip = $(".xpress_xeditor_editingArea_verticalResizer", oAppContainer).get(0);
	},

	_mousedown : function(oEvent){
		this.iStartHeight = oEvent.clientY;

		$(document).bind("mousemove", this.$FnMouseMove);
		$(document).bind("mouseup", this.$FnMouseUp);

		this.oApp.exec("MSG_EDITING_AREA_RESIZE_STARTED", [this.$FnMouseDown, this.$FnMouseMove, this.$FnMouseUp]);
	},

	_mousemove : function(oEvent){
		var iHeightChange = oEvent.clientY - this.iStartHeight;

		this.oApp.exec("RESIZE_EDITING_AREA_BY", [0, iHeightChange]);
	},

	_mouseup : function(oEvent){
		$(document).unbind("mousemove", this.$FnMouseMove);
		$(document).unbind("mouseup", this.$FnMouseUp);

		this.oApp.exec("MSG_EDITING_AREA_RESIZE_ENDED", [this.$FnMouseDown, this.$FnMouseMove, this.$FnMouseUp]);
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the basic editor commands
 * @name hp_XE_ExecCommand.js
 */
xe.XE_ExecCommand = $.Class({
	name : "XE_ExecCommand",
	oEditingArea : null,

	$init : function(oEditingArea){
		this.oEditingArea = oEditingArea;
	},

	$BEFORE_MSG_APP_READY : function(){
		// the right document will be available only when the src is completely loaded
		if(this.oEditingArea && this.oEditingArea.tagName == "IFRAME")
			this.oEditingArea = this.oEditingArea.contentWindow.document;
	},

	$ON_MSG_APP_READY : function(){
		this.oApp.exec("REGISTER_HOTKEY", ["ctrl+b", "EXECCOMMAND", ["bold", false, false]]);
		this.oApp.exec("REGISTER_HOTKEY", ["ctrl+u", "EXECCOMMAND", ["underline", false, false]]);
		this.oApp.exec("REGISTER_HOTKEY", ["ctrl+i", "EXECCOMMAND", ["italic", false, false]]);
		this.oApp.exec("REGISTER_HOTKEY", ["ctrl+d", "EXECCOMMAND", ["strikethrough", false, false]]);

		this.oApp.exec("REGISTER_UI_EVENT", ["bold", "click", "EXECCOMMAND", ["bold", false, false]]);
		this.oApp.exec("REGISTER_UI_EVENT", ["underline", "click", "EXECCOMMAND", ["underline", false, false]]);
		this.oApp.exec("REGISTER_UI_EVENT", ["italic", "click", "EXECCOMMAND", ["italic", false, false]]);
		this.oApp.exec("REGISTER_UI_EVENT", ["lineThrough", "click", "EXECCOMMAND", ["strikethrough", false, false]]);
		this.oApp.exec("REGISTER_UI_EVENT", ["superscript", "click", "EXECCOMMAND", ["superscript", false, false]]);
		this.oApp.exec("REGISTER_UI_EVENT", ["subscript", "click", "EXECCOMMAND", ["subscript", false, false]]);
		this.oApp.exec("REGISTER_UI_EVENT", ["justifyleft", "click", "EXECCOMMAND", ["justifyleft", false, false]]);
		this.oApp.exec("REGISTER_UI_EVENT", ["justifycenter", "click", "EXECCOMMAND", ["justifycenter", false, false]]);
		this.oApp.exec("REGISTER_UI_EVENT", ["justifyright", "click", "EXECCOMMAND", ["justifyright", false, false]]);
		this.oApp.exec("REGISTER_UI_EVENT", ["justifyfull", "click", "EXECCOMMAND", ["justifyfull", false, false]]);
		this.oApp.exec("REGISTER_UI_EVENT", ["orderedlist", "click", "EXECCOMMAND", ["insertorderedlist", false, false]]);
		this.oApp.exec("REGISTER_UI_EVENT", ["unorderedlist", "click", "EXECCOMMAND", ["insertunorderedlist", false, false]]);
		this.oApp.exec("REGISTER_UI_EVENT", ["outdent", "click", "EXECCOMMAND", ["outdent", false, false]]);
		this.oApp.exec("REGISTER_UI_EVENT", ["indent", "click", "EXECCOMMAND", ["indent", false, false]]);
	},

	$BEFORE_EXECCOMMAND : function(sCommand, bUserInterface, vValue){
		this._bOnlyCursorChanged = false;

		this.oApp.exec("FOCUS", []);

		if(sCommand.match(/^bold|underline|italic|strikethrough|superscript|subscript$/i)){
			var oSelection = this.oApp.getSelection();
			if(oSelection.collapsed) this._bOnlyCursorChanged = true;
		}

		if(!this._bOnlyCursorChanged){
			this.oApp.exec("RECORD_UNDO_BEFORE_ACTION", [sCommand]);
		}
	},

	$ON_EXECCOMMAND : function(sCommand, bUserInterface, vValue){
		bUserInterface = (bUserInterface == "" || bUserInterface)?bUserInterface:false;
		vValue = (vValue == "" || vValue)?vValue:false;

		this.oEditingArea.execCommand(sCommand, bUserInterface, vValue);
	},

	$AFTER_EXECCOMMAND : function(sCommand, bUserInterface, vValue){
		if(!this._bOnlyCursorChanged){
			this.oApp.exec("RECORD_UNDO_AFTER_ACTION", [sCommand]);
		}

		this.oApp.exec("CHECK_STYLE_CHANGE", []);
	}
});
//}

//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations related to wrapping the sentence around with a <P> tag when enter key is pressed
 * @name hp_XE_WYSIWYGEnterKey.js
 */
xe.XE_WYSIWYGEnterKey = $.Class({
	name : "XE_WYSIWYGEnterKey",
	// IE/Opera do not need this
	unsupportedBrowser : ['ie', 'opera'],
	oEditingArea : null,

	$init : function(oEditingArea){
		this.oEditingArea = oEditingArea;
	},

	$BEFORE_MSG_APP_READY : function(){
		// the right document will be available only when the src is completely loaded
		if(this.oEditingArea && this.oEditingArea.tagName == "IFRAME")
			this.oEditingArea = this.oEditingArea.contentWindow.document;
	},

	$ON_EVENT_EDITING_AREA_KEYDOWN : function(oEvent){
		if(this.oApp.getEditingMode() != "WYSIWYG") return;

		if(oEvent.shiftKey) return;

		if(oEvent.keyCode == 13){
			var oSelection = this.oApp.getSelection();
			var sBM = oSelection.placeStringBookmark();
			var oLineInfo = oSelection.getLineInfo();
			var oStart = oLineInfo.oStart;
			var oEnd = oLineInfo.oEnd;

			//top.document.title = oStart.oNode.tagName+":"+oStart.oNode.nodeValue+", "+oEnd.oNode.tagName+":"+oEnd.oNode.nodeValue+"::"+oStart.bParentBreak+", "+oStart.oLineBreaker.tagName;

			// line broke by sibling
			// or
			// the parent line breaker is just a block container
			if(!oStart.bParentBreak || oSelection.rxBlockContainer.test(oStart.oLineBreaker.tagName)){
				oEvent.stopPropagation();
				oEvent.preventDefault();

				var oSWrapper = this.oEditingArea.createElement("P");
				oSelection.moveToBookmark(sBM);
				oSelection.setStartBefore(oStart.oNode);
				oSelection.surroundContents(oSWrapper);

				oSelection.collapseToEnd();

				var oEWrapper = this.oEditingArea.createElement("P");
				oSelection.setEndAfter(oEnd.oNode);
				oSelection.surroundContents(oEWrapper);

				oSelection.removeStringBookmark(sBM);

				if(oSWrapper.innerHTML == "") oSWrapper.innerHTML = "<br>";
				if(oEWrapper.innerHTML == "") oEWrapper.innerHTML = "<br>";

				if(oEWrapper.nextSibling && oEWrapper.nextSibling.tagName == "BR") oEWrapper.parentNode.removeChild(oEWrapper.nextSibling);

				oSelection.selectNodeContents(oEWrapper);
				oSelection.collapseToStart();
				oSelection.select();
				this.oApp.exec("CHECK_STYLE_CHANGE", []);
			}else{
				oSelection.removeStringBookmark(sBM);
			}
		}
	}
});
//}

//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations related to styling the font
 * @name hp_XE_WYSIWYGStyler.js
 * @required XE_EditingArea_WYSIWYG, XpressRangeManager
 */
xe.XE_WYSIWYGStyler = $.Class({
	name : "XE_WYSIWYGStyler",

	$PRECONDITION : function(sFullCommand, aArgs){
		return (this.oApp.getEditingMode() == "WYSIWYG");
	},

	$ON_SET_WYSIWYG_STYLE : function(oStyles){
		var oSelection = this.oApp.getSelection();

		// style cursor
		if(oSelection.collapsed){
			var oSpan = this.oApp.getWYSIWYGDocument().createElement("SPAN");
			oSelection.insertNode(oSpan);
			oSpan.innerHTML = unescape("%uFEFF");

			var sValue;
			for(var sName in oStyles){
				sValue = oStyles[sName];

				if(typeof sValue != "string") continue;

				oSpan.style[sName] = sValue;
			}

			oSelection.selectNodeContents(oSpan);
			oSelection.collapseToEnd();
			oSelection._window.focus();
			oSelection._window.document.body.focus();
			oSelection.select();

			// FF3 will actually display %uFEFF when it is followed by a number AND certain font-family is used(like Gulim), so remove the chcaracter for FF3
			if($.browser.mozilla && $.browser.nVersion == 3)
				oSpan.innerHTML = "";

			return;
		}

		this.oApp.exec("RECORD_UNDO_BEFORE_ACTION", ["FONT STYLE"]);

		oSelection.styleRange(oStyles);
		oSelection._window.focus();
		oSelection.select();

		this.oApp.exec("RECORD_UNDO_AFTER_ACTION", ["FONT STYLE"]);
	}
});
//}

//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations related to detecting the style change
 * @name hp_XE_WYSIWYGStyleGetter.js
 */
xe.XE_WYSIWYGStyleGetter = $.Class({
	name : "XE_WYSIWYGStyleGetter",

	hKeyUp : null,

	getStyleInterval : 200,

	oStyleMap : {
		fontFamily : {
			type : "Value",
			css : "fontFamily"
		},
		fontSize : {
			type : "Value",
			css : "fontSize"
		},
		lineHeight : {
			type : "Value",
			css : "lineHeight",
			converter : function(sValue, oStyle){
				if(!sValue.match(/px$/)) return sValue;

				return Math.ceil((parseInt(sValue)/parseInt(oStyle.fontSize))*10)/10;
			}
		},
		bold : {
			command : "bold"
		},
		underline : {
			command : "underline"
		},
		italic : {
			command : "italic"
		},
		lineThrough : {
			command : "strikethrough"
		},
		superscript : {
			command : "superscript"
		},
		subscript : {
			command : "subscript"
		},
		justifyleft : {
			command : "justifyleft"
		},
		justifycenter : {
			command : "justifycenter"
		},
		justifyright : {
			command : "justifyright"
		},
		justifyfull : {
			command : "justifyfull"
		},
		orderedlist : {
			command : "insertorderedlist"
		},
		unorderedlist : {
			command : "insertunorderedlist"
		}
	},

	$init : function(){
		this.oStyle = this._getBlankStyle();
	},

	$PRECONDITION : function(){
		if(this.oApp.getEditingMode() != "WYSIWYG") return false;

		return true;
	},

	$ON_MSG_APP_READY : function(){
		this.oDocument = this.oApp.getWYSIWYGDocument();
		this.oApp.exec("ADD_APP_PROPERTY", ["getCurrentStyle", $.fnBind(this.getCurrentStyle, this)]);
	},

	$ON_EVENT_EDITING_AREA_MOUSEUP : function(oEvnet){
		if(this.hKeyUp) clearTimeout(this.hKeyUp);
		this.oApp.exec("CHECK_STYLE_CHANGE", []);
	},

	$ON_EVENT_EDITING_AREA_KEYUP : function(oEvent){
		/*
		backspace 8
		page up 33
		page down 34
		end 35
		home 36
		left arrow 37
		up arrow 38
		right arrow 39
		down arrow 40
		insert 45
		delete 46
		*/
		if(!(oEvent.keyCode == 8 || (oEvent.keyCode >= 33 && oEvent.keyCode <= 40) || oEvent.keyCode == 45 || oEvent.keyCode == 46)) return;

		if(this.hKeyUp) clearTimeout(this.hKeyUp);

		this.hKeyUp = setTimeout($.fnBind(this.oApp.exec, this.oApp, "CHECK_STYLE_CHANGE", []), this.getStyleInterval);
	},

	$ON_CHECK_STYLE_CHANGE : function(){
		this._getStyle();
	},

	$ON_RESET_STYLE_STATUS : function(){
		var oBlankStyle = this._getBlankStyle();
		for(var sAttributeName in oBlankStyle)
			this.oApp.exec("SET_STYLE_STATUS", [sAttributeName, oBlankStyle[sAttributeName]]);
	},

	getCurrentStyle : function(){
		return this.oStyle;
	},

	_check_style_change : function(){
		this.oApp.exec("CHECK_STYLE_CHANGE", []);
	},

	_getBlankStyle : function(){
		var oBlankStyle = {};
		for(var attributeName in this.oStyleMap){
			if(this.oStyleMap[attributeName].type == "Value")
				oBlankStyle[attributeName] = "";
			else
				oBlankStyle[attributeName] = 0;
		}

		return oBlankStyle;
	},

	_getStyle : function(){
		var oSelection = this.oApp.getSelection();

		var funcFilter = function(oNode){
			if (!oNode.childNodes || oNode.childNodes.length == 0)
				return true;
			else
				return false;
		}

		var aBottomNodes = oSelection.getNodes(false, funcFilter);

		var oStyle, oBaseStyle, oTmpStyle, attributeName;
		if(aBottomNodes.length == 0){
			oStyle = this._getStyleOf(oSelection.commonAncestorContainer);
		}else{
			oStyle = this._getStyleOf(aBottomNodes[0]);
		}

		for(attributeName in oStyle){
			if(this.oStyleMap[attributeName].converter){
				oStyle[attributeName] = this.oStyleMap[attributeName].converter(oStyle[attributeName], oStyle);
			}

			if(this.oStyle[attributeName] != oStyle[attributeName])
				this.oApp.exec("MSG_STYLE_CHANGED", [attributeName, oStyle[attributeName]]);
		}

		this.oStyle = oStyle;
	},

	_getStyleOf : function(oNode){
		var oStyle = this._getBlankStyle();

		// this must not happen
		if(!oNode) return oStyle;

		if(oNode.nodeType == 3) oNode = oNode.parentNode;

		var welNode = $(oNode);
		var attribute, cssName;
		for(var styleName in this.oStyle){
			attribute = this.oStyleMap[styleName];

			if(attribute.type && attribute.type == "Value"){
				if(attribute.css){
					var sValue = welNode.css(attribute.css);

					if(styleName == "fontFamily"){
						sValue = sValue.split(/,/)[0];
					}

					oStyle[styleName] = sValue;
				}else{
					if(attribute.command){
						try{
							oStyle[styleName] = this.oDocument.queryCommandState(attribute.command);
						}catch(e){}
					}else{
						// todo
					}
				}
			}else{
				if(attribute.command){
					try{
						if(this.oDocument.queryCommandState(attribute.command)){
							oStyle[styleName] = 1;
						}else{
							oStyle[styleName] = 0;
						}
					}catch(e){}
				}else{
					// todo
				}
			}
		}
		return oStyle;
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations related to changing the font size using Select element
 * @name hp_XE_FontSizeWithSelectUI.js
 */
xe.XE_FontSizeWithSelectUI = $.Class({
	name : "XE_FontSizeWithSelectUI",

	$init : function(elAppContainer){
		this._assignHTMLObjects(elAppContainer);
	},

	_assignHTMLObjects : function(elAppContainer){
		this.elFontSizeSelect = $("SELECT.xpress_xeditor_ui_fontSize_select", elAppContainer).get(0);
	},

	$ON_MSG_APP_READY : function(){
		this.oApp.registerBrowserEvent(this.elFontSizeSelect, "change", "SET_FONTSIZE_FROM_SELECT_UI");
		this.elFontSizeSelect.selectedIndex = 0;
	},

	$ON_MSG_STYLE_CHANGED : function(sAttributeName, sAttributeValue){
		if(sAttributeName == "fontSize"){
			this.elFontSizeSelect.value = sAttributeValue;
			if(this.elFontSizeSelect.selectedIndex < 0) this.elFontSizeSelect.selectedIndex = 0;
		}
	},

	$ON_SET_FONTSIZE_FROM_SELECT_UI : function(){
		var sFontSize = this.elFontSizeSelect.value;
		if(!sFontSize) return;

		this.oApp.exec("SET_WYSIWYG_STYLE", [{"fontSize":sFontSize}]);
		this.oApp.exec("CHECK_STYLE_CHANGE", []);
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations related to changing the font name using Select element
 * @name hp_XE_FontNameWithSelectUI.js
 */
xe.XE_FontNameWithSelectUI = $.Class({
	name : "XE_FontNameWithSelectUI",

	$init : function(elAppContainer){
		this._assignHTMLObjects(elAppContainer);
	},

	_assignHTMLObjects : function(elAppContainer){
		this.elFontNameSelect = $("SELECT.xpress_xeditor_ui_fontName_select", elAppContainer).get(0);
	},

	$ON_MSG_APP_READY : function(){
		this.oApp.registerBrowserEvent(this.elFontNameSelect, "change", "SET_FONTNAME_FROM_SELECT_UI");
		this.elFontNameSelect.selectedIndex = 0;
	},

	$ON_MSG_STYLE_CHANGED : function(sAttributeName, sAttributeValue){
		if(sAttributeName == "fontFamily"){
			this.elFontNameSelect.value = sAttributeValue.toLowerCase();
			if(this.elFontNameSelect.selectedIndex < 0) this.elFontNameSelect.selectedIndex = 0;
		}
	},

	$ON_SET_FONTNAME_FROM_SELECT_UI : function(){
		var sFontName = this.elFontNameSelect.value;
		if(!sFontName) return;

		this.oApp.exec("SET_WYSIWYG_STYLE", [{"fontFamily":sFontName}]);
		this.oApp.exec("CHECK_STYLE_CHANGE", []);
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations related to setting/changing the lineheight
 * @name hp_XE_LineHeight.js
 */
xe.XE_LineHeight = $.Class({
	name : "XE_LineHeight",

	$init : function(oAppContainer){
		this._assignHTMLObjects(oAppContainer);
	},

	_assignHTMLObjects : function(oAppContainer){
	},

	$ON_SET_LINEHEIGHT : function(nLineHeight){
		this.setLineHeight(nLineHeight);
	},

	getLineHeight : function(){
		var nodes = this._getSelectedNodes(false);

		var curWrapper, prevWrapper;
		var iCurHeight, iHeight;

		if(nodes.length == 0) return -1;

		var iLength = nodes.length;

		if(iLength == 0){
			iHeight = -1;
		}else{
			prevWrapper = this._getLineWrapper(nodes[0]);
			iHeight = this._getWrapperLineheight(prevWrapper);
		}

		var firstNode = this.oSelection.getStartNode();

		if(iHeight > 0){
			for(var i=1; i<iLength; i++){
				if(this._isChildOf(nodes[i], curWrapper)) continue;
				if(!nodes[i]) continue;

				curWrapper = this._getLineWrapper(nodes[i]);
				if(curWrapper == prevWrapper) continue;

				curHeight = this._getWrapperLineheight(curWrapper);

				if(curHeight != iHeight){
					iHeight = -1;
					break;
				}

				prevWrapper = curWrapper;
			}
		}

		curWrapper = this._getLineWrapper(nodes[iLength-1]);

		var lastNode = this.oSelection.getEndNode();

		selectText = $.fnBind(function(firstNode, lastNode){
			this.oSelection.setEndNodes(firstNode, lastNode);
			this.oSelection.select();
		}, this, firstNode, lastNode);

		setTimeout(selectText, 100);

		return iHeight;
	},

	// height in percentage. For example pass 1 to set the line height to 100% and 1.5 to set it to 150%
	setLineHeight : function(height) {
		thisRef = this;

		function _setLineheight(div, height){
			if(!div){
				// try to wrap with P first
				try{
					div = thisRef.oSelection.surroundContentsWithNewNode("P");
				// if the range contains a block-level tag, wrap it with a DIV
				}catch(e){
					div = thisRef.oSelection.surroundContentsWithNewNode("DIV");
				}
			}

			div.style.lineHeight = height;

			return div;
		}

		function isInBody(node){
			while(node && node.tagName != "BODY"){
				node = xe.DOMFix.parentNode(node);
			}
			if(!node) return false;

			return true;
		}

		var nodes = this._getSelectedNodes(false);
		if(nodes.length == 0){
			return;
		}

		var curWrapper, prevWrapper;
		var iLength = nodes.length;

		this.oApp.exec("RECORD_UNDO_BEFORE_ACTION", ["LINEHEIGHT"]);

		prevWrapper = this._getLineWrapper(nodes[0]);
		prevWrapper = _setLineheight(prevWrapper, height);

		var startNode = prevWrapper;
		var endNode = prevWrapper;

		for(var i=1; i<iLength; i++){
			// Skip the node if a copy of the node were wrapped and the actual node no longer exists within the document.
			try{
				if(!isInBody(xe.DOMFix.parentNode(nodes[i]))) continue;
			}catch(e){continue;}

			if(this._isChildOf(nodes[i], curWrapper)) continue;

			curWrapper = this._getLineWrapper(nodes[i]);

			if(curWrapper == prevWrapper) continue;

			curWrapper = _setLineheight(curWrapper, height);

			prevWrapper = curWrapper;
		}

		endNode = curWrapper || startNode;

		setTimeout($.fnBind(function(startNode, endNode){
			this.oSelection.setEndNodes(startNode, endNode);
			this.oSelection.select();
			this.oApp.exec("RECORD_UNDO_AFTER_ACTION", ["LINEHEIGHT"]);
		}, this, startNode, endNode), 100);
	},
	_getSelectedNodes : function(bDontUpdate){
		if(!bDontUpdate)
			this.oSelection = this.oApp.getSelection();

		if(this.oSelection.collapsed) this.oSelection.selectNode(this.oSelection.commonAncestorContainer);

		var nodes = this.oSelection.getTextNodes();

		if(nodes.length == 0){
			var tmp = this.oSelection.getStartNode();
			if(tmp){
				nodes[0] = tmp;
			}else{
				nodes = [];
			}
		}

		return nodes;
	},
	_getWrapperLineheight : function(div){
		var iLineHeight = '';
		if(div && div.style.lineHeight){
			iLineHeight = div.style.lineHeight;
		}else{
			div = this.oSelection.commonAncesterContainer;
			while(div && !this.oSelection.rxLineBreaker.test(div.tagName)){
				if(div && div.style.lineHeight){
					iLineHeight = div.style.lineHeight;
					break;
				}
				div = xe.DOMFix.parentNode(div);
			}
		}

		return iLineHeight;
	},

	_isChildOf : function(node, container){
		while(node && node.tagName != "BODY"){
			if(node == container) return true;
			node = xe.DOMFix.parentNode(node);
		}

		return false;
	},
 	_getLineWrapper : function(node){
		var oTmpSelection = this.oApp.getEmptySelection();
		oTmpSelection.selectNode(node);
		var oLineInfo = oTmpSelection.getLineInfo();
		var oStart = oLineInfo.oStart;
		var oEnd = oLineInfo.oEnd;

		var a, b;
		var breakerA, breakerB;
		var div = null;

		a = oStart.oNode;
		breakerA = oStart.oLineBreaker;
		b = oEnd.oNode;
		breakerB = oEnd.oLineBreaker;

		this.oSelection.setEndNodes(a, b);

		if(breakerA == breakerB){
			if(breakerA.tagName == "P" || breakerA.tagName == "DIV"){
				div = breakerA;
			}else{
				this.oSelection.setEndNodes(breakerA.firstChild, breakerA.lastChild);
			}
		}

		return div;
 	}
 });
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations related to changing the lineheight using Select element
 * @name hp_XE_LineHeightWithSelectUI.js
 */
xe.XE_LineHeightWithSelectUI = $.Class({
	name : "XE_LineHeightWithSelectUI",

	_assignHTMLObjects : function(elAppContainer){
		this.elLineHeightSelect = $("SELECT.xpress_xeditor_ui_lineHeight_select", elAppContainer).get(0);
	},

	$ON_MSG_APP_READY : function(){
		this.oApp.registerBrowserEvent(this.elLineHeightSelect, "change", "SET_LINEHEIGHT_FROM_SELECT_UI");
		this.elLineHeightSelect.selectedIndex = 0;
	},

	$ON_MSG_STYLE_CHANGED : function(sAttributeName, sAttributeValue){
		if(sAttributeName == "lineHeight"){
			this.elLineHeightSelect.value = sAttributeValue;
			if(this.elLineHeightSelect.selectedIndex < 0) this.elLineHeightSelect.selectedIndex = 0;
		}
	},

	$ON_SET_LINEHEIGHT_FROM_SELECT_UI : function(){
		var nLineHeight = this.elLineHeightSelect.value;
		if(!nLineHeight) return;

		this.elLineHeightSelect.selectedIndex = 0;
		this.oApp.exec("SET_LINEHEIGHT", [nLineHeight]);
		this.oApp.exec("CHECK_STYLE_CHANGE", []);
	}
}).extend(xe.XE_LineHeight);
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations directly related to the color palette
 * @name hp_XE_ColorPalette.js
 */
 xe.XE_ColorPalette = $.Class({
	name : "XE_ColorPalette",
	rxRGBColorPattern : /rgb\((\d+), ?(\d+), ?(\d+)\)/i,

	$init : function(oAppContainer){
		this._assignHTMLObjects(oAppContainer);
	},

	_assignHTMLObjects : function(oAppContainer){
		this.elColorPaletteLayer = $("UL.xpress_xeditor_color_palette", oAppContainer).get(0);
	},

	$ON_MSG_APP_READY : function(){
		this.oApp.registerBrowserEvent(this.elColorPaletteLayer, "click", "EVENT_MOUSEUP_COLOR_PALETTE");
	},

	$ON_SHOW_COLOR_PALETTE : function(sCallbackCmd, oLayerContainer){
		this.sCallbackCmd = sCallbackCmd;
		this.oLayerContainer = oLayerContainer;

		this.oLayerContainer.insertBefore(this.elColorPaletteLayer, null);

		this.elColorPaletteLayer.style.display = "block";
	},

	$ON_HIDE_COLOR_PALETTE : function(){
		this.elColorPaletteLayer.style.display = "none";
	},

	$ON_COLOR_PALETTE_APPLY_COLOR : function(sColorCode){
		if(this.rxRGBColorPattern.test(sColorCode)){

			function dec2Hex(sDec){
				var sTmp = parseInt(sDec).toString(16);
				if(sTmp.length<2) sTmp = "0"+sTmp;
				return sTmp.toUpperCase();
			}

			var sR = dec2Hex(RegExp.$1);
			var sG = dec2Hex(RegExp.$2);
			var sB = dec2Hex(RegExp.$3);
			sColorCode = "#"+sR+sG+sB;
		}
		this.oApp.exec(this.sCallbackCmd, [sColorCode]);
	},

	$ON_EVENT_MOUSEUP_COLOR_PALETTE : function(oEvent){
		var elButton = oEvent.target;
		if(! elButton.style.backgroundColor) return;

		this.oApp.exec("COLOR_PALETTE_APPLY_COLOR", [elButton.style.backgroundColor]);
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations related to changing the font color
 * @name hp_XE_FontColor.js
 */
xe.XE_FontColor = $.Class({
	name : "XE_FontColor",
	rxColorPattern : /^#?[0-9a-fA-F]{6}$|^rgb\(\d+, ?\d+, ?\d+\)$/i,

	$init : function(elAppContainer){
		this._assignHTMLObjects(elAppContainer);
	},

	_assignHTMLObjects : function(elAppContainer){
		this.elDropdownLayer = $("DIV.xpress_xeditor_fontcolor_layer", elAppContainer).get(0);
	},

	$ON_MSG_APP_READY : function(){
		this.oApp.exec("REGISTER_UI_EVENT", ["fontColor", "click", "TOGGLE_FONTCOLOR_LAYER"]);
	},

	$ON_TOGGLE_FONTCOLOR_LAYER : function(){
		this.oApp.exec("TOGGLE_TOOLBAR_ACTIVE_LAYER", [this.elDropdownLayer, null, "SHOW_COLOR_PALETTE", ["APPLY_FONTCOLOR", this.elDropdownLayer]]);
	},

	$ON_APPLY_FONTCOLOR : function(sFontColor){
		if(!this.rxColorPattern.test(sFontColor)){
			alert(this.oApp.$MSG("XE_FontColor.invalidColorCode"));
			return;
		}

		this.oApp.exec("SET_WYSIWYG_STYLE", [{"color":sFontColor}]);

		this.oApp.exec("HIDE_ACTIVE_LAYER");
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of changing the background color
 * @name hp_XE_BGColor.js
 */
xe.XE_BGColor = $.Class({
	name : "XE_BGColor",
	rxColorPattern : /^#?[0-9a-fA-F]{6}$|^rgb\(\d+, ?\d+, ?\d+\)$/i,

	$init : function(elAppContainer){
		this._assignHTMLObjects(elAppContainer);
	},

	_assignHTMLObjects : function(elAppContainer){
		this.elDropdownLayer = $("DIV.xpress_xeditor_bgcolor_layer", elAppContainer).get(0);
	},

	$ON_MSG_APP_READY : function(){
		this.oApp.exec("REGISTER_UI_EVENT", ["bgColor", "click", "TOGGLE_BGCOLOR_LAYER"]);

		this.oApp.registerBrowserEvent(this.elDropdownLayer, "click", "EVENT_APPLY_BGCOLOR", []);
	},

	$ON_TOGGLE_BGCOLOR_LAYER : function(){
		this.oApp.exec("TOGGLE_TOOLBAR_ACTIVE_LAYER", [this.elDropdownLayer, null, "SHOW_COLOR_PALETTE", ["APPLY_BGCOLOR", this.elDropdownLayer]]);
	},

	$ON_EVENT_APPLY_BGCOLOR : function(weEvent){
		var elButton = weEvent.target;

		// Safari/Chrome/Opera may capture the event on Span
		if(elButton.tagName == "SPAN") elButton = elButton.parentNode;
		if(elButton.tagName != "BUTTON") return;

		var sBGColor, sFontColor;

		sBGColor = elButton.style.backgroundColor;
		sFontColor = elButton.style.color;

		this.oApp.exec("APPLY_BGCOLOR", [sBGColor, sFontColor]);
	},

	$ON_APPLY_BGCOLOR : function(sBGColor, sFontColor){
		if(!this.rxColorPattern.test(sBGColor)){
			alert(this.oApp.$MSG("XE_BGColor.invalidColorCode"));
			return;
		}

		var oStyle = {"backgroundColor": sBGColor}
		if(sFontColor) oStyle.color = sFontColor;

		this.oApp.exec("SET_WYSIWYG_STYLE", [oStyle]);

		this.oApp.exec("HIDE_ACTIVE_LAYER");
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations related to quote
 * @name hp_XE_Quote.js
 * @required XE_EditingArea_WYSIWYG
 */
xe.XE_Quote = $.Class({
	name : "XE_Quote",

	$init : function(elAppContainer){
		this._assignHTMLObjects(elAppContainer);
	},

	_assignHTMLObjects : function(elAppContainer){
		this.elDropdownLayer = $("DIV.xpress_xeditor_blockquote_layer", elAppContainer).get(0);
	},

	$ON_MSG_APP_READY: function(){
		this.oApp.exec("REGISTER_UI_EVENT", ["quote", "click", "TOGGLE_BLOCKQUOTE_LAYER"]);

		this.oApp.registerBrowserEvent(this.elDropdownLayer, "click", "EVENT_APPLY_SEDITOR_BLOCKQUOTE", []);
	},

	$ON_TOGGLE_BLOCKQUOTE_LAYER : function(){
		this.oApp.exec("TOGGLE_TOOLBAR_ACTIVE_LAYER", [this.elDropdownLayer]);
	},

	$ON_EVENT_APPLY_SEDITOR_BLOCKQUOTE : function(weEvent){
		var elButton = weEvent.target;
		if(elButton.tagName != "BUTTON") return;

		var sClass = elButton.parentNode.className;

		if(sClass != "q8")
			this._wrapBlock("BLOCKQUOTE", sClass);
		else
			this._unwrapBlock("BLOCKQUOTE");

		this.oApp.exec("HIDE_ACTIVE_LAYER", []);
	},

	_unwrapBlock : function(tag){
		var oSelection = this.oApp.getSelection();
		var oC = oSelection.commonAncestorContainer;

		while(oC && oC.tagName != tag) oC = oC.parentNode;
		if(!oC) return;

		while(oC.firstChild) oC.parentNode.insertBefore(oC.firstChild, oC);

		oC.parentNode.removeChild(oC);
	},

	_wrapBlock : function(tag, className){
		var oSelection = this.oApp.getSelection();
		var oLineInfo = oSelection.getLineInfo();
		var oStart = oLineInfo.oStart;
		var oEnd = oLineInfo.oEnd;

		var rxDontUseAsWhole = /BODY|TD|LI/i;

		var oStartNode, oEndNode;

		if(oStart.bParentBreak && !rxDontUseAsWhole.test(oStart.oLineBreaker.tagName)) oStartNode = oStart.oNode.parentNode;
		else oStartNode = oStart.oNode;

		if(oEnd.bParentBreak && !rxDontUseAsWhole.test(oEnd.oLineBreaker.tagName)) oEndNode = oEnd.oNode.parentNode;
		else oEndNode = oEnd.oNode;

		oSelection.setStartBefore(oStartNode);
		oSelection.setEndAfter(oEndNode);

		var oNode = this._expandToTableStart(oSelection, oEndNode);
		if(oNode){
			oEndNode = oNode;
			oSelection.setEndAfter(oNode);
		}

		oNode = this._expandToTableStart(oSelection, oStartNode);
		if(oNode){
			oStartNode = oNode;
			oSelection.setStartBefore(oNode);
		}

		oNode = oStartNode;
		var oC = oSelection.commonAncestorContainer;

		// find the insertion position for the formatting tag right beneath the common ancestor container
		while(oNode && oNode != oC && oNode.parentNode != oC) oNode = oNode.parentNode;

		oFormattingNode = oSelection._document.createElement(tag);
		if(className) oFormattingNode.className = className;

		if(oNode == oC){
			oC.insertBefore(oFormattingNode, oC.firstChild);
		}else{
			oC.insertBefore(oFormattingNode, oNode);
		}

		oSelection.setStartAfter(oFormattingNode);

		oSelection.setEndAfter(oEndNode);
		oSelection.surroundContents(oFormattingNode);

		var aNodes = oFormattingNode.childNodes;
		var oInsertionPoint;
		for(var i=aNodes.length-1; i>=0; i--){
			if(aNodes[i].nodeType == 3 || aNodes[i].tagName == "BR"){
				var oP = oSelection._document.createElement("P");
				oInsertionPoint = aNodes[i].nextSibling;
				while(i>=0 && aNodes[i] && (aNodes[i].nodeType == 3 || aNodes[i].tagName == "BR")){
					oP.insertBefore(aNodes[i--], oP.firstChild);
				}
				oFormattingNode.insertBefore(oP, oInsertionPoint);
				i++;
			}
		}

		if(oFormattingNode && oFormattingNode.parentNode){
			var oP = oSelection._document.createElement("P");
			oP.innerHTML = unescape("<br/>");
			oFormattingNode.parentNode.insertBefore(oP, oFormattingNode.nextSibling);
		}

		this.oApp.exec("RECORD_UNDO_ACTION", ["Block Quote"]);

		return oFormattingNode;
	},

	_expandToTableStart : function(oSelection, oNode){
		var oC = oSelection.commonAncestorContainer;
		var oResultNode = null;

		var bLastIteration = false;
		while(oNode && !bLastIteration){
			if(oNode == oC) bLastIteration = true;

			if(/TBODY|TFOOT|THEAD|TR/i.test(oNode.tagName)){
				oResultNode = this._getTableRoot(oNode);
				break;
			}
			oNode = oNode.parentNode;
		}

		return oResultNode;
	},

	_getTableRoot : function(oNode){
		while(oNode && oNode.tagName != "TABLE") oNode = oNode.parentNode;

		return oNode;
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations related to inserting special characters
 * @name hp_XE_SCharacter.js
 * @required XpressRangeManager
 */
xe.XE_SCharacter = $.Class({
	name : "XE_SCharacter",

	$init : function(oAppContainer){
		this.bIE = $.browser.msie;

		this._assignHTMLObjects(oAppContainer);

		this.charSet = [];
		this.charSet[0] = unescape('FF5B FF5D 3014 3015 3008 3009 300A 300B 300C 300D 300E 300F 3010 3011 2018 2019 201C 201D 3001 3002 %B7 2025 2026 %A7 203B 2606 2605 25CB 25CF 25CE 25C7 25C6 25A1 25A0 25B3 25B2 25BD 25BC 25C1 25C0 25B7 25B6 2664 2660 2661 2665 2667 2663 2299 25C8 25A3 25D0 25D1 2592 25A4 25A5 25A8 25A7 25A6 25A9 %B1 %D7 %F7 2260 2264 2265 221E 2234 %B0 2032 2033 2220 22A5 2312 2202 2261 2252 226A 226B 221A 223D 221D 2235 222B 222C 2208 220B 2286 2287 2282 2283 222A 2229 2227 2228 FFE2 21D2 21D4 2200 2203 %B4 FF5E 02C7 02D8 02DD 02DA 02D9 %B8 02DB %A1 %BF 02D0 222E 2211 220F 266D 2669 266A 266C 327F 2192 2190 2191 2193 2194 2195 2197 2199 2196 2198 321C 2116 33C7 2122 33C2 33D8 2121 2668 260F 260E 261C 261E %B6 2020 2021 %AE %AA %BA 2642 2640').replace(/(\S{4})/g, function(a){return "%u"+a}).split(' ');
		this.charSet[1] = unescape('%BD 2153 2154 %BC %BE 215B 215C 215D 215E %B9 %B2 %B3 2074 207F 2081 2082 2083 2084 2160 2161 2162 2163 2164 2165 2166 2167 2168 2169 2170 2171 2172 2173 2174 2175 2176 2177 2178 2179 FFE6 %24 FFE5 FFE1 20AC 2103 212B 2109 FFE0 %A4 2030 3395 3396 3397 2113 3398 33C4 33A3 33A4 33A5 33A6 3399 339A 339B 339C 339D 339E 339F 33A0 33A1 33A2 33CA 338D 338E 338F 33CF 3388 3389 33C8 33A7 33A8 33B0 33B1 33B2 33B3 33B4 33B5 33B6 33B7 33B8 33B9 3380 3381 3382 3383 3384 33BA 33BB 33BC 33BD 33BE 33BF 3390 3391 3392 3393 3394 2126 33C0 33C1 338A 338B 338C 33D6 33C5 33AD 33AE 33AF 33DB 33A9 33AA 33AB 33AC 33DD 33D0 33D3 33C3 33C9 33DC 33C6').replace(/(\S{4})/g, function(a){return "%u"+a}).split(' ');
		this.charSet[2] = unescape('3260 3261 3262 3263 3264 3265 3266 3267 3268 3269 326A 326B 326C 326D 326E 326F 3270 3271 3272 3273 3274 3275 3276 3277 3278 3279 327A 327B 24D0 24D1 24D2 24D3 24D4 24D5 24D6 24D7 24D8 24D9 24DA 24DB 24DC 24DD 24DE 24DF 24E0 24E1 24E2 24E3 24E4 24E5 24E6 24E7 24E8 24E9 2460 2461 2462 2463 2464 2465 2466 2467 2468 2469 246A 246B 246C 246D 246E 3200 3201 3202 3203 3204 3205 3206 3207 3208 3209 320A 320B 320C 320D 320E 320F 3210 3211 3212 3213 3214 3215 3216 3217 3218 3219 321A 321B 249C 249D 249E 249F 24A0 24A1 24A2 24A3 24A4 24A5 24A6 24A7 24A8 24A9 24AA 24AB 24AC 24AD 24AE 24AF 24B0 24B1 24B2 24B3 24B4 24B5 2474 2475 2476 2477 2478 2479 247A 247B 247C 247D 247E 247F 2480 2481 2482').replace(/(\S{4})/g, function(a){return "%u"+a}).split(' ');
		this.charSet[3] = unescape('3131 3132 3133 3134 3135 3136 3137 3138 3139 313A 313B 313C 313D 313E 313F 3140 3141 3142 3143 3144 3145 3146 3147 3148 3149 314A 314B 314C 314D 314E 314F 3150 3151 3152 3153 3154 3155 3156 3157 3158 3159 315A 315B 315C 315D 315E 315F 3160 3161 3162 3163 3165 3166 3167 3168 3169 316A 316B 316C 316D 316E 316F 3170 3171 3172 3173 3174 3175 3176 3177 3178 3179 317A 317B 317C 317D 317E 317F 3180 3181 3182 3183 3184 3185 3186 3187 3188 3189 318A 318B 318C 318D 318E').replace(/(\S{4})/g, function(a){return "%u"+a}).split(' ');
		this.charSet[4] = unescape('0391 0392 0393 0394 0395 0396 0397 0398 0399 039A 039B 039C 039D 039E 039F 03A0 03A1 03A3 03A4 03A5 03A6 03A7 03A8 03A9 03B1 03B2 03B3 03B4 03B5 03B6 03B7 03B8 03B9 03BA 03BB 03BC 03BD 03BE 03BF 03C0 03C1 03C3 03C4 03C5 03C6 03C7 03C8 03C9 %C6 %D0 0126 0132 013F 0141 %D8 0152 %DE 0166 014A %E6 0111 %F0 0127 I 0133 0138 0140 0142 0142 0153 %DF %FE 0167 014B 0149 0411 0413 0414 0401 0416 0417 0418 0419 041B 041F 0426 0427 0428 0429 042A 042B 042C 042D 042E 042F 0431 0432 0433 0434 0451 0436 0437 0438 0439 043B 043F 0444 0446 0447 0448 0449 044A 044B 044C 044D 044E 044F').replace(/(\S{4})/g, function(a){return "%u"+a}).split(' ');
		this.charSet[5] = unescape('3041 3042 3043 3044 3045 3046 3047 3048 3049 304A 304B 304C 304D 304E 304F 3050 3051 3052 3053 3054 3055 3056 3057 3058 3059 305A 305B 305C 305D 305E 305F 3060 3061 3062 3063 3064 3065 3066 3067 3068 3069 306A 306B 306C 306D 306E 306F 3070 3071 3072 3073 3074 3075 3076 3077 3078 3079 307A 307B 307C 307D 307E 307F 3080 3081 3082 3083 3084 3085 3086 3087 3088 3089 308A 308B 308C 308D 308E 308F 3090 3091 3092 3093 30A1 30A2 30A3 30A4 30A5 30A6 30A7 30A8 30A9 30AA 30AB 30AC 30AD 30AE 30AF 30B0 30B1 30B2 30B3 30B4 30B5 30B6 30B7 30B8 30B9 30BA 30BB 30BC 30BD 30BE 30BF 30C0 30C1 30C2 30C3 30C4 30C5 30C6 30C7 30C8 30C9 30CA 30CB 30CC 30CD 30CE 30CF 30D0 30D1 30D2 30D3 30D4 30D5 30D6 30D7 30D8 30D9 30DA 30DB 30DC 30DD 30DE 30DF 30E0 30E1 30E2 30E3 30E4 30E5 30E6 30E7 30E8 30E9 30EA 30EB 30EC 30ED 30EE 30EF 30F0 30F1 30F2 30F3 30F4 30F5 30F6').replace(/(\S{4})/g, function(a){return "%u"+a}).split(' ');
	},

	_assignHTMLObjects : function(oAppContainer){
		oAppContainer = $.$(oAppContainer) || document;

		this.elDropdownLayer = $("DIV.xpress_xeditor_sCharacter_layer", oAppContainer).get(0);

		this.oTextField = $("INPUT", this.elDropdownLayer).get(0);
		this.oInsertButton = $("+ BUTTON", this.oTextField).get(0);
		this.aCloseButton = $("BUTTON.close", this.elDropdownLayer).get();
		this.aSCharList = $(".list", this.elDropdownLayer).get();
		var oLabelUL = $(">UL", this.elDropdownLayer).get(0);
		this.aLabelA = $("A", oLabelUL).get();
	},

	$ON_MSG_APP_READY : function(){
		var funcInsert = $.fnBind(this.oApp.exec, this.oApp, "INSERT_SCHARACTERS", [this.oTextField.value]);
		$(this.oInsertButton).click(funcInsert, this);

		this.oApp.exec("SET_SCHARACTER_LIST", [this.charSet]);

		for(var i=0; i<this.aLabelA.length; i++){
			var func = $.fnBind(this.oApp.exec, this.oApp, "CHANGE_SCHARACTER_SET", [i]);
			$(this.aLabelA[i]).mousedown(func);

			this._stopBrowserEvent(this.aLabelA[i], "click");
		}

		for(var i=0; i<this.aCloseButton.length; i++){
			this.oApp.registerBrowserEvent(this.aCloseButton[i], "click", "HIDE_ACTIVE_LAYER", []);
		}

		this.oApp.registerBrowserEvent(this.elDropdownLayer, "click", "EVENT_SCHARACTER_CLICKED", []);

		this.oApp.exec("REGISTER_UI_EVENT", ["sCharacter", "click", "TOGGLE_SCHARACTER_LAYER"]);
	},

	$ON_TOGGLE_SCHARACTER_LAYER : function(){
		this.oTextField.value = "";
		this.oSelection = this.oApp.getSelection();

		this.oApp.exec("TOGGLE_TOOLBAR_ACTIVE_LAYER", [this.elDropdownLayer]);
	},

	$ON_EVENT_SCHARACTER_CLICKED : function(weEvent){
		var elButton = weEvent.target;
		if(elButton.tagName != "BUTTON") return;
		if(elButton.parentNode.tagName != "LI") return;

		var sChar = elButton.firstChild.innerHTML;

		this.oApp.exec("SELECT_SCHARACTER", [sChar]);
	},

	$ON_SELECT_SCHARACTER : function(schar){
		this.oTextField.value += schar;

		if(this.oTextField.createTextRange){
			var oTextRange = this.oTextField.createTextRange();
			oTextRange.collapse(false);
			oTextRange.select();
		}else{
			if(this.oTextField.selectionEnd){
				this.oTextField.selectionEnd = this.oTextField.value.length;
				this.oTextField.focus();
			}
		}
	},

	$ON_INSERT_SCHARACTERS : function(){
		this.oSelection.pasteHTML(this.oTextField.value);
		this.oApp.exec("HIDE_ACTIVE_LAYER", []);
	},

	$ON_CHANGE_SCHARACTER_SET : function(nSCharSet){
		for(var i=0; i<this.aSCharList.length; i++){
			if(this.aSCharList[i].style.display == "block"){
				if(i == nSCharSet) return;

				$(this.aLabelA[i]).removeClass("on");
				this.aSCharList[i].style.display = "none";
			}
		}

		this._drawSCharList(nSCharSet);
		$(this.aLabelA[nSCharSet]).addClass("on");
		this.aSCharList[nSCharSet].style.display = "block";
	},

	$ON_SET_SCHARACTER_LIST : function(charSet){
		this.charSet = charSet;
		this.bSCharSetDrawn = new Array(this.charSet.length);
		this._drawSCharList(0);
	},

	_drawSCharList : function(i){
		if(this.bSCharSetDrawn[i]) return;
		this.bSCharSetDrawn[i] = true;

		var len = this.charSet[i].length;
		var aLI = new Array(len);

		this.aSCharList[i].innerHTML = '';

		var button, span;
		for(var ii=0; ii<len; ii++){
			aLI[ii] = document.createElement("LI");

			aLI[ii].innerHTML = '<button type="button"><span>'+unescape(this.charSet[i][ii])+'</span></button>';
			this.aSCharList[i].appendChild(aLI[ii]);
		}

		// enable this after Jindo framework is updated
//		this.oApp.exec("ATTACH_HOVER_EVENTS", [$(">LI>BUTTON", this.aSCharList[i])]).get();
	},

	_stopBrowserEvent : function(obj, sEvent){
		$(obj).bind(sEvent, function(e){e.stopPropagation();e.preventDefault();} )
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations related to Undo/Redo
 * @name hp_XE_UndoRedo.js
 * @required XE_EditingAreaManager, XpressRangeManager
 */
xe.XE_UndoRedo = $.Class({
	name : "XE_UndoRedo",
	actionHistory : null,
	// this may also be called, lastAdded/lastRestored
	oCurStateIdx : null,
	iMinimumSizeChange : 10,
	sBlankContentsForFF : "<br>",

	$init : function(){
		this.aUndoHistory = [];
		this.oCurStateIdx = {nIdx: 0, nStep: 0};
	},

	$PRECONDITION : function(sCmd){
		if(sCmd.match(/_DO_RECORD_UNDO_HISTORY_AT$/)) return true;

		try{
			if(this.oApp.getEditingMode() != "WYSIWYG") return false;
		}catch(e){
			return false;
		}

		return true;
	},

	$BEFORE_MSG_APP_READY : function(){
		this.oApp.exec("DO_RECORD_UNDO_HISTORY_AT", [this.oCurStateIdx, "", "", null]);
	},

	$ON_MSG_APP_READY : function(){
		this.bFF = $.browser.mozilla;

		this.oApp.exec("ADD_APP_PROPERTY", ["getUndoHistory", $.fnBind(this.getUndoHistory, this)]);
		this.oApp.exec("ADD_APP_PROPERTY", ["getUndoStateIdx", $.fnBind(this.getUndoStateIdx, this)]);

		this.oApp.exec("REGISTER_UI_EVENT", ["undo", "click", "UNDO"]);
		this.oApp.exec("REGISTER_UI_EVENT", ["redo", "click", "REDO"]);

		this.oApp.exec("REGISTER_HOTKEY", ["ctrl+z", "UNDO"]);
		this.oApp.exec("REGISTER_HOTKEY", ["ctrl+y", "REDO"]);
	},

	$ON_UNDO : function(){
		var oTmpStateIdx = {};
		this.oApp.exec("DO_RECORD_UNDO_HISTORY", ["KEYPRESS", false, false, 1]);
		if(this.oCurStateIdx.nIdx == 0) return;

		if(this.oCurStateIdx.nStep > 0){
			this.oCurStateIdx.nStep--;
		}else{
			var oTmpHistory = this.aUndoHistory[this.oCurStateIdx.nIdx];

			this.oCurStateIdx.nIdx--;

			if(oTmpHistory.nTotalSteps>1){
				this.oCurStateIdx.nStep = 0;
			}else{
				oTmpHistory = this.aUndoHistory[this.oCurStateIdx.nIdx];
				this.oCurStateIdx.nStep = oTmpHistory.nTotalSteps-1;
			}
		}

		this.oApp.exec("RESTORE_UNDO_HISTORY", [this.oCurStateIdx.nIdx, this.oCurStateIdx.nStep]);

		this.oApp.exec("CHECK_STYLE_CHANGE", []);
	},


	$ON_REDO : function(){
		if(this.oCurStateIdx.nIdx >= this.aUndoHistory.length) return;

		var oCurHistory = this.aUndoHistory[this.oCurStateIdx.nIdx];
		if(this.oCurStateIdx.nIdx == this.aUndoHistory.length-1 && this.oCurStateIdx.nStep >= oCurHistory.nTotalSteps-1) return;

		if(this.oCurStateIdx.nStep < oCurHistory.nTotalSteps-1){
			this.oCurStateIdx.nStep++;
		}else{
			this.oCurStateIdx.nIdx++;
			oCurHistory = this.aUndoHistory[this.oCurStateIdx.nIdx];
			this.oCurStateIdx.nStep = oCurHistory.nTotalSteps-1;
		}

		this.oApp.exec("RESTORE_UNDO_HISTORY", [this.oCurStateIdx.nIdx, this.oCurStateIdx.nStep]);

		this.oApp.exec("CHECK_STYLE_CHANGE", []);
	},

	$ON_RECORD_UNDO_ACTION : function(sAction){
		this.oApp.exec("DO_RECORD_UNDO_HISTORY", [sAction]);
	},

	$ON_RECORD_UNDO_BEFORE_ACTION : function(sAction){
		this.oApp.exec("DO_RECORD_UNDO_HISTORY", [sAction, true, true]);
	},

	$ON_RECORD_UNDO_AFTER_ACTION : function(sAction){
		this.oApp.exec("DO_RECORD_UNDO_HISTORY", [sAction, true, false]);
	},

	$ON_RESTORE_UNDO_HISTORY : function(nUndoIdx, nUndoStateStep){
		this.oCurStateIdx.nIdx = nUndoIdx;
		this.oCurStateIdx.nStep = nUndoStateStep;

		var oCurHistory = this.aUndoHistory[this.oCurStateIdx.nIdx];
		var sContent = oCurHistory.sContent[this.oCurStateIdx.nStep];
		var oBookmark = oCurHistory.oBookmark[this.oCurStateIdx.nStep];

		this.oApp.setIR(sContent, true);

		// setting the innerHTML may change the internal DOM structure, so save the value again.
		var sCurContent = this.oApp.getIR();
		if(this.bFF && sCurContent == this.sBlankContentsForFF){
			sCurContent = "";
		}
		oCurHistory.sContent[this.oCurStateIdx.nStep] = sCurContent;

		var oSelection = this.oApp.getEmptySelection();
		if(oSelection.selectionLoaded){
			if(oBookmark){
				oSelection.moveToXPathBookmark(oBookmark);
			}else{
				oSelection = this.oApp.getEmptySelection();
			}

			oSelection.select();
		}
	},

	$ON_DO_RECORD_UNDO_HISTORY : function(sAction, bTwoStepAction, bBeforeAction, nForceAddUnlessEqual){
		bTwoStepAction = bTwoStepAction || false;
		bBeforeAction = bBeforeAction || false;
		nForceAddUnlessEqual = nForceAddUnlessEqual || 0;

		// if we're in the middle of some action history, remove everything after current idx if any "little" change is made
		if(!(this.oCurStateIdx.nIdx == this.aUndoHistory.length-1)) nForceAddUnlessEqual = 1;

		var oCurHistory = this.aUndoHistory[this.oCurStateIdx.nIdx];

		var sCurContent = this.oApp.getIR();
		var sHistoryContent = oCurHistory.sContent[this.oCurStateIdx.nStep];

		if(this.bFF && sCurContent == this.sBlankContentsForFF){
			sCurContent = "";
		}

		// every TwoStepAction needs to be recorded
		if(!bTwoStepAction){
			switch(nForceAddUnlessEqual){
				case 0:
					if(Math.abs(sHistoryContent.length - sCurContent.length)<this.iMinimumSizeChange) return;
					break;

				case 1:
					if(sHistoryContent == sCurContent) return;
					break;

				// write at all times
				case 2:
					break;
			}
		}

		var oSelection = this.oApp.getSelection();

		var oBookmark=null;
		if(oSelection.selectionLoaded){
			oBookmark = oSelection.getXPathBookmark();
		}

		var oInsertionIdx = {nIdx:this.oCurStateIdx.nIdx, nStep:this.oCurStateIdx.nStep};
		if(bTwoStepAction){
			if(bBeforeAction){
				oInsertionIdx.nStep = 0;
			}else{
				oInsertionIdx.nStep = 1;
			}
		}else{
			oInsertionIdx.nStep = 0;
		}

		if(oInsertionIdx.nStep == 0 && this.oCurStateIdx.nStep == oCurHistory.nTotalSteps-1){
			oInsertionIdx.nIdx = this.oCurStateIdx.nIdx+1;
		}

		this.oApp.exec("DO_RECORD_UNDO_HISTORY_AT", [oInsertionIdx, sAction, sCurContent, oBookmark]);
	},

	$ON_DO_RECORD_UNDO_HISTORY_AT : function(oInsertionIdx, sAction, sContent, oBookmark){
		if(oInsertionIdx.nStep != 0){
			this.aUndoHistory[oInsertionIdx.nIdx].nTotalSteps = oInsertionIdx.nStep+1;
			this.aUndoHistory[oInsertionIdx.nIdx].sContent[oInsertionIdx.nStep] = sContent;
			this.aUndoHistory[oInsertionIdx.nIdx].oBookmark[oInsertionIdx.nStep] = oBookmark;
		}else{
			var oNewHistory = {sAction:sAction, nTotalSteps: 1};
			oNewHistory.sContent = [];
			oNewHistory.sContent[0] = sContent;

			oNewHistory.oBookmark = [];
			oNewHistory.oBookmark[0] = oBookmark;
			this.aUndoHistory.splice(oInsertionIdx.nIdx, this.aUndoHistory.length - oInsertionIdx.nIdx, oNewHistory);
		}

		this.oCurStateIdx.nIdx = oInsertionIdx.nIdx;
		this.oCurStateIdx.nStep = oInsertionIdx.nStep;
	},

	_getUndoHistory : function(){
		return this.aUndoHistory;
	},

	_getUndoStateIdx : function(){
		return this.oCurStateIdx;
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations related to Find/Replace
 * @name hp_XE_FindReplacePlugin.js
 */
xe.XE_FindReplacePlugin = $.Class({
	name : "XE_FindReplacePlugin",
	oEditingWindow : null,
	oFindReplace :  null,
	oUILayer : null,
	bFindMode : true,

	$init : function(oAppContainer){
		this._assignHTMLObjects(oAppContainer);
	},

	_assignHTMLObjects : function(oAppContainer){
		oAppContainer = $.$(oAppContainer) || document;

		this.oEditingWindow = $("IFRAME", oAppContainer).get(0);
		this.oUILayer = $("DIV.xpress_xeditor_findAndReplace_layer", oAppContainer).get(0);

		var oTmp = $("LI", this.oUILayer).get();

		this.oFindTab = oTmp[0];
		this.oReplaceTab = oTmp[1];

		oTmp = $(".container > .bx", this.oUILayer).get();

		this.oFindInputSet = oTmp[0];
		this.oReplaceInputSet = oTmp[1];

		this.oFindInput_Keyword = $("INPUT", this.oFindInputSet).get(0);

		oTmp = $("INPUT", this.oReplaceInputSet).get();
		this.oReplaceInput_Original = oTmp[0];
		this.oReplaceInput_Replacement = oTmp[1];

		this.oFindNextButton = $("BUTTON.find_next", this.oUILayer).get(0);
		this.oCancelButton = $("BUTTON.cancel", this.oUILayer).get(0);

		this.oReplaceButton = $("BUTTON.replace", this.oUILayer).get(0);
		this.oReplaceAllButton = $("BUTTON.replace_all", this.oUILayer).get(0);

		this.aCloseButtons = $("BUTTON.close", this.oUILayer).get();
		this.aCloseButtons[this.aCloseButtons.length] = this.oCancelButton;
	},

	$ON_MSG_APP_READY : function(){
		// the right document will be available only when the src is completely loaded
		if(this.oEditingWindow && this.oEditingWindow.tagName == "IFRAME")
			this.oEditingWindow = this.oEditingWindow.contentWindow;

		this.oFindReplace = new xe.FindReplace(this.oEditingWindow);
		if(!this.oFindReplace.bBrowserSupported){
			this.oApp.exec("DISABLE_UI", ["find_replace"]);
			return;
		}

		for(var i=0; i<this.aCloseButtons.length; i++){
			var func = $.fnBind(this.oApp.exec, this.oApp, "HIDE_DIALOG_LAYER", [this.oUILayer]);
			$(this.aCloseButtons[i]).bind("click", func);
		}

		$(this.oFindTab).bind("mousedown", $.fnBind(this.oApp.exec, this.oApp, "SHOW_FIND", []));
		$(this.oReplaceTab).bind("mousedown", $.fnBind(this.oApp.exec, this.oApp, "SHOW_REPLACE", []));

		$(this.oFindNextButton).bind("click", $.fnBind(this.oApp.exec, this.oApp, "FIND", []));
		$(this.oReplaceButton).bind("click", $.fnBind(this.oApp.exec, this.oApp, "REPLACE", []));
		$(this.oReplaceAllButton).bind("click", $.fnBind(this.oApp.exec, this.oApp, "REPLACE_ALL", []));

		this.oApp.exec("REGISTER_UI_EVENT", ["findAndReplace", "click", "SHOW_FIND_REPLACE_LAYER"]);
	},

	$ON_SHOW_ACTIVE_LAYER : function(){
		this.oApp.exec("HIDE_DIALOG_LAYER", [this.oUILayer]);
	},

	$ON_SHOW_FIND_REPLACE_LAYER : function(){
		this.oApp.exec("SHOW_DIALOG_LAYER", [this.oUILayer]);
		this.oApp.exec("POSITION_TOOLBAR_LAYER", [this.oUILayer]);
		this.oApp.exec("HIDE_CURRENT_ACTIVE_LAYER", []);
	},

	$ON_SHOW_FIND : function(){
		this.bFindMode = true;

		$(this.oFindTab).addClass("on");
		$(this.oReplaceTab).removeClass("on");

		$(this.oFindNextButton).removeClass("normal");
		$(this.oFindNextButton).addClass("strong");

		this.oFindInputSet.style.display = "block";
		this.oReplaceInputSet.style.display = "none";

		this.oReplaceButton.style.display = "none";
		this.oReplaceAllButton.style.display = "none";

		$(this.oUILayer).removeClass("replace");
		$(this.oUILayer).addClass("find");

		this.oFindInput_Keyword.value = this.oReplaceInput_Original.value;
	},

	$ON_SHOW_REPLACE : function(){
		this.bFindMode = false;

		$(this.oFindTab).removeClass("on");
		$(this.oReplaceTab).addClass("on");

		$(this.oFindNextButton).removeClass("strong");
		$(this.oFindNextButton).addClass("normal");

		this.oFindInputSet.style.display = "none";
		this.oReplaceInputSet.style.display = "block";

		this.oReplaceButton.style.display = "inline";
		this.oReplaceAllButton.style.display = "inline";

		$(this.oUILayer).removeClass("find");
		$(this.oUILayer).addClass("replace");

		this.oReplaceInput_Original.value = this.oFindInput_Keyword.value;
	},

	$ON_FIND : function(){
		var sKeyword;
		if(this.bFindMode)
			sKeyword = this.oFindInput_Keyword.value;
		else
			sKeyword = this.oReplaceInput_Original.value;

		switch(this.oFindReplace.find(sKeyword, false)){
			case 1:
				alert(this.oApp.$MSG("XE_FindReplace.keywordNotFound"));
				break;
			case 2:
				alert(this.oApp.$MSG("XE_FindReplace.keywordMissing"));
				break;
		}
	},

	$ON_REPLACE : function(){
		var sOriginal = this.oReplaceInput_Original.value;
		var sReplacement = this.oReplaceInput_Replacement.value;

		this.oApp.exec("RECORD_UNDO_BEFORE_ACTION", ["REPLACE"]);
		var iReplaceResult = this.oFindReplace.replace(sOriginal, sReplacement, false);
		this.oApp.exec("RECORD_UNDO_AFTER_ACTION", ["REPLACE"]);

		switch(iReplaceResult){
			case 1:
			case 3:
				alert(this.oApp.$MSG("XE_FindReplace.keywordNotFound"));
				break;
			case 4:
				alert(this.oApp.$MSG("XE_FindReplace.keywordMissing"));
				break;
		}
	},

	$ON_REPLACE_ALL : function(){
		var sOriginal = this.oReplaceInput_Original.value;
		var sReplacement = this.oReplaceInput_Replacement.value;

		this.oApp.exec("RECORD_UNDO_BEFORE_ACTION", ["REPLACE ALL"]);
		var iReplaceAllResult = this.oFindReplace.replaceAll(sOriginal, sReplacement, false);
		this.oApp.exec("RECORD_UNDO_AFTER_ACTION", ["REPLACE ALL"]);

		if(iReplaceAllResult<0){
			alert(this.oApp.$MSG("XE_FindReplace.keywordMissing"));
		}else{
			alert(this.oApp.$MSG("XE_FindReplace.replaceAllResultP1")+iReplaceAllResult+this.oApp.$MSG("XE_FindReplace.replaceAllResultP2"));
		}
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations related to hyperlink
 * @name hp_XE_Hyperlink.js
 */
xe.XE_Hyperlink = $.Class({
	name : "XE_Hyperlink",
	sATagMarker : "HTTP://HUSKY_TMP.MARKER/",

	$init : function(elAppContainer){
		this._assignHTMLObjects(elAppContainer);
		this.sRXATagMarker = this.sATagMarker.replace(/\//g, "\\/").replace(/\./g, "\\.");
	},

	_assignHTMLObjects : function(elAppContainer){
		this.oHyperlinkLayer = $("DIV.xpress_xeditor_hyperlink_layer", elAppContainer).get(0);
		this.oLinkInput  = $("INPUT[type=text]", this.oHyperlinkLayer).get(0);
		this.oBtnConfirm = $("BUTTON.confirm", this.oHyperlinkLayer).get(0);
		this.oBtnCancel  = $("BUTTON.cancel", this.oHyperlinkLayer).get(0);
		this.oCbNewWin   = $("INPUT[type=checkbox]", this.oHyperlinkLayer).get(0);
	},

	$ON_MSG_APP_READY : function(){
		this.oApp.exec("REGISTER_HOTKEY", ["ctrl+k", "XE_TOGGLE_HYPERLINK_LAYER", []]);

		this.oApp.registerBrowserEvent(this.oBtnConfirm, "mousedown", "XE_APPLY_HYPERLINK");
		this.oApp.registerBrowserEvent(this.oBtnCancel, "mousedown", "HIDE_ACTIVE_LAYER");
		this.oApp.registerBrowserEvent(this.oLinkInput, "keydown", "EVENT_XE_HYPERLINK_KEYDOWN");
		//this.oApp.registerBrowserEvent(this.oLinkInput

		this.oApp.exec("REGISTER_UI_EVENT", ["hyperlink", "click", "XE_TOGGLE_HYPERLINK_LAYER"]);
	},

	$ON_XE_TOGGLE_HYPERLINK_LAYER : function(){
		// hotkey may close the layer right away so delay here
		this.oApp.delayedExec("TOGGLE_TOOLBAR_ACTIVE_LAYER", [this.oHyperlinkLayer, null, "XE_RESET_HYPERLINK_LAYER", []], 0);
	},

	$ON_XE_RESET_HYPERLINK_LAYER : function(){
		this.oApp.exec("FOCUS", []);
		this.oSelection = this.oApp.getSelection();
		var oAnchor = this.oSelection.findAncestorByTagName("A");
		this.oCbNewWin.checked = false;
		if(oAnchor){
			this.oSelection.selectNode(oAnchor);
			this.oSelection.select();

			var sTarget = oAnchor.target;
			if(sTarget && sTarget == "_blank") this.oCbNewWin.checked = true;

			this.oLinkInput.value = oAnchor.href?oAnchor.href:"http://";
		}else{
			this.oLinkInput.value = "http://";
		}

		this.oLinkInput.focus();
		this.oLinkInput.value = this.oLinkInput.value;
	},

	$ON_XE_APPLY_HYPERLINK : function(){
		var sURL = this.oLinkInput.value;

		this.oApp.exec("FOCUS", []);
		this.oSelection = this.oApp.getSelection();

		//if(this._validateURL(sURL)){
		var sTarget = "";
		if(this.oCbNewWin.checked)
			sTarget = "_blank";
		else
			sTarget = "_self";

		if(this.oSelection.collapsed){
			var str = "<a href='" + sURL + "' target="+sTarget+">" + sURL + "</a>";
			this.oSelection.pasteHTML(str);
		}else{
			var nSession = Math.ceil(Math.random()*10000);
			var arg = ( sURL == "" ? ["unlink"] : ["createLink", false, this.sATagMarker+nSession+sURL] );
			this.oApp.exec("EXECCOMMAND", arg);

			this.oSelection.setFromSelection();

			var oDoc = this.oApp.getWYSIWYGDocument();
			var aATags = oDoc.body.getElementsByTagName("A");
			var nLen = aATags.length;
			var rxMarker = new RegExp(this.sRXATagMarker+nSession, "i");
			var elATag;
			for(var i=0; i<nLen; i++){
				elATag = aATags[i];
				if(elATag.href && elATag.href.match(rxMarker)){
					elATag.href = elATag.href.replace(rxMarker, "");
					elATag.target = sTarget;
				}
			}
		}
		this.oApp.exec("HIDE_ACTIVE_LAYER");

		setTimeout($.fnBind(function(){this.oSelection.select()}, this), 0);
		//}else{
			//alert(this.oApp.$MSG("XE_Hyperlink.invalidURL"));
			//this.oLinkInput.focus();
		//}
	},

	_validateURL : function(sURL){
		return /^(http|https|ftp|mailto):(?:\/\/)?((\w|-)+(?:[\.:@](\w|-))+)(?:\/|@)?([^"\?]*?)(?:\?([^\?"]*?))?$/.test(sURL);
	},

	$ON_EVENT_XE_HYPERLINK_KEYDOWN : function(oEvent){
		if (oEvent.keyCode == 13){
			this.oApp.exec("XE_APPLY_HYPERLINK");
			oEvent.preventDefault(); oEvent.stopPropagation();
		}
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations related to table creation
 * @name hp_XE_Table.js
 */
xe.XE_Table = $.Class({
	name : "XE_Table",
	iMinRows : 1,
	iMaxRows : 20,

	iMinColumns : 1,
	iMaxColumns : 10,

	iMinBorderWidth : 1,
	iMaxBorderWidth : 10,

	oSelection : null,

	$init : function(oAppContainer){
		this._assignHTMLObjects(oAppContainer);
	},

	_assignHTMLObjects : function(oAppContainer){
		var tmp = null;

		this.elDropdownLayer = $("DIV.xpress_xeditor_table_layer", oAppContainer).get(0);
		this.welDropdownLayer = $(this.elDropdownLayer);

		tmp = $("INPUT", this.elDropdownLayer).get();
		this.oRowInput = tmp[0];
		this.oColumnInput = tmp[1];
		this.oBorderWidthInput = tmp[2];
		this.oBorderColorInput = tmp[3];
		this.oBGColorInput = tmp[4];

		tmp = $("BUTTON", this.elDropdownLayer).get();
		this.oButton_AddRow = tmp[0];
		this.oButton_RemoveRow = tmp[1];
		this.oButton_AddColumn = tmp[2];
		this.oButton_RemoveColumn = tmp[3];
		this.oButton_IncBorderWidth = tmp[4];
		this.oButton_DecBorderWidth = tmp[5];
		this.oButton_BorderColorPreview = tmp[6];
		this.oButton_BorderColor = tmp[7];
		this.oButton_BGColorPreview = tmp[8];
		this.oButton_BGColor = tmp[9];
		this.oButton_Insert = tmp[10];
		this.oButton_Cancel = tmp[11];

		this.oSampleTable = $("TABLE", this.elDropdownLayer).get(0);
	},

	$ON_MSG_APP_READY : function(){
		this.oApp.exec("REGISTER_UI_EVENT", ["table", "click", "ST_TOGGLE_TOOLBAR_LAYER"]);

		this.oApp.registerBrowserEvent(this.oRowInput, "change", "ST_SET_ROW_NUM", [null, 0]);
		this.oApp.registerBrowserEvent(this.oColumnInput, "change", "ST_SET_COLUMN_NUM", [null, 0]);
		this.oApp.registerBrowserEvent(this.oBorderWidthInput, "change", "ST_SET_BORDER_WIDTH", [null, 0]);

		this.oApp.registerBrowserEvent(this.oButton_AddRow, "click", "ST_ADD_ROW");
		this.oApp.registerBrowserEvent(this.oButton_RemoveRow, "click", "ST_REMOVE_ROW");
		this.oApp.registerBrowserEvent(this.oButton_AddColumn, "click", "ST_ADD_COLUMN");
		this.oApp.registerBrowserEvent(this.oButton_RemoveColumn, "click", "ST_REMOVE_COLUMN");

		this.oApp.registerBrowserEvent(this.oButton_IncBorderWidth, "click", "ST_INC_BORDER_WIDTH");
		this.oApp.registerBrowserEvent(this.oButton_DecBorderWidth, "click", "ST_DEC_BORDER_WIDTH");

		this.oApp.registerBrowserEvent(this.oButton_BorderColorPreview, "click", "ST_TOGGLE_BORDER_COLOR_LAYER");
		this.oApp.registerBrowserEvent(this.oButton_BGColorPreview, "click", "ST_TOGGLE_BGCOLOR_LAYER");

		this.oApp.registerBrowserEvent(this.oButton_BorderColor, "click", "ST_TOGGLE_BORDER_COLOR_LAYER");
		this.oApp.registerBrowserEvent(this.oButton_BGColor, "click", "ST_TOGGLE_BGCOLOR_LAYER");

		this.oApp.registerBrowserEvent(this.oButton_Insert, "click", "ST_INSERT_TABLE");
		this.oApp.registerBrowserEvent(this.oButton_Cancel, "click", "ST_CLOSE");

		this.oApp.exec("ST_SET_BORDER_COLOR", ["#CCCCCC"]);
		this.oApp.exec("ST_SET_BGCOLOR", ["#FFFFFF"]);
	},

	$ON_ST_TOGGLE_TOOLBAR_LAYER : function(){
		this.oApp.exec("RECORD_UNDO_ACTION_FORCED", ["KEYPRESS"]);

		this._showNewTable();
		this.oApp.exec("TOGGLE_TOOLBAR_ACTIVE_LAYER", [this.elDropdownLayer]);
	},

	$ON_ST_ADD_ROW : function(){
		this.oApp.exec("ST_SET_ROW_NUM", [null, 1]);
	},

	$ON_ST_REMOVE_ROW : function(){
		this.oApp.exec("ST_SET_ROW_NUM", [null, -1]);
	},

	$ON_ST_ADD_COLUMN : function(){
		this.oApp.exec("ST_SET_COLUMN_NUM", [null, 1]);
	},

	$ON_ST_REMOVE_COLUMN : function(){
		this.oApp.exec("ST_SET_COLUMN_NUM", [null, -1]);
	},

	$ON_ST_SET_ROW_NUM : function(iRows, iRowDiff){
		iRows = iRows || parseInt(this.oRowInput.value);
		iRowDiff = iRowDiff || 0;

		iRows += iRowDiff;

		if(iRows < this.iMinRows) iRows = this.iMinRows;
		if(iRows > this.iMaxRows) iRows = this.iMaxRows;

		this.oRowInput.value = iRows;
		this._showNewTable();
	},

	$ON_ST_SET_COLUMN_NUM : function(iColumns, iColumnDiff){
		iColumns = iColumns || parseInt(this.oColumnInput.value);
		iColumnDiff = iColumnDiff || 0;

		iColumns += iColumnDiff;

		if(iColumns < this.iMinColumns) iColumns = this.iMinColumns;
		if(iColumns > this.iMaxColumns) iColumns = this.iMaxColumns;

		this.oColumnInput.value = iColumns;
		this._showNewTable();
	},

	$ON_ST_INSERT_TABLE : function(){
		var sTable = this._getTableString();

		this.oApp.exec("PASTE_HTML", [sTable]);

		this.oApp.exec("ST_CLOSE", []);
	},

	$ON_ST_CLOSE : function(){
		this.oApp.exec("HIDE_ACTIVE_LAYER", []);
	},

	$ON_ST_SET_BORDER_WIDTH : function(iBorderWidth, iBorderWidthDiff){
		iBorderWidth = iBorderWidth || parseInt(this.oBorderWidthInput.value);
		iBorderWidthDiff = iBorderWidthDiff || 0;

		iBorderWidth += iBorderWidthDiff;

		if(iBorderWidth < this.iMinBorderWidth) iBorderWidth = this.iMinBorderWidth;
		if(iBorderWidth > this.iMaxBorderWidth) iBorderWidth = this.iMaxBorderWidth;

		this.oBorderWidthInput.value = iBorderWidth;
		this._showNewTable();
	},

	$ON_ST_INC_BORDER_WIDTH : function(){
		this.oApp.exec("ST_SET_BORDER_WIDTH", [null, 1]);
	},

	$ON_ST_DEC_BORDER_WIDTH : function(){
		this.oApp.exec("ST_SET_BORDER_WIDTH", [null, -1]);
	},

	$ON_ST_TOGGLE_BORDER_COLOR_LAYER : function(){
		if(this.welDropdownLayer.hasClass("p1"))
			this.oApp.exec("ST_HIDE_BORDER_COLOR_LAYER", []);
		else
			this.oApp.exec("ST_SHOW_BORDER_COLOR_LAYER", []);
	},

	$ON_ST_SHOW_BORDER_COLOR_LAYER : function(){
		this.welDropdownLayer.addClass("p1");
		this.welDropdownLayer.removeClass("p2");

		this.oApp.exec("SHOW_COLOR_PALETTE", ["ST_SET_BORDER_COLOR_FROM_PALETTE", this.elDropdownLayer]);
	},

	$ON_ST_HIDE_BORDER_COLOR_LAYER : function(){
		this.welDropdownLayer.removeClass("p1");

		this.oApp.exec("HIDE_COLOR_PALETTE", []);
	},

	$ON_ST_TOGGLE_BGCOLOR_LAYER : function(){
		if(this.welDropdownLayer.hasClass("p2"))
			this.oApp.exec("ST_HIDE_BGCOLOR_LAYER", []);
		else
			this.oApp.exec("ST_SHOW_BGCOLOR_LAYER", []);
	},

	$ON_ST_SHOW_BGCOLOR_LAYER : function(){
		this.welDropdownLayer.removeClass("p1");
		this.welDropdownLayer.addClass("p2");

		this.oApp.exec("SHOW_COLOR_PALETTE", ["ST_SET_BGCOLOR_FROM_PALETTE", this.elDropdownLayer]);
	},

	$ON_ST_HIDE_BGCOLOR_LAYER : function(){
		this.welDropdownLayer.removeClass("p2");

		this.oApp.exec("HIDE_COLOR_PALETTE", []);
	},

	$ON_ST_SET_BORDER_COLOR_FROM_PALETTE : function(sColorCode){
		this.oApp.exec("ST_SET_BORDER_COLOR", [sColorCode]);
		this.oApp.exec("ST_HIDE_BORDER_COLOR_LAYER", []);
	},

	$ON_ST_SET_BORDER_COLOR : function(sColorCode){
		this.oBorderColorInput.value = sColorCode;
		this.oButton_BorderColorPreview.style.backgroundColor = sColorCode;

		this._showNewTable();
	},

	$ON_ST_SET_BGCOLOR_FROM_PALETTE : function(sColorCode){
		this.oApp.exec("ST_SET_BGCOLOR", [sColorCode]);
		this.oApp.exec("ST_HIDE_BGCOLOR_LAYER", []);
	},

	$ON_ST_SET_BGCOLOR : function(sColorCode){
		this.oBGColorInput.value = sColorCode;
		this.oButton_BGColorPreview.style.backgroundColor = sColorCode;

		this._showNewTable();
	},

	_showNewTable : function(){
		var oTmp = document.createElement("DIV");
		oTmp.innerHTML = this._getTableString();
		var oNewTable = oTmp.firstChild;
		this.oSampleTable.parentNode.insertBefore(oNewTable, this.oSampleTable);
		this.oSampleTable.parentNode.removeChild(this.oSampleTable);
		this.oSampleTable = oNewTable;
	},

	// need to do something about the table width as the same HTML code is being used to the actual table and the preview table
	_getTableString : function(){
		var sBorderColorCode = this.oBorderColorInput.value;
		var sBGColorCode = this.oBGColorInput.value;
		var iBorderWidth = this.oBorderWidthInput.value;
		var sTD = "";
		if($.browser.msie){
			sTD = "<td><p></p></td>";
		}else{
			if($.browser.firefox){
				sTD = "<td><p><br/></p></td>";
			}else{
				sTD = "<td><p>&nbsp;</p></td>";
			}
		}

		var sTable = '<table style="background:'+sBorderColorCode+'" cellspacing="'+iBorderWidth+'">';
		var sRow = '<tr style="background:'+sBGColorCode+'">';
		var iColumns = this.oColumnInput.value;
		for(var i=0; i<iColumns; i++){
			sRow += sTD;
		}
		sRow += "</tr>\n";

		var iRows = this.oRowInput.value;

		sTable += "<tbody>";
		for(var i=0; i<iRows; i++){
			sTable += sRow;
		}
		sTable += "</tbody>";

		sTable += "</table>";

		return sTable;
	}
});
//}
//{
/**
 * @fileOverview This file contains Xpress plugin that takes care of the operations related to changing the editing mode using a Button element
 * @name hp_XE_EditingModeToggler.js
 */
xe.XE_EditingModeToggler = $.Class({
	name : "XE_EditingModeToggler",

	$init : function(elAppContainer){
		this._assignHTMLObjects(elAppContainer);
	},

	_assignHTMLObjects : function(elAppContainer){
		elAppContainer = $.$(elAppContainer) || document;

		this.elModeToggleButton = $("BUTTON.xpress_xeditor_mode_toggle_button", elAppContainer).get(0);
		this.welModeToggleButton = $(this.elModeToggleButton);
	},

	$ON_MSG_APP_READY : function(){
		this.oApp.registerBrowserEvent(this.elModeToggleButton, "click", "EVENT_TOGGLE_EDITING_MODE", []);
	},

	$ON_EVENT_TOGGLE_EDITING_MODE : function(){
		if(this.oApp.getEditingMode() == "WYSIWYG")
			this.oApp.exec("CHANGE_EDITING_MODE", ["HTMLSrc"]);
		else
			this.oApp.exec("CHANGE_EDITING_MODE", ["WYSIWYG"]);
	},

	$ON_CHANGE_EDITING_MODE : function(sMode){
		if(sMode == "HTMLSrc"){
			this.welModeToggleButton.addClass("active").parent("span").addClass("active");
			this.oApp.exec("DISABLE_ALL_UI", []);
		}else{
			this.welModeToggleButton.removeClass("active").parent("span").removeClass("active");
			this.oApp.exec("ENABLE_ALL_UI", []);
		}
	}
});
//}
/**
 * @fileOverview This file contains a message mapping(Korean), which is used to map the message code to the actual message
 * @name xpress_XE_Lang_KR.js
 */
var oMessageMap = {
	'XE_EditingAreaManager.onExit' : '%uB0B4%uC6A9%uC774%20%uBCC0%uACBD%uB418%uC5C8%uC2B5%uB2C8%uB2E4.',
	'XE_FontColor.invalidColorCode' : '%uC0C9%uC0C1%20%uCF54%uB4DC%uB97C%20%uC62C%uBC14%uB974%uAC8C%20%uC785%uB825%uD558%uC5EC%20%uC8FC%uC2DC%uAE30%20%uBC14%uB78D%uB2C8%uB2E4.\n\n%uC608%29%20%23000000%2C%20%23FF0000%2C%20%23FFFFFF%2C%20%23ffffff%2C%20ffffff',
	'XE_BGColor.invalidColorCode' : '%uC0C9%uC0C1%20%uCF54%uB4DC%uB97C%20%uC62C%uBC14%uB974%uAC8C%20%uC785%uB825%uD558%uC5EC%20%uC8FC%uC2DC%uAE30%20%uBC14%uB78D%uB2C8%uB2E4.\n\n%uC608%29%20%23000000%2C%20%23FF0000%2C%20%23FFFFFF%2C%20%23ffffff%2C%20ffffff',
	'XE_Hyperlink.invalidURL' : '%uC785%uB825%uD558%uC2E0%20URL%uC774%20%uC62C%uBC14%uB974%uC9C0%20%uC54A%uC2B5%uB2C8%uB2E4.',
	'XE_FindReplace.keywordMissing' : '%uCC3E%uC73C%uC2E4%20%uB2E8%uC5B4%uB97C%20%uC785%uB825%uD574%20%uC8FC%uC138%uC694.',
	'XE_FindReplace.keywordNotFound' : '%uCC3E%uC73C%uC2E4%20%uB2E8%uC5B4%uAC00%20%uC5C6%uC2B5%uB2C8%uB2E4.',
	'XE_FindReplace.replaceAllResultP1' : '%uC77C%uCE58%uD558%uB294%20%uB0B4%uC6A9%uC774%20%uCD1D%20',
	'XE_FindReplace.replaceAllResultP2' : '%uAC74%20%uBC14%uB00C%uC5C8%uC2B5%uB2C8%uB2E4.',
	'XE_FindReplace.notSupportedBrowser' : '%uD604%uC7AC%20%uC0AC%uC6A9%uD558%uACE0%20%uACC4%uC2E0%20%uBE0C%uB77C%uC6B0%uC800%uC5D0%uC11C%uB294%20%uC0AC%uC6A9%uD558%uC2E4%uC218%20%uC5C6%uB294%20%uAE30%uB2A5%uC785%uB2C8%uB2E4.%5Cn%uC774%uC6A9%uC5D0%20%uBD88%uD3B8%uC744%20%uB4DC%uB824%20%uC8C4%uC1A1%uD569%uB2C8%uB2E4.'
};
xe.XpressCore.oMessageMap = oMessageMap;
/**
 * XHTML Formatter
 * @author gony
 */
var
	regex_meanless_css1 = /<(.*?)\s+style\s*=\s*"(.*?(?:margin|padding)\s*:\s*0(?:px)?.*?|.*?\-(?:moz|ms|webkit|opera).*?)"(.*?)>/ig,
	regex_meanless_css2 = /(?:(?:margin|padding)\s*:\s*0(?:px)?|\-(?:moz|ms|webkit|opera)\-[\w-]+\s*:\s*.*?|[\w-]+\s*:\s*\-(?:moz|ms|webkit|opera)\-[\w-]+|(?:line-height|font-variant|font-stretch|font-size-adjust|font-size)\s*:\s*[a-z_-]+)\s*;?\s*|font-(?:weight|style)\s*:\s*normal;?/ig,
	regex_class  = /<(.*?)\s+class\s*=(?:\s*"(.*?)"|\s*'(.*?)'|([^\s>]+))(.*?)>/ig,
	regex_class2 = /xe_selected_cell/g;
	regex_handler = /<(.*?)\s+on[a-z]+\s*=(?:\s*".*?"|\s*'.*?'|[^\s>]+)(.*?)>/ig,
	//regex_id = /<(.*?)\s+id\s*=(?:[^\s>]+|\s*".*?"|\s*'.*?')(.*?)>/ig,
	//regex_script = /<script[\s\S]+?<\/script>/ig,
	regex_font_color = /color\s*=(?:\s*"(.*?)"|\s*'(.*?)'|([^\s>]+))/i,
	regex_font_face  = /face\s*=(?:\s*"(.*?)"|\s*'(.*?)'|([^\s>]+))/i,
	regex_font_size  = /size\s*=(?:\s*"(\d+)"|\s*'(\d+)'|(\d+))/i,
	regex_style = /style\s*=\s*(?:\s*"(.*?)"|\s*'(.*?)'|([^\s>]+))/i,
	regex_font_weight = /font-weight\s*:\s*([a-z]+);?/i,
	regex_font_style = /font-style\s*:\s*italic;?/i,
	regex_font_decoration = /text-decoration\s*:\s*([a-z -]+);?/i,
	regex_jquery = /jQuery\d+\s*=(\s*"\d+"|\d+)/ig,
	regex_quote_attr = /([\w-]+\s*=(?:\s*"[^"]+"|\s*'[^']+'))|([\w-]+)=([^\s]+)/g; //"

var
	allow_tags  = 'a,abbr,acronym,address,area,blockquote,br,caption,center,cite,code,col,colgroup,dd,del,dfn,div,dl,dt,em,embed,h1,h2,h3,h4,h5,h6,hr,img,ins,kbd,li,map,object,ol,p,param,pre,q,samp,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,tt,u,ul,var,iframe,object,param,style'.split(','),
	lonely_tags = 'area,br,col,embed,hr,img,input,param'.split(',');

var
	replace_tags = {
		'b' : 'strong',
		'i' : 'em',
		's' : 'del',
		'strike' : 'del'
	};

xe.XE_XHTMLFormatter = $.Class({
	name : "XE_XHTMLFormatter",

	$ON_MSG_APP_READY : function() {
		this.oApp.addConverter("WYSIWYG_TO_IR", this.TO_IR);
		this.oApp.addConverter("HTMLSrc_TO_IR", this.TO_IR);
		this.oApp.addConverter("IR_TO_HTMLSrc", this.IR_TO);
		this.oApp.addConverter("IR_TO_WYSIWYG", this.IR_TO);
	},

	TO_IR : function(sContent) {
		var stack = [];

		// remove xeHandled attrs
		/*
		sContent = sContent.replace(/xeHandled="YES"/ig,'');
		*/


		// remove all useless styles
		/*
		sContent = sContent.replace(regex_meanless_css1, function(m0,m1,m2,m3){
			m2 = m2.replace(regex_meanless_css2, '');

			return '<'+m1+(m2?' style="'+m2+'"':'')+m3+'>';
		});
		*/

		// remove all useless classes
		/*
		sContent = sContent.replace(regex_class, function(m0,m1,m2,m3,m4,m5){
			var cls = $.trim((m2 || m3 || m4 || "").replace(regex_class2, ''));

			return '<'+(m1||"")+(cls?' class="'+cls+'"':'')+(m5||"")+'>';
		});
		*/

		// remove all event handler
		//sContent = sContent.replace(regex_handler, '<$1$2>');

		// remove all id
		//sContent = sContent.replace(regex_id, '<$1$2>');

		// remove all scripts
		//sContent = sContent.replace(regex_script, '');

		if ($.browser.msie) {
			// remove $ attributes
			sContent = sContent.replace(regex_jquery, '');

			// quote all attrs
			sContent = sContent.replace(/<(\w+) ([^>]+)>/g, function(m0,m1,m2){
				return '<'+m1+' '+
					m2.replace(regex_quote_attr, function(s0,s1,s2,s3){
						if (s1) return s1;
						if(/^"/.test(s3)||/"$/.test(s3)) return s2+'='+s3;
						return s2+'="'+s3+'"';
					}) + '>';
			});
		}

		// remove all useless tag and enclose tags
		regex = /<(\/)?([:\w\/-]+)(.*?)>/ig;
		sContent = sContent.replace(regex, function(m0,m1,m2,m3){
			var m3s = [];
			var state = '';

			m1 = m1 || '';
			m2 = m2.toLowerCase();
			m3 = $.trim(m3 || '');

			if (!m1) {
				if ($.inArray(m2,lonely_tags) >= 0) {
					var len = m3.length;
					if (m2 == 'br') m3 = '';
					if (!m3 || m3.substring(len-1,len) != '/') m3 += ' /';

					return '<'+m2+' '+m3+'>';
				}

				/*
				if (replace_tags[m2]) {
					stack.push({tag:m2, state:'deleted'});

					m2 = replace_tags[m2];
					state = 'inserted';
				} else if (m2 == 'font') {
					stack.push({tag:m2, state:'deleted'});

					m2 = 'span';
					m3s = [];
					if (regex_font_color.test(m3)) m3s.push('color:'+(RegExp.$1||RegExp.$2||RegExp.$3)+';');
					if (regex_font_face.test(m3)) m3s.push('font-family:'+(RegExp.$1||RegExp.$2||RegExp.$3)+';');

					m3 = m3s.length?'style="'+m3s.join('')+'"':'';
					state = 'inserted';
				} else if (m2 == 'center') {
					stack.push({tag:m2, state:'deleted'});

					m2 = 'div'
					m3 = 'style="text-align:center"';

					state = 'inserted';
				} else if (m2 == 'span') {
					var style = '';

					if (!m3) {
						stack.push({tag:m3, state:'deleted'});
						return '';
					}

					if (regex_style.test(m3)) {
						var tmpstack = [];
						var tmptag   = '';

						style = RegExp.$1||RegExp.$2||RegExp.$3;
						m3 = m3.replace(regex_style, '');

						if (regex_font_weight.test(style)) {
							if (RegExp.$1 == 'bold' || RegExp.$1 == 'bolder') {
								style = style.replace(regex_font_weight, '');
								tmpstack.push({tag:'strong', state:'inserted'});
								tmptag += '<strong>';
							}
						}

						if (regex_font_style.test(style)) {
							style = style.replace(regex_font_style, '');
							tmpstack.push({tag:'em', state:'inserted'});
							tmptag += '<em>';
						}

						if (regex_font_decoration.test(style)) {
							var deco_css = ' '+RegExp.$1.toLowerCase()+' ';

							if (deco_css.indexOf('underline ') > 0) {
								deco_css = deco_css.replace('underline ', '');
								tmpstack.push({tag:'u', state:'inserted'});
								tmptag += '<u>';
							}

							if (deco_css.indexOf('line-through ') > 0) {
								deco_css = deco_css.replace('line-through ', '');
								tmpstack.push({tag:'del', state:'inserted'});
								tmptag += '<del>';
							}

							deco_css = $.trim(deco_css);
							style = style.replace(regex_font_decoration, (deco_css?'text-decoration:'+deco_css+';':''));
						}

						style = $.trim(style);

						stack.push({tag:m2, state:(!m3&&!style?'deleted':'')});
						stack = stack.concat(tmpstack);

						return (!m3&&!style?'':'<span '+m3+' style="'+style+'">')+tmptag;
					}
				} else {
					state = ($.inArray(m2,allow_tags) < 0)?'deleted':'';
					if (state == 'deleted') return '';
				}
				*/

				stack.push({tag:m2, state:state});
			} else {
				var tags = [], t = '';

				if (!stack.length) return '';

				do {
					t = stack.pop();
					if (t.state != 'inserted' && t.tag != m2) {
						stack.push(t);
						return tags.join('');
					}
					if (t.state != 'deleted') tags.push('</'+t.tag+'>');
				} while(stack.length && t.tag != m2);

				return tags.join('');
			}

			return '<'+m1+m2+(m3?' '+m3:'')+'>';
		});
		if (stack.length) {
			var t = '';

			do {
				t = stack.pop();
				if (t.state != 'deleted') sContent += '</'+t.tag+'>';
			} while(stack.length);
		}

		return sContent;
	},

	IR_TO : function(sContent) {
		return sContent;
	}
});
// center, font, b, i, s, strike

/**
 * Support XE extensions
 * @author gony
 */
xe.XE_Extension = $.Class({
	name  : "XE_Extension",
	seq   : '',
	last_doc : '',

	$init : function(elAppContainer, editor_sequence) {
		this.seq = editor_sequence;
		this._assignHTMLObjects(elAppContainer);
	},

	_assignHTMLObjects : function(elAppContainer) {
		this.elDropdownLayer = $('DIV.xpress_xeditor_extension_layer', elAppContainer).get(0);
	},

	_removeAttrs : function(sContent) {
		return sContent;
	},

	_addEvent : function() {
		if (this.oApp.getEditingMode() != 'WYSIWYG') return;

		var doc = this.oApp.getWYSIWYGDocument();
		var seq = this.seq;
		var fn  = function(){
			var obj  = $(this);
			var comp = obj.attr('editor_component');
			if (comp && $.isFunction(openComponent)) {
				editorPrevNode = obj.get(0);
				openComponent(comp, seq);
			}
		};

		$('img,div[editor_component]', doc).each(function(){
			var obj = $(this);
			if(this.nodeName == 'IMG' && !obj.attr('editor_component')) {
				obj.attr('editor_component','image_link');
			}
			if(this.last_doc != doc) {
				obj.dblclick(fn);
				this.last_doc = doc;
			}
		});
	},

	$ON_MSG_APP_READY : function() {
		var oApp = this.oApp;
		oApp.exec('REGISTER_UI_EVENT', ['extension', 'click', 'TOGGLE_EXTENSION_LAYER']);
		var functn  = function(){
			oApp.exec("HIDE_ACTIVE_LAYER", []);
		};
		$('a', this.elDropdownLayer).each(function(){
			var obj = $(this);
			if(!obj.attr('component_onclick_event_added')) {
				obj.click(functn);
				obj.attr('component_onclick_event_added','Y');
			}
		});
	},

	$ON_TOGGLE_EXTENSION_LAYER : function() {
		this.oApp.exec('TOGGLE_TOOLBAR_ACTIVE_LAYER', [this.elDropdownLayer]);
	},

	$ON_CHANGE_EDITING_MODE : function(mode) {
		var self = this;
		setTimeout(function(){ self._addEvent(); }, 100);
	},

	$ON_PASTE_HTML : function() {
		var self = this;
		setTimeout(function(){ self._addEvent(); }, 100);
	},

	$ON_LOAD_IR_FIELD : function() {
		var self = this;
		setTimeout(function(){ self._addEvent(); }, 100);
	},

	$ON_SET_IR : function() {
		var self = this;
		setTimeout(function(){ self._addEvent(); }, 100);
	}
});
/**
 * Auto saving
 * @author gony
 */
xe.XE_AutoSave = $.Class({
	name : "XE_AutoSave",
	form : null,
	textarea : null,

	$init : function(oIRTextarea, elAppContainer) {
		this.form = oIRTextarea.form;
		this.textarea = oIRTextarea;

		this._assignHTMLObjects(elAppContainer);
	},

	_assignHTMLObjects : function(elAppContainer) {
		this.welMessageBox = $('autosave_message');
	},

	$ON_MSG_APP_READY : function() {
		var elTitle   = $(this.form._saved_doc_title);
		var elContent = $(this.form._saved_doc_content);

		var title   = $.trim(elTitle.val());
		var content = $.trim(elContent.val());

		if (title || content) {
			if (confirm(this.form._saved_doc_message.value)) {
				$(this.form.title).val(title);
				this.oApp.setIR(content);
				if(typeof(editorGetAutoSavedDoc) == 'function') editorGetAutoSavedDoc(this.form);
			} else {
				editorRemoveSavedDoc();
			}
		}

		editorEnableAutoSave(this.form, $(this.form).attr("editor_sequence"));

		// register hotkey
		this.oApp.exec('REGISTER_HOTKEY', ['ctrl+shift+s','AUTO_SAVE']);
	},

	$ON_AUTO_SAVE : function() {
		_editorAutoSave();
	}
});
/**
 * Format Block plugin
 * @author gony
 */
xe.XE_FormatWithSelectUI = $.Class({
	name : "XE_FormatWithSelectUI",

	$init : function(elAppContainer){
		this._assignHTMLObjects(elAppContainer);
	},

	_assignHTMLObjects : function(elAppContainer){
		this.elFormatSelect = $("SELECT.xpress_xeditor_ui_format_select", elAppContainer).get(0);
	},

	$ON_MSG_APP_READY : function(){
		this.oApp.registerBrowserEvent(this.elFormatSelect, "change", "SET_FORMAT_FROM_SELECT_UI");
		this.elFormatSelect.selectedIndex = 0;
	},

	$ON_MSG_STYLE_CHANGED : function(sAttributeName, sAttributeValue){
		var blockName = this.oApp.getWYSIWYGDocument().queryCommandValue("FormatBlock");

		if (!blockName) return (this.elFormatSelect.selectedIndex = 0);
		if ($.browser.msie && /([0-9])/.test(blockName)) blockName = 'h'+(RegExp.$1);

		this.elFormatSelect.value = blockName.toLowerCase();
		if(this.elFormatSelect.selectedIndex < 0) this.elFormatSelect.selectedIndex = 0;
		if(this.elFormatSelect.value != blockName.toLowerCase()) this.elFormatSelect.selectedIndex = 0;
	},

	$ON_SET_FORMAT_FROM_SELECT_UI : function(){
		var sFormat = this.elFormatSelect.value;
		if(!sFormat) return;
		if($.browser.msie) sFormat = '<'+sFormat+'>';

		this.oApp.exec("EXECCOMMAND", ["FormatBlock", false, sFormat]);
		this.oApp.exec("CHECK_STYLE_CHANGE", []);
	}
});
/**
 * Enhanced Table Fetures
 * @author gony
 */

// 표 편집 확장 기능
xe.XE_Table = $.Class({
	_startSel : null,
	_endSel   : null,

	$ON_MSG_APP_READY : function() {
		this._doc = $(this.oApp.getWYSIWYGDocument());

		this.$FnMouseDown = $.fnBind(this._mousedown, this);
		this.$FnMouseUp   = $.fnBind(this._mouseup, this);
		this.$FnMouseMove = $.fnBind(this._mousemove, this);

		this._doc.mousedown(this.$FnMouseDown);

		// initialize
		this._startSel = null;
		this._endSel   = null;

		// register buttons
		this.oApp.exec('REGISTER_UI_EVENT', ['merge_cells', 'click', 'MERGE_CELLS']);
		this.oApp.exec('REGISTER_UI_EVENT', ['split_col', 'click', 'CELL_SPLIT_BY_COL']);
		this.oApp.exec('REGISTER_UI_EVENT', ['split_row', 'click', 'CELL_SPLIT_BY_ROW']);

		// register hotkeys
		this.oApp.exec('REGISTER_HOTKEY', ['ctrl+alt+m', 'MERGE_CELLS']);

		// perform default ready action
		this.$super.$ON_MSG_APP_READY();
	},

	$ON_MERGE_CELLS : function() {
		var html = "";
		var cell = $('.xe_selected_cell', this.oApp.getWYSIWYGDocument()).filter('td,th');
		var self = this;

		// 선택된 셀이 없으면 종료
		if (!cell.length) return;

		// UNDO 지점 기록
		this.oApp.exec("RECORD_UNDO_ACTION", ["Cell:Merge"]);

		// 선택한 모든 셀의 데이터를 첫번째 셀로 복사
		cell.each(function(){ html += $(this).html() }).eq(0).html(html);

		// 첫번째 셀 가로 확장
		var colspan = 0;
		cell.eq(0).nextAll('td,th').andSelf().filter('.xe_selected_cell').each(function(idx){
			colspan += self._getSpan(this, 'col');
		});

		// 마지막 셀까지 줄의 갯수 계산
		var rect = this._getRect(cell.eq(0));
		var start_tr = cell.eq(0).parent('tr');
		var end_tr   = cell.eq(cell.length-1).parent('tr');
		var all_rows = cell.parents('table').eq(0).find('tr');
		var rowspan  = all_rows.index(end_tr.get(0)) - all_rows.index(start_tr.get(0)) + this._getSpan(cell.eq(cell.length-1), 'row');

		// 첫번째 셀 colspan, rowspan 속성 지정
		cell.eq(0).attr('colSpan', colspan).attr('rowSpan', rowspan);

		// 첫번째 셀을 제외한 다른 모든 셀 제거
		cell.slice(1).remove();
	},

	$ON_CELL_SPLIT_BY_ROW : function(many) {
		var cell  = $('.xe_selected_cell', this.oApp.getWYSIWYGDocument()).filter('td,th');
		var table = cell.parents('table').eq(0);
		var self  = this;

		// 선택된 셀이 없으면 종료
		if (!cell.length) return;

		// UNDO 지점 기록
		this.oApp.exec("RECORD_UNDO_ACTION", ["Cell:Split By Row"]);

		// 선택 영역의 상하 좌표 구함
		var _top	= this._getRect(cell.eq(0)).top;
		var _bottom = this._getRect(cell.eq(cell.length-1)).bottom;

		// 테이블의 모든 셀에서 선택영역에 해당하는 셀을 구한다(상하 기준).
		(cell = table.find('td,th').filter(function(){
			var rect = self._getRect($(this));

			return !(rect.bottom <= _top || rect.top >= _bottom);
		})).filter('.xe_selected_cell').each(function(){
			var t	   = $(this);
			var row	 = t.parent('tr');
			var rowspan = self._getSpan(t, 'row');
			var rect	= self._getRect(t);
			var queue   = [];
			var clone   = t.clone().html('<br />');
			var topspan = 1, botspan = 1;

			// rowspan > 1이면 현재 셀의 rowspan을 절반으로 분할한다.
			if (rowspan > 1) {

				topspan = Math.ceil(rowspan/2);
				botspan = rowspan - topspan;

				queue.push(function(){
					t.attr('rowSpan', topspan);
				});

				clone.attr('rowSpan', botspan);
			} else {
				// rowspan이 없으면 현재 셀과 영역이 겹치는 모든 셀에 rowspan을 추가
				cell.filter(function(){
					if (t.get(0) == this) return false;

					var tt = $(this);
					var rc = self._getRect(tt);

					// 범위를 넘은 부분은 제외
					if (rc.bottom <= rect.top || rc.top >= rect.bottom) return false;

					return true;
				}).each(function(){
					var tt = $(this);
					var sp = self._getSpan(tt, 'row')+1;

					// rowspan 1 추가
					queue.push(function(){
						tt.attr('rowSpan', sp);
					});
				});

				// 새 줄을 추가한다.
				if ($.browser.msie) {
					// Fix bug for IE
					row.after(row.clone().empty().get(0).outerHTML);
				} else {
					row.after(row.clone().empty());
				}
			}

			var rows  = row.nextAll('tr');

			// 현재 셀이 마지막 줄에 있다면 한 줄 추가 후 새로운 셀 추가
			if (!rows.length) {
				row.after(row.clone().empty().append(clone));
			} else {
				var next_sib  = rows.eq(topspan - 1).children('td,th').filter(function(){
					return ( self._getRect($(this)).left > rect.left );
				});

				if ($.browser.msie) {
					next_sib.length?
						next_sib.eq(0).before(clone.get(0).outerHTML):
						rows.eq(topspan-1).append(clone.get(0).outerHTML);
				} else {
					next_sib.length?
						next_sib.slice(0,1).before(clone):
						rows.slice(topspan-1,1).append(clone);
				}
			}

			// 함수를 바로 실행하면 좌표가 틀어지므로, 큐에 넣은 후 실행
			$.each(queue, function(){ this(); });

		});
	},

	$ON_CELL_SPLIT_BY_COL : function(many) {
		var cell   = $('.xe_selected_cell', this.oApp.getWYSIWYGDocument()).filter('td,th');
		var table  = cell.parents('table').slice(0,1);
		var self   = this;
		var ie_bug = [], tmpId = (new Date).getTime(), tmpStr = '';

		// 선택된 셀이 없으면 종료
		if (!cell.length) return;

		// UNDO 지점 기록
		this.oApp.exec("RECORD_UNDO_ACTION", ["Cell:Split By Column"]);

		// 선택 영역의 좌우 좌표 구함
		var first_row = cell.eq(0).parent('tr');
		var _left = this._getRect(first_row.find('.xe_selected_cell:first')).left;
		var _right = this._getRect(first_row.find('.xe_selected_cell:last')).right;

		// 테이블의 모든 셀에서 선택영역에 해당하는 셀을 구한다(좌우 기준).
		(cell = table.find('td,th').filter(function(){
			var rect = self._getRect($(this));

			return !(rect.right <= _left || rect.left >= _right);
		})).filter('.xe_selected_cell').each(function(idx){
			var t	   = $(this);
			var colspan = self._getSpan(t, 'col');
			var clone   = t.clone().html('<br />');

			// colspan > 1 이면 colspan을 절반으로 분할한다.
			if (colspan > 1) {
				var leftspan  = Math.ceil(colspan/2);
				var rightspan = colspan - leftspan;

				t.attr('colSpan', leftspan);
				clone.attr('colSpan', rightspan);
			} else {
				// colspan이 없으면 현재 셀과 영역이 겹치는 모든 셀에 colspan을 추가
				var rect = self._getRect(t);

				cell.filter(function(){
					if (t.get(0) == this) return false;

					var tt = $(this);
					var rc = self._getRect(tt);

					// 범위를 넘은 부분은 제외
					if (rc.right <= rect.left || rc.left >= rect.right) return false;

					return true;
				}).each(function(){
					var tt = $(this);

					// colspan 1 추가
					tt.attr('colSpan', self._getSpan(tt, 'col')+1);
				});

				clone.attr('colSpan', 1);
			}

			if ($.browser.msie) {
				// Fix for IE bug
				t.after(clone.get(0).outerHTML);
			} else {
				t.after(clone);
			}
		});
	},

	$ON_CHECK_STYLE_CHANGE : function(){
		var ui  = ['merge_cells', 'split_col', 'split_row'];
		var app = this.oApp;
		var command = (this._startSel && this._startSel.is('.xe_selected_cell'))?'ENABLE_UI':'DISABLE_UI';

		$.each(ui, function(){ app.exec(command, [this]); });
	},

	_mousedown : function(event) {
		var cur = $(event.target);
		var sel = cur.parents().andSelf().filter('td,th,table');
		var app = this.oApp;
		var self = this;

		// 모든 선택영역 해제
		$('td.xe_selected_cell', this.oApp.getWYSIWYGDocument()).removeClass('xe_selected_cell');

		this._startSel = null;
		this._endSel   = null;

		if (!sel.length || !this._isLeftClicked(event.button)) return;

		function delayed(){
			sel = app.getSelection().cloneRange();
			sel.collapseToStart();
			sel = $(sel.startContainer).parents().andSelf().filter('td,th').eq(0);

			if (!sel.length) return self._removeAllListener()||true;

			// 좌표를 구한다
			self._getRect(self._startSel = sel);

			// 이벤트 바인딩
			self._doc.bind('mousemove', self.$FnMouseMove);
			self._doc.bind('mouseup', self.$FnMouseUp);
		}

		// mousedown이 일어난 후에 선택 영역이 설정되므로 실행을 지연시킨다.
		setTimeout(delayed, 0);
	},

	_mouseup : function(event) {
		// 선택된 셀 확인
		this._removeAllListener();

		// 시작셀과 종료셀 제거
		this._startSel = this._endSel = null;
	},

	_mousemove : function(event) {
		var cur  = $(event.target);
		var cell = cur.parents().andSelf().filter('td,th').eq(0);
		var self = this;

		// 마우스 왼쪽 버튼이 눌리지 않았으면 종료
		if (!cell.length || !this._isLeftClicked(event.button)) return;
		if (!this._endSel && cell.get(0) == this._startSel.get(0)) return;
		if (this._endSel && cell.get(0) == this._endSel.get(0)) return;

		// 종료셀 && 종료셀의 좌표
		this._getRect(this._endSel = cell);

		// 선택 범위를 구한다
		var _top	= Math.min(this._startSel.rect.top,  this._endSel.rect.top);
		var _left   = Math.min(this._startSel.rect.left, this._endSel.rect.left);
		var _bottom = Math.max(this._startSel.rect.bottom, this._endSel.rect.bottom);
		var _right  = Math.max(this._startSel.rect.right,  this._endSel.rect.right);

		var table = cell.parents('table');
		var cells = table.find('td,th').removeClass('xe_selected_cell');
		var i = 0;

		// 복잡한 모양의 테이블을 위한 반복 처리
		var selected = $();
		do {
			// 선택한 셀로 최대 영역 재계산
			selected.each(function(){
				var rect = self._getRect($(this));

				// 영역 재계산
				if (rect.right  > _right)  _right  = rect.right;
				if (rect.left   < _left)   _left   = rect.left;
				if (rect.top	< _top)	_top	= rect.top;
				if (rect.bottom > _bottom) _bottom = rect.bottom;
			});

			// 좌표 범위 안에 있는 선택할 셀을 추린다.
			cells = cells.filter(':not(.xe_selected_cell)');
			selected = cells.filter(function(){
				var rect = self._getRect($(this));

				if (rect.right <= _left || rect.left >= _right || rect.bottom <= _top || rect.top >= _bottom) return false;

				return true;
			}).addClass('xe_selected_cell');
		} while(selected.length);

		// 브라우저의 기본 선택영역 해제 : FF 제외 - 기본 기능이 충분히 좋아서 + 이 부분을 실행하면 오류가 발생해서
		if (!$.browser.mozilla) {
			function delayed() {
				var sel = self.oApp.getSelection();

				if (!self._startSel) return;
				if (!self._startSel.get(0).firstChild) self._startSel.text(" ");

				sel.selectNode(self._startSel.get(0).firstChild);
				sel.collapseToStart();
				sel.select();
			}

			setTimeout(delayed, 0);
		}

		return false;
	},

	_removeAllListener : function() {
		// 이벤트 해제
		this._doc.unbind("mousemove", this.$FnMouseMove);
		this._doc.unbind("mouseup", this.$FnMouseUp);
	},

	_isLeftClicked : function(value) {
		return $.browser.msie?!!(value & 1):(value == 0);
	},

	_getRect : function(obj) {
		var el = obj.get(0);

		obj.rect = {};
		obj.rect.top	= el.offsetTop;
		obj.rect.left   = el.offsetLeft;
		obj.rect.bottom = obj.rect.top  + el.offsetHeight;
		obj.rect.right  = obj.rect.left + el.offsetWidth;

		return obj.rect;
	},

	_getSpan : function(obj, type) {
		var span = parseInt($(obj).attr(type+'span'));

		return isNaN(span)?1:span;
	}
}).extend(xe.XE_Table);

})(jQuery);
if (!window.xe) xe = {};

xe.Editors = [];

function editorStart_xe(editor_sequence, primary_key, content_key, editor_height, colorset, content_style, content_font, content_font_size) {
	if(typeof(colorset)=='undefined') colorset = 'white';
	if(typeof(content_style)=='undefined') content_style = 'xeStyle';
	if(typeof(content_font)=='undefined') content_font= '';
	if(typeof(content_font_size)=='undefined') content_font_size= '';

	var target_src = request_uri+'modules/editor/styles/'+content_style+'/editor.html';

	var textarea = jQuery("#xpress-editor-"+editor_sequence);
	var iframe   = jQuery('<iframe id="editor_iframe_'+editor_sequence+'" allowTransparency="true" frameborder="0" src="'+target_src+'" scrolling="yes" style="width:100%;height:'+editor_height+'px">');
	var htmlsrc  = jQuery('<textarea rows="10" cols="20" class="input_syntax '+colorset+'" style="display:none"></textarea>');
	var form	 = textarea.get(0).form;
	form.setAttribute('editor_sequence', editor_sequence);
	textarea.css("display","none");

	var saved_content = '';
	if(jQuery("input[name=content]",form).size()>0){
		saved_content=jQuery("input[name=content]",form).val().replace(/src=\"files\/attach/g,'src="'+request_uri+'files/attach'); //'
		jQuery("#xpress-editor-"+editor_sequence).val(saved_content);
	}

	// hide textarea
	textarea.hide().css('width', '99%').before(iframe).after(htmlsrc);

	// create an editor
	var oEditor		  = new xe.XpressCore();
	var oWYSIWYGIFrame   = iframe.get(0);
	var oIRTextarea	  = textarea.get(0);
	var oHTMLSrcTextarea = htmlsrc.get(0);
	var elAppContainer   = jQuery('.xpress-editor', form).get(0);

	oEditor.getFrame = function(){ return oWYSIWYGIFrame;}
	oEditor.getContent = function(){
		editorGetContentTextarea_xe(editor_sequence);
	}
	
	var content = form[content_key].value;
	if(xFF && !content) content = '<br />';

	// src, href, url의 XE 상대경로를 http로 시작하는 full path로 변경
	content = editorReplacePath(content);

	form[content_key].value = content;
	jQuery("#xpress-editor-"+editor_sequence).val(content);

	// Set standard API
	editorRelKeys[editor_sequence] = new Array();
	editorRelKeys[editor_sequence]["primary"]   = form[primary_key];
	editorRelKeys[editor_sequence]["content"]   = form[content_key];
	editorRelKeys[editor_sequence]["func"]	  = editorGetContentTextarea_xe;
	editorRelKeys[editor_sequence]["editor"]	= oEditor;
	editorRelKeys[editor_sequence]["pasteHTML"] = function(text){
		oEditor.exec('PASTE_HTML',[text]);
	}
	xe.Editors[editor_sequence] = oEditor;

	// register plugins
	oEditor.registerPlugin(new xe.CorePlugin(null));

	oEditor.registerPlugin(new xe.XE_PreservTemplate(jQuery("#xpress-editor-"+editor_sequence).val()));
	oEditor.registerPlugin(new xe.StringConverterManager());
	oEditor.registerPlugin(new xe.XE_EditingAreaManager("WYSIWYG", oIRTextarea, {nHeight:parseInt(editor_height), nMinHeight:205}, null, elAppContainer));
	oEditor.registerPlugin(new xe.XE_EditingArea_HTMLSrc(oHTMLSrcTextarea));
	oEditor.registerPlugin(new xe.XE_EditingAreaVerticalResizer(elAppContainer));
	oEditor.registerPlugin(new xe.Utils());
	oEditor.registerPlugin(new xe.DialogLayerManager());
	oEditor.registerPlugin(new xe.ActiveLayerManager());
	oEditor.registerPlugin(new xe.Hotkey());
	oEditor.registerPlugin(new xe.XE_WYSIWYGStyler());
	oEditor.registerPlugin(new xe.XE_WYSIWYGStyleGetter());
	oEditor.registerPlugin(new xe.MessageManager(xe.XpressCore.oMessageMap));
	oEditor.registerPlugin(new xe.XE_Toolbar(elAppContainer));

	oEditor.registerPlugin(new xe.XE_XHTMLFormatter);
	oEditor.registerPlugin(new xe.XE_GET_WYSYWYG_MODE(editor_sequence));
	oEditor.registerPlugin(new xe.XE_GET_WYSYWYG_CONTENT());

	if(jQuery("ul.extra1").length) {
		oEditor.registerPlugin(new xe.XE_ColorPalette(elAppContainer));
		oEditor.registerPlugin(new xe.XE_FontColor(elAppContainer));
		oEditor.registerPlugin(new xe.XE_BGColor(elAppContainer));
		oEditor.registerPlugin(new xe.XE_Quote(elAppContainer));
		oEditor.registerPlugin(new xe.XE_FontNameWithSelectUI(elAppContainer));
		oEditor.registerPlugin(new xe.XE_FontSizeWithSelectUI(elAppContainer));
		oEditor.registerPlugin(new xe.XE_LineHeightWithSelectUI(elAppContainer));
		oEditor.registerPlugin(new xe.XE_UndoRedo());
		oEditor.registerPlugin(new xe.XE_Table(elAppContainer));
		oEditor.registerPlugin(new xe.XE_Hyperlink(elAppContainer));
		oEditor.registerPlugin(new xe.XE_FindReplacePlugin(elAppContainer));
		oEditor.registerPlugin(new xe.XE_FormatWithSelectUI(elAppContainer));
		oEditor.registerPlugin(new xe.XE_SCharacter(elAppContainer));
	}

	if(jQuery("ul.extra2").length) {
		oEditor.registerPlugin(new xe.XE_Extension(elAppContainer, editor_sequence));
	}

	if(jQuery("ul.extra3").length) {
		oEditor.registerPlugin(new xe.XE_EditingModeToggler(elAppContainer));
	}


	//oEditor.registerPlugin(new xe.XE_Preview(elAppContainer));

	if (!jQuery.browser.msie && !jQuery.browser.opera) {
		oEditor.registerPlugin(new xe.XE_WYSIWYGEnterKey(oWYSIWYGIFrame));
	}

	// 자동 저장 사용
	if (s=form._saved_doc_title) {
		oEditor.registerPlugin(new xe.XE_AutoSave(oIRTextarea, elAppContainer));
	}

	function load_proc() {
		try {
			var doc = oWYSIWYGIFrame.contentWindow.document, str;
			if (doc.location == 'about:blank') throw 'blank';

			// get innerHTML
			str = doc.body.innerHTML;

			// register plugin
			oEditor.registerPlugin(new xe.XE_EditingArea_WYSIWYG(oWYSIWYGIFrame));
			oEditor.registerPlugin(new xe.XpressRangeManager(oWYSIWYGIFrame));
			oEditor.registerPlugin(new xe.XE_ExecCommand(oWYSIWYGIFrame));

			if(content_font && !doc.body.style.fontFamily) {
				doc.body.style.fontFamily = content_font;
			}
			if(content_font_size && !doc.body.style.fontSize) {
				doc.body.style.fontSize = content_font_size;
			}

			// run
			oEditor.run();
		} catch(e) {
			setTimeout(load_proc, 0);
		}
	}

	load_proc();

	return oEditor;
}

function editorGetContentTextarea_xe(editor_sequence) {
	var oEditor = xe.Editors[editor_sequence] || null;

	if (!oEditor) return '';

	var str = oEditor.getIR();

	if(!jQuery.trim(str.replace(/(&nbsp;|<\/?(p|br|span|div)([^>]+)?>)/ig, ''))) return '';

	// 파이어폭스의 경우 의미없는 <br>이 컨텐트 마지막에 추가될 수 있다.
	str = str.replace(/<br ?\/?>$/i, '');

	// 속도 문제가 있으므로 1024 문자 미만일 때만 첫 노드가 텍스트 노드인지 테스트
	// 그 이상이면 P 노드가 정상적으로 생성되었다고 가정한다.
	if (str.length < 1024) {
		var inline_elements = Array('#text','A','BR','IMG','EM','STRONG','SPAN','BIG','CITE','CODE','DD','DFN','HR','INS','KBD','LINK','Q','SAMP','SMALL','SUB','SUP','TT');
		var is_inline_contents = true;
		var div   = jQuery('<div>'+str+'</div>').eq(0);
		var nodes = div.contents();
		jQuery.each(nodes, function() {
			if (this.nodeType != 3) {
				if(jQuery.inArray(this.nodeName, inline_elements ) == -1) {
					is_inline_contents = false;
				}
			}
		});
		if(is_inline_contents) str = '<p>'+str+'</p>';
	}

	// 이미지 경로를 수정한다. - 20091125
	str = str.replace(/src\s?=\s?(["']?)(?:\.\.\/)+(files\/attach\/)/ig, function(m0,m1,m2){
		return 'src='+(m1||'')+m2;
	});

	str = str.replace(/\<(\/)?([A-Z]+)([^>]*)\>/ig, function(m0,m1,m2,m3) {
		if(m3) {
			m3 = m3.replace(/ ([A-Z]+?)\=/ig, function(n0,n1) {
				n1 = n1.toLowerCase();
				return ' '+n1+'=';
			});
		} else { m3 = ''; }
		m2 = m2.toLowerCase();
		if(!m1) m1='';
		return '<'+m1+m2+m3+'>';
	});
	str = str.replace('<br>','<br />');

	return str;
}

function editorGetIframe(srl) {
	return jQuery('iframe#editor_iframe_'+srl).get(0);
}

function editorReplaceHTML(iframe_obj, content) {
	// src, href, url의 XE 상대경로를 http로 시작하는 full path로 변경
	content = editorReplacePath(content);

	var srl = parseInt(iframe_obj.id.replace(/^.*_/,''),10);
	editorRelKeys[srl]["pasteHTML"](content);
}

function editorReplacePath(content) {
	// 태그 내 src, href, url의 XE 상대경로를 http로 시작하는 full path로 변경
	content = content.replace(/\<([^\>\<]*)(src=|href=|url\()("|\')*([^"\'\)]+)("|\'|\))*(\s|>)*/ig, function(m0,m1,m2,m3,m4,m5,m6) {
		if(m2=="url(") { m3=''; m5=')'; } else { if(typeof(m3)=='undefined') m3 = '"'; if(typeof(m5)=='undefined') m5 = '"'; if(typeof(m6)=='undefined') m6 = ''; }
		var val = jQuery.trim(m4).replace(/^\.\//,'');
		if(/^(http\:|https\:|ftp\:|telnet\:|mms\:|mailto\:|\/|\.\.|\#)/i.test(val)) return m0;
		return '<'+m1+m2+m3+request_uri+val+m5+m6;
	});
	return content;
}

function editorGetAutoSavedDoc(form) {
	var param = new Array();
	param['mid'] = current_mid;
	param['editor_sequence'] = form.getAttribute('editor_sequence')
	setTimeout(function() {
	  var response_tags = new Array("error","message","editor_sequence","title","content","document_srl");
	  exec_xml('editor',"procEditorLoadSavedDocument", param, function(a,b,c) { editorRelKeys[param['editor_sequence']]['primary'].value = a['document_srl']; if(typeof(uploadSettingObj[param['editor_sequence']]) == 'object') editorUploadInit(uploadSettingObj[param['editor_sequence']], true); }, response_tags);
	}, 0);
	
}

// WYSIWYG 모드를 저장하는 확장기능
xe.XE_GET_WYSYWYG_MODE = jQuery.Class({
	name : "XE_GET_WYSYWYG_MODE",

	$init : function(editor_sequence) {
		this.editor_sequence = editor_sequence;
	},

	$ON_CHANGE_EDITING_MODE : function(mode) {
		editorMode[this.editor_sequence] = (mode =='HTMLSrc') ? 'html' : 'wysiwyg';
	}
});

// 이미지등의 상대경로를 절대경로로 바꾸는 플러그인
xe.XE_GET_WYSYWYG_CONTENT = jQuery.Class({
	name : "XE_GET_WYSYWYG_CONTENT",

	$ON_MSG_APP_READY : function() {
		this.oApp.addConverter("IR_TO_WYSIWYG", this.replaceXE2HTTP);
		this.oApp.addConverter("WYSIWYG_TO_IR", this.replaceHTTP2XE);
	},

	replaceXE2HTTP : function(content) {
		// src, href, url의 XE 상대경로를 http로 시작하는 full path로 변경
		content = editorReplacePath(content);

		return content;
	},

	replaceHTTP2XE : function(content) {
		// src, href, url에서 http로 시작하는 full path를 XE 상대경로로 변경
		content = content.replace(/(src=|href=|url\()("|\')*([^"\'\)]+)("|\'|\))*(\s|>|\))*/ig, function(m0,m1,m2,m3,m4,m5) {
			var uriReg = new RegExp('^'+request_uri.replace('\/','\\/'),'ig');
			if(m1=="url(") { m2=''; m4=')'; } else { if(typeof(m2)=='undefined') m2 = '"'; if(typeof(m4)=='undefined') m4 = '"'; if(typeof(m5)=='undefined') m5 = ''; }
			var val = jQuery.trim(m3);
			if(uriReg.test(val)) val = val.replace(uriReg,'');
			else val = m3;
			return m1+m2+val+m4+m5;
		});
		return content;
	}
});

// 서식 기본 내용을 보존
xe.XE_PreservTemplate = jQuery.Class({
	name : "XE_PreservTemplate",
	isRun : false,

	$BEFORE_SET_IR : function(content) {
		if(!this.isRun && !content) {
			this.isRun = true;
			return false;
		}
	}
});

// 미리보기 확장기능
xe.XE_Preview = jQuery.Class({
	name  : "XE_Preview",
	elPreviewButton : null,

	$init : function(elAppContainer) {
		this._assignHTMLObjects(elAppContainer);
	},

	_assignHTMLObjects : function(elAppContainer) {
		this.elPreviewButton = jQuery("BUTTON.xpress_xeditor_preview_button", elAppContainer);
	},

	$ON_MSG_APP_READY : function() {
		this.oApp.registerBrowserEvent(this.elPreviewButton.get(0), "click", "EVENT_PREVIEW", []);
	},

	$ON_EVENT_PREVIEW : function() {
		// TODO : 버튼이 눌렸을 때의 동작 정의
	}
});
/**
 * @file   modules/blog/js/blog.js
 * @author zero (zero@nzeo.com)
 * @brief  blog 모듈의 javascript
 **/

/* 글쓰기 작성후 */
function completeDocumentInserted(ret_obj) {
    var error = ret_obj['error'];
    var message = ret_obj['message'];
    var mid = ret_obj['mid'];
    var document_srl = ret_obj['document_srl'];
    var category_srl = ret_obj['category_srl'];

    alert(message);

    var url = current_url.setQuery('mid',mid).setQuery('document_srl',document_srl).setQuery('act','');
    if(category_srl) url = url.setQuery('category',category_srl);
    location.href = url;
}

/* 글 삭제 */
function completeDeleteDocument(ret_obj) {
    var error = ret_obj['error'];
    var message = ret_obj['message'];
    var mid = ret_obj['mid'];
    var page = ret_obj['page'];

    var url = "./?mid="+mid;
    if(page) url += "&page="+page;

    alert(message);

    location.href = url;
}

/* 검색 실행 */
function completeSearch(fo_obj, params) {
    fo_obj.submit();
}

// 현재 페이지 reload
function completeReload(ret_obj) {
    var error = ret_obj['error'];
    var message = ret_obj['message'];

    location.href = location.href;
}

/* 댓글 글쓰기 작성후 */
function completeInsertComment(ret_obj) {
    var error = ret_obj['error'];
    var message = ret_obj['message'];

    alert(message);
    location.href = current_url.setQuery('comment_srl','').setQuery('act','');
}

/* 댓글 삭제 */
function completeDeleteComment(ret_obj) {
    var error = ret_obj['error'];
    var message = ret_obj['message'];
    var mid = ret_obj['mid'];
    var document_srl = ret_obj['document_srl'];
    var page = ret_obj['page'];

    var url = "./?mid="+mid+'&document_srl='+document_srl;
    if(page) url += "&page="+page;

    alert(message);

    location.href = url;
}

/* 트랙백 삭제 */
function completeDeleteTrackback(ret_obj) {
    var error = ret_obj['error'];
    var message = ret_obj['message'];
    var mid = ret_obj['mid'];
    var document_srl = ret_obj['document_srl'];
    var page = ret_obj['page'];

    var url = "./?mid="+mid+'&document_srl='+document_srl;
    if(page) url += "&page="+page;

    alert(message);

    location.href = url;
}

/* 카테고리 이동 */
function doChangeCategory(sel_obj, url) {
    var category_srl = sel_obj.options[sel_obj.selectedIndex].value;
    if(!category_srl) location.href=url;
    else location.href=url+'&category='+category_srl;
}
// Flash Player Version Detection - Rev 1.6
// Detect Client Browser type
// Copyright(c) 2005-2006 Adobe Macromedia Software, LLC. All rights reserved.
var isIE  = (navigator.appVersion.indexOf("MSIE") != -1) ? true : false;
var isWin = (navigator.appVersion.toLowerCase().indexOf("win") != -1) ? true : false;
var isOpera = (navigator.userAgent.indexOf("Opera") != -1) ? true : false;

function ControlVersion()
{
    var version;
    var axo;
    var e;

    // NOTE : new ActiveXObject(strFoo) throws an exception if strFoo isn't in the registry

    try {
        // version will be set for 7.X or greater players
        axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");
        version = axo.GetVariable("$version");
    } catch (e) {
    }

    if (!version)
    {
        try {
            // version will be set for 6.X players only
            axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");
            
            // installed player is some revision of 6.0
            // GetVariable("$version") crashes for versions 6.0.22 through 6.0.29,
            // so we have to be careful. 
            
            // default to the first public version
            version = "WIN 6,0,21,0";

            // throws if AllowScripAccess does not exist (introduced in 6.0r47)        
            axo.AllowScriptAccess = "always";

            // safe to call for 6.0r47 or greater
            version = axo.GetVariable("$version");

        } catch (e) {
        }
    }

    if (!version)
    {
        try {
            // version will be set for 4.X or 5.X player
            axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.3");
            version = axo.GetVariable("$version");
        } catch (e) {
        }
    }

    if (!version)
    {
        try {
            // version will be set for 3.X player
            axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.3");
            version = "WIN 3,0,18,0";
        } catch (e) {
        }
    }

    if (!version)
    {
        try {
            // version will be set for 2.X player
            axo = new ActiveXObject("ShockwaveFlash.ShockwaveFlash");
            version = "WIN 2,0,0,11";
        } catch (e) {
            version = -1;
        }
    }
    
    return version;
}

// JavaScript helper required to detect Flash Player PlugIn version information
function GetSwfVer(){
    // NS/Opera version >= 3 check for Flash plugin in plugin array
    var flashVer = -1;
    
    if (navigator.plugins != null && navigator.plugins.length > 0) {
        if (navigator.plugins["Shockwave Flash 2.0"] || navigator.plugins["Shockwave Flash"]) {
            var swVer2 = navigator.plugins["Shockwave Flash 2.0"] ? " 2.0" : "";
            var flashDescription = navigator.plugins["Shockwave Flash" + swVer2].description;
            var descArray = flashDescription.split(" ");
            var tempArrayMajor = descArray[2].split(".");            
            var versionMajor = tempArrayMajor[0];
            var versionMinor = tempArrayMajor[1];
            var versionRevision = descArray[3];
            if (versionRevision == "") {
                versionRevision = descArray[4];
            }
            if (versionRevision[0] == "d") {
                versionRevision = versionRevision.substring(1);
            } else if (versionRevision[0] == "r") {
                versionRevision = versionRevision.substring(1);
                if (versionRevision.indexOf("d") > 0) {
                    versionRevision = versionRevision.substring(0, versionRevision.indexOf("d"));
                }
            }
            var flashVer = versionMajor + "." + versionMinor + "." + versionRevision;
        }
    }
    // MSN/WebTV 2.6 supports Flash 4
    else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.6") != -1) flashVer = 4;
    // WebTV 2.5 supports Flash 3
    else if (navigator.userAgent.toLowerCase().indexOf("webtv/2.5") != -1) flashVer = 3;
    // older WebTV supports Flash 2
    else if (navigator.userAgent.toLowerCase().indexOf("webtv") != -1) flashVer = 2;
    else if ( isIE && isWin && !isOpera ) {
        flashVer = ControlVersion();
    }    
    return flashVer;
}

// When called with reqMajorVer, reqMinorVer, reqRevision returns true if that version or greater is available
function DetectFlashVer(reqMajorVer, reqMinorVer, reqRevision)
{
    versionStr = GetSwfVer();
    if (versionStr == -1 ) {
        return false;
    } else if (versionStr != 0) {
        if(isIE && isWin && !isOpera) {
            // Given "WIN 2,0,0,11"
            tempArray         = versionStr.split(" ");     // ["WIN", "2,0,0,11"]
            tempString        = tempArray[1];            // "2,0,0,11"
            versionArray      = tempString.split(",");    // ['2', '0', '0', '11']
        } else {
            versionArray      = versionStr.split(".");
        }
        var versionMajor      = versionArray[0];
        var versionMinor      = versionArray[1];
        var versionRevision   = versionArray[2];

            // is the major.revision >= requested major.revision AND the minor version >= requested minor
        if (versionMajor > parseFloat(reqMajorVer)) {
            return true;
        } else if (versionMajor == parseFloat(reqMajorVer)) {
            if (versionMinor > parseFloat(reqMinorVer))
                return true;
            else if (versionMinor == parseFloat(reqMinorVer)) {
                if (versionRevision >= parseFloat(reqRevision))
                    return true;
            }
        }
        return false;
    }
}

function AC_AddExtension(src, ext)
{
  if (src.indexOf('?') != -1)
    return src.replace(/\?/, ext+'?'); 
  else
    return src + ext;
}

function AC_Generateobj(objAttrs, params, embedAttrs) 
{ 
    var str = '';
    if (isIE && isWin && !isOpera)
    {
          str += '<object ';
          for (var i in objAttrs)
              str += i + '="' + objAttrs[i] + '" ';
          str += '>';
          for (var i in params)
              str += '<param name="' + i + '" value="' + params[i] + '" /> ';
          str += '</object>';
    } else {
          str += '<embed ';
          for (var i in embedAttrs)
              str += i + '="' + embedAttrs[i] + '" ';
          str += '> </embed>';
    }

    return str;
}

function AC_FL_RunContent(){
  var ret = 
    AC_GetArgs
    (  arguments, ".swf", "movie", "clsid:d27cdb6e-ae6d-11cf-96b8-444553540000"
     , "application/x-shockwave-flash"
    );
  return AC_Generateobj(ret.objAttrs, ret.params, ret.embedAttrs);
}

function AC_GetArgs(args, ext, srcParamName, classid, mimeType){
  var ret = new Object();
  ret.embedAttrs = new Object();
  ret.params = new Object();
  ret.objAttrs = new Object();
  for (var i=0; i < args.length; i=i+2){
    var currArg = args[i].toLowerCase();    

    switch (currArg){    
      case "classid":
        break;
      case "pluginspage":
        ret.embedAttrs[args[i]] = args[i+1];
        break;
      case "src":
      case "movie":    
        args[i+1] = AC_AddExtension(args[i+1], ext);
        ret.embedAttrs["src"] = args[i+1];
        ret.params[srcParamName] = args[i+1];
        break;
      case "onafterupdate":
      case "onbeforeupdate":
      case "onblur":
      case "oncellchange":
      case "onclick":
      case "ondblClick":
      case "ondrag":
      case "ondragend":
      case "ondragenter":
      case "ondragleave":
      case "ondragover":
      case "ondrop":
      case "onfinish":
      case "onfocus":
      case "onhelp":
      case "onmousedown":
      case "onmouseup":
      case "onmouseover":
      case "onmousemove":
      case "onmouseout":
      case "onkeypress":
      case "onkeydown":
      case "onkeyup":
      case "onload":
      case "onlosecapture":
      case "onpropertychange":
      case "onreadystatechange":
      case "onrowsdelete":
      case "onrowenter":
      case "onrowexit":
      case "onrowsinserted":
      case "onstart":
      case "onscroll":
      case "onbeforeeditfocus":
      case "onactivate":
      case "onbeforedeactivate":
      case "ondeactivate":
      case "type":
      case "codebase":
        ret.objAttrs[args[i]] = args[i+1];
        break;
      case "id":
      case "width":
      case "height":
      case "align":
      case "vspace": 
      case "hspace":
      case "class":
      case "title":
      case "accesskey":
      case "name":
      case "tabindex":
        ret.embedAttrs[args[i]] = ret.objAttrs[args[i]] = args[i+1];
        break;
      default:
        ret.embedAttrs[args[i]] = ret.params[args[i]] = args[i+1];
    }
  }
  ret.objAttrs["classid"] = classid;
  if (mimeType) ret.embedAttrs["type"] = mimeType;
  return ret;
}

var requiredMajorVersion = 9;
var requiredMinorVersion = 0;
var requiredRevision = 28;

var hasProductInstall = DetectFlashVer(6, 0, 65);
var hasRequestedVersion = DetectFlashVer(requiredMajorVersion, requiredMinorVersion, requiredRevision);

function vFlexShow(src, install_src) {
    if ( hasProductInstall && !hasRequestedVersion ) {
        document.title = document.title.slice(0, 47) + " - Flash Player Installation";
        return install_src;
    } else if (hasRequestedVersion) {
        return src;
    } else {
        var alternateContent = 'Alternate HTML content should be placed here. '
        + 'This content requires the Adobe Flash Player. '
        + '<a href=http://www.adobe.com/go/getflash/>Get Flash</a>';
        return alternateContent;
    }
}
function toggleCategory(evt) {
    var e = new xEvent(evt);
    var obj = e.target;
    var srl = null;
    if(obj.nodeName != 'DIV') return;

    if(obj.id && /^category_parent_/.test(obj.id)) {
        srl = obj.id.replace(/^category_parent_/,'');
    } else if(obj.id && /^category_/.test(obj.id)) {
        srl = obj.id.replace(/^category_parent_/,'');
    } else if(obj.className && /item/.test(obj.className)) {
        var pObj = obj.parentNode;
        srl = pObj.id.replace(/^category_parent_/,'');
    }

    if(!srl) return;
    var obj = xGetElementById("category_"+srl);
    if(!obj) return;

    var selObj = xGetElementById("category_parent_"+srl);
    if(!selObj) return;

    if(!obj.style.display || obj.style.display == 'block') {
        obj.style.display = 'none';
        selObj.className = selObj.className.replace('minus','plus');
    } else {
        obj.style.display = 'block';
        selObj.className = selObj.className.replace('plus','minus');
    }
}

xAddEventListener(document, 'click', toggleCategory);
