/*
* Copyright (c) 2000 - 2001 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: 
*
*/


#include "XhtmlTableElementHandler.h"

#include "BrsrStatusCodes.h"
#include "GDIDevicecontext.h"
#include "HEDDocumentListener.h"
#include "LMgrAnonTableBox.h"

#include "nw_xhtml_xhtmlcontenthandler.h"
#include "nw_lmgr_rootbox.h"
#include "nw_lmgr_statictablebox.h"
#include "nw_lmgr_verticaltablebox.h"
#include "nw_basictables_trelementhandler.h"
#include "nw_basictables_captionelementhandler.h"
#include "nw_basictables_rowgroupelementhandler.h"
#include "nw_basicforms_formelementhandler.h"
#include "nw_basicforms_xhtmlformliaison.h"
#include "nw_basicforms_inputelementhandler.h"
//#include "nw_structure_scriptelementhandler.h"
#include "nw_object_exceptions.h"
#include "nw_dom_domvisitor.h"
#include "nwx_settings.h"
#include "nwx_statuscodeconvert.h"


#define GRID_MODE_MAX_NUMBER_OF_ELEMEMTS 500
#define GRID_MODE_MAX_TEXT_BLOCK 100
#define GRID_MODE_COL_RATIO 0.025
#define GRID_MODE_IMAGE_SCALE_RATIO 3


// ============================ MEMBER FUNCTIONS ===============================

// -----------------------------------------------------------------------------
// CXHTMLTableElementHandler::CXHTMLTableElementHandler
//
// C++ default constructor can NOT contain any code, that
// might leave.
// -----------------------------------------------------------------------------
//
CXHTMLTableElementHandler::CXHTMLTableElementHandler(const NW_XHTML_ElementHandler_t* aElementHandler,
    NW_XHTML_ContentHandler_t* aContentHandler, NW_DOM_ElementNode_t* aElementNode)        
  {
  iElementHandler = aElementHandler;
  iContentHandler = aContentHandler;
  iElementNode = aElementNode;

  iIsGridMode = ETrue;
  }

// -----------------------------------------------------------------------------
// CXHTMLTableElementHandler::ConstructL
//
// Symbian 2nd phase constructor can leave.
// -----------------------------------------------------------------------------
//
void CXHTMLTableElementHandler::ConstructL(void)
  {
    NW_HED_DocumentRoot_t*  docRoot = NULL;
    CGDIDeviceContext*      deviceContext = NULL;

    // Get the doc-root, root-box and device context.
    docRoot = (NW_HED_DocumentRoot_t*) NW_HED_DocumentNode_GetRootNode(iContentHandler);
    NW_ASSERT(docRoot != NULL);

    iRootBox = NW_HED_DocumentRoot_GetRootBox(docRoot);
    NW_ASSERT(iRootBox != NULL);

    deviceContext = NW_LMgr_RootBox_GetDeviceContext(iRootBox);

    // Get the screen width from the device context.
    iScreenWidth = (NW_Float32) deviceContext->DisplayBounds()->dimension.width;
  }

// -----------------------------------------------------------------------------
// CXHTMLTableElementHandler::~CXHTMLTableElementHandler
//
// Deconstructor.
// -----------------------------------------------------------------------------
//
CXHTMLTableElementHandler::~CXHTMLTableElementHandler()
  {
  }

// -----------------------------------------------------------------------------
// CXHTMLTableElementHandler::NewL
// Two-phased constructor.
// -----------------------------------------------------------------------------
//
CXHTMLTableElementHandler* CXHTMLTableElementHandler::NewL(const NW_XHTML_ElementHandler_t* 
    aElementHandler, NW_XHTML_ContentHandler_t* aContentHandler, 
    NW_DOM_ElementNode_t* aElementNode)
  {
  NW_ASSERT(aElementHandler != NULL);
  NW_ASSERT(aContentHandler != NULL);
  NW_ASSERT(aElementNode != NULL);

  CXHTMLTableElementHandler* self = new(ELeave) CXHTMLTableElementHandler(aElementHandler,
      aContentHandler, aElementNode);

  CleanupStack::PushL(self);
  self->ConstructL();
  CleanupStack::Pop();
  
  return self;
  }

// -----------------------------------------------------------------------------
// CXHTMLTableElementHandler::Initialize
//
// Initializes the ElementHandler.
// -----------------------------------------------------------------------------
//
TBrowserStatusCode CXHTMLTableElementHandler::Initialize(void)
  {
  NW_TRY (status) 
    {
    // If the table has already been determined not to be a grid mode
    // table then just exit the method successfully.
    if (!iIsGridMode)
      {
      NW_THROW_SUCCESS(status);
      }

    // Evalulate the table to determine whether it's suitable for grid mode layout.
    // This does the pre-validation of the table (as the chunks arrive).
    status = PreEvalTable();
    _NW_THROW_ON_ERROR(status);

    // Stop initialzation if the entire table isn't available.
    if (!NW_XHTML_ElementHandler_NextSiblingExists (iContentHandler, iElementNode, NW_TRUE))
      {
      NW_THROW_SUCCESS (status);
      }

    // The entire table has arrived so clear iLastEvaluatedElement as it's not needed 
    // any more.
    iLastEvaluatedElement = NULL;

    // Evalulate the table to determine whether it's suitable for grid mode layout.
    // This does the post-validation of the table (after the entire table is available).
    status = PostEvalTable();
    _NW_THROW_ON_ERROR(status);
    }

  NW_CATCH (status) 
    {
    // Suppress the status if the exception occurred because 
    // the table was disqualified for grid mode.
    if (!iIsGridMode)
      {
      status = KBrsrSuccess;
      }
    }

  NW_FINALLY 
    {
    return status;
    } NW_END_TRY
  }

// -----------------------------------------------------------------------------
// CXHTMLTableElementHandler::CreateBoxTree
//
// Creates the box-tree associated with the tag.
// -----------------------------------------------------------------------------
//
TBrowserStatusCode CXHTMLTableElementHandler::CreateBoxTree(NW_LMgr_ContainerBox_t& aParentBox)
  {
  NW_LMgr_Box_t*          tableBox = NULL;
  NW_LMgr_Box_t*          tableContainer = NULL;
  NW_Bool                 ownstableBox = NW_TRUE;
  NW_Bool                 ownsTableContainer = NW_TRUE;
  NW_ADT_Vector_Metric_t  tableContainerIndex = 0;

  NW_TRY (status) 
    {
    NW_LMgr_Property_t prop;

    // Stop creation if the entire table isn't available -- iIsGridMode is
    // also checked because if it is set to false it should always allow the
    // method to continue.
    if (iIsGridMode && !NW_XHTML_ElementHandler_NextSiblingExists(
        iContentHandler, iElementNode, NW_FALSE))
      {
      NW_THROW_SUCCESS (status);
      }

    // If need be set the grid mode flag on the root-box.
    if (iIsGridMode)
      {
      iRootBox->iHasGridModeTables = NW_TRUE;
      }

    // Allocate and initialize the anonymous container for the table and 
    // an optional caption
    tableContainer = (NW_LMgr_Box_t*) LMgrAnonTableBox_New(1);
    NW_THROW_OOM_ON_NULL (tableContainer, status);

    // Set the table display property 
    prop.type = NW_CSS_ValueType_Token;
    prop.value.token = NW_CSS_PropValue_display_block;
    status = NW_LMgr_Box_SetProperty(tableContainer, NW_CSS_Prop_display, &prop);
    _NW_THROW_ON_ERROR(status);

    status = NW_LMgr_ContainerBox_AddChild(&aParentBox, tableContainer);
    _NW_THROW_ON_ERROR(status);
    ownsTableContainer = NW_FALSE;
  
    // Allocate and initialize the box for the table 
    if ((DoesGridModeApply() == NW_TRUE) ||
        (NW_LMgr_RootBox_GetSmallScreenOn(iRootBox) == NW_FALSE))

      {
      tableBox = (NW_LMgr_Box_t*) NW_LMgr_StaticTableBox_New(1);
      }
    else
      {
      tableBox = (NW_LMgr_Box_t*) NW_LMgr_VerticalTableBox_New(1);
      }
    NW_THROW_OOM_ON_NULL(tableBox, status);

    // Add the boxes to the tree 
    status = NW_LMgr_ContainerBox_AddChild(NW_LMgr_ContainerBoxOf(tableContainer), tableBox);
    _NW_THROW_ON_ERROR(status);
    ownstableBox = NW_FALSE;

    tableContainerIndex = NW_LMgr_ContainerBox_GetChildCount(&aParentBox);

    // Create the subtree of boxes for elements within the table element 
    status = CreateSubTree(iElementNode, tableBox, &aParentBox);
    _NW_THROW_ON_ERROR(status);

    // If form and input controls are not within td, they need to be put in the beginning of the table
    //  For e.g 
    //  <TABLE>
    //    <caption> Table Test </caption>
    //    <TR><TD>Search</TD><TD>Nokia</TD></TR>
    //
    //    <TR>
    //    <FORM>
    //    <INPUT type=text value=001 name=wsid1> 
    //    <TD>Search <INPUT type=text value=001 name=wsid2> 
    //    </TD><TD>Nokia</TD>
    //    <input type="reset"/>
    //    </FORM>
    //    </TR>
    //  </table>
    //
    //  Here wsid1 should be put before table box, but while adding boxes we added it after the tableContainerBox.
    //  So, we see that more boxes were added after tableContainerBox, we move this box to the end.
      
    if (NW_LMgr_ContainerBox_GetChildCount(&aParentBox) > tableContainerIndex)
      {
      NW_LMgr_ContainerBox_InsertChildAt(&aParentBox, tableContainer, NW_ADT_Vector_AtEnd);
      }

    status = NW_XHTML_ElementHandler_ApplyStyles(iElementHandler, iContentHandler, 
        iElementNode, &tableBox, 0);
    _NW_THROW_ON_ERROR(status);

    // Apply Styles will take care of replacing container box if display was block, list-item or marquee.
    // In case of 'display:none' or an error it removes the container box. That's why we check box
    if (tableBox == NULL)
      {
      NW_THROW_SUCCESS(status);
      }
    } 

  NW_CATCH (status) 
    {
    }
     
  NW_FINALLY 
    {
    if (ownstableBox) 
      {
      NW_Object_Delete(tableBox);
      }

    if (ownsTableContainer) 
      {
      NW_Object_Delete(tableContainer);
      }
    return status;
    } NW_END_TRY
  }

// -----------------------------------------------------------------------------
// CXHTMLTableElementHandler::DoesGridModeApply
// 
// Returns whether or not grid mode applies to this table.
// -----------------------------------------------------------------------------
//
TBool CXHTMLTableElementHandler::DoesGridModeApply(void)
  {
  return iIsGridMode;
  }

// -----------------------------------------------------------------------------
// CXHTMLTableElementHandler::CreateSubTree
// 
// -----------------------------------------------------------------------------
//
TBrowserStatusCode 
CXHTMLTableElementHandler::CreateSubTree(NW_DOM_ElementNode_t* aElementNode,
    NW_LMgr_Box_t* aTableBox, NW_LMgr_ContainerBox_t* aTableContainerParent)
  {
  NW_DOM_ElementNode_t* childNode;

  NW_TRY (status) 
    {
    /* We visit the children ourselves, rather than calling the superclass.
     * This is necessary so we can filter any unwanted elements from
     * the table. A table can only handle <tr> elements. */
    for (childNode = NW_DOM_Node_getFirstChild(aElementNode); childNode != NULL;
        childNode = NW_DOM_Node_getNextSibling(childNode)) 
      {
      const NW_XHTML_ElementHandler_t* elementHandler;

      /* get the ElementHandler for the aElementNode */
      elementHandler = NW_XHTML_ContentHandler_GetElementHandler(iContentHandler,
          (NW_DOM_ElementNode_t*) childNode);

      /* we want to ignore the entire script tag */
      if (elementHandler == NULL)
        {
        continue;
        }

      status = CreateSubTreeHelper(elementHandler, childNode,aTableBox, 
          aTableContainerParent);
      _NW_THROW_ON_ERROR(status);
      }
    } 
    
  NW_CATCH (status) 
    {
    } 
  
  NW_FINALLY 
    {
    return status;
    } NW_END_TRY
  }


// -----------------------------------------------------------------------------
// CXHTMLTableElementHandler::CreateSubTreeHelper
// 
// -----------------------------------------------------------------------------
//
TBrowserStatusCode 
CXHTMLTableElementHandler::CreateSubTreeHelper(const NW_XHTML_ElementHandler_t* aElementHandler, 
    NW_DOM_ElementNode_t* aElementNode, NW_LMgr_Box_t* aTableBox, 
    NW_LMgr_ContainerBox_t* aTableContainerParent)
  {
  NW_TRY (status) 
    {
    /* for handling forms embedded in tables */
    if (NW_Object_IsInstanceOf(aElementHandler, &NW_XHTML_formElementHandler_Class))
      {
      NW_XHTML_FormLiaison_t* formLiaison = (NW_XHTML_FormLiaison_t*)
          NW_XHTML_ContentHandler_GetFormLiaison(iContentHandler, aElementNode);
      NW_REQUIRED_PARAM(formLiaison);

      status = CreateSubTree(aElementNode, aTableBox, aTableContainerParent);
      _NW_THROW_ON_ERROR(status);
      }

    else if (NW_Object_IsInstanceOf(aElementHandler, &NW_XHTML_inputElementHandler_Class))
      {
      status = NW_XHTML_ElementHandler_CreateBoxTree(aElementHandler, iContentHandler,
          aElementNode, aTableContainerParent);
      _NW_THROW_ON_ERROR(status);
      }

    /* If this is not a table row or a caption, skip to next */
    else if (NW_Object_IsInstanceOf(aElementHandler, &NW_XHTML_trElementHandler_Class) ||
        NW_Object_IsInstanceOf(aElementHandler, &NW_XHTML_rowgroupElementHandler_Class) )
      {
      status = NW_XHTML_ElementHandler_CreateBoxTree (aElementHandler, iContentHandler,
          aElementNode, NW_LMgr_ContainerBoxOf(aTableBox));
      _NW_THROW_ON_ERROR(status);
      }
       
    else if (NW_Object_IsInstanceOf(aElementHandler, &NW_XHTML_captionElementHandler_Class)) 
      {
      status = NW_XHTML_ElementHandler_CreateBoxTree (aElementHandler, iContentHandler,
          aElementNode, aTableBox->parent);
      _NW_THROW_ON_ERROR(status);
      }
    } 
  
  NW_CATCH (status) 
    {
    } 
    
  NW_FINALLY 
    {
    return status;
    } NW_END_TRY
  }

// -----------------------------------------------------------------------------
// CXHTMLTableElementHandler::HasOnlyOneElementChild
// 
// Returns true if the parent has only one element, ignoring text nodes that
// only contain whitespace.
// -----------------------------------------------------------------------------
//
NW_Bool
CXHTMLTableElementHandler::HasOnlyOneElementChild(NW_DOM_Node_t* aParentNode)
  {
  TBrowserStatusCode  status;
  NW_DOM_Node_t*      child;
  NW_Int32            childCount = 0;
  
  NW_ASSERT(aParentNode != NULL);

  // Return false if it has no children.
  if (NW_DOM_Node_hasChildNodes(aParentNode) == NW_FALSE)
    {
    return NW_FALSE;
    }

  for (child = NW_DOM_Node_getFirstChild(aParentNode);
      child != NULL; child = NW_DOM_Node_getNextSibling(child))
    {
    if (NW_DOM_Node_getNodeType(child) == NW_DOM_ELEMENT_NODE)
      {
      childCount++;
      }

    else if (NW_DOM_Node_getNodeType(child) == NW_DOM_TEXT_NODE)
      {
      NW_String_t string;

      status = StatusCodeConvert(NW_DOM_TextNode_getData(child, &string));

      if (status == KBrsrSuccess)
        {
        NW_Ucs2*   temp = NULL;

        temp = NW_Str_TrimInPlace((NW_Ucs2*) string.storage, NW_Str_Both);
        if (temp && (*temp != 0x00))
          {
          childCount++;
          }
          
        NW_String_deleteStorage(&string);
        }
      }

    if (childCount > 1)
      {
      return NW_FALSE;
      }
    }

  return NW_TRUE;
  }

// -----------------------------------------------------------------------------
// CXHTMLTableElementHandler::PreEvalTable
// 
// This method is called each time a table chunk arrives.  It is used
// to determine whether or not that table should be laid out in grid mode.
// -----------------------------------------------------------------------------
//
TBrowserStatusCode
CXHTMLTableElementHandler::PreEvalTable()
  {
  const NW_Int32 validTokens[] = 
      {
      NW_XHTML_ElementToken_td,
      NW_XHTML_ElementToken_tr,
      NW_XHTML_ElementToken_a,
      NW_XHTML_ElementToken_img,
      NW_XHTML_ElementToken_input,
      NW_XHTML_ElementToken_th,
      NW_XHTML_ElementToken_form,
      NW_XHTML_ElementToken_label,
      NW_XHTML_ElementToken_button,
      NW_XHTML_ElementToken_caption,
      NW_XHTML_ElementToken_tbody,
      NW_XHTML_ElementToken_tfoot,
      NW_XHTML_ElementToken_thead,
      NW_XHTML_ElementToken_span,
      NW_XHTML_ElementToken_font,
      NW_XHTML_ElementToken_b,
      NW_XHTML_ElementToken_big,
      NW_XHTML_ElementToken_i,
      NW_XHTML_ElementToken_p,
      NW_XHTML_ElementToken_center,
      NW_XHTML_ElementToken_br,
      NW_XHTML_ElementToken_nobr,
      NW_XHTML_ElementToken_small
      };
  
  NW_cXML_DOM_DOMVisitor_t   visitor;
  NW_DOM_Node_t*             domNode = NULL;
  NW_Int32                   tokenCount = sizeof(validTokens) / sizeof(validTokens[0]);
  NW_Ucs2*                   widthStr = NULL;
  NW_Ucs2*                   typeStr = NULL;

  NW_TRY (status) 
    {
    // Reject if mobile profile mode is enabled.
    if (NW_Settings_GetDisableSmallScreenLayout())
      {
      NW_THROW_STATUS(status, KBrsrFailure);
      }

    // Init the dom visitor.
    status = NW_cXML_DOM_DOMVisitor_Initialize(&visitor, (NW_DOM_Node_t*) iElementNode);
    NW_THROW_ON_ERROR(status);
    
    // iLastEvaluatedElement is null during the first call to PreEvalTable on a given
    // table.  In this condition a call to "next" is made to skip the table element.
    if (iLastEvaluatedElement == NULL)
      {
      (void) NW_cXML_DOM_DOMVisitor_Next(&visitor, NW_DOM_ELEMENT_NODE | NW_DOM_TEXT_NODE);
      }

    // Otherwise the vistor's current node is set to the last node processed during the
    // previous call to PreEvalTable.
    else
      {
      visitor.currentNode = iLastEvaluatedElement;
      }

    // Validate the children.
    while ((domNode = NW_cXML_DOM_DOMVisitor_Next(&visitor, 
        NW_DOM_ELEMENT_NODE | NW_DOM_TEXT_NODE)) != NULL) 
      {
      NW_Bool    tokenValid = NW_FALSE;
      NW_Uint16  token;

      // Update and test the element count, reject the table if there are
      // to many elements.
      iNumberOfElements++;

      if (iNumberOfElements >= GRID_MODE_MAX_NUMBER_OF_ELEMEMTS)
        {
        NW_THROW_STATUS(status, KBrsrFailure);
        }

      // Ensure that text nodes aren't to large.
      if (NW_DOM_Node_getNodeType(domNode) == NW_DOM_TEXT_NODE)
        {
        NW_String_t  text;
        NW_Uint16    length;

        status = StatusCodeConvert(NW_DOM_TextNode_getData(domNode, &text));
        NW_THROW_ON_ERROR(status);

        length = NW_String_getCharCount(&text, HTTP_iso_10646_ucs_2);

        NW_String_deleteStorage(&text);

        // The text block is to large, reject the table.
        if (length > GRID_MODE_MAX_TEXT_BLOCK)
          {
          NW_THROW_STATUS(status, KBrsrFailure);
          }
        
        // Otherwise skip to the next child -- no text relevant checks follow.
        else
          {
          continue;
          }
        }

      // Get the element's token.
      token = NW_HED_DomHelper_GetElementToken(domNode);

      // The element is valid if its token is found in validTokens.
      for (NW_Int32 i = 0; i < tokenCount; i++)
        {
        if (token == validTokens[i])
          {
          tokenValid = NW_TRUE;

          // Break out of the loop.
          break;
          }
        }

      // The token is invalid, reject the table.
      if (tokenValid == NW_FALSE)
        {
        NW_THROW_STATUS(status, KBrsrFailure);
        }

      // Do additional element-level validation.
      switch (token)
        {
        case NW_XHTML_ElementToken_img:
          {
          NW_Int32        width;
          NW_DOM_Node_t*  parentNode;
          NW_Bool         siblingCheckDone = NW_FALSE;

          // Both the hight and width attributes are needed.
          status = NW_HED_DomHelper_HasAttribute(domNode, NW_XHTML_AttributeToken_height);
          NW_THROW_ON_ERROR(status);
  
          status = NW_XHTML_ElementHandler_GetAttribute(iContentHandler, domNode,
              NW_XHTML_AttributeToken_width, &widthStr);
          NW_THROW_ON_ERROR(status);

          // Reject the table if the width is to large.
          width = NW_Str_Atoi(widthStr);
          if ((width / iScreenWidth) > GRID_MODE_IMAGE_SCALE_RATIO)
            {
            NW_THROW_STATUS(status, KBrsrFailure);
            }

          NW_Str_Delete(widthStr);
          widthStr = NULL;

          // Reject the table if the image has an usemap
          status = NW_HED_DomHelper_HasAttribute(domNode, NW_XHTML_AttributeToken_usemap);
          if (status == KBrsrSuccess)
            {
            NW_THROW_STATUS(status, KBrsrFailure);
            }

          // Reject the table if the image has any siblings
          parentNode = domNode;

          do
            {
            NW_Uint16  parentToken;

            parentNode = NW_DOM_Node_getParentNode(parentNode);
            NW_THROW_ON_NULL(parentNode, status, KBrsrUnexpectedError);

            parentToken = NW_HED_DomHelper_GetElementToken(parentNode);
            
            // If the parent node is either a td or th the sibling check is done.
            // tr is also check as to handle malformed content.
            if ((parentToken == NW_XHTML_ElementToken_td) ||
                (parentToken == NW_XHTML_ElementToken_th) ||
                (parentToken == NW_XHTML_ElementToken_tr))
              {
              siblingCheckDone = NW_TRUE;
              }

            // Reject, if the node has two or more child.
            if (HasOnlyOneElementChild(parentNode) == NW_FALSE)
              {
              NW_THROW_STATUS(status, KBrsrFailure);
              }
            }
          while (!siblingCheckDone);
          }
          break;

        case NW_XHTML_ElementToken_input:
          {
          // Get the type attribute.
          status = NW_XHTML_ElementHandler_GetAttribute(iContentHandler, domNode,
              NW_XHTML_AttributeToken_type, &typeStr);

          // Reject the table if type is missing.
          NW_THROW_ON_ERROR(status);

          // Reject the table if type equals "text", "password", "file", "image".
          if ((NW_Str_StricmpConst(typeStr, "text") == 0) || 
              (NW_Str_StricmpConst(typeStr, "image") == 0) ||
              (NW_Str_StricmpConst(typeStr, "file") == 0) ||
              (NW_Str_StricmpConst(typeStr, "password") == 0))
            {
            NW_THROW_STATUS(status, KBrsrFailure);
            }

          NW_Str_Delete(typeStr);
          typeStr = NULL;
          }

        default:
          break;
        }
      
      iLastEvaluatedElement = domNode;
      }
    }

  NW_CATCH (status) 
    {
    NW_Str_Delete(widthStr);
    NW_Str_Delete(typeStr);

    iIsGridMode = EFalse;
    iLastEvaluatedElement = NULL;
    }

  NW_FINALLY 
    {
    return status;
    } NW_END_TRY
  }

// -----------------------------------------------------------------------------
// CXHTMLTableElementHandler::PostEvalTable
// 
// This method is called after all of the chunks of a table arrive.  It is used
// to determine whether or not that table should be laid out in grid mode.
// 
// Note: This method should be restructured to be like PreEvalTable if any non
//       tr tests are added.  It's like this because it only needs to check the 
//       top level children to validate tr's.
// -----------------------------------------------------------------------------
//
TBrowserStatusCode
CXHTMLTableElementHandler::PostEvalTable()
  {
  NW_DOM_Node_t*    domNode = NULL;
  NW_DOM_Node_t*    child = NULL;
  NW_Uint16         token;
  NW_Int32          childCount;
  NW_ADT_Vector_t*  secondLevelElementQueue = NULL; 
  NW_Bool           firstLevelElement = NW_TRUE;

  NW_TRY (status) 
    {
    // Create a vector to hold first level children that need their second
    // level children checked as well.
    secondLevelElementQueue = (NW_ADT_Vector_t*) NW_ADT_ResizableVector_New(
        sizeof(NW_DOM_Node_t*), 1, 2);
    NW_THROW_OOM_ON_NULL(secondLevelElementQueue, status);

    domNode = NW_DOM_Node_getFirstChild(iElementNode);

    while (domNode != NULL)
      {
      for (; domNode != NULL; domNode = NW_DOM_Node_getNextSibling(domNode))
        {
        // Skip non-element nodes.
        if (NW_DOM_Node_getNodeType(domNode) != NW_DOM_ELEMENT_NODE)
          {
          continue;
          }

        // Handle non-tr children.  A queue is used to handle malformed tables like:
        // <table><form><tr>...</tr><tr>...</tr></form></table> -- note: the 
        // trs are second level children not first level as they should be.  
        token = NW_HED_DomHelper_GetElementToken(domNode);
        if (token != NW_XHTML_ElementToken_tr)
          {
          // When processing the first level children append any non-tr node to 
          // the queue the children of these node will be checked in later iterations.
          if (firstLevelElement)
            {
            NW_ADT_DynamicVector_InsertAt(secondLevelElementQueue, &domNode, NW_ADT_Vector_AtEnd);
            continue;
            }

          // Otherwise reject the table as its too malformed to reliably handled.
          else
            {
            NW_THROW_STATUS(status, KBrsrFailure);
            }
          }
      
        // Skip tr's without children.
        if (NW_DOM_Node_hasChildNodes(domNode) == NW_FALSE)
          {
          continue;
          }

        // Count the number of columns in this row.
        childCount = 0;
      
        // Count the first child.     
        child = NW_DOM_Node_getFirstChild(domNode);
        if (NW_DOM_Node_getNodeType(child) == NW_DOM_ELEMENT_NODE)
          {
          childCount++;
          }

        // Count the other children.
        while ((child = NW_DOM_Node_getNextSibling(child)) != NULL)
          {
          if (NW_DOM_Node_getNodeType(child) == NW_DOM_ELEMENT_NODE)
            {
            childCount++;
            }
          }
    
        // Reject the table if there are to many columns.
        if ((childCount / iScreenWidth) > GRID_MODE_COL_RATIO)
          {
          NW_THROW_STATUS(status, KBrsrFailure);
          }
        }

      // If need be process the next queued first level non-tr node.
      if ((secondLevelElementQueue != NULL) && 
          (NW_ADT_Vector_GetSize(secondLevelElementQueue) > 0))
        {
        status = NW_ADT_Vector_GetElementAt(secondLevelElementQueue, 0, &domNode);
        NW_THROW_ON_ERROR(status);

        status = NW_ADT_DynamicVector_RemoveAt(secondLevelElementQueue, 0);
        NW_THROW_ON_ERROR(status);

        firstLevelElement = NW_FALSE;
        domNode = NW_DOM_Node_getFirstChild(domNode);
        }
      }
    }

  NW_CATCH (status) 
    {
    iIsGridMode = EFalse;
    }

  NW_FINALLY 
    {
    NW_Object_Delete(secondLevelElementQueue);

    return status;
    } NW_END_TRY
  }
