window.VisionX_crossPlatform = true;
//////////////////////////////////////////////////////////////////////////////////////

/////*******************************************************************************//
// Copyright Centre Technologies Ltd. (System 7 and VisionX). All Rights Reserved.  //
//********************************************************************************////

//**********************************************************************************//
// IMPORTANT LEGAL NOTICE
// Access to this source code is only permitted in relation to the license purchased.
// It may only be modified to customize the behaviour of the VisionX product, and no
// part of it may be used in the construction or development of derivative works or
// products, or to inform the construction or development of derivative works or
// products, unless specifically authorized by an OEM license. You must take all
// reasonable measures to ensure that this source code is not made available to third
// parties that do not have the relevant license.
//***********************************************************************************//

/*
if(typeof HTMLElement!="undefined"){
	HTMLElement.prototype.innerText getter = function(){
		var tmp = this.innerHTML.replace(/<br>/gi,"\n");
		return tmp.replace(/<[^>]+>/g,"");
	}

	HTMLElement.prototype.innerText setter = function(txtStr){
		var parsedText = document.createTextNode(txtStr);
		this.innerHTML = "";
		this.appendChild( parsedText );
	}
}*/


/*
 * Browser type, document type etc...
 */
 
var browser; // current browser
var docType; // current document type

function Browser()
{
    // Record standard strings
    //
    this.agent = navigator.userAgent;
    
    // Record supported standards
    //
    this.supportsDOMRanges = false;
    if (document.implementation && document.implementation.hasFeature)
        this.supportsDOMRanges = document.implementation.hasFeature("Range", "2.0");

    // Record browser type
    //
    this.isIE    = false;  // Internet Explorer
    this.isOP    = false;  // Opera
    this.isNS    = false;  // Netscape
    this.isFF    = false;  // Firefox
    this.isSI    = false;  // Safari
    this.isCR    = false;  // Chrome
    this.version = null;
    
    var s, i;

    s = "Opera";
    if ((i = this.agent.indexOf(s)) >= 0)
    {
        this.isOP = true;
        this.version = parseFloat(this.agent.substr(i + s.length));
        return;
    }

    s = "Netscape6/";
    if ((i = this.agent.indexOf(s)) >= 0)
    {
        this.isNS = true;
        this.version = parseFloat(this.agent.substr(i + s.length));
        return;
    }
    
    s = "Firefox/";
    if ((i = this.agent.indexOf(s)) >= 0)
    {
        this.isFF = true;
        this.version = parseFloat(this.agent.substr(i + s.length));
        return;
    }

    s = "Chrome/";
    if ((i = this.agent.indexOf(s)) >= 0)
    {
        this.isCR = true;
        //this.version = parseFloat(this.agent.substr(i + s.length));
        return;
    }  
    
    s = "Safari/";
    if ((i = this.agent.indexOf(s)) >= 0)
    {
        this.isSI = true;
        this.version = parseFloat(this.agent.substr(i + s.length));
        return;
    }  

    // Treat any other "Gecko" browser as Netscape 6.1.

    s = "Gecko";
    if ((i = this.agent.indexOf(s)) >= 0)
    {
        this.isNS = true;
        this.version = 6.1;
        return;
    }

    s = "MSIE";
    if ((i = this.agent.indexOf(s)))
    {
        this.isIE = true;
        this.version = parseFloat(this.agent.substr(i + s.length));
        return;
    }
}

browser = new Browser();

function DocType()
{
    var re = /\s+(X?HTML)\s+([\d\.]+)\s*([^\/]+)*\//gi;
    var ok = false;
    
    if (typeof document.namespaces != "undefined")
        ok = document.all[0].nodeType == 8 ? re.test(document.all[0].nodeValue) : false;
    else
        ok = document.doctype != null ? re.test(document.doctype.publicId) : false;
    
    var dt = new Object();
    
    if (ok)
    {
        dt['found'] = true;
        dt['xhtml'] = RegExp.$1;
        dt['version'] = RegExp.$2;
        dt['importance'] = RegExp.$3;
    } else {
        dt['found'] = false;
    }
    
    return dt;
}

docType = new DocType();

/*
 * Querying window, document, iframes, container window 
 */
 
 function _getWindow(oWindow)
{
    if (!oWindow) return window;
    else return oWindow;
}

function _getDocument(oDocument)
{
    if (!oDocument)
    {
        alert("oops");   
        return document;
    }
    else return oDocument;
}

function getContainerWindow(oDoc)
{
    if (browser.isIE)
    {
        return oDoc.parentWindow;
    }
    else
    {
        return oDoc.defaultView;
    }
}

function getIframeDocument(iframe)
{
	if (browser.isIE)
	{
		return iframe.document;
	}
	
	return iframe.contentDocument;	
}

/*
 * Style sheets and javascript
 */

function resetDocument(oDoc)
{
    oDoc.body.innerHTML = "";

    for (i=0; (a = oDoc.getElementsByTagName("link")[i]); i++)
    {
	    if (a.getAttribute("rel").indexOf("style") != -1)
	    {
		    a.disabled = true;
	    }
    }
}

function addStyleSheet(oDoc, url)
{
    var css = oDoc.createElement("link");
    css.rel = "stylesheet";
    css.type = "text/css";
    css.href = url;
    oDoc.getElementsByTagName("head")[0].appendChild(css); 
}

function loadScriptModule(oDoc, url)
{
    var js = oDoc.createElement("script");
    js.language = "javascript";
    js.type = "text/javascript";
    js.src = url;
    oDoc.getElementsByTagName("head")[0].appendChild(js); 
}

/*
 * Events
 */
 
function getEventSource(e, oWin)
{
	var targ;
	var evt;
	if (!e) evt = _getWindow(oWin).event;
	else evt = e;
	
	if (evt.target) targ = evt.target;
	else if (evt.srcElement) targ = evt.srcElement;

	if (targ.nodeType == 3) // defeat Safari bug
		targ = targ.parentNode;
		
	return targ;
}

function cancelBubbling(e, oWin)
{
    // see http://www.w3.org/TR/DOM-Level-3-Events/events.html#Events-flow
    var _e = e;
    
    if (!_e)
    {
        _e = _getWindow(oWin).event;
	}
	
	if (_e.cancelBubble != undefined)
	{
	    _e.cancelBubble = true;
	}
	
	if (_e.preventDefault)
	{
	    _e.preventDefault();
	}
	
	if (_e.stopPropagation)
	{
	    _e.stopPropagation();
	}
	
	return false;
}

function attachEventToElement(oElement, eventName, fnEvent, useCapture)
{
    // Typically you will always set useCapture to false, simply because IE does not support useCapture == true.
    // SEE http://www.w3.org/TR/DOM-Level-3-Events/events.html#Events-flow to understand how this issue works. 
    //
    if (oElement.attachEvent) // IE
    {
        oElement.attachEvent(eventName, fnEvent);
    }
    else if (oElement.addEventListener) // FF, Safari etc
    {
        // ondblclick -> dblclick so using substr
        oElement.addEventListener(eventName.substr(2), fnEvent, useCapture);   
    }
    else
        alert("Warning addEventListener not supported!");
}

function detachEventFromElement(oElement, eventName, fnEvent, useCapture)
{
    if (oElement.detachEvent) // IE
    {
        oElement.detachEvent(eventName, fnEvent);
    }
    else if (oElement.removeEventListener)
    {
        oElement.removeEventListener(eventName.substr(2), fnEvent, useCapture);
    }
    else
        alert("Warning detachEventFromElement not supported!");
}

/* 
 * Keyboard events and keystroke manipulation
 */

function setKeyPressed(e, code, oWin)
{
	if (!e) e = _getWindow(oWin).event;
	
	if (e.keyCode)
	{
	    // sometimes property only a getter
	    try {
	        e.keyCode = code;
	    }
	    catch(e)
	    {
	    }
	}
}

function getKeyPressed(e, oWin)
{
    var code;
	if (!e) e = _getWindow(oWin).event;
	if (e.keyCode) code = e.keyCode;
	else if (e.which) code = e.which;
	
	return code;
}

function wasShiftKeyPressed(e, oWin)
{
	if (!e) e = _getWindow(oWin).event;
	if (e.shiftKey) return e.shiftKey;
	return false;
}

function wasCtrlKeyPressed(e, oWin)
{
	if (!e) e = _getWindow(oWin).event;
	if (e.ctrlKey) return e.ctrlKey;
	return false;
}

function wasAltKeyPressed(e, oWin)
{
	if (!e) e = _getWindow(oWin).event;
	if (e.altKey) return e.altKey;
	return false;
}

function wasMetaKeyPressed(e, oWin)
{
	if (!e) e = _getWindow(oWin).event;
	if (e.metaKey) return e.metaKey;
	return false;
}

/*
 * Mouse events and pointer position
 */

function getMicrosoftMouseClick(e, oWin)
{
	var clicked;
	if (!e) e = _getWindow(oWin).event;
	if (e.which) clicked = e.which;
	else clicked = e.button;

	if (!browser.isIE)
	{
	    // TO DO: Only checked on Firefox!! Probably all different!
	    if (clicked == 1)
	        return 0;
	    if (clicked == 2)
	        return 4;
	    if (clicked == 3)
	        return 2;
	}
	
	return clicked;
}

function getMouseCoordinates(e, oWin)
{
	var posx = 0;
	var posy = 0;
	if (!e) e = _getWindow(oWin).event;
	if (e.pageX || e.pageY)
	{
		posx = e.pageX;
		posy = e.pageY;
	}
	else if (e.clientX || e.clientY)
	{
	    var scroll = getWindowScrollOffsets(_getWindow(oWin));
		posx = e.clientX + scroll["x"];
		posy = e.clientY + scroll["y"];
	}
	
	var coords = { left:posx, top:posy };
	
	return coords;
	
	// warning, changed return mechanism
}

function getMouseComeFrom(e, oWin)
{
	var relTarg = null;
	if (!e) e = _getWindow(oWin).event;
	if (e.relatedTarget) relTarg = e.relatedTarget;
	else if (e.fromElement) relTarg = e.fromElement;
	return relTarg;
}

function getMouseGoneToo(e, oWin)
{
	var relTarg = null;
	if (!e) e = _getWindow(oWin).event;
	if (e.relatedTarget) relTarg = e.relatedTarget;
	else if (e.toElement) relTarg = e.toElement;
	return relTarg;
}

function didMouseLeaveClientArea(e, oWin)
{
    // ASSUMPTION: client objects will not have set onmouseout event
    // On M$ systems use onmouseleave which doesn't bubble (also has a onmouseenter function)
    // Remember too, that if you move your mouse away too quickly, onmouseout
    // may not get called by the browser. Ideally then, you should also set a timeout
    // that checks whether mouse out has occurred... doesn't that make you want to be sick!
	if (!e) e = _getWindow(oWin).event;
	var tg = (_getWindow(oWin).event) ? e.srcElement : e.target;
	//if (tg.onmouseout == undefined) return false;
	// Curses to the assumption!
	var reltg = (e.relatedTarget) ? e.relatedTarget : e.toElement;
	while (reltg != tg && reltg.nodeName != 'BODY')
		reltg = reltg.parentNode
	if (reltg == tg) return false;
	return true;
}

/*
 * Selection and range object querying and manipulation
 */

function getSelectionObject(oWin)
{
	if (oWin.getSelection)
	{
		return oWin.getSelection();
	}
	else if (oWin.document.getSelection)
	{
		return oWin.document.getSelection();
	}
	else if (oWin.document.selection)
	{
		return oWin.document.selection;
	}
}

function getSelectionRange(oWin)
{
    var oSel = getSelectionObject(oWin);
    var oRng;
	if (_getWindow(oWin).document.all)
	{
		if (oSel != null) 
		{
		    oRng = oSel.createRange();
	    }
	} else {
		oRng = oSel.getRangeAt(oSel.rangeCount - 1).cloneRange();
	}
	return oRng;
}

function isSelectionEmpty(oWin)
{
    var oSel = getSelectionObject(oWin);
    
	if (_getWindow(oWin).document.all)
	{
		if (oSel)
		{
		    try {
		        var oRng = oSel.createRange();
		        
		        if (oRng && oRng.boundingWidth == 0 && oRng.htmlText == "")
		        {
		            return true;   
		        }		    
		    }
		    catch (e)
		    {
		        // Access Denied sometimes given
		        return false;
		    }
        }
	} else {
		return oSel.isCollapsed;
	}
	return false;
}

function clearSelectedText(oWin)
{
    var oSel = getSelectionObject(oWin);
    // TO DO: Need to make this work for Safari 3.0.4+
    //
    if (browser.isIE)
    {
        // By clearing the selection, we cause the contents of the end text node to be merged
        // into the contents of the start text node. This is desirable behaviour, but does not
        // happen in Firefox. Therefore we might choose the inferior implementation below to
        // achieve consistency.
        //
        oSel.clear();
        var oRange = oSel.createRange();
        //alert(oRange.text);
        return;
        
        // The code below produces behaviour more similar to Firefox.
        //
        var oRng;
        if (oSel != null) oRng = oSel.createRange();
        if (oRng)
        {
            oRng.text = "";
        }
        oSel.empty(); // otherwise we get a 1 char selection remaining
    } else {
        oSel.deleteFromDocument();
    }
}

function unselectSelection(oWin)
{
    if (browser.isIE)
    {
        oWin.document.selection.empty();
    }
    else
    {
        getSelectionObject(oWin).removeAllRanges();
    }
}

function selectNode(oNode)
{
    // We need to create a range from the start of the first text node
    // to the end of the last text node within the node object.
    
    var firstTextNode = GetLeftmostLeafNodeBelowWith(oNode, 3, null, null, null);
    var lastTextNode = GetRightmostLeafNodeBelowWith(oNode, 3, null, null, null);

    var oWin = VxWindow();        
    var oRange = createTextRange(oWin);
    
    if (oRange.setStart) // FF, Safari, Chrome, ...
    {        
        oRange.setStart(firstTextNode, 0);
        oRange.setEnd(lastTextNode, lastTextNode.data.length);
    }
    else if (oRange.moveToElementText) // IE
    {
        var oRange2 = oRange.duplicate();
        oRange.moveToElementText(firstTextNode.parentNode);        
        oRange2.moveToElementText(lastTextNode.parentNode);
        oRange.setEndPoint("EndToEnd", oRange2);
    }
    else {
        alert("Selection not possible on this browser.");
    }            
    
    selectRangeObject(oWin, oRange);
}

function createTextRange(oWin)
{
    if (oWin.document.createRange)
    {
        return oWin.document.createRange();
    }
    else if (oWin.document.body.createTextRange)
    {
        return oWin.document.body.createTextRange();
    }
    return null;
}

function selectRangeObject(oWin, oRange)
{
    if (oWin.getSelection)
    {
        var oSelection = oWin.getSelection();

        oSelection.removeAllRanges();
        oSelection.addRange(oRange);
    }
    else if (oRange.select)
    {
        oRange.select();
    }
}

function getRangeNodeAndOffset(oRng, start)
{
    if (browser.supportsDOMRanges)
    {
        // Yippeeee the browser supports DOM Ranges 2.0... everything is really easy!
        //
        if (start)
            return { nodeObject: oRng.startContainer, offset: oRng.startOffset };
        else
            return { nodeObject: oRng.endContainer, offset: oRng.endOffset };
    }
    else if (browser.isIE)
    { 
        var nodeOffset = { nodeObject: null, offset: null };
        
        var oCursorRng = oRng.duplicate();
        
        var oOrigParent = getRangeParent(oRng);
        
        // Find parent node (at start or end of selection as appropriate)
        //
        oCursorRng.collapse(start);
        var oParent = getRangeParent(oCursorRng); 
        
        // See if the start/end of the range is an object or is text
        //
        if (oParent.hasChildNodes() == false)
        {
            // It is an object!
            //
            nodeOffset["nodeObject"] = oParent;
            nodeOffset["offset"] = GetChildIndex(oParent);
            return nodeOffset;
        }
        
        // We are working with a text node: move cursor range to the start
        //
        oCursorRng.moveToElementText(oParent);      // and reset cursor range to...
        oCursorRng.collapse(true);                  // ...beginning of this parent
        
        // IE Bug: if we do not do the following, oRng.compareEndPoints(operation, oCursorRng) fails when
        // oCursorRng is "" and oRng starts at the first character of oParent. It seems that the following
        // two lines initialize the internal pointers of oCursorRng so that the comparison works correctly.
        oCursorRng.moveEnd("character", 1);
        oCursorRng.moveEnd("character", -1);
        //
        
        // Use cursor range to calculate offset of either beginning or end of
        // the selection range
        //
        var charCount = 0;
        var operation = start ? "StartToEnd" : "EndToEnd";
        var result;
        while ((result = oRng.compareEndPoints(operation, oCursorRng)) > 0)
        {
            oCursorRng.moveEnd("character", 1);
        }

        charCount = oCursorRng.text.length; // cannot charCount++ or charCount += oCursorRng.moveEnd counts extra chars when <img> <br> etc appear
        
        //if (!start) charCount--;

        // There are spurious \r and \n chars in the range so count them and then decrement the charCount
        // by that number of characters
        //
        charCount -= countCRLF(oCursorRng.text);
        
        // Get node and offset within node from overall char count within parent
        //
        var tnPosition = getTextnodeOffsetAtElementCharCount(oParent, charCount, start);
        
        if (tnPosition["textNode"] != null)
        {
            // We have found offset within expected element
            //
            nodeOffset["nodeObject"] = tnPosition["textNode"];
            nodeOffset["offset"] = tnPosition["offset"];
        }
        else
        {
            // Offset does not exist within oParent element, since tnPosition["textNode"] == null. In
            // this eventuality, offset records the number of characters remaining to be counted in 
            // seqential elements. If the offset is 0, this implies the count takes us to the beginning of
            // the next element.
            //
            if (tnPosition["offset"] == 0)
            {
                nodeOffset["nodeObject"] = GetNextLeafNode(tnPosition["lastTextNode"], 0);
                
                if (nodeOffset["nodeObject"].nodeType == 1)
                {
                    nodeOffset["offset"] = GetChildIndex(nodeOffset["nodeObject"]);
                }
                else {
                    nodeOffset["offset"] = 0; // i.e. beginning of text in following node
                }
            }
            else
            {
                debugOut("getRangeNodeAndOffset - insufficient characters in node for offset (" + tnPosition["offset"] + ") remaining");
           
                nodeOffset["nodeObject"] = null;
                nodeOffset["offset"] = tnPosition["offset"];
                nodeOffset["lastTextNode"] = tnPosition["lastTextNode"];
            }
        }

        return nodeOffset;
    }
    
    debugOut("getRangeNodeAndOffset - not implemented on\n" + browser.agent);
    return null;
}

function getTextnodeOffsetAtElementCharCount(oElem, count, bStart)
{
    var oLastTN = null;
    
    for (var i=0; i < oElem.childNodes.length; i++)
    {
        if (oElem.childNodes[i].nodeType == 3)
        {
            oLastTN = oElem.childNodes[i];

            if (oElem.childNodes[i].data.length > (count + (bStart ? 0 : -1)))
            //if (oElem.childNodes[i].data.length >= count)
            {
                return { textNode: oElem.childNodes[i], offset: count, lastTextNode: oLastTN }
            }
            else
            {
                count -= oElem.childNodes[i].data.length;
            }
        }
        else
        {
//            // Madness No. 1... on IE, if we are transitioning from plain text within the parent to a block-level
//            // node then the selection range contains and extra CR LF
//            var preCnt = 0;
//            if (i && oElem.childNodes[i-1].nodeType == 3)
//            {
//                preCnt = -2;
//            }
//             
            var r = getTextnodeOffsetAtElementCharCount(oElem.childNodes[i], count);
            
//            // Madness No. 2... on IE, if we are selecting text within a node that contains preceding *element* nodes,
//            // and those element nodes are block-level, then the selection range contains an extra CR LF as we
//            // leave each one!
//            
//            // TO DO: Sort out this nightmare... for example, what happens if we define display: block on an
//            // inline element, will this effect the way everything works?
//            
//            if (IsBlockLevelElement(oElem.childNodes[i]))
//            {
//                r["offset"] -= 2;
//                r["offset"] += preCnt;
//            }
//            
            if (r["textNode"] != null)
            {
                return r;
            }
            
            count = r["offset"];
            oLastTN = r["lastTextNode"]
        }
    }
    
    return { textNode: null, offset: count, lastTextNode: oLastTN };
}

function countCRLF(sText)
{
    var count = 0;

    for (var i = 0; i < sText.length; i++)
    {
        var c = sText.charAt(i);

        if (c == "\r" || c == "\n")
        {
            count++;
        }
    }

    return count;
}

function getCharCountInElement(oElem)
{
    var cnt = 0;
    
    for (var i=0; i < oElem.childNodes.length; i++)
    {
        if (oElem.childNodes[i].nodeType == 3)
        {
            cnt += oElem.childNodes[i].data.length;
        }
        else
            cnt += getCharCountInElement(oElem.childNodes[i]);
    }
    
    return cnt;
}

function getRangeStartElement(oRng)
{
    if (browser.isIE)
    {
        var oStartRng = oRng.duplicate();
        oStartRng.collapse(true);
        return getRangeParent(oStartRng);        
    }
    else
    {
        var oSC = oRng.startContainer;

        if (oSC.nodeType != 1)
        {
            return oSC.parentNode;
        }
        return oSC;
    }
}

function getRangeEndElement(oRng)
{
    if (browser.isIE)
    {
        var oEndRng = oRng.duplicate();
        oEndRng.collapse(false);
        return getRangeParent(oEndRng);        
    }
    else
    {
        var oEC = oRng.endContainer;

        if (oEC.nodeType != 1)
        {
            return oEC.parentNode;
        }
        return oEC;
    }
}

function getRangeParent(oRng)
{
    if (browser.isIE)
    {
        return oRng.parentElement();
    }
    else {
        return oRng.endContainer.parentNode;
    }
}

/*
 * Query element and document dimensions and position
 */

function getElementHeight(e)
{
	if (browser.isNS)
	{
	    return e.ownerDocument.getBoxObjectFor(e).height;
	}
	return e.offsetHeight;
}

function getElementWidth(e)
{
	if (browser.isNS)
	{
		return e.ownerDocument.getBoxObjectFor(e).width;
	}
	return e.offsetWidth;
}

// Super reliable function for finding the position of an element within the document.
// If you use this function, you do not need to worry about scroll offsets etc.
//
function getDocumentCoords(elem)
{
	var curleft = curtop = 0;
	
	if (elem.offsetParent)
	{
		do
		{
		    curleft += elem.offsetLeft;
		    curtop += elem.offsetTop;
		}
		while (elem = elem.offsetParent);
	}
	return { left:curleft, top:curtop};
}

function getBrowserViewport(oWindow)
{
    var vpWidth;
    var vpHeight;

    var oDoc = _getWindow(oWindow).document;
    var oDocElem = oDoc.documentElement;
    
    if (typeof oDocElem != 'undefined')
    {
        // This works so long as we are in stanards compliant mode ie. with a DOC type
        vpWidth = oDocElem.clientWidth;
        vpHeight = oDocElem.clientHeight;   
    }
    else
    {
        if (typeof window.innerWidth != 'undefined')
        {
            // maybe we're not running in standards mode. O dear...
            vpWidth = window.innerWidth;
            vpHeight = window.innerHeight;
            
            // TO DO: we need to detect if the scrollbars are showing and substract them if they are
        }
        else
        {
            // REALLY old IE... getting desperate...
            vpWidth = document.body.clientWidth;
            vpHeight = document.body.clientHeight;
        }
    }
    
    return { width: vpWidth, height: vpHeight }
}

function getWindowScrollOffsets(oWin)
{
    var x,y;
    
    if (docType['found'])
    {
        if (browser.isSI || browser.isCR)
        {
            x = oWin.document.body.scrollLeft;
            y = oWin.document.body.scrollTop;
        } else {
            var oDE = oWin.document.documentElement;
            x = oDE.scrollLeft;
            y = oDE.scrollTop;
        }
    }
    else
    {
        x = oWin.document.body.scrollLeft;
        y = oWin.document.body.scrollTop; 
    }

    return { x:x, y:y };    
}

//function getBoxAroundElementContents(e, oWin, adjustForWindow)
//{   
//    // get element extremities
//    var left = getElementLeft(e);
//    var top = getElementTop(e);
//    var right = getElementLeft(e) + getElementWidth(e);
//    var bottom = getElementTop(e) + getElementHeight(e);

//    // get extremities of children
//    
//    // Yup boys and girls, FF doesn't necessarily make the width of a container such as
//    // a span or div equal to width of its contents... for example if the contents happens
//    // to be a table rather than simple text... so we have to look at all its contents
//    // to try and work out the best dimensions! In fairness, even M$ IE can also screw up
//    // in a few cases, such as ignoring a trailing <BR> inside a span contents
//     
//    var cCoords = _getBoxAroundElementContents(e);
//    
//    // get overall extremities
//    
//    if (left > cCoords.left) left = cCoords.left;
//    if (right < cCoords.right) right = cCoords.right;
//    if (top > cCoords.top) top = cCoords.top;
//    if (bottom < cCoords.bottom) bottom = cCoords.bottom;
//    
//    // create box
//    
//    var oBox = { left:left, top:top, width:(right-left), height:(bottom-top)};
//    
//    // adjust to legal position and to fit window size 
//    
//    if (oWin && adjustForWindow)
//    {
//        if (oBox.left < 0)
//        {
//            oBox.left = 0;
//        }
//        
//        if (oBox.top < 0)
//        {
//            oBox.top = 0;
//        }
//            
//        var pageSize = getBrowserViewport(oWin);

//        if ((oBox.left + oBox.width) > pageSize.width)
//        {
//            oBox.width = oBox.width - ((oBox.left + oBox.width) - pageSize.width);
//            
//            if (oBox.width < 0)
//            {
//                oBox.width = 0;
//            }
//        }
//        
//        if ((oBox.bottom + oBox.height) > pageSize.height)
//        {
//            oBox.height = oBox.height - ((oBox.bottom + oBox.height) - pageSize.height);
//            
//            if (oBox.height < 0)
//            {
//                oBox.height = 0;
//            }
//        }
//    }
//    
//    return oBox;
//}

//function _getBoxAroundElementContents(e)
//{   
//    var left = 999999;
//    var top = 99999999;
//    var right = 0;
//    var bottom = 0; 
//    
//    for (var i=0; i<e.childNodes.length; i++)
//    {
//        var c = e.childNodes[i];

//        if (c.nodeType == 1 && c.style.position.toLowerCase() != "absolute")
//        {
//            // First examine extremities of child...
//            //
//            if (getElementLeft(c) < left)
//                left = getElementLeft(c);        

//            if ((getElementLeft(c) + getElementWidth(c)) > right)
//                right = (getElementLeft(c) + getElementWidth(c));                 
//                
//            if (getElementTop(c) < top)
//                top = getElementTop(c);
//                
//            if ((getElementTop(c) + getElementHeight(c)) > bottom)
//                bottom = (getElementTop(c) + getElementHeight(c));
//            
//            // ...then of its children
//            //
//            var cCoords = _getBoxAroundElementContents(c);
//            
//            if (cCoords.left < left)
//                left = cCoords.left;
//            
//            if (cCoords.right > right)
//                right = cCoords.right;
//            
//            if (cCoords.top < top)
//                top = cCoords.top;
//                
//            if (cCoords.bottom > bottom)
//                bottom = cCoords.bottom;
//        }
//    }
//    
//    var coords = { left:left, top:top, right:right, bottom:bottom };
//    
//    return coords;
//}

/*
 * Querying nodes, child nodes and node tree cloning
 */

function getFirstChildElem(oNode)
{
    if (oNode.childNodes.length == 0)
    {
        return null;
    }
    
    if (oNode.childNodes[0].nodeType == 1)
    {
        return oNode.childNodes[0];
    }
    
    return getNextSiblingElem(oNode.childNodes[0]);
}

function getNextSiblingElem(oNode)
{
    oNode = oNode.nextSibling;
    while (oNode && oNode.nodeType != 1)
    {
        oNode = oNode.nextSibling;
    }
    
    return oNode;
}

function getPreviousSiblingElem(oNode)
{
    oNode = oNode.previousSibling;
    while (oNode && oNode.nodeType != 1)
    {
        oNode = oNode.previousSibling;
    }
    
    return oNode;
}

function getChildElemCount(oNode)
{
    var c = 0;
    for (var i = 0; i < oNode.childNodes.length; i++)
    {
        if (oNode.childNodes[i].nodeType == 1)
        {
            c++;
        }
    }
    
    return c;
}

function cloneNode(node, deep)
{
    if (browser.isIE)
    {
        var cloned = node.cloneNode(deep);
        
        // deal with IE bug where relative hrefs in cloned anchor nodes are prefixed with about:
        
        // process sub nodes
        var aAnchors = cloned.getElementsByTagName("a");
        
        for (var i = 0, oAnchor; oAnchor = aAnchors[i]; i++)
        {    
            var href = oAnchor.getAttribute("href");
            if (href.indexOf("about:") == 0)
            {
                oAnchor.setAttribute("href", href.replace(/about:/, ""));
            } 
        }
        
        // process node
        if (cloned.nodeName.toLowerCase() == "a" && cloned.href.indexOf("about:") == 0)
        {
            cloned.setAttribute("href", cloned.href.replace(/about:/, ""));
        }
        
        return cloned;
    }
    else
    {
        return node.cloneNode(deep);
    }
}

/* 
 * CSS styles assigned to elements
 */

function hasCssClass(elem, className)
{
    className = className.toLowerCase();
    var aClasses = elem.className.split(" ");

    for (var i = 0; i < aClasses.length;  i++)
    {
        if (aClasses[i].toLowerCase() == className)
        {
            return true;
        }
    }

    return false;
}

function appendCssClass(elem, className)
{
    elem.className = elem.className + " " + className;
}

function removeCssClass(elem, className)
{
    var aClasses = elem.className.split(" ");

    for (var i = 0; i < aClasses.length; i++)
    {
        if (aClasses[i].toLowerCase() == className.toLowerCase())
        {
            aClasses[i] = "";
        }
    }

    elem.className = aClasses.join(" ");
}

function setElemCssStyle(oElem, cssText)
{
	if (browser.isIE)
	    oElem.style.setAttribute("cssText", cssText);
	else
	    oElem.setAttribute("style", cssText);
}

function getElemCssStyle(oElem)
{
    return oElem.style.cssText;
}

function setIndividualStyle(oElem, sStyleName, sStyleValue)
{    
    // Split the style string into individual style attributes
    // and then split each style into name and value
    // rebuilding the full style string as we go

    var sFullStyle = getElemCssStyle(oElem);
    var aStyles = sFullStyle.split(";");

    sFullStyle = "";

    for (var i = 0; i < aStyles.length; i++)
    {
        var sStyle = TrimWhitespace(aStyles[i]);

        if (sStyle.length > 0)
        {
            // Split the style into a name and value

            var aParts = sStyle.split(":");
            var sName = TrimWhitespace(aParts[0]);
            var sValue = TrimWhitespace(aParts[1]);

            if (sName.toLowerCase() != sStyleName.toLowerCase())
            {
                // Add the style attribute to the string if it does not
                // match the style we are setting
                
                sFullStyle += sName + ": " + sValue + ";";
            }
        }
    }

    // Add the style we are setting to the end of the style string

    sFullStyle += sStyleName + ": " + sStyleValue + ";";
    
    // Set the style property of the element

    setElemCssStyle(oElem, sFullStyle);
}

function getCurrentStyle(oElem, sStyleProp)
{
    var sStyle;
    
    if (oElem.currentStyle)
        sStyle = oElem.currentStyle[sStyleProp];
    else if (window.getComputedStyle)
        sStyle = document.defaultView.getComputedStyle(oElem, null).getPropertyValue(sStyleProp);
        
    return sStyle;
}

function getStyleFloat(oElem)
{
    var floatStyle = getCurrentStyle(oElem, "styleFloat");

    if (!floatStyle)
        floatStyle = getCurrentStyle(oElem, "float");

    return floatStyle;
}
function getStylePaddingLeft(oElem)
{
    var paddingStyle = getCurrentStyle(oElem, "paddingLeft");

    if (!paddingStyle)
        paddingStyle = getCurrentStyle(oElem, "padding-left");

    return paddingStyle;
}
function getStylePaddingRight(oElem)
{
    var paddingStyle = getCurrentStyle(oElem, "paddingRight");

    if (!paddingStyle)
        paddingStyle = getCurrentStyle(oElem, "padding-right");

    return paddingStyle;
}
function getWidthWithoutPadding(oElem)
{
    // TODO: Currently this only works for padding in px not em, %, or pt

    var iPaddingLeft = parseInt(getStylePaddingLeft(oElem));
    var iPaddingRight = parseInt(getStylePaddingRight(oElem));

    return oElem.offsetWidth - iPaddingLeft - iPaddingRight;
}

/*
 * Dealing with text and html code
 */
 
 function getTextFromHTML(html)
{
    html = html.replace(/<br[\/]?>/gim, "\n"); // replace all line break tags with \n
    html = html.replace(/<\/p>/gim, "\n\n"); // replace all closing paragraph tags with \n\n
    html = html.replace(/<[^>]*>/gim, ""); // make sure all tags are gone, otherwise firstChild may not be a text node!
    var tmp = document.createElement("div");
    tmp.innerHTML = html;
    return tmp.firstChild.nodeValue;
}

function htmlEscapeText(text)
{
    var tn = document.createTextNode(text);
    var sp = document.createElement("span");
    sp.appendChild(tn);
    return sp.innerHTML;
}

function getInnerText(oNode)
{
    if (oNode.nodeType == 3)
        return node.data;

    return oNode.innerText || oNode.textContent;
}

/*
 * Miscellaneous
 */

function setAttribute(n, attr, value)
{
	if (browser.isIE)
	{
		return n[attr] = value;
	}

	return n.setAttribute(attr, value);
}

function makeOpaque(oElem, factor)
{
	if (browser.isIE)
	{
		if (factor < 1)
		{
		    var perc = Math.floor(factor * 100);
			oElem.style.filter = "filter:gray() alpha(opacity=" + perc + ")";
		}
		else {
			oElem.style.filter = "filter:gray() alpha(opacity=100)";		
			oElem.style.filter = "";
        }
	}
	else if (browser.isSI || browser.isCR)
	{
	    if (factor < 1)
	    {
		    oElem.style.opacity = factor;
		}
		else {
		    oElem.style.opacity = 1;
		}
	}
	else
	{
	    if (factor < 1)
	    {
		    oElem.style.MozOpacity = factor;
		}
		else {
		    oElem.style.MozOpacity = 1;
		}
	}	
}

function shiftArray(arr) // check whether still necessary
{
    if (arr.shift)
    {
        return arr.shift();
    }
    
    if (arr.length)
    {
        var i = arr[0];
        
        arr = arr.slice(1);
        
        return i;
    }
    
    return null;
}

function unshiftToArray(arr, item) // check whether still necessary
{
    if (arr.unshift)
    {
        return arr.unshift(item);
    }
    
    var res = new Array(item);
    res.concat(arr);
    arr = res;
    return arr.length;
}

/*
 * Internal
 */
 
function alertCrossPlatform()
{
	alert("Cross-platform support missing from function: Please implement");
}

