List<TableCell> _Items;			// list of TableCell

		public TableCells(ReportDefn r, ReportLink p, XmlNode xNode) : base(r, p)
		{
			TableCell tc;
            _Items = new List<TableCell>();
			// Loop thru all the child nodes
			int colIndex=0;			// keep track of the column numbers
			foreach(XmlNode xNodeLoop in xNode.ChildNodes)
			{
				if (xNodeLoop.NodeType != XmlNodeType.Element)
					continue;
				switch (xNodeLoop.Name)
				{
					case "TableCell":
						tc = new TableCell(r, this, xNodeLoop, colIndex);
						colIndex += tc.ColSpan;
						break;
					default:	
						tc=null;		// don't know what this is
						// don't know this element - log it
						OwnerReport.rl.LogError(4, "Unknown TableCells element '" + xNodeLoop.Name + "' ignored.");
						break;
				}
				if (tc != null)
					_Items.Add(tc);
			}
			if (_Items.Count > 0)
                _Items.TrimExcess();
		}
		public void TableCellStart(TableCell t, Row row)
		{
			string cellType = t.InTableHeader? "th": "td";

			ReportItem r = t.ReportItems.Items[0];

			string cssName = CssAdd(r.Style, r, row);	// get the style name for this item

			tw.Write("<{0} id='{1}'", cellType, cssName);

			// calculate width of column
			if (t.InTableHeader && t.OwnerTable.TableColumns != null)
			{
				// Calculate the width across all the spanned columns
				int width = 0;
				for (int ci=t.ColIndex; ci < t.ColIndex + t.ColSpan; ci++)
				{
					TableColumn tc = t.OwnerTable.TableColumns.Items[ci] as TableColumn;
					if (tc != null && tc.Width != null)
						width += tc.Width.PixelsX;
				}
				if (width > 0)
					tw.Write(" width={0}", width);
			}

			if (t.ColSpan > 1)
				tw.Write(" colspan={0}", t.ColSpan);

			Textbox tb = r as Textbox;
			if (tb != null &&				// have textbox
				tb.IsToggle &&				//   and its a toggle
				tb.Name != null)			//   and need name as well
			{
				int groupNestCount = t.OwnerTable.GetGroupNestCount(this.r);
				if (groupNestCount > 0) // anything to toggle?
				{
					string name = tb.Name.Nm + "_" + (tb.RunCount(this.r)+1).ToString();
					bScriptToggle = true;

					// need both hand and pointer because IE and Firefox use different names
					tw.Write(" onClick=\"hideShow(this, {0}, '{1}')\" onMouseOver=\"style.cursor ='hand';style.cursor ='pointer'\">", groupNestCount, name);
                    tw.Write("<img class='toggle' src=\"plus.gif\" align=\"top\"/>");
				}
				else
                    tw.Write("<img src=\"empty.gif\" align=\"top\"/>");
			}
			else
				tw.Write(">");

			if (t.InTableHeader)
			{	
				// put the second half of the sort tags for the column; if needed
				// first half ---- <a href="#" onclick="sort_table(this,sort_cmp_string,1,0);return false;">
				// next half follows text  ---- <span class="sortarrow">&nbsp;&nbsp;&nbsp;</span></a></th>

				string sortcmp = SortType(t, tb);	// obtain the sort type
				if (sortcmp != null)				// null if sort not needed
				{
					int headerRows, footerRows;
					headerRows = t.OwnerTable.Header.TableRows.Items.Count;	// since we're in header we know we have some rows
					if (t.OwnerTable.Footer != null &&
						t.OwnerTable.Footer.TableRows != null)
						footerRows = t.OwnerTable.Footer.TableRows.Items.Count;
					else
						footerRows = 0;
					tw.Write("<a href=\"#\" title='Sort' onclick=\"sort_table(this,{0},{1},{2});return false;\">",sortcmp, headerRows, footerRows);
				}
			}

			return;
		}
		public void TableCellEnd(TableCell t, Row row)
		{
			string cellType = t.InTableHeader? "th": "td";
			Textbox tb = t.ReportItems.Items[0] as Textbox;
			if (cellType == "th" && SortType(t, tb) != null)
			{	// put the second half of the sort tags for the column
				// first half ---- <a href="#" onclick="sort_table(this,sort_cmp_string,1,0);return false;">
				// next half follows text  ---- <span class="sortarrow">&nbsp;&nbsp;&nbsp;</span></a></th>
				tw.Write("<span class=\"sortarrow\">&nbsp;&nbsp;&nbsp;</span></a>");
			}

			tw.Write("</{0}>", cellType);
			return;
		}
		private string SortType(TableCell tc, Textbox tb)
		{
			// return of null means don't sort
			if (tb == null || !IsTableSortable(tc.OwnerTable))
				return null;

			// default is true if table is sortable;
			//   but user may place override on Textbox custom tag
			if (tb.Custom != null)
			{
				// Loop thru all the child nodes
				foreach(XmlNode xNodeLoop in tb.Custom.CustomXmlNode.ChildNodes)
				{
					if (xNodeLoop.Name == "HTML")
					{
						if (xNodeLoop.LastChild.InnerText.ToLower() == "false")
						{
							return null;
						}
						break;
					}
				}
			}

			// Must find out the type of the detail column
			Details d = tc.OwnerTable.Details;
			if (d == null)
				return null;
			TableRow tr = d.TableRows.Items[0] as TableRow;
			if (tr == null)
				return null;
			TableCell dtc = tr.TableCells.Items[tc.ColIndex] as TableCell;
			if (dtc == null)
				return null;
			Textbox dtb = dtc.ReportItems.Items[0] as Textbox;
			if (dtb == null)
				return null;

			string sortcmp;
			switch (dtb.Value.Type)
			{
				case TypeCode.DateTime:
					sortcmp = "sort_cmp_date";
					break;
				case TypeCode.Int16:
				case TypeCode.UInt16:
				case TypeCode.Int32:
				case TypeCode.UInt32:
				case TypeCode.Int64:
				case TypeCode.UInt64:
				case TypeCode.Decimal:
				case TypeCode.Single:
				case TypeCode.Double:
					sortcmp = "sort_cmp_number";
					break;
				case TypeCode.String:
					sortcmp = "sort_cmp_string";
					break;
				case TypeCode.Empty:	// Not a type we know how to sort
				default:		
					sortcmp = null;
					break;
			}

			return sortcmp;
		}
 public void TableCellEnd(TableCell t, Row r)
 {
 }
 public void TableCellStart(TableCell t, Row r)
 {
 }
		// Handle parsing of function in final pass
		override public void FinalPass()
		{
			if (_Style != null)
				_Style.FinalPass();
			if (_Action != null)
				_Action.FinalPass();
			if (_Visibility != null)
				_Visibility.FinalPass();
			if (_ToolTip != null)
				_ToolTip.FinalPass();
			if (_Label != null)
				_Label.FinalPass();
			if (_Bookmark != null)
				_Bookmark.FinalPass();
			if (_Custom != null)
				_Custom.FinalPass();

			if (Parent.Parent is TableCell)	// This is part of a table
			{
				_TC = Parent.Parent as TableCell;
			}
			else
			{
				_TC = null;
			}

			// Determine if ReportItem is defined inside of a Matrix
			_InMatrix = false;
			for (ReportLink rl = this.Parent; rl != null; rl = rl.Parent)
			{
				if (rl is Matrix)
				{
					_InMatrix = true;
					break;
				}
				if (rl is Table || rl is List || rl is Chart)
					break;
			}

			return;
		}
		public void TableCellEnd(TableCell t, Row row)
		{
			return;
		}
		public void TableCellStart(TableCell t, Row row)
		{
			return;
		}
		public void TableCellEnd(TableCell t, Row row)
		{
            // ajm 20062008 need to increase to cover the merged cells, excel still defines every cell
            _ExcelCol += t.ColSpan - 1;
			return;
		}
		public void TableCellStart(TableCell t, Row row)
		{
            _ExcelCol++;
            if (t.ColSpan > 1) {
                _Excel.SetMerge(string.Format("{0}{1}:{2}{3}", (char)('A' + _ExcelCol), _ExcelRow + 1, (char)('A' + _ExcelCol + t.ColSpan - 1), _ExcelRow + 1), SheetName);
            }
            return;
		}