window.VisionX_nodes = true;
//////////////////////////////////////////////////////////////////////////////////////

//**********************************************************************************//
// 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.
//***********************************************************************************//

function InsertAfterNode(oNode, oInsertNode)
{
    if (oNode.nextSibling)
    {
        oNode.parentNode.insertBefore(oInsertNode, oNode.nextSibling);
    } else
    {
        oNode.parentNode.appendChild(oInsertNode);
    }
}

function AppendNextSiblingsTo(oNode, oDestination)
{
    var oCurr = oNode.nextSibling;
    while (oCurr != null)
    {
        var oAdd = oCurr;
        oCurr = oCurr.nextSibling;
        oDestination.appendChild(oAdd);
    }
}

function InsertInParent(oInsert, oParent, childIdx)
{
    if (oParent.hasChildNodes() == false || childIdx == oParent.childNodes.length)
    {
        oParent.appendChild(oInsert);
    }   
    else
    {
        oParent.insertBefore(oInsert, oParent.childNodes[childIdx]);
    }
}

function IsElement(oNode)
{
    return oNode.nodeType == 1;
}

function IsNodeNotElement(oNode)
{
    return oNode.nodeType != 1;
}

function IsNodeTextNode(oNode)
{
    return oNode.nodeType == 3;
}
//function IsNodeTextNode(oNode)
//{
//    return oNode.nodeType == 1;
//}

function GetChildIndex(oChild, fShouldIgnore)
{
    var idx = 0;
    var oParent = oChild.parentNode;
    
    for (var i = 0; i < oParent.childNodes.length; i++)
    {
        if (oChild == oParent.childNodes[i])
        {
            return idx;
        }
        
        if (!fShouldIgnore || !fShouldIgnore(oParent.childNodes[i]))
        {
            idx++;
        }    
    }
    
    return -1;
}

function CountChildNodes(oParent, fShouldIgnore)
{
    var c = 0;
    
    for (var i = oParent.childNodes.length-1; i >= 0; i--)
    {
        if (!fShouldIgnore || !fShouldIgnore(oParent.childNodes[i]))
        {
            c++;
        }
    }
    
    return c;
}

function GetChildNode(oParent, index, fShouldIgnore)
{   
    var c = -1;
    
    for (var i = 0; i < oParent.childNodes.length; i++)
    {
        if (!fShouldIgnore || !fShouldIgnore(oParent.childNodes[i]))
        {
            c++;
        }
        
        if (c == index)
        {
            return oParent.childNodes[i];
        }
    }
    
    return null;
}

function RemoveChildNodes(oParent, fShouldDelete, recursive)
{
    for (var i = oParent.childNodes.length-1; i >= 0; i--)
    {
        var oChild = oParent.childNodes[i];
        
        if (!fShouldDelete || fShouldDelete(oChild))
        {
            oParent.removeChild(oChild);
        }
        else if (recursive && oChild.nodeType == 1)
        {
            RemoveChildNodes(oChild, fShouldDelete, true);
        }
    }
}

function GetPreviousSibling(oNode, fShouldIgnore)
{
    while (oNode.previousSibling)
    {
        if (fShouldIgnore && !fShouldIgnore(oNode.previousSibling))
            return oNode.previousSibling;
        
        oNode = oNode.previousSibling;
    }
    
    return null;
}

function GetNextSibling(oNode, fShouldIgnore)
{
    while (oNode.nextSibling)
    {
        if (fShouldIgnore && !fShouldIgnore(oNode.nextSibling))
            return oNode.nextSibling;
        
        oNode = oNode.nextSibling;
    }
    
    return null;
}

function GetLowestNextSibling(oNode, fShouldIgnore)
{
    do
    {
        var oNS = GetNextSibling(oNode, fShouldIgnore);
        if (oNS)
            return oNS;
        
        oNode = oNode.parentNode;
    }
    while (oNode.tagName.toLowerCase() != "body");
    
    return null;
}

function MoveSiblingsTo(oStart, oEnd, oNewParent)
{
    if (oStart == oEnd)
    {
        oNewParent.appendChild(oStart);   
    }
    else
    {
        var oCurr = oStart;
        var oPrev = null;
        
        while (oPrev != oEnd)
        {
            oPrev = oCurr;
            var oNext = oCurr ? oCurr.nextSibling : null;
            oNewParent.appendChild(oCurr);
            oCurr = oNext;
        }
    }
}

function MoveSiblingsAfter(oStart, oEnd, oNode)
{
    var oCurr = oStart;
    var oPrev = null;
    var oInsertBefore = oNode.nextSibling;
    
    while (oPrev != oEnd)
    {
        oPrev = oCurr;
        var oNext = oCurr ? oCurr.nextSibling : null;

        oNode.parentNode.insertBefore(oCurr, oInsertBefore);
        
        oCurr = oNext;
    }
}

function MergeContentsAndDeleteInvalidated(oElem, oElemMerge, oStopParent, fAutoCleanChild, fAllowToBeEmpty)
{
    var oCurr = oElemMerge.firstChild;
    while (oCurr != null)
    {
        var oAdd = oCurr;
        oCurr = oCurr.nextSibling;
        oElem.appendChild(oAdd);
    }
    
    DeleteNodeAndInvalidatedParents(oElemMerge, oStopParent, fAutoCleanChild, fAllowToBeEmpty);
}

// oStopParent - specifies highest ancestor we may delete
// fAutoCleanChild - returns whether a child should be automatically removed if no other children exist
// fAllowToBeEmpty - returns whether an ancestor is allowed to be empty
function DeleteNodeAndInvalidatedParents(oNode, oStopParent, fAutoCleanChild, fAllowToBeEmpty)
{
    do
    {            
        var oParent = oNode.parentNode;
        oParent.removeChild(oNode);
        oNode = oParent;
        
        // stop deleting up ancestor chain if current ancestor is allowed to be empty
        if (fAllowToBeEmpty && fAllowToBeEmpty(oNode))
            break;
            
        // stop deleting up the ancestor chain if we have reached the max ancestor we are allowed to delete
        if (oStopParent && oStopParent == oNode)
            break;        
    }
    while (CountChildNodes(oNode, fAutoCleanChild) == 0);
    
    // return the ancestor we stopped at
    return oNode;
}

function GetSharedParentAndBaseRoots(oNode1, oNode2)
{
    var oStack1 = GetNodeStack(oNode1);
    var oStack2 = GetNodeStack(oNode2);
    var oJoint = null;

    for (var i=0; i<oStack1.length; i++)
    {
        if (oStack1[i] == oStack2[i])
        {         
            if (oStack1[i].nodeName) //filter spurious ghost nodes inserted by browser
            {
                oJoint = oStack1[i];
            }
        }
        else
        {
            break;
        }
    }
    
    var oNode1Root = oStack1[i];
    var oNode2Root = oStack2[i];
    
    if (!oNode1Root) oNode1Root = oStack1[i-1];
    if (!oNode2Root) oNode2Root = oStack1[i-1];
    
    if (!oJoint) return null;
    
    var roots = { node1Root: oNode1Root, node2Root: oNode2Root, jointParent: oJoint }
    
    return roots;
}

function GetNodeStack(oNode)
{
    var res = new Array();
    
    while (oNode != null && oNode.nodeName.toLowerCase() != "html")
    {
        unshiftToArray(res, oNode);
        oNode = oNode.parentNode;
    }
    
    return res;
}

function GetAllBLCsInRange(oRange)
{    
    // Find the block level container ancestors of the start and end nodes
    
    var oBLC_Start = GetBlockLevelContainerAncestor(oRange.startNode);
    var oBLC_End = GetBlockLevelContainerAncestor(oRange.endNode);
    
    if (!oBLC_Start || !oBLC_End)
        return null;
        
    var aBLCs = new Array();
    
    aBLCs.push(oBLC_Start);
    
    if (oBLC_Start != oBLC_End)
    {
        var oCurrBLC = oBLC_Start;
        
        while (oCurrBLC)
        {
            oCurrBLC = GetNextBLC(oCurrBLC, oBLC_End);
            
            if (!oCurrBLC)
                break;

            aBLCs.push(oCurrBLC);
        }

        aBLCs.push(oBLC_End);
    }
    
    return aBLCs;
}

function GetNextBLC(oNode, oStopNode)
{
    var oCurrNode = GetNextNode(oNode);
    
    while (oCurrNode && oCurrNode != oStopNode)
    {    
        var bIsBLE = IsBlockLevelElement(oCurrNode);
        
        if (bIsBLE)
        {
            var bIsBLC = IsBlockLevelContainer(oCurrNode);
            var oInternalBLC = GetBLCInside(oCurrNode);
            
            if (oInternalBLC)
            {
                oCurrNode = oInternalBLC;
                continue;
            }
            else if (bIsBLC)
            {
                return oCurrNode;
            }            
        }
                    
        oCurrNode = GetNextNode(oCurrNode);
    }
    
    return null;
}

function GetNextNode(oNode)
{
    while (oNode)
    {
        if (oNode.nextSibling)
            return oNode.nextSibling;
            
        oNode = oNode.parentNode;        
    }
    
    return null;
}

function GetBLCInside(oNode)
{
    for (var i = 0; i < oNode.childNodes.length; i++)
    {
        var oCurr = oNode.childNodes[i];
        
        if (IsBlockLevelContainer(oCurr))
            return oCurr;
    }
    
    return null;
}

function NormalizeTextContainer(oContainer, recursive)
{
    var lastChild;
    var firstChild;
    
    if (oContainer.hasChildNodes())
    {
        var oCurr = oContainer.firstChild;
        
        while (oCurr)
        {
            if (oCurr.nodeType == 3) // text
            {
                if (oCurr.data.length == 0)
                {
                    var oNext = oCurr.nextSibling;
                    oCurr.parentNode.removeChild(oCurr);
                    oCurr = oNext;                    
                    continue;   
                }
                if (oCurr.nextSibling && oCurr.nextSibling.nodeType == 3)
                {
                    oCurr.appendData(oCurr.nextSibling.data);
                    oContainer.removeChild(oCurr.nextSibling);
                    continue;
                }
            }
            else if (oCurr.nodeType == 1 && recursive) // element
            {
                NormalizeTextContainer(oCurr, true);
            }

            if (!firstChild)
            {
                firstChild = oCurr;
            }
            
            lastChild = oCurr;
            oCurr = oCurr.nextSibling;
        }
    }
    
    return { startNode : firstChild, endNode : lastChild};
}

function NormalizeTwoSiblings(oOne, oTwo)
{
    if (oOne != null && oTwo != null)
    {
        oOne.appendData(oTwo.data);
        oOne.parentNode.removeChild(oTwo);
    }
}

function NormalizeTextNodesToRight(oNode)
{
    if (oNode)
    {
        var oBucketNode = oNode.nextSibling;
        
        if (oBucketNode)
        {
            var oNextSibling = oBucketNode.nextSibling;
             
            while (oNextSibling)
            {
                var oCurr = oNextSibling;
                oNextSibling = oCurr.nextSibling;
                
                if (oCurr.data.length > 0)
                {
                    oBucketNode.appendData(oCurr.data);
                }
                oCurr.parentNode.removeChild(oCurr);
            }
        }
    }
}

function NormalizeTextNodesToLeft(oNode)
{
    if (oNode)
    {   
        var oBucketNode = oNode.previousSibling;
        
        if (oBucketNode)
        {
            var oPrevSibling = oBucketNode.previousSibling;
             
            while (oPrevSibling)
            {
                var oCurr = oPrevSibling;
                oPrevSibling = oCurr.previousSibling;
                
                if (oCurr.data.length > 0)
                {
                    oBucketNode.insertData(0, oCurr.data)
                }
                oCurr.parentNode.removeChild(oCurr);
            }
        }
    }
}

function GetNextLeafNode(oNode, nodeType, attrNames, fIgnoreNode, bIncludeThisNode)
{
    if (bIncludeThisNode && IsElement(oNode) && oNode.firstChild)
    {
        var oLeafNode = GetLeftmostLeafNodeBelowWith(oNode.firstChild, nodeType, attrNames, null, fIgnoreNode);
        if (oLeafNode)
        {
            return oLeafNode;
        }
    }

    while (oNode.nodeType == 3 || oNode.tagName.toLowerCase() != "body")
    {
        while (oNode.nextSibling != null)
        {   
            var oLeafNode = GetLeftmostLeafNodeBelowWith(oNode.nextSibling, nodeType, attrNames, null, fIgnoreNode);
            if (oLeafNode)
            {
                return oLeafNode;
            }
            
            oNode = oNode.nextSibling;
        }
        
        oNode = oNode.parentNode;
    }
    
    return null;
}

function GetPreviousLeafNode(oNode, nodeType, attrNames, fIgnoreNode, bIncludeThisNode)
{
    if (bIncludeThisNode && IsElement(oNode) && oNode.lastChild)
    {
        var oLeafNode = GetRightmostLeafNodeBelowWith(oNode.lastChild, nodeType, attrNames, null, fIgnoreNode);
        if (oLeafNode)
        {
            return oLeafNode;
        }
    }

    while (oNode.nodeType == 3 || oNode.tagName.toLowerCase() != "body")
    {
        while (oNode.previousSibling != null)
        {            
            var oLeafNode = GetRightmostLeafNodeBelowWith(oNode.previousSibling, nodeType, attrNames, null, fIgnoreNode);
            if (oLeafNode)
            {
                return oLeafNode;
            }
            
            oNode = oNode.previousSibling;
        }
        
        oNode = oNode.parentNode;
    }
    
    return null;
}

function GetRightmostLeafNodeBelowWith(oNode, nodeType, attrNames, className, fIgnoreNode)
{
    if (oNode.childNodes && oNode.childNodes.length > 0)
    {
        for (var i=(oNode.childNodes.length-1); i >= 0; i--)
        {
            var oResult = GetRightmostLeafNodeBelowWith(oNode.childNodes[i], nodeType, attrNames, className, fIgnoreNode);
            if (oResult)
            {
                return oResult;
            }
        }
    }
    
    if (!HasMatchingCharacteristics(oNode, nodeType, attrNames, className, fIgnoreNode))
    {
        return null;
    }
        
    return oNode;
}

function GetLeftmostLeafNodeBelowWith(oNode, nodeType, attrNames, className, fIgnoreNode)
{
    if (oNode.childNodes && oNode.childNodes.length > 0)
    {
        for (var i=0; i < oNode.childNodes.length; i++)
        {
            var oResult = GetLeftmostLeafNodeBelowWith(oNode.childNodes[i], nodeType, attrNames, className, fIgnoreNode);
            if (oResult)
            {
                return oResult;
            }
        }
    }

    if (!HasMatchingCharacteristics(oNode, nodeType, attrNames, className, fIgnoreNode))
    {
        return null;
    }
        
    return oNode;
}

function AssignEventToMatchingChildren(oNode, eventName, fnEvent, fnIsMatching)
{
    function _AssignEventToMatchingChildren(_oNode)
    {            
        if (_oNode.childNodes.length > 0)
        {
            for (var i=0; i < _oNode.childNodes.length; i++)
            {
                if (_oNode.childNodes[i].nodeType == 1)
                {
                    _AssignEventToMatchingChildren(_oNode.childNodes[i]);
                }
            }
        }
        
        if (fnIsMatching(_oNode))
        {
            attachEventToElement(_oNode, eventName, fnEvent); 
        }
    }
    
    _AssignEventToMatchingChildren(oNode);
}

function GetFirstMatchingAncestor(oNode, tagName, className, fNodeMatches, nodeCanMatch, fStopSearch)
{
    if (!nodeCanMatch)
    {
        oNode = oNode.parentNode;
    }
    
    while (oNode.nodeType != 9 && (oNode.nodeType != 1 || (!fStopSearch || !fStopSearch(oNode))))
    {
        var matchTag = false;
        var matchCss = false;
        
        if (tagName)
        {   
            var aTagNames = new Array();
            
            if (tagName.indexOf(",") >= 0)
            {
                aTagNames = tagName.split(",");
            } else
                aTagNames.push(tagName);
            
            for (var i=0; i<aTagNames.length; i++)
            {
                if (HasMatchingTagName(oNode, aTagNames[i]))
                {
                    matchTag = true;
                    break;
                }
            }
        }
        
        if (className)
        {
            matchCss = HasClassName(oNode, className);
        }
        
        var cannotMatch = false;
        
        if (tagName && !matchTag)
        {
            cannotMatch = true;
        }
        
        if (className && !matchCss)
        {
            cannotMatch = true;
        }
        
        if (fNodeMatches && !fNodeMatches(oNode))
        {
            cannotMatch = true;
        }
                
        if (!cannotMatch && (matchTag || matchCss || fNodeMatches(oNode)))
        {
            return oNode;
        }
        
        oNode = oNode.parentNode;
    }

    return null;
}

function HasMatchingCharacteristics(oNode, nodeType, attrNames, className, fIgnoreNode)
{
    if (nodeType && oNode.nodeType != nodeType)
    {
        return false;
    }
    
    var oAttrOwner = oNode;
    if (nodeType == 3)
    {
        oAttrOwner = oNode.parentNode;
    }
    
    if (attrNames && attrNames.length && !HasMatchingAttributeName(oAttrOwner, attrNames))
    {
        return false; 
    }
    
    if (className && !HasClassName(oAttrOwner, className))
    {
        return false;
    }
    
    if (fIgnoreNode && fIgnoreNode(oNode))
    {
        return false;
    }
        
    return oNode;
}

function HasMatchingAttributeName(oNode, attrNames)
{
    for (var i=0; i<attrNames.length; i++)
    {
        if (HasAttributeName(oNode, attrNames[i]))
        {
            return true;
        }
    }
    return false;
}

function HasAttributeName(oNode, attrName)
{
    return oNode.getAttribute(attrName) != null;
}

function HasClassName(oNode, className)
{
    return hasCssClass(oNode, className);
}

function IsAncestorDescendent(oAncestor, oDescendent)
{
    if (!oDescendent)
        return false;

    do
    {
        oDescendent = oDescendent.parentNode;
        
        if (oDescendent == oAncestor)
        {
            return true;
        }
    }
    while (oDescendent.tagName && oDescendent.tagName.toLowerCase() != "body");
    
    return false;
}

function HasMatchingTagName(oNode, tagName)
{
    if (oNode.nodeType == 1)
    {
        var aTagNames = tagName.split(",");
        
        for (var i=0; i<aTagNames.length; i++)
        {
            if (oNode.tagName.toLowerCase() == aTagNames[i].toLowerCase())
                return true;
        }
    }
    return false;   
}
function GetParentWithCssClassname(node, className)
{
    while (hasCssClass(node, className) == false)
    {
        node = node.parentNode;
        if (node.nodeType != 1)
        {
            return null;
        }
    }
    
    return node;
}

function GetChildWithCssClassname(node, className, recurse)
{
    for (var i = 0; i < node.childNodes.length; i++)
    {
        var oCurrChild = node.childNodes[i];

        if (oCurrChild.nodeType == 1 && hasCssClass(oCurrChild, className))
        {
            return oCurrChild;
        }
    }
    
    if (recurse)
    {
        for (var i = 0; i < node.childNodes.length; i++)
        {
            var oCurrChild = node.childNodes[i];
            
            var oMatch = GetChildWithCssClassname(oCurrChild, className, true);
            
            if (oMatch)
                return oMatch;
        }    
    }
    
    return null;
}

function TraverseDescendants(oNode, fnOnVisit)
{
    fnOnVisit(oNode);

    if (oNode.nodeType == 1)
    {
        for (var i=0; i < oNode.childNodes.length; i++)
        {
            TraverseDescendants(oNode.childNodes[i], fnOnVisit);
        }
    }
}

function TraverseDescendantsBottomUp(oNode, fnOnVisit)
{
    if (oNode.nodeType == 1)
    {
        for (var i=0; i < oNode.childNodes.length; i++)
        {
            TraverseDescendants(oNode.childNodes[i], fnOnVisit);
        }
    }

    fnOnVisit(oNode);
}

function IsNodeOrphaned(oNode)
{
    while (oNode)
    {
        if (oNode == oCurrEditor)
            return false;
            
        oNode = oNode.parentNode;
    }
    
    return true;
}

function ContainsBlockLevelElement(oNode)
{
    if (oNode.nodeType != 1)
        return false;
        
    for (var i = 0; i < oNode.childNodes.length; i++)
    {
        if (IsBlockLevelElement(oNode.childNodes[i]))
        {
            return true;
        }
    }
    
    return false;
}

function IsTextNode(oNode)
{
    return oNode.nodeType == 3;
}

function IsLinkBreak(oElem)
{
    return oElem.nodeType == 1 && oElem.nodeName.toLowerCase() == "br";
}

function IsElementDeletable(oElem)
{
    if (oElem.nodeType != 1)
        return false;

    var tag = oElem.nodeName.toLowerCase();
    switch (tag)
    {
        case "img":
            return true;
        default:
            return false;
    }
}


//
// DEBUGGING
//

function DebugCreateEditorSnippet(editorId)
{
    var oEditor = VxDocument().getElementById(editorId);
    if (oEditor)
    {
        return oEditor.innerHTML;
    }
    else
    {
        alert("DebugCreateEditorSnippet: Could not get handle to editor: " + editorId);
        return "<Editor not available>";
    }
}

function DebugIntegrity()
{  
    if (false)//VxDebugMode)
    {
        var aEditorListCopy = _oVxEditorIdList.slice(0);
        
        for (var i=0; i<aEditorListCopy.length; i++)
        {
            var oEditor = VxDocument().getElementById(aEditorListCopy[i]);
            if (oEditor)
            {
                TraverseDescendants(oEditor,
                    function(oNode)
                    {
                        var id = aEditorListCopy[i];
                        
                        if (!IsCursorElement(oNode.parentNode))
                        {
                            if (IsEmptyTextNode(oNode))
                            {   
                                alert("DebugIntegrity() says empty text node detected in editor:" + id + "\n\n"
                                + DebugCreateEditorSnippet(id));
                            }
                            
                            if (IsPureSpaceValue(oNode))
                            {
                                alert("DebugIntegrity() says pure space text node in editor: " + id + "\n\n"
                                + DebugCreateEditorSnippet(id)); 
                            }
                        }
                    }   
                );
            }
        }
    }
}