/*******************************************************************************
 * reportlistcl.js
 *
 * HTML Rendering of the ReportList
 *
 * Copyright:    Copyright (c) 2008 - 2010
 * Company:      Neolane
 *
 * $Author$
 * $Revision$
 ******************************************************************************/ 

/** Constructor 
  *
  * @htmlParent HTML element in the document where to render the list. 
  * @data       Data to display in an XML format. 
  * @style      Cell style definitions. */ 
function ReportList(htmlParent, data, styles, defaultFontSize)
{
  this.data       = data
  this.htmlTable  = htmlParent
  this.styles     = new Array()
  this.rowNum     = 0 // used by css generation (even odd classes)
  
  // compile styles
  var childNode = styles.documentElement.firstChild
  while ( childNode != null )
  {
    if ( childNode.nodeType == XML.ELEMENT_NODE )
      this.compileStyle(childNode, defaultFontSize)

    childNode = childNode.nextSibling
  }
}

ReportList.prototype.COLORS  = new Array("#3B87A2", "#FF8012", "#8CA23B", "#A23B5E", "#004A62",
                                "#FFCCA0", "#C75400", "#CAD4A5", "#A5C8D4", "#6D0026",
                                "#D4A5B4", "#4F6400")

ReportList.prototype.IMG_PLUS  = "/xtk/img/plus.png"
ReportList.prototype.IMG_MINUS = "/xtk/img/minus.png"

/** Compile a style to create a template of the <td>.
  *
  * @style the XML definition of the style. The schema of 
  *        this element is the one defined for the flat report
  *        format:
  *
  *   <style format="percentage" alignement="center">
  *     <font bold="true"/>
  *   </style>
  *
  * @see https://wiki.neolane.net/wiki/Reporting_5.00#srcSchema_like_xml_structure. */
ReportList.prototype.compileStyle = function(style, defaultFontSize)
{
  var td = document.createElement("td")
  var compiledStyle = compileStyle(style, td, defaultFontSize)
  compiledStyle.td = td
  this.styles.push(compiledStyle)
}

/** Render the list as an HTML table. */
ReportList.prototype.render = function()
{
  // clear the previous rendering of the table
  while ( this.htmlTable.firstChild != null )
    this.htmlTable.removeChild(this.htmlTable.firstChild)
    
  var htmlBody = document.createElement("tbody")
  this.renderNode(this.data, htmlBody)
  this.htmlTable.appendChild(htmlBody)
}

/** Click on a cell */
ReportList.prototype.onCellClick = function(ctx, action, target, transition, option, param, e)
{
  PerformClickAction(ctx, action, target, transition, option, param, e)
}

/** Generate a function for a clickable cell
  *
  * @object     the list.
  * @ctx        an array of value to set in the context before executing the action.
  * @action     action to perform (url, next, previous, refresh, transition)
  * @target     window target.
  * @transition name of transition if action parameter is 'transition'. 
  * @href       URL field. 
  * @param      URL parameters. */
ReportList.prototype.makeOnClickEvent = function(object, ctx, action, target, transition, href, param)
{
  return function(e)
  {
    return ReportList.prototype.onCellClick.call(object, ctx, action, target, transition, href, param, e)
  }
}

/** Render a node from the given data.
  *
  * @aData      list of rows to render, along with their cells infos.
  * @htmlParent HTML element in the document where to render the node. */
ReportList.prototype.renderNode = function(aData, htmlParent)
{
  // array for headers whose rows should be collapsed by default
  var groupsToCollapse = new Array()
  
  for( var iRowIndex = 0 ; iRowIndex < aData.length ; iRowIndex++ )
  {
    var row = aData[iRowIndex]
    trNode = document.createElement("tr")
    trNode.className = row.t //type of the row (header, footer, row) + level
    trNode.className += ++this.rowNum % 2 == 0 ? " even" : " odd"
    htmlParent.appendChild(trNode)
    
    for( var iCellIndex = 0 ; iCellIndex < row.d.length ; iCellIndex++ )
    {
      var cell = row.d[iCellIndex]
      var value = null
      
      // a cell can have a description attached to it
      var helpContent = new Object()
      helpContent.title = ""
      helpContent.content = ""
      if( typeof cell.de == "string" )
        helpContent.content = cell.de.toString()
      
      // cell representation in HTML (TD) with its colspan and etc.:
      var htmlNode
      // TD which contains the cell's elements (it is the htmlNode itself by default)
      // Beware when and how you may initialize it ;)
      var htmlNodeTxt
      
      if ( typeof cell != "undefined" && typeof cell.s != "undefined" )
      {
        var rawValue = cell.v
        var style = this.styles[cell.s]
        
        // Set htmlNode with this cell style
        htmlNode = style.td.cloneNode(true)
        // htmlNodeTxt is htmlNode by default
        htmlNodeTxt = htmlNode
        
        // Add an image for the collapsing of inner rows
        var nextRow = aData[iRowIndex+1]
        if( trNode.className.indexOf("header") != -1    // only for header rows
         && iCellIndex == 0                             // on their first cell
         && this.getLevel(trNode.className) > 0         // if their level is > 0
         && this.canBeCollapsed(nextRow != undefined ? nextRow.t : null,
                                this.getLevel(trNode.className)) )  // no rows to collapse ?
        {
          // Use our private function to add a table into our htmlNode
          // and update the htmlNodeTxt variable.
          addTable(htmlNode, style)
          htmlNodeTxt = htmlNode.getMainTD()
          
          // We only need to put our collapsing image in this table now
          var img = document.createElement("img")
          img.src = this.IMG_MINUS
          img.onclick = function()
          {
            ReportList.prototype.toggleCollapsing(this);
          }
          img.className = "collapse"
          
          // => create a new TD at the first position of the table row
          var tdImg = htmlNode.addInnerCell(style, "first")
          // and insert our image into it
          tdImg.appendChild(img)
          
          // Register this image for collapsing if the collapsing is set 
          // to default for this header (cf row.c).
          if( row.c )
            groupsToCollapse.push(img)
        }
        
        // The user may want to display a link to open up the description
        // attached to this cell
        if( typeof cell.di == "string" && helpContent.content != ""
          && ( cell.di == "right" || cell.di == "left" ) )
        {
          // Check whether an inner table was already set for this cell or not
          if( !htmlNode.innerTable )
          {
            addTable(htmlNode, style)
            htmlNodeTxt = htmlNode.getMainTD()
          }
          
          // the table is set, now we can ask it to add a td at the right pos
          var imgTD
          if( cell.di == "right" )
            imgTD = htmlNode.addInnerCell(style, "after")
          else
            imgTD = htmlNode.addInnerCell(style, "before")
          
          // let's pimp this td!
          var a = document.createElement("a")
          a.className = "helpBubble"
          a.href = "#"
          // we will define the onmouseover once we will have this cell's value
          // so we only need to remember this link for now
          helpContent.link = a
          a.innerHTML = "&nbsp;"
          
          imgTD.appendChild(a)
        }
        
        if ( style.hideValue != true )
        {
          var valueToFormat = rawValue
          if( style.format == "enum" )
          {
            // As this is an enumeration, we need to pass the label and/or
            // image as well as the "rawValue", plus the node ref.
            var enumValues = 
            {
              rawValue : rawValue,
              labelValue : cell.vl,
              imageValue : cell.vi,
              htmlNodeRef : htmlNodeTxt
            }
            valueToFormat = enumValues
          }
          value = FormatHelper.format[style.format].call(this, valueToFormat, style)
        }
        
        // Clickable cells
        if ( style.onclick == true )
        {
          var aContext = new Array()
          var aParam = new Array()
          var bNotClickable = false
          
          if( typeof cell.l != "undefined" )
            for( var i = 0 ; i < cell.l.length ; i++ )
            {
              if( cell.l[i].t == "set" )
                aContext.push({"xpath" : cell.l[i].xpath, "value" : cell.l[i].v})
              else if( cell.l[i].t == "param" )
                aParam.push({"name" : cell.l[i].name, "value" : cell.l[i].v})
              else if( cell.l[i].t == "notClickableIf" )
                bNotClickable = cell.l[i].v
            }

          if( bNotClickable == "false" || bNotClickable == false )
            htmlNodeTxt.onclick = this.makeOnClickEvent(this, aContext, style.onclickAction, 
              style.onclickTarget, style.onclickTransition, style.onclickOption, aParam)
          else
          {
            if( htmlNodeTxt.firstChild != null )
              // Remove "A" element 
              htmlNodeTxt.removeChild(htmlNodeTxt.firstChild)
          }
        }
        
        // showing extra visuals
        if ( style.bargraph != undefined )
        {
          var bargraph = style.bargraph.cloneNode(true)
          // Bargraph value must be within [0, 100]
          var bargraphValue = Math.min(Math.max(parseFloat(rawValue) * 100, 0), 100)
          if( isNaN(bargraphValue) )
            bargraphValue = 0
          bargraph.firstChild.firstChild.style.width = bargraphValue.toString() + "%"
          htmlNodeTxt.appendChild(bargraph)
        }
        else if ( style.rating == true )
        {
          var fValue = parseFloat(rawValue)
          for (var i=1; i <= style.ratingMax; i++)
          {
            var div = document.createElement("div")
            div.className = "rating" + (fValue >= i ? "On" : "Off")
            htmlNodeTxt.appendChild(div)
          }
        }
        else if ( style.color == true && rawValue != "" && rawValue != "none" )
        {
          // Either our raw value is an int, and as such an index into the 
          // color table, or a CSS color (#A78B34, red, none, ...), in 
          // which case we set it directly.
          var color = rawValue
          if( !isNaN(color) )
          {
            var colorIndex = parseInt(color)
            color = this.COLORS[colorIndex % this.COLORS.length]
          }
          
          var div = document.createElement("div")
          div.className = "color"
          
          // The user could have given us a bad color, in which case we 
          // use a color cell with a transparent background (for IE).
          try
          {
            div.style.backgroundColor = color
          }
          catch(e)
          {
            div.style.backgroundColor = "transparent"
          }
          
          htmlNodeTxt.appendChild(div)
        }
      }
      else
        // no style defined => empty cell
        htmlNode = document.createElement("td")

      if ( value != null )
      {
        // If the value isn't a string, we consider it to be a valid node.
        var ndValue
        if( typeof value == "string" )
          ndValue = document.createTextNode(value)
        else
          ndValue = value
        
        var parentNode = htmlNodeTxt || htmlNode
        // Keep this value in mind for the helpContent popup
        helpContent.title = value
        
        if ( style.onclick == true )
        {
          var childNode = parentNode.firstChild
          while ( childNode != null && childNode.nodeName != "A" )
            childNode = childNode.nextSibling
          
          if( childNode == null ) 
            // "A" element was removed previously, add text element
            parentNode.appendChild(ndValue)
          else
            childNode.appendChild(ndValue)
        }
        else
          parentNode.appendChild(ndValue)
      }
      
      if ( typeof cell.cs != "undefined" )
        htmlNode.colSpan = cell.cs
      
      // Define the popups for additionnal informations
      if( helpContent.content != "" )
      {
        if( helpContent.link )
          new HelpBubble(helpContent.link, helpContent, { eAttachmentMode : HelpBubbleOptions.EAttachmentMode.ON_CLICK } )
        
        // Anyway, we show the description on mouse overs.
        var mainNode = htmlNode.innerTable ? htmlNode.getMainTD() : htmlNode
        new HelpBubble(mainNode, helpContent)
      }
      
      trNode.appendChild(htmlNode)
    }
  }

  // Post process rows :
  // collapse all the rows below headers registered in groupsToCollapse
  for( var iRowIndex = 0 ; iRowIndex < groupsToCollapse.length ; iRowIndex++ )
    this.toggleCollapsing(groupsToCollapse[iRowIndex])
}

// Retrieve the level of a row from its class
// example of a tr with a level : <tr class="header level0 odd">
ReportList.prototype.getLevel = function(className)
{
  return className.match(/level(\d+)/)[1]
}

// Return true if the row can be collapsed or not for the given level
// of collapsing.
ReportList.prototype.canBeCollapsed = function(className, level)
{
  var canBeCollapsed = true
  if( className != undefined )
  {
    if( className.indexOf("header") != -1
      && this.getLevel(className) <= level )
      canBeCollapsed = false
    else if( className.indexOf("footer") != -1
      && this.getLevel(className) < level )
      canBeCollapsed = false
  }
  return canBeCollapsed
}

// ToggleVisibility takes an img DOM element as its argument
// and then loop other its parent's following tr elements which
// levels are superior to its parent's.
ReportList.prototype.toggleCollapsing = function(image)
{
  var displayModifier
  if( image.src.indexOf(ReportList.prototype.IMG_PLUS) == -1 )
  {
    displayModifier = function(row)
    {
      // Function used to hide the children rows
      row.style.display = "none"
    }
    // Set the plus image for decollapsing
    image.src = ReportList.prototype.IMG_PLUS
  }
  else
  {
    displayModifier = function(row)
    {
      // Function used to show the children rows
      row.style.display = ""
      
      // And change the image src back to its decollapsed state as we extend the
      // rows regardless of their previous state.
      var img = row.getElementsByTagName("img")[0]
      if( img != undefined && img.src.indexOf(ReportList.prototype.IMG_PLUS) != -1 )
        img.src = ReportList.prototype.IMG_MINUS
    }
    // Set the minus image for collapsing
    image.src = ReportList.prototype.IMG_MINUS
  }
  
  var row = image.parentNode;
  // Inner table => avoid TR with tryAgain attribute.
  while( row != undefined && ( row.nodeName != "TR" || row.tryAgain == true ) )
    row = row.parentNode;
  var level = this.getLevel(row.className)

  row = row.nextSibling
  // We continue only if the current row is not a header or a footer unless 
  // their level is superior to the original one.
  while( row != undefined && this.canBeCollapsed(row.className, level) )
  {
    displayModifier(row)
    
    row = row.nextSibling
  }
  if( typeof resizeParentFrame == "function" )
    resizeParentFrame()
}

// Function used to add a table into an html node, which can be necessary
// if you want to align several elements (divs, images, ...) horizontally.
var addTable = function(htmlNode, style)
{
  if( htmlNode.innerTable )
    return htmlNode.innerTable  // There already is an inner table
  
  var table = document.createElement("table")
  var tbody = document.createElement("tbody")
  // construct the table hierarchy
  table.appendChild(tbody)
  htmlNode.appendChild(table)
  
  htmlNode.innerTable = table
  
  // Insert a node at a given position
  // ndParent : parent node
  // ndNode : node to insert
  // strPos : "first", "last" -> first/last position
  //          "before", "after" -> before/after text node
  // ndRef : node of refence for the "before" and "after" cases
  var insertNodeAtPos = function(ndParent, ndNode, strPos, ndRef)
  {
    if( strPos == "first" )
      ndParent.insertBefore(ndNode, ndParent.firstChild)
    else if( strPos == "last" )
      ndParent.appendChild(ndNode)
    else if( strPos == "before" )
      ndParent.insertBefore(ndNode, ndRef)
    else if( strPos == "after" )
    {
      if( ndRef.nextSibling )
        ndParent.insertBefore(ndNode, ndRef.nextSibling)
      else
        ndParent.appendChild(ndNode)
    }
  }
  
  // Adds another row at the desired position, with the table inner 
  // row (the one containing the main TD) as reference.
  // strPos : "first", "last" -> first/last position
  //          "before", "after" -> before/after row containing the text node
  htmlNode.addInnerRow = function(style, strPos)
  {
    if( !htmlNode.innerTable )
      throw "You can't add a row into a table you destroyed.."
    
    var newRow = document.createElement("tr")
    // Avoid considering this row as the parent row of the elements we are going
    // to put in it (in case we want to search their ancestry through recursion)
    newRow.tryAgain = true
    
    // Create the main row if necessary
    if( !htmlNode.getMainTR() )
    {
      // The main TD is the TD which contains the table standard data
      htmlNode.innerTable.firstChild.appendChild(newRow)
      htmlNode.innerTable.mainTR = newRow
    }
    else if( strPos )
    {
      // Put the new row at the right place. InnerTable.firstChild : tbody.
      insertNodeAtPos(htmlNode.innerTable.firstChild, newRow, 
                      strPos, htmlNode.getMainTR())
    }
    
    // Function used to add a new td into an inner table
    // strPos : "first", "last" -> first/last position
    //          "before", "after" -> before/after text node
    newRow.addInnerCell = function(style, strPos)
    {
      if( !htmlNode.innerTable )
        throw "You can't add a cell into a table you destroyed.."
      
      var innerTD
      // inherit the cell style if defined in style
      if( style && style.td )
        innerTD = style.td.cloneNode(true)
      else
        innerTD = document.createElement("td")
      innerTD.className = "collapse"          // Slim
      innerTD.style.border = "none"
      
      // Create the "mainTD" if necessary
      if( !newRow.mainTD )
      {
        // The main TD is the TD which contains the table standard data
        innerTD.style.width = "100%"
        newRow.appendChild(innerTD)
        newRow.mainTD = innerTD
      }
      else if( strPos )
      {
        // Put the new TD at the right place
        insertNodeAtPos(newRow, innerTD, strPos, newRow.mainTD)
      }
      
      return innerTD
    }
    
    // Create the main TD
    newRow.mainTD = newRow.addInnerCell(style)
    
    newRow.getMainTD = function()
    {
      return newRow.mainTD
    }
    
    // Return the new row
    return newRow
  }
  
  // Main row
  htmlNode.getMainTR = function()
  {
    // This is ok as the main row is built during the table setup
    return htmlNode.innerTable.mainTR
  }
  // Main cell of the main row
  htmlNode.getMainTD = function()
  {
    // This is ok as the main cell is built during the table setup
    return htmlNode.getMainTR().getMainTD()
  }
  
  // Allow the user to add new cells into the table main row directly
  htmlNode.addInnerCell = function(style, strPos)
  {
    return htmlNode.getMainTR().addInnerCell(style, strPos)
  }
  
  // Finalise the setup
  // We create the main row and cell so that the text will be put into it
  htmlNode.addInnerRow(style)
}

// Called client side from within the xsl to set the value in its div once
// correctly formatted.
// div : div containing style info and where the formatted value should go
// xpath : used only for enumerations AFAN
// value : raw value
function formatAndSetValue(div, value, context, xpath)
{
  if( value == null )
    return
  
  // Special preprocessing for enums as their formatting function needs more 
  // informations. 
  if( div.compiledStyle.format == "enum" )
  {
    var enumFormat = div.compiledStyle.enumFormat
    var enumValues = 
    {
      rawValue : value,
      htmlNodeRef : div
    }
    
    if( enumFormat == "label" )
      enumValues.labelValue = getXPathValue(context.ctx, xpath + "Label")
    else if( enumFormat == "image" )
      enumValues.imageValue = getXPathValue(context.ctx, xpath + "Img")
    else if( enumFormat != "" )
    {
      enumValues.labelValue = getXPathValue(context.ctx, xpath + "Label")
      enumValues.imageValue = getXPathValue(context.ctx, xpath + "Img")
    }
    value = enumValues
  }
  
  value = FormatHelper.format[div.compiledStyle.format].call(context, value, div.compiledStyle)
  
  div.appendChild(typeof value == "string" ? document.createTextNode(value) : value)
}

function FormatHelper()
{}

/** Formating values functions */
FormatHelper.format = {
 
  "number": function(text, style)
  {
    var value  = parseFloat(text)
    var options = {
      "pattern": style.pattern
    }
    
    return value.format(options)
  },

  "percentage": function(text, style)
  {
    var value  = parseFloat(text) * 100
    var options = {
      "pattern":  style.pattern,
      "symbol":   style.symbol
    }
    
    return value.format(options)
  },

  "currency": function(text, style)
  {
    var value  = parseFloat(text) 
    var options = {
      "pattern":  style.pattern,
      "symbol":   '$'
    }
    
    return value.format(options)
  },
  
  "date": function(text, style)
  {
    var d = Format.parseDateTimeInter(text)
    if( style.useTime )
      return Format.formatDateTime(d, false, style.useSec)

    return Format.formatDateTime(d, Format.getSettings().shortDate, false)
  },
  "time": function(text, style)
  {
    if( text.indexOf('-') > -1 )
      return Format.formatDateTime(Format.parseDateTimeInter(text), Format.getSettings().time, style.useSec)
    else
      return Format.formatTime(Format.parseTime(text), !style.useSec)
  },
  "timespan": function(text, style)
  {
    var value = parseFloat(text)
    var fmt   = Format.formatTimeSpan(value)
    return fmt
  },
  "string": function(text)
  {
    return text
  },
  "image": function(text)
  {
    if( text )
    {
      // Only support images from neolane (xtk:foobar.png, ...)
      var ndImage = document.createElement("img")
      ndImage.className = "raw"
      ndImage.src = ParseXtkImg(text)
      return ndImage
    }
  },
  "enum": function(enumValues, style)
  {
    // enumValues : struct with a rawValue, labelValue and imageValue.
    if( typeof enumValues == "string" )
      return FormatHelper.format.string(enumValues) // For compatibility purposes
    
    // the enumeration formats are defined in "enumDisplayFormat".
    var format = style.enumFormat
    var ndEnum = ""
    
    if( format == "label" && enumValues.labelValue )
      ndEnum = FormatHelper.format.string(enumValues.labelValue)
    else if( format == "image" && enumValues.imageValue )
      ndEnum = FormatHelper.format.image(enumValues.imageValue)
    else if( format != "" && enumValues.labelValue && enumValues.imageValue )
    {
      // we create an inner table to get the horizontal alignment right.
      var htmlNode = enumValues.htmlNodeRef
      
      // Check whether an inner table was already set for this cell or not
      if( !htmlNode.innerTable )
        addTable(htmlNode, style)
      
      // Put the label text in the table main TD
      htmlNode.getMainTD().appendChild(document.createTextNode(
                            FormatHelper.format.string(enumValues.labelValue)))
      
      // And put the img tag as well
      var tdImage
      if( format == "HLabelImage" )
        tdImage = htmlNode.addInnerCell(style, "after")
      else if( format == "HImageLabel" )
        tdImage = htmlNode.addInnerCell(style, "before")
      else if( format == "VLabelImage" )
        tdImage = htmlNode.addInnerRow(style, "after").getMainTD()
      else if( format == "VImageLabel" )
        tdImage = htmlNode.addInnerRow(style, "before").getMainTD()
      
      var ndImg = FormatHelper.format.image(enumValues.imageValue)
      tdImage.appendChild(ndImg)
      
      // Don't forget to update the html node reference so it points to our main TD
      enumValues.htmlNodeRef = htmlNode.getMainTD()
      
      ndEnum = htmlNode.innerTable
    }
    
    return ndEnum
  }
}

/** Compile a style to create a template of the <td>.
  *
  * @style the XML definition of the style. The schema of 
  *        this element is the one defined for the flat report
  *        format:
  *
  *   <style format="percentage" alignement="center">
  *     <font bold="true"/>
  *   </style>
  *
  * @see https://wiki.neolane.net/wiki/Reporting_5.00#srcSchema_like_xml_structure. */

function compileStyle(style, htmlElement, defaultFontSize)
{
  var format = style.getAttribute("format")
  
  if ( format == null )
    format = "string"

  var compiledStyle = {
    "format":             format, 
    "negativeNumberRed":  style.getAttribute("negativeNumberRed") == "true" ? true : false
  }
  
  // In IE, td inherits text-align property from their parents
  htmlElement.style.textAlign = "left"

  if ( format == "number" || format == "currency" || format == "percentage" )
  { // 
    // numeric formats
    //   
    
    // create the formating pattern
    var pattern
    if ( style.getAttribute("use1000Sep") == "true" )
      pattern = "#,###"
    else
      pattern = "#"
      
    var decimalPlaces = style.getAttribute("decimalPlaces")
    decimalPlaces = decimalPlaces == null ? 2 : decimalPlaces
    if ( decimalPlaces > 0 )
    {
      pattern += "."
      for (var i=0; i < decimalPlaces; i++)
        pattern += "0"
    }
    
    if ( format == "percentage" )
    {
      pattern += "¤"
      if( Format.settings.percentSeparator )
        compiledStyle.symbol = Format.settings.percentSeparator + "%"
      else
        compiledStyle.symbol = "%"
    }
    
    compiledStyle.pattern = pattern
  
    // default alignment for numeric values is right 
    htmlElement.style.textAlign = "right"
  }
  else if ( format == "date" || format == "time" )
  {
    compiledStyle.useTime = style.getAttribute("useTime") == "true"
    compiledStyle.useSec  = style.getAttribute("useSec") == "true"
  }
  else if ( format == "enum" )
  {
    // Register the enumeration format into the style (label, label + image, ...)
    compiledStyle.enumFormat = style.getAttribute("enumFormat")
  }
    
  if ( style.getAttribute("alignment") != null 
    && style.getAttribute("alignment") != "auto" )
    htmlElement.style.textAlign = style.getAttribute("alignment")
    
  if( style.getAttribute("verticalAlignment") != null && style.getAttribute("verticalAlignment") != "default" )
    htmlElement.style.verticalAlign = style.getAttribute("verticalAlignment")
  
  var widthMode = style.getAttribute("widthMode")
  if ( widthMode == 'max' )
    htmlElement.style.width = style.getAttribute("width") + "%"
  else if ( widthMode == 'manual' )
    htmlElement.style.width = style.getAttribute("width") + "em"
  // automatic widthMode has no width, it uses content to size itself
  
  if( style.getAttribute("wrapText") != null && 
      style.getAttribute("wrapText") == "true" )
    htmlElement.style.whiteSpace = 'normal'
  else
    // By default, no wrap
    htmlElement.style.whiteSpace = 'nowrap'
  
  var childNode = style.firstChild
  while ( childNode != null )
  {
    if ( childNode.nodeType == XML.ELEMENT_NODE )
    {
      if ( childNode.nodeName == "font" )
      {
        var fontStyle = childNode.getAttribute("style")
        if ( fontStyle == "bold" || fontStyle == "boldItalic" )
          htmlElement.style.fontWeight = "bold"
        if ( fontStyle == "italic" || fontStyle == "boldItalic" )
          htmlElement.style.fontStyle = "italic"
        if( fontStyle == "normal" )
        {
          htmlElement.style.fontWeight = "normal"
          htmlElement.style.fontStyle = "normal"
        }
          
        var fontSize = childNode.getAttribute("size")
        if ( fontSize != undefined && fontSize != "0" && fontSize != "" )
        {
          if( isNaN(defaultFontSize) )
            htmlElement.style.fontSize = "1em"
          else
            htmlElement.style.fontSize = (fontSize / defaultFontSize) + "em"
        }
        var color = childNode.getAttribute("color")
        if ( color != undefined )
          htmlElement.style.color = color
      }
      else if ( childNode.nodeName == "extra" )
      {
        var visual = childNode.getAttribute("visual")
        if ( visual == "bargraph" )
        {
          var bargraph = document.createElement("div")
          bargraph.className = "bargraph"
          var bargraphContainer = document.createElement("div")
          bargraphContainer.className = "container"
          bargraph.appendChild(bargraphContainer)          
          var bargraphValue = document.createElement("div")
          bargraphValue.className = "blue"
          bargraphContainer.appendChild(bargraphValue)
          compiledStyle.bargraph = bargraph
        }
        else if ( visual == "rating" )
        {
          compiledStyle.rating = true
          compiledStyle.ratingMax = parseInt(childNode.getAttribute("ratingMax"))
        }
        else if ( visual == "color" )
        {
          compiledStyle.color = true
          compiledStyle.hideValue = true
        }
        
        if ( childNode.getAttribute("hideValue") == "true" )
          // do not display the value in cell
          compiledStyle.hideValue = true
      }
      else if ( childNode.nodeName == "onclick" 
        && childNode.getAttribute("action") != "none" )
      {
        var a = document.createElement("a")
        a.href = "#"
        a.className = "linkAction"
        
        // Need to get URL href
        compiledStyle.onclick = true        
        compiledStyle.onclickAction     = childNode.getAttribute("action")
        compiledStyle.onclickTarget     = childNode.getAttribute("target")
        compiledStyle.onclickTransition = childNode.getAttribute("transition")
        if( compiledStyle.onclickAction == "url" )
          compiledStyle.onclickOption = childNode.getAttribute("href")
        else if( compiledStyle.onclickAction == "javascript" )
          compiledStyle.onclickOption = getXPathValue(childNode, "javascript");
        else if( compiledStyle.onclickAction == "refreshData" )
        {
          compiledStyle.onclickOption = new Array()      
          var styleChildNode = childNode.firstChild
          while ( styleChildNode != null )
          {
            if( styleChildNode.nodeName == "objectTarget" )
              compiledStyle.onclickOption.push(styleChildNode.getAttribute("identifier"))              
            styleChildNode = styleChildNode.nextSibling
          }
        }

        htmlElement.appendChild(a)
        
        if ( compiledStyle.onclickAction == "transition" )
          compiledStyle.onclickAction = "next"
      }
      else if ( childNode.nodeName == "border" )
      {
        var ndBorder = childNode.firstChild
        var strToEvalBorderProperties = ""
        while( ndBorder != null )
        {
          if( ndBorder.nodeType == XML.ELEMENT_NODE )
          {
            // As setProperty is not supported under IE, 
            // htmlElement.style.setProperty(ndBorder.nodeName, ndBorder.getAttribute("value"),"")
            // is not possible. Note that in this case, ndBorder.nodeName must be formatted like 
            // "border-top" and not "borderTop".
            try
            {
              htmlElement.style[ndBorder.nodeName] = ndBorder.attributes[0].nodeValue
            }
            catch(e)
            {
              // Silently catch exceptions (typically, ie6/7 can't handle 
              // "grey" as a valid color here, and thus used to throw and break havok).
            }
          }
          ndBorder = ndBorder.nextSibling
        }
      }
    }
    childNode = childNode.nextSibling
  }

  return compiledStyle
}

