// Table.js - Code for loading, resizing and displaying table data.

// Ensure 'undefined' is defined.
var undefined;

// The 4 chunks that can be used to draw a page.
var M_tlChunk;	// top-left chunk
var M_trChunk;	// top-right chunk
var M_blChunk;	// bottom-left chunk	
var M_brChunk;	// bottom-right chunk

var M_xmlData = ""; // the XML data received when the table view page is first loaded

// Define the size of a chunk.
// This is the maximum number of rows and columns returned in a chunk.
var M_rowsPerChunk = 100;
var M_colsPerChunk = 100;
var M_rowsPrintable = 2147483647;   // Max Int (Max cell limit for printing is checked in OnPreparePrint)
var M_colsPrintable = 2147483647;

var M_tableRowCount = 0;
var M_tableColCount = 0;

var M_rowDimCount = 0;
var M_colDimCount = 0;

var M_rowHeaderClass;
var M_colHeaderClass;
var M_colHeaderSortClass;
var M_dataTableClass;
var M_dataTotalClass;

// These variables are used when ActiveX controls are not enabled.
var M_bBusy = false;
var M_iframeObj; // our IFrame object

// This variable is used to record if a refresh was deferred due to the required data not
// being available. When new data is loaded, a fresh will occur when the chunk is loaded.
var M_refreshNeeded = false;

// Additional number of columns to append to table before filling it with data.
var M_additionalColumns = 5;
// Additional number of rows to append to table before filling it with data.
var M_additionalRows = 0;	// set to 0 for now because it does not seem to have an
							// impact on the number of rows that can fit on the screen.

// These constants contain the path of sort arrow images.
var M_strSortAscendingArrow = "../Common/Images/SortTopArrow.gif";
var M_strSortAscendingArrowActive = "../Common/Images/SortTopArrowActive.gif";
var M_strSortDescendingArrow = "../Common/Images/SortBottomArrow.gif";
var M_strSortDescendingArrowActive = "../Common/Images/SortBottomArrowActive.gif";

var M_strSummaryImage = "../Common/Images/info.gif"

// Separator for the footnote identifiers stored in the string in TableChunk.rowNotes
var M_footnoteSep = ","
// Separates the footnotes identifiers from the missing value identifier stored in the
// string in TableChunk.rowNotes
var M_footnoteMissingValSep = ";"

var M_sortItemSep = "\035";
var M_sortSectionSep = "\036";

// contains the index on the page (visible columns) of the sorted column.
var M_sortedColInfo = new SortInfo();

// The following constants are returned by the updateTable() function
var M_Update_Updated = 0; // The table was updated.
var M_Update_WaitingForData = 1; // The table was not updated because not enough data chunks were available.

// If totals/percentages have been selected, M_totalPrctType has one of the 4 following values:
// M_rowTotal, M_colTotal, M_rowPercent, M_colPercent.  Any other value means that totals/percentages
// have not been selected.
var M_totalPrctType;
var M_totalPrctUnspecified = 0;
var M_rowTotal = 1;
var M_colTotal = 2;
var M_rowPercent = 3;
var M_colPercent = 4;

var M_displayGroupLinks = true;

var M_bSupport508 = true;

var M_summaryTooltip = "";

self.setInterval('GHandleFontSizeChange()', 2000)

// This object is simply used to hold the information about a chunk all in one place.
function TableChunk(
	i_firstRow,
	i_lastRow,
	i_firstCol,
	i_lastCol,
	i_rowLabels,
	i_colLabels,
	i_otherLabels,
	i_rowIDs,
	i_colIDs,
	i_otherIDs,
	i_rowDrillLabels,
	i_colDrillLabels,
	i_otherDrillLabels,
	i_rowIsGroup,
	i_colIsGroup,
	i_otherIsGroup,
	i_rowHasSummary,
	i_colHasSummary,
	i_otherHasSummary,
	i_rowIsColTotal,
	i_colIsRowTotal,
	i_rowLabelSpans,
	i_colLabelSpans,
	i_rowData,
	i_rowNotes
	)
{
	this.firstRow  = i_firstRow;
	this.lastRow   = i_lastRow;
	this.firstCol  = i_firstCol;
	this.lastCol   = i_lastCol;
	this.rowLabels = i_rowLabels;
	this.colLabels = i_colLabels;
	this.otherLabels = i_otherLabels;
	this.rowIDs = i_rowIDs;
	this.colIDs = i_colIDs;
	this.otherIDs = i_otherIDs;
	this.rowDrillLabels = i_rowDrillLabels;
	this.colDrillLabels = i_colDrillLabels;
	this.otherDrillLabels = i_otherDrillLabels;
	this.rowIsGroup = i_rowIsGroup;
	this.colIsGroup = i_colIsGroup;
	this.otherIsGroup = i_otherIsGroup;
	this.rowHasSummary = i_rowHasSummary;
	this.colHasSummary = i_colHasSummary;
	this.otherHasSummary = i_otherHasSummary;
	this.rowIsColTotal = i_rowIsColTotal;
	this.colIsRowTotal = i_colIsRowTotal;
	this.rowLabelSpans = i_rowLabelSpans;
	this.colLabelSpans = i_colLabelSpans;
	this.rowData   = i_rowData;
	this.rowNotes   = i_rowNotes;
}

// This object holds information about the table size.
function TableSize()
{
	this.colCount = 0;
	this.rowCount = 0;
	this.tableWidth = 0;
	this.tableHeight = 0;
	this.vScrollWidth = 0;
	this.hScrollHeight = 0;
}

// This object holds information about the sorted column.
function SortInfo()
{
	this.m_index = -1;	// index of the sorted column
	this.m_order = -1;  // order of the sorted column - 0 for descending, 1 for ascending,
						//		 -1 if nothing if there is not sorting.
}

// Initialize the list of table chunks.
var M_tableChunks;

// A reference to the table that contains the data table and the 2 scroll bars.
var M_mainTable;

// This variable is a reference to the HTML data table.
var M_dataTable;

// This variable is a reference to the other dimensions HTML table.
var M_otherDimTable;

// This variable is a reference to the vertical scroll bar cell.
var M_vScrollBarCell;

// This variable is a reference to the horizontal scroll bar cell.
var M_hScrollBarCell;

// An array of size M_rowDimCount - 1 that is used (in resizeTable) to calculate the span
// of items in each nesting row dimension.
var M_rowSpanIndexes;

// An array of size M_rowDimCount - 1 that is used (in resizeTable) to calculate the span
// of items in each nesting row dimension.
var M_rowSpanCounts;

// Arrays of size M_rowDimCount that are used (in updateTable) to hold the index of the first
// item label to draw (index into TableChunk.i_rowLabels, and TableChunk.i_colLabels) for each
// row and column dimension.
var M_firstRowLabelIndexes;
var M_firstColLabelIndexes;
var M_firstRowLabelIndexesOld;
var M_firstColLabelIndexesOld;
var M_oldRowCounts;
var M_oldColCounts;
var M_firstColLabelIndexesForSorting;
	
	
// This variable is used in updateTable().  It is true if the 'bottom' chunks are being used to update
// the table.
var M_startInBottomChunk;
// This variable is used in updateTable().  It is true if the 'right' chunks are being used to update
// the table.
var M_startInRightChunk;

// This variable is an array of boolean values and is used in updateTable().  It contains a value for
// each nesting row dimension.  For a dimension, the corresponding value is True if and only if the
// item label spans the top and bottom chunks of data.
var M_doesRowLabelSpanChunks;

// This variable is an array of boolean values and is used in updateTable().  It contains a value for
// each nesting column dimension.  For a dimension, the corresponding value is True if and only if the
// item label spans the left and right chunks of data.
var M_doesColLabelSpanChunks;

// Arrays of size M_rowDimCount - 1 that are used (in updateTable) to hold the span
// of items in each nesting row and column dimension.
var M_rowLabelSpans;
var M_colLabelSpans;

// The dimension indexes as specified in the XML document by the "id" attribute of each dimension
var M_rowDimIDs = new Array();
var M_colDimIDs = new Array();
var M_otherDimIDs = new Array();
var M_colDimNames = new Array();
var M_rowDimNames = null;
var M_otherDimNames = null;


// IE and NN use different attributes for style class names.
// IE provides innerText which is faster than innerHTML, if available.
var M_className = PWdsapp_bBrowserClassname ? "className" : "class";
var M_innerAttr = PWdsapp_bIsIE ? "innerText" : "innerHTML";	// IE vs NN
var M_emptyCell = PWdsapp_bIsIE ? " " : "&nbsp;";	// IE vs NN

// The following variables are HTML elements that are cloned to create the table.
var M_summaryImage;
	// This is an object that represents the summary image displayed for row and column item labels.
var M_dataCellContent;
	// This is the content of an empty data cell.

// This is an object that represents a blank space.
var M_blankSpace;

// Footnotes and Missing Values
var M_footnotes = new Array();      // Footnotes
var M_missingValues = new Array();  // MissingValues
var M_bFootnotes = false;
var M_bMissingValues = false;
var M_cellNotes = "";               // Current Cell Notes 
var M_currentCell;                  // Current Cell Object
var M_popupOffset = 5;              // Distance from Cell to Popup Box
var M_boundary = 5;                 // Distance from Popup Box to screen boundaries
var M_copyPopupBox;                 // Copy of Div Element for resetting onmouseout
var M_copyCurrentCellClass;         // Copy of Class Attribute of Current Cell for resetting onmouseout
var M_timeoutID = 0;                // Timeout ID returned from setTimeout()
var M_timeoutDelay = 500;           // Amount of time to delay before popping up the Cell Notes

// Printable Version Variables
var M_isPrintableVersion = false;    
var M_titleTableHeight = 0;         // Height of Title Table
var M_otherDimTableHeight = 0;      // Height of the Other Dimensions Table
var M_sourceInfoTableHeight = 0;    // Height of the Source Info Table

var M_printableHeight = 0;          
var M_printableWidth = 0;           
var M_pixelsPerInch = 80;           // High resolution screens range from 80-85 ppi, so we take the smaller one.
var M_paperTypeCount = 8;           // A3, A4, A5, B5, Letter, Legal, Executive, Tabloid

var M_conversionFactor = PWdsapp_bIsIE ? 10/9 : 1;   // Difference between the screen and what a typical printer actually prints
                                                    // TBD: This may be different for horizontal vs vertical, but is currently being
                                                    //      applied to both equally.

// Hard-code the paper sizes in inches (1 inch = 2.54 cm)
// M_paperSizes[type][0] = width in portrait mode
// M_paperSizes[type][1] = height in portrait mode
var M_paperSizes = new Array();
for (type = 0; type < M_paperTypeCount; type++){
	M_paperSizes[type]= new Array();
}
// A3 - 297mm x 420mm
M_paperSizes[0][0] = 29.7 / 2.54;
M_paperSizes[0][1] = 42 / 2.54;
// A4 - 210mm x 297mm
M_paperSizes[1][0] = 21 / 2.54;
M_paperSizes[1][1] = 29.7 / 2.54;
// A5 - 148mm x 210mm
M_paperSizes[2][0] = 14.8 / 2.54;
M_paperSizes[2][1] = 21 / 2.54;
// B5 - 176mm x 250mm
M_paperSizes[3][0] = 17.6 / 2.54;
M_paperSizes[3][1] = 25 / 2.54;

// Letter - 8.5 x 11 
M_paperSizes[4][0] = 8.5;
M_paperSizes[4][1] = 11;
// Legal - 8.5 x 14
M_paperSizes[5][0] = 8.5;
M_paperSizes[5][1] = 14;
// Executive - 7.25 x 10.5
M_paperSizes[6][0] = 7.25;
M_paperSizes[6][1] = 10.5;
// Tabloid - 11 x 17
M_paperSizes[7][0] = 11;
M_paperSizes[7][1] = 17;



// This function will try to find a loaded chunk of table data that contains the specified row
// and column. If found, the chunk will be returned. If not found, then an attempt will be made
// to preload this chunk. This allows predictive loading of data.
function findChunk(row, col)
{
	// Normalize the row and column so they are within the bounds of the current table.
	if( row < 1 )
		row = 1;
	if( row > vScroll.getItems() )
		row = vScroll.getItems();
	if( col < 1 )
		col = 1;
	if( col > hScroll.getItems() )
		col = hScroll.getItems();

	// Try to find an appropriate chunk and return it if found.
	for( var i = 0; i < M_tableChunks.length; i++ )
		{
		var chunk = M_tableChunks[ i ];
		if( ( chunk.firstRow <= row ) && ( row <= chunk.lastRow ) &&
			( chunk.firstCol <= col ) && ( col <= chunk.lastCol ) )
			{
			return chunk;
			}
		}

	// Attempt to preload the chunk which will contain the specified row and column.
	M_xmlData = "";
	
	if (!M_bBusy) {
		loadChunk(row, col);
		if (PWdsapp_bIsIE && !PWdsapp_bActiveXEnabled) {
			M_bBusy = true;
		}
	}

	return null;
}

// This function is called whenever the table size is needed. It will determine how many
// rows and columns can be added, or need to be deleted, to make the table fill the window.
function getTableSize(
	i_tableFirstRow,
	i_tableFirstCol,
	o_tableSize
	)
{
	var newhvis = hScroll.getVisible();
	if (newhvis < 1) {
		newhvis = 1;
	}
	else if (newhvis > (M_tableColCount - i_tableFirstCol)) {
		newhvis = M_tableColCount - i_tableFirstCol;
	}
	var newvvis = vScroll.getVisible();
	if (newvvis < 1) {
		newvvis = 1;
	}
	else if (newvvis > (M_tableRowCount - i_tableFirstRow)) {
		newvvis = M_tableRowCount - i_tableFirstRow;
	}

	o_tableSize.colCount = newhvis;
	o_tableSize.rowCount = newvvis;

	return;
}

// This function sets the scroll bars' sizes.
function setScrollBarsSize(
	io_tableSize
	)
{
	var isVerticalScroll = false;
	var isHorizontalScroll = false;
	var vTop = vScroll.getTop();
	var hTop = hScroll.getTop();
	
	if ((vTop + io_tableSize.rowCount < vScroll.getItems())
			|| ((vTop > 0) && (vTop + io_tableSize.rowCount == vScroll.getItems())))
	{
		isVerticalScroll = true;
	}

	if ((hTop + io_tableSize.colCount < hScroll.getItems())
			|| ((hTop > 0) && (hTop + io_tableSize.colCount == hScroll.getItems())))
	{ 
		isHorizontalScroll = true;
	}

	// If there is a vertical scroll bar...
	if (isVerticalScroll) {
		// ...set its width...
		M_vScrollBarCell.style.width = vScroll.imageWidth + "px";
		io_tableSize.vScrollWidth = vScroll.imageWidth;
		if (isHorizontalScroll) {
			// ...if there is also a horizontal scroll bar, set the row span to 1...
			M_vScrollBarCell.rowSpan = 1;
		}
		else {
			// ...otherwise, set it to 2 so that it spans the data table cell and the cell
			// containing the horizontal scroll bar.
			M_vScrollBarCell.rowSpan = 2;
		}
	}
	else {
		// If there is no vertical scroll bar, set it's cell's width and height to 0.
		M_vScrollBarCell.style.width = "";
		M_vScrollBarCell.style.height = "";
		io_tableSize.vScrollWidth = 0;
		M_vScrollBarCell.rowSpan = 1;
	}

	// If there is a horizontal scroll bar...
	if (isHorizontalScroll) {
		// ...set its height...
		M_hScrollBarCell.style.height = hScroll.imageWidth + "px";
		io_tableSize.hScrollHeight = hScroll.imageWidth;
		if (isVerticalScroll) {
			// ...if there is also a vertical scroll bar, set the column span to 1...
			M_hScrollBarCell.colSpan = 1;
		}
		else {
			// ...otherwise, set it to 2 so that it spans the data table cell and the cell
			// containing the vertical scroll bar.
			M_hScrollBarCell.colSpan = 2;
		}
	}
	else {
		// If there is no horizontal scroll bar, set it's cell's width and height to 0.
		M_hScrollBarCell.style.height = "";
		M_hScrollBarCell.style.width = "";
		io_tableSize.hScrollHeight = 0;
		M_hScrollBarCell.colSpan = 1;
	}

	return;
}

// This function is called whenever the table size is needed. It will determine how many
// rows and columns can be added, or need to be deleted, to make the table fill the window.
function setTableSize(
	o_tableSize
	)
{
	setScrollBarsSize(o_tableSize);

	// Determine the size of the window (leave a little extra space or browser
	// scroll bars may appear. Body style overflow: hidden is strongly recommended).
	var clientX;
	var clientY;
	if (window.innerWidth == undefined) {
		// IE
		clientX = document.body.clientWidth;
		clientY = document.body.clientHeight;
	}
	else {
		// NN
		clientX = window.innerWidth;
		clientY = window.innerHeight;
	}

	// Get the amount of space left over beyond the main table.
	var offsetWidth = GetOffSet(M_mainTable, OFFSET_LEFT) + getRHSWidth();
	var offsetHeight = GetOffSet(M_mainTable, OFFSET_TOP) + getBottomHeight();
	
	if (clientX > offsetWidth) {
		o_tableSize.tableWidth = clientX - offsetWidth;
		M_dataTable.style.width = (o_tableSize.tableWidth - o_tableSize.vScrollWidth) + "px";
	}
	else {
		if (M_dataTable.style.width == "") {
			o_tableSize.tableWidth = 0;
		}
		else {
			o_tableSize.tableWidth = parseInt(M_dataTable.style.width.replace("/px/gi", ""));
		}
	}
	if (clientY > offsetHeight) {
		o_tableSize.tableHeight = clientY - offsetHeight;
		M_dataTable.style.height = (o_tableSize.tableHeight - o_tableSize.hScrollHeight) + "px";
	}
	else {
		if (M_dataTable.style.height == "") {
			o_tableSize.tableHeight = 0;
		}
		else {
			o_tableSize.tableHeight = parseInt(M_dataTable.style.height.replace("/px/gi", ""));
		}
	}

	return;
}

// This function is called whenever the main window is resized. It will determine how many
// rows and columns can be added, or need to be deleted, to make the table fill the window.
function Resize()
{  
	if (ObjWdsForm.CS_InHelp.value == "True") return;

   if (PWdsapp_bIsIE) {
		document.body.style.cursor = "wait";
	}

	if( M_currentCell != undefined )
		HideCellNotes( M_currentCell );

	hideScrollBars();
	refreshTable();
	if (!M_isPrintableVersion){
		showScrollBars();
	}
	else {
		// In the case of the printable version, we want Resize() to be called only once.
		// Resize() is called by createChunk(), and when we resize the Browser window, but we
		// don't want it to be called when the Browser window is resized, because it will try
		// to regenerate the table.
		document.body.setAttribute("onresize", "", 0);
	}
}

// This function returns the number of row item headers that appear on the current row.
function countRowHeaders(
	)
{
	var headerCount;
	var dimIndex;

	headerCount = 0;
	for (dimIndex = 0; dimIndex < M_rowDimCount - 1; dimIndex++) {
		if (M_rowSpanCounts[dimIndex] == 0) {
			headerCount++;
		}
	}
	// +1 for the nested dimension
	headerCount++;
	
	return headerCount;
}

// This function will set the spans on the item headers on a row of the table.
// i_rowCells is an array containing the cells of the row.
// The number of nesting item headers on the row is returned.
function setRowSpans(
	i_rowCells
	)
{
	var headerCount;
	var dimIndex;

	headerCount = 0;
	for (dimIndex = 0; dimIndex < M_rowDimCount - 1; dimIndex++) {
		if (M_rowSpanCounts[dimIndex] == 0) {
			cell = i_rowCells[headerCount];
			cell.setAttribute(M_className, M_rowHeaderClass, 0);
			cell.setAttribute("rowspan", M_rowLabelSpans[dimIndex][M_rowSpanIndexes[dimIndex]], 0);
			headerCount++;
		}
		M_rowSpanCounts[dimIndex] += 1;
		if (M_rowSpanCounts[dimIndex] >= M_rowLabelSpans[dimIndex][M_rowSpanIndexes[dimIndex]]) {
			M_rowSpanIndexes[dimIndex] += 1;
			M_rowSpanCounts[dimIndex] = 0;
		}
	}
	// remove the rowspan of the most nested dimension
	i_rowCells[headerCount].setAttribute("rowspan", "1", 0);
	
	return;
}

// This function removes all the child nodes of i_clElement.
function RemoveAllChildNodes(
	i_clElement
	)
{
	var nIndex;
	
	if (i_clElement != undefined) {
		for (nIndex = i_clElement.childNodes.length - 1; nIndex >= 0; nIndex--) {
			i_clElement.removeChild(i_clElement.childNodes[nIndex]);
		}
	}
}

// This function will resize the HTML table to be the correct number of rows and columns.
function resizeTable(
	i_colLabelSpans,
	i_rowLabelSpans
	)
{
	var anchorTag;
	var c;
	var r;
	var dataRow;
	var row;
	var colCount;
	var rowCount;
	var visibleCols;
	var currentCols;
	var visibleRows;
	var colsToDel;
	var colsToAdd;
	var cell;
	var cells;
	var cellCount;
	var className;
	var rowSpanIndexes;
	var rowSpanCounts;
	var cellIndex;
	var colsToDelete;
	var rowIndex;
	var maxIndex;
	var dataCell;
	var newCell;
	var tableCols;
	var tableRows;
	var rows = M_dataTable.rows;
	var headerCount;
	var emptyCellContent;
	var replaceCellContent = false;

	tableRows = rows.length - 1 - M_colDimCount; // skip dimension headers and scroll bar
	if (tableRows < 0) {
		tableCols = 0;
	}

	if (rows.length > M_colDimCount) {
		// The number of columns on the row containing the most nested dimension's item headers,
		// -1 to skip the column dimension header.
		tableCols = rows[M_colDimCount - 1].cells.length - 1;
	}
	else {
		tableCols = 0;
	}

	// Shrink/grow table rows

	// Update existing rows for nesting dimensions
	visibleRows = vScroll.getVisible();
	if (M_rowDimCount > 1) {
		for (r = 0; r < M_rowDimCount - 1; r++) {
			M_rowSpanIndexes[r] = 0;
			M_rowSpanCounts[r] = 0;
		}

		rowCount = Math.min(visibleRows, tableRows);
		for (r = 0; r < rowCount; r++) {
			// (r + M_colDimCount + 1) to skip column and row dimension headers
			row = rows[r + M_colDimCount + 1];
			cells = row.cells;
			// Count how many item headers there is room for in this row,
			// excluding the most nested dimension's item header
			colsToDelete = cells.length - tableCols - 1;
			headerCount = countRowHeaders(cells);
			colsToDelete -= headerCount - 1;
			if (colsToDelete < 0) {
				// There aren't enough columns, add some.
				for (c = colsToDelete; c < 0; c++) {
					cellIndex = cells.length;
					cell = row.insertCell(cellIndex);
					cell.setAttribute(M_className, M_dataTableClass, 0);
					emptyCellContent = M_dataCellContent.cloneNode(true);
					cell.appendChild(emptyCellContent);
				}
				// We have added cells at the end of the row, therefore existing cells will be used as item headers.
				// Make sure the item headers have the correct style, and contain an anchor tag.
				for (c = 0; c < headerCount; c++) { // + 1 to include the nested dimension.
					replaceCellContent = false;
					cell = cells[c];
					cell.setAttribute(M_className, M_rowHeaderClass, 0);
					if (cell.childNodes[0].nodeType == 3) {
						// The cell contains a text node.
						replaceCellContent = true;
					}
					else if (cell.childNodes[0].tagName.toUpperCase() != "A") {
						replaceCellContent = true;
					}
					if (replaceCellContent) {
						// All item header cells must have an <A> tag.  This cell may
						// have been used as a data cell, so it may not have an <A> tag.
						anchorTag = document.createElement("A");
						anchorTag[M_innerAttr] = M_emptyCell;
						cell.replaceChild(anchorTag, cell.childNodes[0]);
					}
				}
			}
			else if (colsToDelete > 0) {
				cellIndex = cells.length - 1;
				// There are too many columns, remove them.
				for (c = 0; c < colsToDelete; c++) {
					row.deleteCell(cellIndex);
					cellIndex--;
				}
				// Some item header cells might be used as data cells, so we must set their style.
				cellCount = cells.length;
				for (c = 0, cellIndex = headerCount; c < M_rowDimCount && cellIndex < cellCount; c++, cellIndex++) {
					cell = cells[cellIndex];
					cell.setAttribute(M_className, M_dataTableClass, 0);
					cell.setAttribute("rowspan", "1", 0);
					emptyCellContent = M_dataCellContent.cloneNode(true);
					if (cell.childNodes.length > 0) {
						RemoveAllChildNodes(cell);
					}
					cell.appendChild(emptyCellContent);
					
				}
			}
			setRowSpans(cells);
		}
	}

	if (visibleRows < tableRows) {
		// Shrink table by rows
		maxIndex = visibleRows + M_colDimCount + 1;
		for (rowIndex = rows.length - 1; rowIndex >= maxIndex; rowIndex--) {
			M_dataTable.deleteRow(rowIndex);
		}
	}
	else if (visibleRows > tableRows) {
		// Grow table by rows
		var cols = rows[M_colDimCount - 1].cells.length - 1;
		maxIndex = visibleRows + M_colDimCount + 1;
		for (rowIndex = rows.length; rowIndex < maxIndex; rowIndex++) {
			row = M_dataTable.insertRow(rowIndex);
			insertedCount = 0;
			// insert nesting dimensions' row item headers
			for (c = 0; c < M_rowDimCount - 1; c++) {
				if (M_rowSpanCounts[c] == 0) {
					cell = row.insertCell(insertedCount);
					cell.setAttribute(M_className, M_rowHeaderClass, 0);
					cell.setAttribute("rowspan", i_rowLabelSpans[c][M_rowSpanIndexes[c]], 0);
					anchorTag = document.createElement("A");
					anchorTag[M_innerAttr] = M_emptyCell;
					cell.appendChild(anchorTag);
					insertedCount++;
				}
				M_rowSpanCounts[c] += 1;
				if (M_rowSpanCounts[c] >= i_rowLabelSpans[c][M_rowSpanIndexes[c]]) {
					M_rowSpanIndexes[c] += 1;
					M_rowSpanCounts[c] = 0;
				}
			}
			// insert nested dimension's row item headers
			cell = row.insertCell(insertedCount);
			cell.setAttribute(M_className, M_rowHeaderClass, 0);
			anchorTag = document.createElement("A");
			anchorTag[M_innerAttr] = M_emptyCell;
			cell.appendChild(anchorTag);
			insertedCount++;
			for (i = 1; i <= cols; i++) {
				cell = row.insertCell(insertedCount);
				cell.setAttribute(M_className, M_dataTableClass, 0);
				emptyCellContent = M_dataCellContent.cloneNode(true);
				cell.appendChild(emptyCellContent);
				insertedCount++;
			}
		}
	}

	// Shrink/grow table columns
	// use (M_colDimCount - 1) to get the row containing the most nested dimension item headers.
	var sortAnchorTag;
	var sortArrowAscending;
	var sortArrowDescending;
	for (r = 0; r < M_colDimCount; r++) {
		currentCols = rows[r].cells.length - 1;
		if (r < M_colDimCount - 1) {
			// nesting column dimensions
			visibleCols = i_colLabelSpans[r].length;
		}
		else {
			// most nested column dimension
			visibleCols = hScroll.getVisible();
		}
		if (visibleCols < currentCols) {
			// Shrink table by columns.
			colsToDel = currentCols - visibleCols;
			row = rows[r];
			for (var c = 0; c < colsToDel; c++) {
				row.deleteCell(row.cells.length - 1);
				if (r >= M_colDimCount - 1) {
					// This is the most nested column dimension, so delete the columns in the cell data rows.
					// There are 2 rows for this dimension, so delete the columns for the second row as well
					// (that's why we use the "<=" in the while loop below).
					i = 0;
					while (i <= visibleRows) {
						// (+ M_colDimCount + 1) to skip column and row headers
						dataRow = rows[i + M_colDimCount];
						cellIndex = dataRow.cells.length - 1;
						dataRow.deleteCell(cellIndex);
						i++;
					}
				}
			}
            if (r < M_colDimCount - 1) {
				// if this is a nesting dimension, set the colspan of each column.
				for (c = 0; c < visibleCols; c++) {
					row.cells[c + 1].setAttribute("colspan", i_colLabelSpans[r][c], 0);
				}
			}
		}
		else { //if (visibleCols > currentCols)
			// Grow table by columns.
			var cell;
			var col;
			colsToAdd = visibleCols - currentCols;
			if (r < M_colDimCount - 1) {
				// if the dimension is a nesting dimension, then update the span of existing columns
				for (col = 0; col < currentCols; col++) {
					cell = rows[r].cells[col + 1];
					cell.setAttribute("colspan", i_colLabelSpans[r][col], 0);
				}
			}
			for (col = 1; col <= colsToAdd; col++) {
				row = rows[r];
				// add headers
				cell = row.insertCell(row.cells.length);
				cell.setAttribute(M_className, M_colHeaderClass, 0);
				anchorTag = document.createElement("A");
				anchorTag[M_innerAttr] = M_emptyCell;
				cell.appendChild(anchorTag);
				if (r < M_colDimCount - 1) {
					// this is a nesting dimension
					cell.setAttribute("colspan", i_colLabelSpans[r][currentCols + col - 1], 0);
				}
				else {
					cell.style.borderBottomStyle = "none";
					// if this is the most nested column dimension, then it
					// has a row containing the sort arrows.
					row = rows[r + 1];
					cell = row.insertCell(row.cells.length);
					cell.setAttribute(M_className, M_colHeaderSortClass, 0);
					if (M_isPrintableVersion || M_totalPrctType == M_rowPercent || M_totalPrctType == M_colPercent) {
 					   // This is required to add the borders on the Printable Version
 					   //it also should be done for percent views
 					   cell[M_innerAttr] = M_emptyCell;
 					}

					// Loop doing other rows (cell data rows)
					i = 0;
					while (i < vScroll.getVisible()) {
						//  i + M_colDimCount + 1 to skip column and row headers
						row = rows[i + M_colDimCount + 1];
						cellIndex = row.cells.length;
						cell = row.insertCell(cellIndex);
						cell.setAttribute(M_className, M_dataTableClass, 0);
						emptyCellContent = M_dataCellContent.cloneNode(true);
						cell.appendChild(emptyCellContent);
						i++;
					}
				}
			}
		}
		// Add icons for sorting
		tableCols = rows[M_colDimCount - 1].cells.length - 1;
		for (col = 0; col < tableCols; col++) {
			var bIsTotalItem = false;
			if (col == 0) {
				if (M_totalPrctType == M_rowTotal && hScroll.getTop() == 0) {
					bIsTotalItem = true;
				}
			}
			if (!M_isPrintableVersion
				&& (!bIsTotalItem)
				&& (M_totalPrctType != M_rowPercent) 
				&& (M_totalPrctType != M_colPercent))
			{
				cell = rows[M_colDimCount].cells[col + M_rowDimCount];
				var sortArrows = cell.getElementsByTagName("A");
				if (sortArrows.length == 0) {
					// Add the 'sort ascending' arrow
					sortAnchorTag = document.createElement("A");
					sortArrowAscending = document.createElement("IMG");
					sortArrowAscending.setAttribute("src", M_strSortAscendingArrow, 0);
					sortArrowAscending.setAttribute("border", "0", 0);
					sortArrowAscending.setAttribute("title", resSortAsc, 0);
					sortArrowAscending.setAttribute("alt", resSortAsc, 0);
					sortAnchorTag.appendChild(sortArrowAscending);
					cell.appendChild(sortAnchorTag);

					// Add the 'sort descending' arrow
					sortAnchorTag = document.createElement("A");
					sortArrowDescending = document.createElement("IMG");
					sortArrowDescending.setAttribute("src", M_strSortDescendingArrow, 0);
					sortArrowDescending.setAttribute("border", "0", 0);
					sortArrowDescending.setAttribute("title", resSortDesc, 0);
					sortArrowDescending.setAttribute("alt", resSortDesc, 0);
					sortAnchorTag.appendChild(sortArrowDescending);
					cell.appendChild(sortAnchorTag);
				}  
			}
			else if (!M_isPrintableVersion && bIsTotalItem) {
				cell = rows[M_colDimCount].cells[col + M_rowDimCount];
				var sortArrows = cell.getElementsByTagName("A");
				if (sortArrows.length > 0) {
					for (var Index = sortArrows.length - 1; Index >= 0; Index--) {
						cell.removeChild(sortArrows[Index]);
					}
				}   
			}
		}
	}
}

//-------------------------------------------------------------------------
// Sets the vertical and horizontal scroll position form fields to a value of 1.
function resetSavedScrollPosition()
{
	if (typeof(ObjWdsForm.sWD_FirstRow) != "undefined") {
		ObjWdsForm.sWD_FirstRow.value = 0;
	}
	if (typeof(ObjWdsForm.sWD_FirstCol) != "undefined") {
		ObjWdsForm.sWD_FirstCol.value = 0;
	}
	
	if (typeof(ObjWdsForm.sWD_DataFirstRow) != "undefined") {
		ObjWdsForm.sWD_DataFirstRow.value = 1;
	}
	if (typeof(ObjWdsForm.sWD_DataFirstCol) != "undefined") {
		ObjWdsForm.sWD_DataFirstCol.value = 1;
	}
}

// Save the vertical and horizontal scroll positions to form fields.
function saveScrollPosition()
{
	ObjWdsForm.sWD_FirstRow.value = vScroll.getTop();
	ObjWdsForm.sWD_FirstCol.value = hScroll.getTop();

	ObjWdsForm.sWD_DataFirstRow.value = M_tlChunk.firstRow;
	ObjWdsForm.sWD_DataFirstCol.value = M_tlChunk.firstCol;
}	

// Retrieve the vertical scroll position from a form field.
function getSavedVScrollPosition()
{
	return parseInt(ObjWdsForm.sWD_FirstRow.value);
}

// Retrieve the horizontal scroll position from a form field.
function getSavedHScrollPosition()
{
	return parseInt(ObjWdsForm.sWD_FirstCol.value);
}

// Retrieves the sort order from a form field.  Returns 0 for descending, 1 for ascending.
function getSortOrder()
{
	return parseInt(ObjWdsForm.sWD_viewsorttype.value);
}

// This function is called to sort data along a column.  It prepares some input for and calls
// the function onColSortData().
function onSortData(
	i_chunkFirstRow,	// first row of the chunk containing the item to sort on
	i_chunkFirstCol,	// first column of the chunk containing the item to sort on
	i_chunkColIndex,	// index (within the chunk) of item to sort on
	i_ascending			// 1 to sort in ascending order, 0 to sort in descending order
	)
{
	var sortItemIdentifier;
	var chunk;
	var nDim;
	var colLabelSpans;
	var totalSpan;
	var nItemIndex;
	var itemIndexes = new Array(M_colDimCount);

	saveScrollPosition();
	
	// find all the indexes, within the chunk, of the items that nest the item with index i_chunkColIndex
	// in the most nested dimension.
	chunk = findChunk(i_chunkFirstRow, i_chunkFirstCol);
	itemIndexes[M_colDimCount - 1] = i_chunkColIndex;
	for (nDim = M_colDimCount - 2; nDim >= 0 ; nDim--) {
		colLabelSpans = chunk.colLabelSpans[nDim];
		nItemIndex = 0;
		totalSpan = colLabelSpans[nItemIndex];
		nItemCount = colLabelSpans.length;
		while ((totalSpan <= i_chunkColIndex) && (nItemIndex < nItemCount)) {
			nItemIndex++;
			totalSpan += colLabelSpans[nItemIndex];
		}
		itemIndexes[nDim] = nItemIndex;
	}

	// Create the string that will be the first parameter of the onColSortData()
	// function.
	// This string is composed of 2 lists separated by character \036.
	// The first list is composed of the column dimension names, separated by character \035.
	// The second list is composed of the item codes, one for each dimension, separated by
	// character \035.
	sortItemIdentifier = "";
	for (nDim = 0; nDim < M_colDimCount; nDim++) {
		sortItemIdentifier += M_colDimNames[nDim] + M_sortItemSep;
	}
	sortItemIdentifier += M_sortSectionSep;
	for (nDim = 0; nDim < M_colDimCount; nDim++) {
		sortItemIdentifier += chunk.colIDs[nDim][itemIndexes[nDim]] + M_sortItemSep;
	}
	onColSortData(sortItemIdentifier, i_ascending);
}

// This function returns true if a column is sorted and false otherwise.
// If it returns true, it fills io_sortedItemsCodes with the item codes of the item that is sorted
// and the codes of the items that nest it.
function getSortedItemCodes(
	io_sortItemCodes
	)
{
	var index;
	var isSorted;
	var viewSortCode;
	var viewSortCode;
	var sortItemCodes;
	
	viewSortCode = ObjWdsForm.sWD_viewsortcode.value;
	if (viewSortCode == "") {
		isSorted = false;
	}
	else {
		// get the item codes only, not the dimension names
		viewSortCode = viewSortCode.split(M_sortSectionSep)[1];
		sortItemCodes = viewSortCode.split(M_sortItemSep);
		for (index = 0; index < M_colDimCount; index++) {
			io_sortItemCodes[index] = sortItemCodes[index];
		}
		isSorted = true;
	}

	return isSorted;
}

// This function returns true if one of the columns on the screen is sorted and false otherwise.
// If it returns true, the member of o_sortedItemsIndex is set to the index of the sorted column.
//
// i_sortItemCodes: an array containg the item codes (one for each column dimension) of the sorted
//    column.
// i_lChunk: the top-left chunk
// i_rChunk: the top-right chunk
// i_firstColIndexes: an array of indexes into the top-left chunk.  There is one index for each one
//    of the column dimensions.  Each index represents the first item that is displayed on the screen
//    for a dimension.  (this array is obtained from a call to getColSpans()).
// i_colSpans: an array of arrays of column spans.  There is one array for each column dimension.  Each
//    one of these arrays contains the span of each item on the screen.
//    (this array is obtained from a call to getColSpans()).
// i_columnCount: the number of columns on the screen.
// o_sortedColumnIndex: a structure that will hold the index of the sorted column.
function getSortedColumnIndex(
	i_sortItemCodes,
	i_lChunk,
	i_rChunk,
	i_firstColIndexes,
	i_colSpans,
	i_columnCount,
	o_sortedColumnIndex
	)
{
	var chunk;
	var itemCode;
	var itemIndex = -1;
	var index;
	var colIndex;
	var hasSortedColumn;
	var dim;
	var currentDimItemIndexes; 
		// an array containing the index into the colIDs array of a chunk, one index for each
		// column dimension
	
	var nestingDimColCount; // An array that is used to keep track of the current column count for each
	                        // dimension so that we know when to increment the indexes based on the colspans

    var adjustedColIndex;   // The colIndex is the absolute index of item.  We need to adjust it because 
                            // we use this to index a zero-based array (i_colSpans) that only deals with what 
                            // is currently on the screen.

	currentDimItemIndexes = new Array(i_firstColIndexes.length);
	for (index = 0; index < currentDimItemIndexes.length; index++) {
		currentDimItemIndexes[index] = i_firstColIndexes[index];
	}

    // Initialize the column counters for the nesting dimensions
    nestingDimColCount = new Array(currentDimItemIndexes.length - 1);
	for (index = 0; index < nestingDimColCount.length; index++) {
	    nestingDimColCount[index] = 0;
	}
	// start with the left chunk.
	chunk = i_lChunk;
	hasSortedColumn = false;
	// check each column to see if it is sorted. stop once the sorted column is found
	for (colIndex = 0; colIndex < i_columnCount && !hasSortedColumn; colIndex++) {
		dim = M_colDimCount - 1;
		itemCode = chunk.colIDs[dim][currentDimItemIndexes[dim]];
		if (itemCode == i_sortItemCodes[dim]) {
			itemIndex = colIndex;
			hasSortedColumn = true;
			// The item code of the nested dimension matches the one of the sorted column,
			// check the item codes for the nesting dimensions
			for (dim = dim - 1; dim >= 0 && hasSortedColumn; dim--) {
				itemCode = chunk.colIDs[dim][currentDimItemIndexes[dim]];
				if (itemCode != i_sortItemCodes[dim]) {
					hasSortedColumn = false;
				}
			}
		}
		if (!hasSortedColumn) {
			// if the sorted column is not found, increment the current item indexes
			for (dim = 0; dim < currentDimItemIndexes.length; dim++) {
				if (dim == currentDimItemIndexes.length - 1) {
					// always increment the index for the nested dimension
					currentDimItemIndexes[dim]++;
				}
				else {
                    nestingDimColCount[dim]++;
                    // Adjust the column index so that it represents its position on the screen (zero-based).
                    adjustedColIndex = currentDimItemIndexes[dim] - i_firstColIndexes[dim];
    				if (i_colSpans[dim][adjustedColIndex] <= (nestingDimColCount[dim])) {
						// only increment the index for the nesting dimensions if we have
						// reached the end of the span for the item at the current index.
						currentDimItemIndexes[dim]++;
						
						// reset the column counter for this dimension
						nestingDimColCount[dim] = 0;
					}
				}			
			}
			// Check to see if we've reached the end of the chunk.  
			// Since i_firstColIndexes range from 0 -> M_colsPerChunk - 1, we need to adjust the value 
			// based on the first column index in the chunk and our current position on the screen.
			if ((i_firstColIndexes[i_firstColIndexes.length - 1] + (chunk.firstCol - 1) + colIndex) == chunk.lastCol - 1) {
				// this is the end of the left chunk, use the right chunk
				chunk = i_rChunk;
				for (index = 0; index < currentDimItemIndexes.length; index++) {
					currentDimItemIndexes[index] = 0;
				}
			}
		}
	}
	if (hasSortedColumn) {
		o_sortedColumnIndex.m_index = itemIndex;
	}
	
	return hasSortedColumn;
}

// This function changes the specified sort arrow's with the provided image.
// 
// i_sortArrows: an element containing an IMG element representing the sort arrow.
// i_sortOrder: 0 indicates that the descending arrow should be replaced, 1 indicates that the
//		ascending arrow should be replaced.
// i_imagePath: path of the image that will be used to replace existing arrow (used in "src"
//		attribute of the <img> tag.
// i_imageText: text used in "alt" and "title" attributes of the <img> tag.
function changeSortArrow(
	i_sortArrow,
	i_imagePath,
	i_imageText
	)
{
	var sortArrowImage;
	
	sortArrowImage = i_sortArrow.getElementsByTagName("IMG");
	sortArrowImage[0].setAttribute("src", i_imagePath, 0);
	sortArrowImage[0].setAttribute("title", i_imageText, 0);
	sortArrowImage[0].setAttribute("alt", i_imageText, 0);
}

// This function updates the sort arrow images and corresponding text of the
// specified column (i_colIndex).
//
// i_colIndex: index of the column being updated
// i_sortedColInfo: index and sort order of column that is sorted; index is -1 if sorted column is not visible
// i_prevSortedColInfo: index and sort order of previously sorted column (used after scrolling to reset the arrow);
//		to a non-sorted state); index is -1 if not such column exists.
// i_isSortedColOnPage: true if the sorted column is on the page (in other words, if it is visible)
// i_sortArrows: an array of 2 elements, each containing an <IMG> tag representing a sort arrow.
//		the first element contains the ascending arrow, the other one the descending arrow.
function updateSortArrows(
	i_colIndex,
	i_sortedColInfo,
	i_prevSortedColInfo,
	i_isSortedColOnPage,
	i_sortArrows
	)
{
	if (i_isSortedColOnPage) {
		if (i_colIndex == i_sortedColInfo.m_index) {
			// The current column is sorted, change its arrow to the sorted arrow
			if (i_sortedColInfo.m_order == 0) {
				// sorted descending
				changeSortArrow(i_sortArrows[1], M_strSortDescendingArrowActive,
					resSortReset);
			}
			else {
				// sorted ascending
				changeSortArrow(i_sortArrows[0], M_strSortAscendingArrowActive,
					resSortReset);
			}
		}
		else if (i_colIndex == i_prevSortedColInfo.m_index) {
			// The current column was sorted but is not longer sorted.  Change its
			// arrow to the non-sorted arrow.
			if (i_prevSortedColInfo.m_order == 0) {
				// sorted descending
				changeSortArrow(i_sortArrows[1], M_strSortDescendingArrow,
					resSortDesc);
			}
			else {
				// sorted ascending
				changeSortArrow(i_sortArrows[0], M_strSortAscendingArrow,
					resSortAsc);
			}
		}
	}
	else if (i_colIndex == i_prevSortedColInfo.m_index) {
		// The current column was sorted but is not longer sorted.  Change its
		// arrow to the non-sorted arrow.
		if (i_prevSortedColInfo.m_order == 0) {
			// sorted descending
			changeSortArrow(i_sortArrows[1], M_strSortDescendingArrow,
				resSortDesc);
		}
		else {
			// sorted ascending
			changeSortArrow(i_sortArrows[0], M_strSortAscendingArrow,
				resSortAsc);
		}
	}
}

// This function adds a sort arrow to a sorted column in the Printable Version
function addSortArrowPrintable(i_sortOrder, i_currentNode){

		// Add the sort arrow 
		var sortArrowCell;

        if (i_sortOrder == 1){
            // Sort Ascending
            sortArrowCell = document.getElementById("AscendingArrow");
        }
        else if (i_sortOrder == 0){
            // Sort Descending
            sortArrowCell = document.getElementById("DescendingArrow");
        }
        // replace the current node
        i_currentNode.parentNode.replaceChild(sortArrowCell, i_currentNode);
}

// This function resizes and refills the table to be scrolled to the appropriate point.
// If i_forceUpdate is true, the table is updated (refilled) even if it does not need to be.
// If i_forceUpdate is false, the table is updated only if it needs to be (when the scroll
// bar positions have changed, when the table can be resized to occupy more of the Browser window)
// The possible return values are:
//		M_Update_Updated - The table was updated.
//		M_Update_WaitingForData	- The table was not updated because not enough data chunks were available.
function updateTable(
	i_forceUpdate,
	i_newTableSize
	)
{
	var vtop = vScroll.getTop();
	var htop = hScroll.getTop();

	var vvis = i_newTableSize.rowCount;
	var hvis = i_newTableSize.colCount;
	var tableRowsIncreased = false;
	var tableRowsDecreased = false;
	var tableColsIncreased = false;
	var tableSizeIncreased = false;
	var bSpawnWindow = false;

	var dimIndex;

	bSpawnWindow = IsSpawnWindow();

	if (i_forceUpdate) {
		M_startInBottomChunk = false;
		M_startInRightChunk = false;
		for (dimIndex = 0; dimIndex < M_rowDimCount; dimIndex++) {
			M_oldRowCounts[dimIndex] = 0;
			M_doesRowLabelSpanChunks[dimIndex] = false;
		}
		for (dimIndex = 0; dimIndex < M_colDimCount; dimIndex++) {
			M_oldColCounts[dimIndex] = 0;
			M_doesColLabelSpanChunks[dimIndex] = false;
		}
	}
	else {
		for (dimIndex = 0; dimIndex < M_rowDimCount - 1; dimIndex++) {
			M_oldRowCounts[dimIndex] = M_rowLabelSpans[dimIndex].length;
		}		
		M_oldRowCounts[dimIndex] = vScroll.getVisible();
		
		for (dimIndex = 0; dimIndex < M_colDimCount - 1; dimIndex++) {
			M_oldColCounts[dimIndex] = M_colLabelSpans[dimIndex].length;
		}		
		M_oldColCounts[dimIndex] = hScroll.getVisible();
	}

	if (vvis > M_oldRowCounts[M_rowDimCount - 1]) {
		tableRowsIncreased = true;
	}
	else if (vvis < M_oldRowCounts[M_rowDimCount - 1]) {
		tableRowsDecreased = true;
	}
	if (hvis > M_oldColCounts[M_colDimCount - 1]) {
		tableColsIncreased = true;
	}
	if (tableRowsIncreased || tableColsIncreased) {
		tableSizeIncreased = true;
	}

	// Find the (up to) 4 chunks of the table which may be displayed at this time.
	// Also, try to find the next pages of data so that they will be preloaded by the
	// time they are needed.

	// Visible table data relates to chunks as follows.

	// abcd
	// eFGh
	// iJKl
	// mnop

	// The visible data could straddle 4 chunks (FGJK). There are 8 chunks that could be needed within one
	// page scroll (bcehilno). The last 4 chunks (admp) can not be scrolled to with a single scroll (requires
	// at least 1 page in both horizontal and vertical directions.

	// Find the four chunks that this page could possibly cover (FGJK).
	var tlChunk;
	var trChunk;
	var blChunk;
	var brChunk;

	if (i_forceUpdate) {
		M_tlChunk = findChunk( vtop + 1, htop + 1 );				// F
	}
	if (i_forceUpdate || tableColsIncreased) {
		M_trChunk = findChunk( vtop + 1, htop + hvis + 1 );		// G
	}
	if (i_forceUpdate || tableRowsIncreased) {
		M_blChunk = findChunk( vtop + vvis + 1, htop + 1 );		// J
	}
	if (i_forceUpdate || tableSizeIncreased) {
		M_brChunk = findChunk( vtop + vvis + 1, htop + hvis + 1 );// K
	}

	if (PWdsapp_bActiveXEnabled || (!PWdsapp_bIsIE)) {
		// Prefetch chunks which may be visible when scrolling by a page in any direction (bcehilno).
		if (i_forceUpdate || tableRowsIncreased) {
			findChunk( vtop - vvis + 1, htop + 1 );					// b
			findChunk( vtop + 2 * vvis + 1, htop + 1 );				// n
		}
		if (i_forceUpdate || tableColsIncreased) {
			findChunk( vtop + 1, htop - hvis + 1 );					// e
			findChunk( vtop + 1, htop + 2 * hvis + 1 );				// h
		}
		if (i_forceUpdate || tableSizeIncreased) {
			findChunk( vtop - vvis + 1, htop + hvis + 1 );			// c
			findChunk( vtop + vvis + 1, htop - hvis + 1 );			// i
			findChunk( vtop + vvis + 1, htop + 2 * hvis + 1 );		// l
			findChunk( vtop + 2 * vvis + 1, htop + hvis + 1 );		// o
		}
	}

	// Ignore the unreachable chunks (admp).

	// If any of the 4 chunks that are in this page are missing,
	// defer refreshing until the data is available.
	if ((M_tlChunk == null) ||
		(M_trChunk == null) ||
		(M_blChunk == null) ||
		(M_brChunk == null))
	{
		M_refreshNeeded = true;
		return M_Update_WaitingForData;
	}

	// If there are enough items that scroll bars must be shown,
	// fix the size of the table to fill the screen (prevents jumping).
	hScroll.setVisible(i_newTableSize.colCount);
	vScroll.setVisible(i_newTableSize.rowCount);

	for (dimIndex = 0; dimIndex < M_rowDimCount; dimIndex++) {
		M_firstRowLabelIndexesOld[dimIndex] = M_firstRowLabelIndexes[dimIndex];
	}
	for (dimIndex = 0; dimIndex < M_colDimCount; dimIndex++) {
		M_firstColLabelIndexesOld[dimIndex] = M_firstColLabelIndexes[dimIndex];
	}

	// calculate the span of nesting column dimension labels
	getColSpans(0, M_tlChunk, M_trChunk, hScroll.getVisible(), M_colLabelSpans, M_firstColLabelIndexes);

	// On the first pass through, M_firstColLabelIndexes provides us with the index of the 
	// first column on the screen.  On subsequent iterations through this function, it is 
	// updated to provide the index of the first column (not printed) to the right of the screen.  
	// For determining which column to change the sort arrows on, we require the former, therefore
	// M_firstColLabelIndexesForSorting is only set the first time through.
	if (i_forceUpdate){
		for (dimIndex = 0; dimIndex < M_colDimCount; dimIndex++) {
			M_firstColLabelIndexesForSorting[dimIndex] = M_firstColLabelIndexes[dimIndex];
		}
	}

	// calculate the span of nesting row dimension labels
	getRowSpans(0, M_tlChunk, M_blChunk, vScroll.getVisible(), M_rowLabelSpans, M_firstRowLabelIndexes);

	// Resize the table.  The top-left, top-right and bottom-left chunks are required to insert the column
	// and row item headers of nesting dimensions.
	resizeTable(M_colLabelSpans, M_rowLabelSpans);

	var increaseCount;
	if (!i_forceUpdate) {
		var switchedToTopChunk = false;
		// Set the first row and column indexes to their old value, accounting for a decrease in size.
		for (dimIndex = M_rowDimCount - 1; dimIndex >= 0; dimIndex--) {
			M_firstRowLabelIndexes[dimIndex] = M_firstRowLabelIndexesOld[dimIndex];
			// calculate the number of rows by which the table has increased/decreased.
			if (dimIndex < M_rowDimCount - 1) {
				increaseCount = M_rowLabelSpans[dimIndex].length - M_oldRowCounts[dimIndex];
			}
			else {
				increaseCount = vScroll.getVisible() - M_oldRowCounts[dimIndex];
			}
			if (increaseCount < 0) {
				// The number of rows has decreased, so we must decrement the label index. 
				M_firstRowLabelIndexes[dimIndex] += increaseCount;
				if (dimIndex == (M_rowDimCount - 1)) {
					// If this is the most nested dimension...
					if (M_firstRowLabelIndexes[dimIndex] < 0) {
						// ...and the index is negative, then we have to move to the top chunk
						// and calculate the label index accordingly
						switchedToTopChunk = true;
						M_startInBottomChunk = false;
						M_firstRowLabelIndexes[dimIndex] += M_tlChunk.rowLabels[dimIndex].length;
					}
				}
				else {
					// If this is a nesting dimension...
					if (switchedToTopChunk) {
						// ... and if we have to switch to the top chunk, then we have to
						// calculate the label index accordingly.
						M_firstRowLabelIndexes[dimIndex] += M_tlChunk.rowLabels[dimIndex].length;
						if (M_doesRowLabelSpanChunks[dimIndex]) {
							// If a label spanned the 2 chunks (it is the last label of the top chunk
							// and first label of the bottom chunk), then it counts as 1 label we have to
							// remove one more.
							M_firstRowLabelIndexes[dimIndex]--;
						}
					}
				}
				// M_firstRowLabelIndexes will be modified when we update the rowumn labels,
				// but we need the original values for updating the cell values of each row.
				// So store the original values into M_firstRowLabelIndexesOld.
				M_firstRowLabelIndexesOld[dimIndex] = M_firstRowLabelIndexes[dimIndex];
			}
		}
		var switchedToLeftChunk = false;
		for (dimIndex = M_colDimCount - 1; dimIndex >= 0; dimIndex--) {
			M_firstColLabelIndexes[dimIndex] = M_firstColLabelIndexesOld[dimIndex];
			// calculate the number of columns by which the table has increased/decreased.
			if (dimIndex < M_colDimCount - 1) {
				increaseCount = M_colLabelSpans[dimIndex].length - M_oldColCounts[dimIndex];
			}
			else {
				increaseCount = hScroll.getVisible() - M_oldColCounts[dimIndex];
			}
			if (increaseCount < 0) {
				// The number of columns has decreased, so we must decrement the label index. 
				M_firstColLabelIndexes[dimIndex] += increaseCount;
				if (dimIndex == (M_colDimCount - 1)) {
					// If this is the most nested dimension...
					if (M_firstColLabelIndexes[dimIndex] < 0) {
						// ...and the index is negative, then we have to move to the left chunk
						// and calculate the label index accordingly
						switchedToLeftChunk = true;
						M_startInRightChunk = false;
						M_firstColLabelIndexes[dimIndex] += M_tlChunk.colLabels[dimIndex].length;
					}
				}
				else {
					// If this is a nesting dimension...
					if (switchedToLeftChunk) {
						// ... and if we have to switch to the left chunk, then we have to
						// calculate the label index accordingly.
						M_firstColLabelIndexes[dimIndex] += M_tlChunk.colLabels[dimIndex].length;
						if (M_doesColLabelSpanChunks[dimIndex]) {
							// If a label spanned the 2 chunks (it is the last label of the left chunk
							// and first label of the right chunk), then it counts as 1 label we have to
							// remove one more.
							M_firstColLabelIndexes[dimIndex]--;
						}
					}
				}
				// M_firstColLabelIndexes will be modified when we update the column labels,
				// but we need the original values for updating the cell values of each row.
				// So store the original values into M_firstColLabelIndexesOld.
				M_firstColLabelIndexesOld[dimIndex] = M_firstColLabelIndexes[dimIndex];
			}
		}
	}

	var hasSortedCol = false;
	var isSortedColOnPage = false;
	var sortItemCodes = new Array();
	var sortedColInfo = new SortInfo();
	hasSortedCol = getSortedItemCodes(sortItemCodes);
	   
	if (hasSortedCol) {
		isSortedColOnPage = getSortedColumnIndex(sortItemCodes, M_tlChunk, M_trChunk, M_firstColLabelIndexesForSorting,
						M_colLabelSpans, hScroll.getVisible(), sortedColInfo);
		if (isSortedColOnPage) {
			sortedColInfo.m_order = getSortOrder();
		}
	}

	var quoteRegExp = /'/gi;
	var escapedItemCode;
	var cell;
	var selMembersCall;
	var summaryHandler;
	var summaryImage;
	var blankSpace;
	var tableRows = M_dataTable.rows;
	var cells;
	var c;
	var sortIndex;
	var dim;
	var colsLeft;
	var colCount;
	var firstCol;
	var visibleCount;
	var sortArrowCells;
	var sortArrows;
	var sortHandler;
	var sortItemIdentifier;
	var tChunk;
	var colLabelIndex;
	var usingRightChunks;
	var dimLabelCount;
	var childIndex;
	var childCount;
	var childNodes;
	var labelNode;
	var isRowTotal;
	var isColTotal;
	var startCol;

	// Update the column labels, using the cells in the top left chunk first.
	for (dim = 0; dim < M_colDimCount; dim++) {
		if (M_startInRightChunk) {
			tChunk = M_trChunk;
			usingRightChunk = true;
		}
		else {
			tChunk = M_tlChunk;
			usingRightChunk = false;
			dimLabelCount = M_tlChunk.colLabels[dim].length;
		}
		dimLabelCount = tChunk.colLabels[dim].length;
		cells = tableRows[dim].cells;
		if (dim < M_colDimCount - 1) {
			// nesting dimensions
			visibleCount = cells.length - 1; // -1 to skip dimension headers
		}
		else {
			// nested dimension
			visibleCount = hvis;
			sortArrowCells = tableRows[dim + 1].cells;
		}
		for (c = M_oldColCounts[dim]; c < visibleCount; c++) {
			if (!usingRightChunk) {
				// Check if we need to switch to the top-right chunk.
				if (M_firstColLabelIndexes[dim] >= dimLabelCount) {
					// Since we will be using another chunk, we must reset the label indexes for each dimension.
					if (dim < M_colDimCount - 1) {
						M_doesColLabelSpanChunks[dim] = doesLabelSpanChunks(tChunk, M_trChunk, false, dim);
						if (M_doesColLabelSpanChunks[dim]) {
							// For nesting dimensions, the last label in the top-left chunk might be the same as
							// the first label in the top-right chunk.  In this case, the index of the next label
							// to use (in the top-right chunk) is 1, not 0...except for the case where there is only
							// one label.
							lastIndex = M_trChunk.colLabels[dim].length - 1;
							M_firstColLabelIndexes[dim] = Math.min(1, lastIndex);
						}
						else {
							M_firstColLabelIndexes[dim] = 0;
						}
					}
					else {
						// If this is the most nested dimension, then the labels don't
						// span other nested labels, so set the index to 0.
						M_firstColLabelIndexes[dim] = 0;
					}

					tChunk = M_trChunk;
					usingRightChunk = true;
				}
			}
			// c + 1 to skip the row header cell.
			cell = cells[c + 1];
			labelNode = cell;
			colLabelIndex = M_firstColLabelIndexes[dim];
			if (tChunk.colIsGroup[dim][colLabelIndex]) {
				if (!M_isPrintableVersion){
						// Add a "A" tag for the link.
					if (cell.childNodes[0].tagName != "A") {
						anchorTag = document.createElement("A");
						anchorTag[M_innerAttr] = "&nbsp;"
						cell.replaceChild(anchorTag,cell.childNodes[0]);
					}
					labelNode = cell.childNodes[0];
					if (M_displayGroupLinks) {
						selMembersCall = "javascript:OnSelectMembers('";
						selMembersCall = selMembersCall + GetViewDim(M_colDimIDs[dim]) + "','";
						escapedItemCode = tChunk.colIDs[dim][colLabelIndex].replace(quoteRegExp, "\\'");
						selMembersCall = selMembersCall + escapedItemCode + "',0);";
						if (!PWdsapp_bIsIE){
							labelNode.setAttribute("href", selMembersCall, 0);
							selMembersCall = selMembersCall + "event.cancelBubble=true; return false;";
							labelNode.setAttribute("onmousedown", selMembersCall, 0);
						}
						else {
							labelNode.setAttribute("href", selMembersCall, 0);
						}
					}
				}
				else {
					labelNode.style.textDecoration = "underline";
				}
			}
			else {
				if (cell.childNodes[0].tagName == "A") {
					var aTextNode = document.createTextNode(" ");
					cell.replaceChild(aTextNode,cell.childNodes[0]);
					labelNode = cell;
				}
			}
			labelNode.firstChild.data = tChunk.colLabels[dim][colLabelIndex];

    		if (labelNode.firstChild.data.length == 0) {
    			labelNode[M_innerAttr] = M_emptyCell;
    		}
    		
			if (!M_isPrintableVersion) {
				// add/remove summary image
				childNodes = cell.childNodes;
				childCount = childNodes.length;
				if (tChunk.colHasSummary[dim][colLabelIndex]) {
					summaryHandler = "javascript:OnItemSummaryByCode('";
					escapedItemCode = tChunk.colIDs[dim][colLabelIndex].replace(quoteRegExp, "\\'");
					summaryHandler = summaryHandler + escapedItemCode + "','";
					summaryHandler = summaryHandler + GetViewDim(M_colDimIDs[dim]) + "');";					
					if (childCount == 1) {
						// only create the summary image if it's not already there
						summaryImage = M_summaryImage.cloneNode(true);						
						blankSpace = M_blankSpace.cloneNode(true);
						cell.appendChild(blankSpace);
						cell.appendChild(summaryImage);
					}
					else {
						summaryImage = childNodes[2];
					}
					summaryImage.setAttribute("href", summaryHandler, 0);
				}
				else {
					for (childIndex = childCount - 1; childIndex > 0; childIndex--) {
						cell.removeChild(childNodes[childIndex]);
					}
				}
			}
			// Set the sort arrow handlers
			if (sortArrowCells != undefined) {
    			cell = sortArrowCells[c + M_rowDimCount];
				if (!M_isPrintableVersion) {
					sortArrows = cell.getElementsByTagName("A");
					if (sortArrows.length > 0) {
			    		sortHandler = "javascript:onSortData(" + tChunk.firstRow + "," + tChunk.firstCol + "," + (colLabelIndex) + ",1)";
						sortArrows[0].setAttribute("href", sortHandler, 0);
    					sortHandler = "javascript:onSortData(" + tChunk.firstRow + "," + tChunk.firstCol + "," + (colLabelIndex) + ",0)";
	    				sortArrows[1].setAttribute("href", sortHandler, 0);
			    		
		    			if (hasSortedCol) {
			    			// change the image and corresponding alt text if the column is sorted
				    		updateSortArrows(c, sortedColInfo, M_sortedColInfo, isSortedColOnPage,
					    		sortArrows);
						}
					}
				}
				else {
					// We don't show the sort arrow when percentages are applied.
					if ((M_totalPrctType != M_rowPercent) && (M_totalPrctType != M_colPercent)) {
    					if ((hasSortedCol) && (c == sortedColInfo.m_index)) {
	    					// add in the sorted arrow
		    				addSortArrowPrintable(sortedColInfo.m_order, cell);
    					}   
    				}
				}
			}
			M_firstColLabelIndexes[dim] += 1;
		}
	}
	M_startInRightChunk = usingRightChunk;

	if (isSortedColOnPage) {
		M_sortedColInfo.m_index = sortedColInfo.m_index;
		M_sortedColInfo.m_order = sortedColInfo.m_order;
	}

	var lChunk;
	var rChunk = M_trChunk;

	var headerCount;
	var vOffset;
	var hOffset;

	var rowData;
	var rowNotes;
	var rowStart;
	var colStart;
	var colIsRowTotal;
	var cellData;
	var cellNotes;
	var cellNoteData;
	var hasFootnotes;
	var dataIndex;
	var usingRightChunkCells;

	if (hvis > M_oldColCounts[M_colDimCount - 1]) {
		rowStart = 0;
		M_startInBottomChunk = false;
	}
	else {
		rowStart = M_oldRowCounts[M_rowDimCount - 1];
		for (dimIndex = 0; dimIndex < M_rowDimCount; dimIndex++) {
			M_firstRowLabelIndexes[dimIndex] = M_firstRowLabelIndexesOld[dimIndex];
		}
	}
	var lastIndex;
	// For all of the rows in the body of the table
	if (rowStart < vvis) {
		if (M_startInBottomChunk) {
			lChunk = M_blChunk;
		}
		else {
			lChunk = M_tlChunk;
		}
		vOffset = vtop - lChunk.firstRow + 1;
		for (var r = rowStart; r < vvis; r++) {
			isColTotal = false;
			// If we have gone past the bottom of the top chunks, reset to continue with the bottom chunks.
			if (!M_startInBottomChunk) {
				if (r + vtop >= lChunk.lastRow) {
					for (dim = 0; dim < M_rowDimCount; dim++) {
						// Since we will be using another chunk, we must reset the label indexes for each dimension.
						if (dim < M_rowDimCount - 1) {
							if (doesLabelSpanChunks(lChunk, M_blChunk, true, dim)) {
								// For nesting dimensions, the last label in the top-left chunk might be the same as
								// the first label in the bottom-left chunk.  In this case, the index of the next label
								// to use (in the bottom-left chunk) is 1, not 0...except for the case where there is only
								// one label.
								lastIndex = M_blChunk.rowLabels[dim].length - 1;
								M_firstRowLabelIndexes[dim] = Math.min(1, lastIndex);
							}
							else {
								M_firstRowLabelIndexes[dim] = 0;
							}
						}
						else {
							// If this is the most nested dimension, then the labels don't
							// span other nested labels, so set the index to 0.
							M_firstRowLabelIndexes[dim] = 0;
						}
					}

					lChunk = M_blChunk;
					rChunk = M_brChunk;

					vOffset = vtop - lChunk.firstRow + 1;
					
					M_startInBottomChunk = true;
				}
			}
		
			// Update the cells in the left chunk
			rowData = lChunk.rowData[r + vOffset];
			rowNotes = lChunk.rowNotes[r + vOffset];
			
			if (M_totalPrctType == M_colTotal) {
				// There is only a 'Total' for the most nested dimension.
				isColTotal = lChunk.rowIsColTotal[M_rowDimCount - 1][r + vOffset];
			}

			// r + M_colDimCount to skip the dimension headers
			cells = tableRows[r + M_colDimCount + 1].cells;

			// Write the row item headers
			// headerCount is the number of dimension item headers in the row
			headerCount = cells.length - hvis;
			if (r >= M_oldRowCounts[M_rowDimCount - 1]) {
				// We are updating a new row, so we have to update all the cell values of the row.
				colStart = 0;
				hOffset = htop - lChunk.firstCol + 1;
			}
			else {
				// We are updating an existing row, so we need to update only the cell values
				// of the new columns.
				colStart = M_oldColCounts[M_colDimCount - 1];
				hOffset = M_firstColLabelIndexesOld[M_colDimCount - 1];
			}

			// Update the row item headers only if rows have been removed or added.
			// We can't do this only for the new rows because adding a row or removing a row
			// can affect the span of existing item headers.
			if (tableRowsDecreased || tableRowsIncreased) {
				for (c = 0; c < headerCount; c++) {
					dimIndex = M_rowDimCount - headerCount + c;
					cell = cells[c];
					labelNode = cell;
					if (lChunk.rowIsGroup[dimIndex][M_firstRowLabelIndexes[dimIndex]]) {
						if (!M_isPrintableVersion) {
							// Add a "A" tag for the link.
							var anchorTag = cell.getElementsByTagName("A");
							if (cell.childNodes[0].tagName != "A") {
								anchorTag = document.createElement("A");
								anchorTag[M_innerAttr] = "&nbsp;"
								cell.replaceChild(anchorTag,cell.childNodes[0]);
							}
							labelNode = cell.childNodes[0];
							if (M_displayGroupLinks) {
								selMembersCall = "javascript:OnSelectMembers('";
								selMembersCall = selMembersCall + GetViewDim(M_rowDimIDs[dimIndex]) + "','";
								escapedItemCode = lChunk.rowIDs[dimIndex][M_firstRowLabelIndexes[dimIndex]].replace(quoteRegExp, "\\'");
								selMembersCall = selMembersCall + escapedItemCode + "',0);";
								labelNode.setAttribute("href", selMembersCall, 0);
							}
						}
						else {
							labelNode.style.textDecoration = "underline";
						}
					}
					else {
						if (cell.childNodes[0].tagName == "A") {
							var aTextNode = document.createTextNode(" ");
							cell.replaceChild(aTextNode,cell.childNodes[0]);
							labelNode = cell;
						}
					}	
					labelNode.firstChild.data = lChunk.rowLabels[dimIndex][M_firstRowLabelIndexes[dimIndex]];
					if (labelNode.firstChild.data.length == 0 || labelNode.firstChild.data==" ") {
						labelNode[M_innerAttr] = M_emptyCell;
					}

					if (!M_isPrintableVersion) {
						// add/remove summary image
						childNodes = cell.childNodes;
						childCount = childNodes.length;
						if (lChunk.rowHasSummary[dimIndex][M_firstRowLabelIndexes[dimIndex]]) {
							summaryHandler = "javascript:OnItemSummaryByCode('";
							escapedItemCode = lChunk.rowIDs[dimIndex][M_firstRowLabelIndexes[dimIndex]].replace(quoteRegExp, "\\'");
							summaryHandler = summaryHandler + escapedItemCode + "','";
							summaryHandler = summaryHandler + GetViewDim(M_rowDimIDs[dimIndex]) + "');";
							if (childCount == 1) {
								// only add a new summary image if it's not already there
								summaryImage = M_summaryImage.cloneNode(true);								
								blankSpace = M_blankSpace.cloneNode(true);
								cell.appendChild(blankSpace);
								cell.appendChild(summaryImage);
							}
							else {
								summaryImage = childNodes[2];
							}
							summaryImage.setAttribute("href", summaryHandler, 0);
						}
						else {
							for (childIndex = childCount - 1; childIndex > 0; childIndex--) {
								cell.removeChild(childNodes[childIndex]);
							}
						}
					}
					M_firstRowLabelIndexes[dimIndex] += 1;
				}
			}

			// write the cell data for the row
			usingRightChunkCells = false;
				// There is only a 'Total' for the most nested dimension.
			colIsRowTotal = lChunk.colIsRowTotal[M_colDimCount - 1];
			for (dataIndex = hOffset, c = colStart; c < hvis; dataIndex++, c++) {
				if (!usingRightChunkCells) {
					if (c + htop >= lChunk.lastCol) {
						// If there are cells in the right chunk, update them as well.
						rowData = rChunk.rowData[r + vOffset];
						rowNotes = rChunk.rowNotes[r + vOffset];
						colIsRowTotal = rChunk.colIsRowTotal[M_colDimCount - 1];
						dataIndex = c + htop - lChunk.lastCol;
						usingRightChunkCells = true;
					}
				}

				isRowTotal = false;
				hasFootnotes = false;
				cellData = rowData[dataIndex];
				cellNotes = rowNotes[dataIndex];
				currentCell = cells[c + headerCount];
				if (!isColTotal) {
					// Check if there is a row total.
					if (M_totalPrctType == M_rowTotal) {
						isRowTotal = colIsRowTotal[dataIndex];
					}
				}

				// If there are footnotes or missing values we need to set the attribute for onmouseover
				if (cellNotes != "") {
					if (cellNotes.charAt(0) != M_footnoteMissingValSep) {
						// If the first character is not the character that separates the footnotes
						// identifiers from the missing value identifier, then there are footnotes
						// for this cell.
						hasFootnotes = true;
					}
					if (!M_isPrintableVersion) {
						cellNoteData = "javascript:DisplayCellNotes('";
						cellNoteData = cellNoteData + cellNotes + "', this);";

						// if Netscape then OK to use the standard setAttribute otherwise we use a workaround
						// for IE
						if (PWdsapp_bIsIE) {
							currentCell["onmouseover"] = new Function("DisplayCellNotes('" + cellNotes + "', this);");
							currentCell["onmouseout"] = new Function("HideCellNotes(this);");
						}
						else {
							currentCell.setAttribute("onmouseover", cellNoteData, 0);
							currentCell.setAttribute("onmouseout", "javascript:HideCellNotes(this)", 0);
						}
					}
				}

				// Because we reuse the same cells, we need to clear any mouseover event handler that
				// was set in a previous update of the table.
				else {
					if (PWdsapp_bIsIE){
						currentCell["onmouseover"] = "";
						currentCell["onmouseout"] = "";
					}
					else {
						currentCell.setAttribute("onmouseover", "", 0);
						currentCell.setAttribute("onmouseout", "", 0);
					}
				}

				var tempCellData = TrimWhiteSpace(cellData);
				var currentCellContent = currentCell.childNodes[0];

				if (hasFootnotes) {
					// If the cell has footnotes, use .innerHTML because there is an "&nbsp;"
					// in the string.  We have 2 separate cases here because using .innerHTML
					// is a lot slower than using childNodes[0].data, but we have to use it because
					// of the &nbsp;.
					currentCellContent.innerHTML = cellData;
				}
				else if (tempCellData.length == 0) {
					currentCellContent[M_innerAttr] = M_emptyCell;
				}
				else {
					currentCellContent.firstChild.data = cellData;
				}
				if (M_totalPrctType != M_totalPrctUnspecified) {
					if (isColTotal || isRowTotal) {
						currentCell.setAttribute(M_className, M_dataTotalClass, 0);
					}
					else {
						// We need to set the class attribute in the Table View in the case where the user
						// has scrolled when Totals are applied.  Because of the performance impact of using
						// setAttribute, we don't need to do this in the printable version.
						if (!M_isPrintableVersion) {
							currentCell.setAttribute(M_className, M_dataTableClass, 0);
						}
					}
				}
			}
		}
		for (dimIndex = 0; dimIndex < M_rowDimCount; dimIndex++) {
			if (M_firstRowLabelIndexes[dimIndex] > lChunk.rowLabels[dimIndex].length) {
				M_firstRowLabelIndexes[dimIndex] = lChunk.rowLabels[dimIndex].length;
			}
		}
		for (dimIndex = 0; dimIndex < M_colDimCount; dimIndex++) {
			if (M_firstColLabelIndexes[dimIndex] > lChunk.colLabels[dimIndex].length) {
				M_firstColLabelIndexes[dimIndex] = lChunk.colLabels[dimIndex].length;
			}
		}
	}

	// If Printable Version, we want to create and display the 
	// footnotes and missing values.
	if ((M_isPrintableVersion) && (i_forceUpdate)) {
		if ((M_bFootnotes) || (M_bMissingValues)) {
			createCellNoteTables();
		}
	}

	// setTableSize() has to be called resizeTable() may have changed the size of the table
	// (removed rows, columns) and this sometimes changes the property "offsetTop" of M_mainTable,
	// and this property is used to properly fit the table on the screen.
		setTableSize(i_newTableSize);

	return M_Update_Updated;
}

// This function retrieves the span of columns in the nesting column dimensions.
// It also retrieves the index of the first label to display for each column
// dimension.  This is a recursive function.
// i_dim: the dimension index (should be zero)
// i_tlChunk: top-left chunk of data
// i_trChunk: top-right chunk of data
// i_nestedColCount: number of visible columns in the most nested dimension
// io_colLabelSpans: array containing one array for each nesting column dimension.  Each of the arrays
//                   contains the span of its visible columns (labels).
// io_firstLabelIndexes: an array containg the the index of the first column label for each dimension.
function getColSpans(
	i_dim,
	i_tlChunk,
	i_trChunk,
	i_nestedColCount,
	io_colLabelSpans,
	io_firstLabelIndexes
	)
{
	var blIndex = 0;
	var colLabelIndex = 0;
	var colLabelCount = 0;
	var colSpanCount = 0;
	var totalSpan = 0;
	var foundFirstCol = false;
	var firstLabelIndex;

	if (i_dim >= M_colDimCount - 1) {
		io_firstLabelIndexes[i_dim] = hScroll.getTop() - i_tlChunk.firstCol + 1;
	}
	else {
		getColSpans(i_dim + 1, i_tlChunk, i_trChunk, i_nestedColCount,
			io_colLabelSpans, io_firstLabelIndexes);

		colLabelCount = i_tlChunk.colLabelSpans[i_dim].length;
		io_colLabelSpans[i_dim] = new Array();
		// get the span of the first label
		while (!foundFirstCol) {
			labelSpan = i_tlChunk.colLabelSpans[i_dim][colLabelIndex];
			if (colLabelIndex == colLabelCount - 1) {
				// This is the last label of the top-left chunk.  It is possible that the label
				// spans 2 chunks.  In that case, we must check if the first label of the top-right
				// chunk is the same label, and if it is we must add the two spans.
				if (i_trChunk.firstCol != i_tlChunk.firstCol) {
					// the two chunks are different chunks
					if (i_tlChunk.colLabels[i_dim][colLabelIndex]
							== i_trChunk.colLabels[i_dim][0])
					{
						// the label spans two chunks, so add the label spans together
						labelSpan += i_trChunk.colLabelSpans[i_dim][0];
						// When using the top-right chunk, we will start at index 1 instead of 0,
						// because we already used the span at index 0.
						blIndex = 1;
					}
				}
			}
			totalSpan += labelSpan;
			if (io_firstLabelIndexes[M_colDimCount - 1] < totalSpan) {
				foundFirstCol = true;
				labelSpan = totalSpan - io_firstLabelIndexes[M_colDimCount - 1];
				if (labelSpan > i_nestedColCount) {
					labelSpan = i_nestedColCount;
				}
				io_colLabelSpans[i_dim][0] = labelSpan;
				io_firstLabelIndexes[i_dim] = colLabelIndex;
				colSpanCount++;
				totalSpan = labelSpan;
			}
			colLabelIndex++;
		}
		// get the span of the other column labels
		while ((totalSpan < i_nestedColCount) && (colLabelIndex < colLabelCount)) {
			labelSpan = i_tlChunk.colLabelSpans[i_dim][colLabelIndex];
			if (colLabelIndex == colLabelCount - 1) {
				// This is the last label of the top-left chunk.  It is possible that the label
				// spans 2 chunks.  In that case, we must check if the first label of the top-right
				// chunk is the same label, and if it is we must add the two spans.
				if (i_trChunk.firstCol != i_tlChunk.firstCol) {
					// the two chunks are different chunks
					if (i_tlChunk.colLabels[i_dim][colLabelIndex]
							== i_trChunk.colLabels[i_dim][0])
					{
						// the label spans two chunks, so add the label spans together
						labelSpan += i_trChunk.colLabelSpans[i_dim][0];
						// When using the top-right chunk, we will start at index 1 instead of 0,
						// because we already used the span at index 0.
						blIndex = 1;
					}
				}
			}
			totalSpan += labelSpan;
			if (totalSpan > i_nestedColCount) {
				labelSpan = labelSpan + i_nestedColCount - totalSpan;
			}
			io_colLabelSpans[i_dim][colSpanCount] = labelSpan;
			colSpanCount++;
			colLabelIndex++;
		}
		if (totalSpan < i_nestedColCount) {
			if (colLabelIndex >= colLabelCount) {
				// continue, using the top-right chunk
				colLabelIndex = blIndex;
				colLabelCount = i_trChunk.colLabelSpans[i_dim].length;
				while ((totalSpan < i_nestedColCount) && (colLabelIndex < colLabelCount)) {
					labelSpan = i_trChunk.colLabelSpans[i_dim][colLabelIndex];
					totalSpan += labelSpan;
					if (totalSpan > i_nestedColCount) {
						labelSpan = labelSpan + i_nestedColCount - totalSpan;
					}
					io_colLabelSpans[i_dim][colSpanCount] = labelSpan;
					colSpanCount++;
					colLabelIndex++;
				}
			}
		}
	}
}

// This function determines if a label for dimension i_dim spans 2 consecutive chunks.
function doesLabelSpanChunks(
	i_chunk1,
	i_chunk2,
	i_vertical,
	i_dim
	)
{
	var spansChunks = false;
	var lastIndex;
	var labels1;
	var labels2;
	
	if (i_vertical) {
		labels1 = i_chunk1.rowLabels[i_dim];
		labels2 = i_chunk2.rowLabels[i_dim];
	}
	else {
		labels1 = i_chunk1.colLabels[i_dim];
		labels2 = i_chunk2.colLabels[i_dim];
	}
	lastIndex = labels1.length - 1;
	if (labels1[lastIndex] == labels2[0]) {
		// If the last label of the first chunk is the same as the first label of
		// the second chunk, it might span the 2 chunks...
		if (i_dim == 0) {
			// If this is the first dimension, then the label spans the 2 chunks..
			spansChunks = true;
		}
		else {
			// If this isn't the first dimension, the label could span the 2 chunks.
			// It will only span the 2 chunks if the label of its nesting dimension also spans the 2 chunks.
			spansChunks = doesLabelSpanChunks(i_chunk1, i_chunk2, i_vertical, i_dim - 1);
		}
	}

	return spansChunks;
}

// This function retrieves the span of rows in the nesting row dimensions.
// It also retrieves the index of the first label to display for each row
// dimension.  This is a recursive function.
// i_dim: the dimension index (should be zero)
// i_tlChunk: top-left chunk of data
// i_blChunk: bottom-left chunk of data
// i_nestedRowCount: number of visible rows in the most nested dimension
// io_labelSpans: array containing one array for each nesting row dimension.  Each of the arrays
//                   contains the span of its visible rows (labels).
// io_firstLabelIndexes: an array containing the index of the first row label for each dimension.
function getRowSpans(
	i_dim,
	i_tlChunk,
	i_blChunk,
	i_nestedRowCount,
	io_labelSpans,
	io_firstLabelIndexes
	)
{
	var blIndex = 0;
	var labelIndex = 0;
	var labelCount = 0;
	var spanCount = 0;
	var totalSpan = 0;
	var foundFirst = false;
	var firstLabelIndex;

	if (i_dim >= M_rowDimCount - 1) {
		io_firstLabelIndexes[i_dim] = vScroll.getTop() - i_tlChunk.firstRow + 1;
	}
	else {
		getRowSpans(i_dim + 1, i_tlChunk, i_blChunk, i_nestedRowCount,
			io_labelSpans, io_firstLabelIndexes);

		labelCount = i_tlChunk.rowLabelSpans[i_dim].length;
		io_labelSpans[i_dim] = new Array();
		// get the span of the first label
		while (!foundFirst) {
			labelSpan = i_tlChunk.rowLabelSpans[i_dim][labelIndex];
			if (labelIndex == labelCount - 1) {
				// This is the last label of the top-left chunk.  It is possible that the label
				// spans 2 chunks.  In that case, we must check if the first label of the top-right
				// chunk is the same label, and if it is we must add the two spans.
				if (i_blChunk.firstRow != i_tlChunk.firstRow) {
					// the two chunks are different chunks
					if (doesLabelSpanChunks(i_tlChunk, i_blChunk, true, i_dim)) {
						// the label spans two chunks, so add the label spans together
						labelSpan += i_blChunk.rowLabelSpans[i_dim][0];
						// When using the bottom-left chunk, we will start at index 1 instead of 0,
						// because we already used the span at index 0.
						blIndex = 1;
					}
				}
			}
			totalSpan += labelSpan;
			if (io_firstLabelIndexes[M_rowDimCount - 1] < totalSpan) {
				foundFirst = true;
				labelSpan = totalSpan - io_firstLabelIndexes[M_rowDimCount - 1];
				if (labelSpan > i_nestedRowCount) {
					labelSpan = i_nestedRowCount;
				}
				io_labelSpans[i_dim][0] = labelSpan;
				io_firstLabelIndexes[i_dim] = labelIndex;
				spanCount++;
				totalSpan = labelSpan;
			}
			labelIndex++;
		}
		// get the span of the other row labels
		while ((totalSpan < i_nestedRowCount) && (labelIndex < labelCount)) {
			labelSpan = i_tlChunk.rowLabelSpans[i_dim][labelIndex];
			if (labelIndex == labelCount - 1) {
				// This is the last label of the top-left chunk.  It is possible that the label
				// spans 2 chunks.  In that case, we must check if the first label of the top-right
				// chunk is the same label, and if it is we must add the two spans.
				if (i_blChunk.firstRow != i_tlChunk.firstRow) {
					// the two chunks are different chunks
					if (doesLabelSpanChunks(i_tlChunk, i_blChunk, true, i_dim)) {
						// the label spans two chunks, so add the label spans together
						labelSpan += i_blChunk.rowLabelSpans[i_dim][0];
						// When using the bottom-left chunk, we will start at index 1 instead of 0,
						// because we already used the span at index 0.
						blIndex = 1;
					}
				}
			}
			totalSpan += labelSpan;
			if (totalSpan > i_nestedRowCount) {
				labelSpan = labelSpan + i_nestedRowCount - totalSpan;
			}
			io_labelSpans[i_dim][spanCount] = labelSpan;
			spanCount++;
			labelIndex++;
		}
		if (totalSpan < i_nestedRowCount) {
			if (labelIndex >= labelCount) {
				// continue, using the bottom-left chunk
				labelIndex = blIndex;
				labelCount = i_blChunk.rowLabelSpans[i_dim].length;
				while ((totalSpan < i_nestedRowCount) && (labelIndex < labelCount)) {
					labelSpan = i_blChunk.rowLabelSpans[i_dim][labelIndex];
					totalSpan += labelSpan;
					if (totalSpan > i_nestedRowCount) {
						labelSpan = labelSpan + i_nestedRowCount - totalSpan;
					}
					io_labelSpans[i_dim][spanCount] = labelSpan;
					spanCount++;
					labelIndex++;
				}
			}
		}
	}
}

// This function refreshes the table to be scrolled to the appropriate point.
function refreshTable()
{
  	var updateResult;
	var done;
	
	// Save the desired top positions (may be reset by changed table sizes)
	var vTop = vScroll.getTop();
	var hTop = hScroll.getTop();
	
	// Calculate the new table size, based on the size of existing columns and rows of the table.
	var newTableSize = new TableSize();
	if (!M_isPrintableVersion) {
		getTableSize(vTop, hTop, newTableSize);
		// Don't force the main table's size. It will be set later when we have determined
		// the data table size.
		M_mainTable.style.width  = "";
		M_mainTable.style.height = "";
	}
	else{
		newTableSize.colCount = M_tableColCount;
		newTableSize.rowCount = M_tableRowCount;
	}

	// Ensure the HTML table is the correct size.
	updateResult = updateTable(true, newTableSize);
	if (updateResult == M_Update_WaitingForData) {
		return;
	}
	
	if (!M_isPrintableVersion) {
		// Remove extra rows.
		while ((newTableSize.rowCount > 1) &&
					(M_dataTable.offsetHeight + newTableSize.hScrollHeight > newTableSize.tableHeight))
		{					
			newTableSize.rowCount--;
			updateResult = updateTable(false, newTableSize);
			if (updateResult == M_Update_WaitingForData) {
				return;
			}
		}

		// Remove extra columns.
		while ((newTableSize.colCount > 1) &&
					(M_dataTable.offsetWidth + newTableSize.vScrollWidth > newTableSize.tableWidth))
		{
			newTableSize.colCount--;
			updateResult = updateTable(false, newTableSize);
			if (updateResult == M_Update_WaitingForData) {
				return;
			}
		}

		// Add extra rows.
		done = false;
		while (!done && (newTableSize.rowCount < (M_tableRowCount - vTop))) {
			// Add an extra row.
			newTableSize.rowCount++;
			updateResult = updateTable(false, newTableSize);
			if (updateResult == M_Update_WaitingForData) {
				return;
			}
			if (M_dataTable.offsetHeight + newTableSize.hScrollHeight > newTableSize.tableHeight)
			{
				// By adding an extra row, we exceeded the allowed height for the table,
				// so remove the extra rows. [if in this loop we went into the "else if"
				// condition below where we removed some columns, this might have caused the 
				// horizontal scroll bar to be required when it wasn't before, in which case
				// we need a "while" loop to remove more than one row].
				done = true;
				while ((newTableSize.rowCount > 1) &&
							(M_dataTable.offsetHeight + newTableSize.hScrollHeight > newTableSize.tableHeight))
				{
					newTableSize.rowCount--;
					updateResult = updateTable(false, newTableSize);
					if (updateResult == M_Update_WaitingForData) {
						return;
					}
				}
			}
			else if (M_dataTable.offsetWidth + newTableSize.vScrollWidth > newTableSize.tableWidth)
			{
				// By adding an extra row, we exceeded the allowed width for the table,
				// so remove the extra columns.
				while ((newTableSize.colCount > 1) &&
							(M_dataTable.offsetWidth + newTableSize.vScrollWidth > newTableSize.tableWidth))
				{
					newTableSize.colCount--;
					updateResult = updateTable(false, newTableSize);
					if (updateResult == M_Update_WaitingForData) {
						return;
					}
				}
			}
		}

		// Add extra columns.
		done = false;
		while (!done && (newTableSize.colCount < (M_tableColCount - hTop))) {
			// Add an extra column.
			newTableSize.colCount++;
			updateResult = updateTable(false, newTableSize);
			if (updateResult == M_Update_WaitingForData) {
				return;
			}
			if (M_dataTable.offsetWidth + newTableSize.vScrollWidth > newTableSize.tableWidth)
			{
				// By adding an extra column, we exceeded the allowed width for the table,
				// so remove the extra column.  [if in this loop we went into the "else if"
				// condition below where we removed some rows, this might have caused the 
				// vertical scroll bar to be required when it wasn't before, in which case
				// we need a "while" loop to remove more than one column].
				done = true;
				while ((newTableSize.colCount > 1) &&
							(M_dataTable.offsetWidth + newTableSize.vScrollWidth > newTableSize.tableWidth))
				{
					newTableSize.colCount--;
					updateResult = updateTable(false, newTableSize);
					if (updateResult == M_Update_WaitingForData) {
						return;
					}
				}
			}
			else if (M_dataTable.offsetHeight + newTableSize.hScrollHeight > newTableSize.tableHeight)
			{
				// By adding an extra column, we exceeded the allowed height for the table,
				// so remove the extra rows.
				while ((newTableSize.rowCount > 1) &&
							(M_dataTable.offsetHeight + newTableSize.hScrollHeight > newTableSize.tableHeight))
				{
					newTableSize.rowCount--;
					updateResult = updateTable(false, newTableSize);
					if (updateResult == M_Update_WaitingForData) {
						return;
					}
				}
			}
		}
	}

	if (M_isPrintableVersion){
		setPrintablePageSize();

		// Force the table to be the size of the page.  This will 'compress'
		// the table if it exceeds the page size (rows labels and column labels
		// are wrapped).  The width is set twice because it is possible that it wasn't
		// set the first time, but after setting the height, the table columns were pushed
		// to the right, therefore increasing the width.
		if (M_dataTable.offsetWidth > M_printableWidth) {
			M_dataTable.style.width = M_printableWidth + "px";
		}
		if (M_dataTable.offsetHeight > M_printableHeight) {
			M_dataTable.style.height = M_printableHeight + "px";
		}
		if (M_dataTable.offsetWidth > M_printableWidth) {
			M_dataTable.style.width = M_printableWidth + "px";
		}

		createViewPrintablePages(M_printableHeight, M_printableWidth);
		//showTablePrintable(); TBD...
	}
	else {
		// Set the table height...
		if (newTableSize.vScrollWidth > 0) {
			// There is a vertical scroll bar (i.e. there are rows beyond the bottom
			// of the window), so extend the height of the table to meet the top edge of the
			// horizontal scroll bar, or the bottom of the window if there is no scroll bar.
			if (newTableSize.tableHeight > newTableSize.hScrollHeight) {
				M_dataTable.style.height = (newTableSize.tableHeight - newTableSize.hScrollHeight) + "px";
			}
		}
		else {
			M_dataTable.style.height = "";
		}
		M_mainTable.style.height = newTableSize.tableHeight + "px";
		
		// Set the table width...
		if (newTableSize.hScrollHeight > 0) {
			// There is a horizontal scroll bar (i.e. there are columns beyond the right
			// of the window), so extend the width of the table to meet the left edge of the
			// vertical scroll bar, or the right edge of the window if there is no scroll bar.
			if (newTableSize.tableWidth > newTableSize.vScrollWidth) {
				M_dataTable.style.width = (newTableSize.tableWidth - newTableSize.vScrollWidth) + "px";
			}
		}
		else {
			M_dataTable.style.width = "";
		}
		M_mainTable.style.width = newTableSize.tableWidth + "px";

		// debug
		// the table is now done.  Check innerHTML to see the html output.
		showTable();
	}

	if (PWdsapp_bIsIE) {
		document.body.style.cursor = "default";
	}
	if (M_bSupport508) {	
		var astrColIds;
		var astrRowIds;
		var clCell;
		var clCells;
		var clTableRows;
		var nCells;
		var nDataCols;
		var nDataRows;
		var nSpan;
		var nSpans;
		var nId = 0;
		var nIndex;
		var nRow;
		var nRows;
		var nRowSpan;
		var strClass;
		var strId;
		
		astrColIds = new Array();
		astrRowIds = new Array();
		clTableRows = M_dataTable.rows;
		nDataRows = vScroll.getVisible();
		nDataCols = hScroll.getVisible();
		nRows = nDataRows + M_colDimCount + 1;
		nCols = nDataCols + M_rowDimCount;
		for (nCol = 0; nCol < nDataCols; nCol++) {
			astrColIds[nCol] = "";
		}
		for (nRow = 0; nRow < nDataRows; nRow++) {
			astrRowIds[nRow] = "";
		}
		for (nRow = 0; nRow < M_colDimCount; nRow++) {
			clCells = clTableRows[nRow].cells;
			nCells = clCells.length;
			nIndex = 0;
			for (nCol = 1; nCol < nCells; nCol++) {
				clCell = clCells[nCol];
			   strId = "a" + nId;
				clCell.id = strId;
				nId++;
				nSpans = (typeof(clCell.colSpan) == undefined) ? 1 : clCell.colSpan;
				for (nSpan = 0; nSpan < nSpans; nSpan++) {
					astrColIds[nIndex++] += strId + " ";
				}
			}
		}
		for (nRow = 0; nRow < nDataRows; nRow++) {
			clCells = clTableRows[nRow + M_colDimCount + 1].cells;
			for (nCol = 0; nCol < M_rowDimCount; nCol++) {
				clCell = clCells[nCol];
				if (clCell[M_className] != "DataTable") {
					strId = "a" + nId;
					clCell.id = strId;
					nId++;
					nSpans = (typeof(clCell.rowSpan) == undefined) ? 1 : clCell.rowSpan;
					for (nSpan = 0; nSpan < nSpans; nSpan++) {
						astrRowIds[nRow + nSpan] += strId + " ";
					}					
				}
			}
		}
		for (nRow = 0; nRow < nDataRows; nRow++) {
			clCells = clTableRows[nRow + M_colDimCount + 1].cells;
			nCells = clCells.length;
			nColIndex = 0;
			for (nCol = 0; nCol < nCells; nCol++) {
				clCell = clCells[nCol];	
				if (clCell[M_className] == "DataTable") {
					strId = astrRowIds[nRow] + astrColIds[nColIndex++];
					clCell.setAttribute("headers", strId, 0);
				}
			}
		}
	}

	return;
}

// These functions are used to show the tooltip that gives the current row or column labels
// when the scroll thumb is being dragged faster than the table can be refreshed.
function showVerticalToolTip( x, y )
	{
	// Find the toolTip element.
	var tt = document.getElementById( "toolTip" );

	// Show the tooltip at the specified location.
	var firstShow = tt.style.visibility == "hidden";
	tt.style.visibility = "visible";

	// Update the tooltip value and position (fixing the original x position).
	tt.childNodes[ 0 ].data = rowLabelArrays[ vScroll.getTop() + 1 ];
	if( firstShow )
		tt.origX = x;
	tt.style.left = tt.origX - ( tt.offsetWidth + 4 );
	tt.style.top  = y;
	}

function showHorizontalToolTip( x, y )
	{
	// Find the toolTip element.
	var tt = document.getElementById( "toolTip" );

	// Show the tooltip at the specified location.
	var firstShow = tt.style.visibility == "hidden";
	tt.style.visibility = "visible";

	// Update the tooltip value and position (fixing the original y position).
	tt.childNodes[ 0 ].data = colLabelArrays[ hScroll.getTop() + 1 ];
	tt.style.left = x - tt.offsetWidth;
	if( firstShow )
		tt.origY = y;
	tt.style.top  = tt.origY - ( tt.offsetHeight + 4 );
	}

// This function is used to hide the tooltip.
function hideToolTip()
	{
	// Hide the toolTip element.
	document.getElementById( "toolTip" ).style.visibility = "hidden";
	}

// This variable is used to keep track of the data that is currently being loaded. This allows
// us to minimize the number of requests to the server (since the data would be redundant).
var beingLoaded = new Object();

// ----------------------------------------------------------------------------
// This function makes the table visible
function showTable()
{
	M_otherDimTable.style.visibility = "visible";
	M_dataTable.style.visibility = "visible";
	M_mainTable.style.visibility = "visible";
	
	if (M_isPrintableVersion) {
	    HidePleaseWait();
	}
	else {
	    document.getElementById("RetrieveData").style.visibility = "hidden";
	}
}

// ----------------------------------------------------------------------------
// This function makes the table hidden
function hideTable()
{
	M_otherDimTable.style.visibility = "hidden";
	M_dataTable.style.visibility = "hidden";
	M_mainTable.style.visibility = "hidden";
	if (M_isPrintableVersion) {
	    pleaseWait();
	}
	else {
	    document.getElementById("RetrieveData").style.visibility = "visible";
	}
}

// ----------------------------------------------------------------------------
function createEmptyText(
	)
{
	M_blankSpace = document.createTextNode(" ");
}

// ----------------------------------------------------------------------------
function createSummaryImage(
	)
{
	var imageTag;

	M_summaryImage = document.createElement("A");
	
	imageTag = document.createElement("IMG");
	imageTag.setAttribute("src", M_strSummaryImage, 0);
	imageTag.setAttribute("border", "0", 0);
	imageTag.setAttribute("title", M_summaryTooltip, 0);
	imageTag.setAttribute("alt", M_summaryTooltip, 0);
	
	M_summaryImage.appendChild(imageTag);
}


// ----------------------------------------------------------------------------
// This function create an empty NOBR element that is used as the data cell
// content.
function createDataCellContent(
	)
{
	M_dataCellContent = document.createElement("NOBR");
	M_dataCellContent[M_innerAttr] = M_emptyCell;
}

// This function is called when a new table is to be loaded
// (on body load, table change in the form or submit of the form).
function newTable(i_rowCount, i_colCount)
{
	M_summaryTooltip = resSummary;
	var bSpawn=document.getElementsByName("CS_spawnwindow")[0].value=="True";
	if(bSpawn) M_summaryTooltip += " " + resOpenWindow;

	var hScrollPos;
	var vScrollPos;
	
	if (PWdsapp_bIsIE) {
		document.body.style.cursor = "wait";
	}
	
	// Save the Printable value so that it can be used to alter behaviour elsewhere.
	if (ObjWdsForm.WD_Printable.value == "0"){
		M_isPrintableVersion = false;
	}
	else {
		M_isPrintableVersion = true;
	}

	if (M_isPrintableVersion || ((parseInt(ObjWdsForm.sWD_Permit.value) & 2) == 0)) {
		M_displayGroupLinks = false;
	}

	// show the proper waiting message (retrieving data/ please wait)
	if (M_isPrintableVersion){
		pleaseWait();
	}
	else {
		ShowRetrievingDataMessage("OtherDimTable");
	}
	
	// Inform the scrollbars what to do when a tooltip needs to be displayed or hidden.
	// These have to be deferred until the body is loaded because the scroll bars won't
	// exist until then. newTable is called seldom enough that there is no need to work
	// on only setting them once.
//TBD: Enable scroll bar tooltip, when it is determined how the item labels will be sent to the client.
//	hScroll.showToolTip = showHorizontalToolTip;
//	hScroll.hideToolTip = hideToolTip;
//	vScroll.showToolTip = showVerticalToolTip;
//	vScroll.hideToolTip = hideToolTip;
	
	// Likewise, what to do when scrolling is needed.
	hScroll.performScroll = Resize;
	vScroll.performScroll = Resize;

	M_mainTable = document.getElementById("MainTable");
	M_dataTable = document.getElementById("DataTable");
	M_otherDimTable = document.getElementById("OtherDimTable");
	M_vScrollBarCell = document.getElementById("vScrollTD");
	M_hScrollBarCell = document.getElementById("hScrollTD");
	
	M_tableChunks = new Array();
	M_tableRowCount = i_rowCount;
	M_tableColCount = i_colCount;
	
	createSummaryImage();
	createEmptyText();
	createDataCellContent();
	
	hScrollPos = getSavedHScrollPosition();
	vScrollPos = getSavedVScrollPosition();
	
	if (PWdsapp_bActiveXEnabled || (!PWdsapp_bIsIE)) {
		hScroll.setTop(hScrollPos);
		vScroll.setTop(vScrollPos);

		M_xmlData = "";
		if (PWdsapp_bIsIE) {
			var xmlDataField = ObjWdsForm.WD_XMLData;
			if (typeof(xmlDataField[0]) != "undefined") {
				// The XML data string was too long and had to be stored into multiple fields.
				// We need to reconstruct the string from all the field values.
				for (var nIndex = 0; nIndex < xmlDataField.length; nIndex++) {
					M_xmlData += xmlDataField[nIndex].value;
					xmlDataField[nIndex].value = "";
				}
			}
			else if (typeof(xmlDataField) != "undefined") {
				// The XML data is stored in only one field, retrieve this value.
				M_xmlData = xmlDataField.value;
				xmlDataField.value = "";
			}
		}
	}
	
	// If we call loadChunk directly, the "Retrieving Data..." message doesn't get displayed.
	// This seems to be because the function loadChunk() is being processed and there isn't time
	// to display this message.  By using the setTimeout() function, this script has time to end and
	// display the message before loadChunk() is called.
	var strLoadChunk = "loadChunk(" + String(vScrollPos + 1) + "," + String(hScrollPos + 1) + ");";
	setTimeout(strLoadChunk, 1);

/*
	The function loadEmbeddedChunk() is used to parse the XML embedded as a data island.
	The purpose of this is to limit the number of trips to the server to 1 instead of 2,
	but the problem is that the code that parses the XML on the client is slower than if we go
	twice to the server.  When we go twice to the server, the XML is returned in an object called
	xmlHttpRequest, which seems to be optimized.  When we embed the XML as a data island, the
	XMLDocument object used on the client seems to be slower at parsing the XML.
	So this code is commented out for now until we can find a way to speed up XML parsing on the
	client.
	var loaded = loadEmbeddedChunk(vScrollPos + 1, hScrollPos + 1);
	if (!loaded) {
		loadChunk(vScrollPos + 1, hScrollPos + 1);
	}
*/
}

//See the comments in newTable() above to find out why this function is commented.
/*
// This function loads a chunk containing the specified row and column from an
// embedded XML data island.
function loadEmbeddedChunk(
	i_row,
	i_col
	)
{
	var loadError;
	var loaded = false;
	var xmlDoc;
	
	try {
		if (PWdsapp_bIsIE) {
			xmlDoc = document.getElementById("FirstChunk");
			if (xmlDoc != undefined) {
				xmlDoc = xmlDoc.removeNode(true);
			}
		}
		if (xmlDoc != undefined) {
			// Get the x and y positions and correct them to be within the table.
			var rowOffset = i_row;
			if (rowOffset < 1) {
				rowOffset = 1;
			}
			else if (rowOffset > M_tableRowCount) {
				rowOffset = M_tableRowCount;
			}
			var colOffset = i_col;
			if (colOffset < 1) {
				colOffset = 1;
			}
			else if (colOffset > M_tableColCount) {
				colOffset = M_tableColCount;
			}

			// Determine which chunk should be downloaded.
			var rowChunk = Math.floor((rowOffset - 1) / M_rowsPerChunk);
			var colChunk = Math.floor((colOffset - 1) / M_colsPerChunk);
			var chunkFirstRow = Math.min(M_tableRowCount, rowChunk * M_rowsPerChunk + 1);
			var chunkFirstCol = Math.min(M_tableColCount, colChunk * M_colsPerChunk + 1);

			// Figure out which chunk is going to be loaded and don't try to load it again if is already being loaded.
			var rc = "x" + Math.floor((i_col - 1) / M_colsPerChunk) + "y" + Math.floor((i_row - 1) / M_rowsPerChunk);
			if (beingLoaded[ rc ] != undefined) {
				return false;
			}
			beingLoaded[rc] = true;

			createChunk(xmlDoc, chunkFirstRow, chunkFirstCol, rowOffset - 1, colOffset - 1);
			resetSavedScrollPosition();
			loaded = true;
		}
	}
	catch(loadError) {
	}
	return loaded;
}
*/

// This function loads a chunk containing the specified row and column.
function loadChunk(
	i_row,
	i_col
	)
{
	// Get the x and y positions and correct them to be within the table.
	var rowOffset = i_row;
	if (rowOffset < 1) {
		rowOffset = 1;
	}
	else if (rowOffset > M_tableRowCount) {
		rowOffset = M_tableRowCount;
	}
	var colOffset = i_col;
	if (colOffset < 1) {
		colOffset = 1;
	}
	else if (colOffset > M_tableColCount) {
		colOffset = M_tableColCount;
	}

	// Determine which chunk should be downloaded.
	var rowChunk = Math.floor((rowOffset - 1) / M_rowsPerChunk);
	var colChunk = Math.floor((colOffset - 1) / M_colsPerChunk);
	var chunkFirstRow = Math.min(M_tableRowCount, rowChunk * M_rowsPerChunk + 1);
	var chunkFirstCol = Math.min(M_tableColCount, colChunk * M_colsPerChunk + 1);
	
	// Figure out which chunk is going to be loaded and don't try to load it again if is already being loaded.
	var rc = "x" + Math.floor((i_col - 1) / M_colsPerChunk) + "y" + Math.floor((i_row - 1) / M_rowsPerChunk);
	if (beingLoaded[ rc ] != undefined) {
		return;
	}
	beingLoaded[rc] = true;

	// Show the loading indicator.
	//document.getElementById("progress").setAttribute(M_className, "ProgressLoading");

	if (PWdsapp_bActiveXEnabled || (!PWdsapp_bIsIE)) {
		if (M_xmlData != "") {
			var xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
			xmlDoc.loadXML(M_xmlData);
			// debug
			// alert(M_xmlData);
			M_xmlData = "";
			createChunk(xmlDoc, chunkFirstRow, chunkFirstCol, rowOffset - 1, colOffset - 1);
			if (!M_isPrintableVersion){
				resetSavedScrollPosition();
			}
		}
		else {	
			// Load the XML by passing the coordinates to the ASP page.
			if (!M_isPrintableVersion){
				loadXML("getData.aspx", createChunk, chunkFirstRow, chunkFirstCol, M_rowsPerChunk, M_colsPerChunk, rowOffset - 1, colOffset - 1);
			}
			else {
				loadXML("getData.aspx", createChunk, chunkFirstRow, chunkFirstCol, M_rowsPrintable, M_colsPrintable, rowOffset - 1, colOffset - 1);
			}
		}
	}
	else {
		try {
			var xmlDoc = document.all("XMLDataIsland");
			if (xmlDoc != null) {
				createChunk(xmlDoc.XMLDocument, chunkFirstRow, chunkFirstCol, rowOffset - 1, colOffset - 1);
				if (!M_isPrintableVersion) {
					resetSavedScrollPosition();
				}
			}
			else {
				// Load the page.
				ObjWdsForm.sWD_ChunkFirstRow.value = chunkFirstRow;
				ObjWdsForm.sWD_FirstDisplayRow.value = rowOffset - 1;
				ObjWdsForm.sWD_FirstDisplayCol.value = colOffset - 1;
				ObjWdsForm.sWD_ChunkFirstCol.value = chunkFirstCol;
				loadPage("getDataToIFrame.aspx");
				hideTable();
				return;
			}
		}
		catch(ex) {
			alert(ex);
		}
	}
}

// ----------------------------------------------------------------------------
// This function gets the XML data island and adds the data to the array of chunks.
function HandleIFrameData(i_Document)
{	
	if ((typeof(ObjWdsForm.sWD_ChunkFirstRow) != "undefined") && (typeof(ObjWdsForm.sWD_FirstDisplayRow) != "undefined") 
		&& (typeof(ObjWdsForm.sWD_ChunkFirstCol) != "undefined") && (typeof(ObjWdsForm.sWD_FirstDisplayCol) != "undefined"))
	{
		var nFirstRowIndex = parseInt(ObjWdsForm.sWD_ChunkFirstRow.value);
		var nFirstDisplayRow = parseInt(ObjWdsForm.sWD_FirstDisplayRow.value);
		var nFirstColIndex = parseInt(ObjWdsForm.sWD_ChunkFirstCol.value);
		var nFirstDisplayCol = parseInt(ObjWdsForm.sWD_FirstDisplayCol.value);
		var xmlDoc = i_Document.all("XMLDataIsland");
		if (xmlDoc != null) {
			createChunk(xmlDoc.XMLDocument, nFirstRowIndex, nFirstColIndex, nFirstDisplayRow, nFirstDisplayCol);
			if (!M_isPrintableVersion){
				resetSavedScrollPosition();
			}
		}
	}
}

// ----------------------------------------------------------------------------
// This function loads the XML document from the specified URL, and when
// it is fully loaded, passes that document and the url to the specified
// handler function. This function works with any XML document
function loadXML(i_url, i_handler, i_row, i_col, i_rowCount, i_colCount, i_firstDisplayRow, i_firstDisplayCol)
{
	var strRequest = "";
	var xmlHttp;
	var url;

	// I shouldn't use this to differentiate between IE and Netscape.
	// Use the standard DOM Level 2 technique, if it is supported
	if( document.implementation && document.implementation.createDocument ) {
		// Create a new XML request object.
		xmlHttp = new XMLHttpRequest();
	}
	// Otherwise use Microsoft's proprietary API for Internet Explorer
	else {
		// Create a new XML request object.
		try
		{
			xmlHttp = new ActiveXObject("Msxml2.XMLHTTP");
		}
		catch( ex )
		{
			try
			{
				xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
			}
			catch( ex )
			{
				//alert( ex )
			}
		}
	}
	if (xmlHttp != undefined) {
		url = i_url + "?row=" + i_row + "&col=" + i_col + "&rowCount=" + i_rowCount + "&colCount=" + i_colCount;
		// Specify a function to get called when the XML is loaded
		xmlHttp.onreadystatechange = function()
		{
			if (xmlHttp.readyState == 4) {
				i_handler(xmlHttp.responseXML, i_row, i_col, i_firstDisplayRow, i_firstDisplayCol);
				if (!M_isPrintableVersion){
				    resetSavedScrollPosition();
				}
				delete xmlHttp;
			}
		}
		xmlHttp.open("POST", url, true);
		xmlHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
		xmlHttp.setRequestHeader("Accept-Language", G_strLanguage);
		strRequest = CreateHttpRequest(ObjWdsForm);
		xmlHttp.send(strRequest);
	}
}

// This function gets the new XML data and builds a chunk structure.
// This is added to the list of chunks. If a refresh has been deferred awaiting new data,
// it will be done.
function createChunk(
	i_xmlDoc,
	i_firstRow,
	i_firstCol,
	i_firstDisplayRow,
	i_firstDisplayCol
	)
{
	var errorTag = i_xmlDoc.getElementsByTagName("Error")
	if (errorTag.length > 0) {
		var errorNumber = errorTag[0].getAttribute("Number");
		DisplayError(errorNumber);
		return;
	}

	// Check for errors in loading the XML (IE).
	if (i_xmlDoc.parseError && (i_xmlDoc.parseError.errorCode != 0)) {
		alert("parseError " + i_xmlDoc.parseError.reason + " (" + i_xmlDoc.parseError.errorCode + ")");
		return;
	}
		
	// Check for errors in loading the XML (NN).
	if (i_xmlDoc.documentElement == null) {
		alert(resXMLLoad);
		return;
	}

	var isFirstChunk = false;
	if (M_tableChunks.length == 0) {
		isFirstChunk = true;
	}

	if (isFirstChunk) {
		// Set the scrolling areas.
		setScrollSizes( M_tableRowCount, M_tableColCount );
		hScroll.setTop(i_firstDisplayCol);
		vScroll.setTop(i_firstDisplayRow);
	}

	var data = i_xmlDoc.getElementsByTagName("Data")[0];
	
	// Get the row, column and other labels.
	var isGroup;
	var hasSummary;
	var index;
	var dimIndex;
	var rowLabelArrays;
	var colLabelArrays;
	var otherLabelArray;
	var otherIDs;
	var otherDrillLabels;
	var otherIsGroup;
	var otherHasSummary;
	var colLabelSpans;
	var rowLabelSpans;
	var rowCount = 0;
	var colCount = 0;

	var headers = data.getElementsByTagName("Headers")[0];
	
	// Get the other dimension labels and IDs
	var otherDim;
	var otherLabel;
	var otherItemLabel;
	var dimID;
	
	var otherDims = headers.getElementsByTagName("OtherDim");
	var otherDimCount = otherDims.length;
	
	otherLabelArray = new Array(otherDimCount);
	otherIDs = new Array(otherDimCount);
	otherIsGroup = new Array(otherDimCount);
	otherHasSummary = new Array(otherDimCount);
	for (dimIndex = 0; dimIndex < otherDimCount; dimIndex++) {
		otherDim = otherDims[dimIndex];

		// get the dimension ID from the <OtherDimLabel> tag, only if this is the first chunk
		if (isFirstChunk) {
			otherLabel = otherDim.getElementsByTagName("OtherDimLabel")[0];
			dimID = otherLabel.getAttribute("id");
			M_otherDimIDs[dimIndex] = parseInt(dimID.substr(3));
		}

		// get the item label from the <ItemLabel> tag
		otherItemLabel = otherDim.getElementsByTagName("ItemLabel")[0];
		isGroup = otherItemLabel.getAttribute("isgroup");
		otherIsGroup[dimIndex] = (isGroup != null && isGroup == "y");
		hasSummary = otherItemLabel.getAttribute("summary");
		otherHasSummary[dimIndex] = (hasSummary != null && hasSummary == "y");
		otherIDs[dimIndex] = otherItemLabel.getAttribute("itemcode");
		if (otherItemLabel.firstChild != null) {
			otherLabelArray[dimIndex] = otherItemLabel.firstChild.data;
		}
		else {
			otherLabelArray[dimIndex] = "";
		}
	}

	// Get the column labels, IDs and spans
	var colDim;
	var colLabel;
	var colItemLabels;
	var colSpan;
	var colIDs;
	var colDrillLabels;
	var colIsGroup;
	var colHasSummary;
	var colIsRowTotal;
	var rowLabelAttributes;
	var itemLabelCount;
	var dimColIsGroup;
	var dimColHasSummary;
	var dimColIsRowTotal;
	var dimColLabels;
	var dimColLabelSpans;
	var isTotal;

	var colDims = headers.getElementsByTagName("ColDim");
	var colDimCount = colDims.length;
	
	colLabelArrays = new Array(colDimCount);
	colIDs = new Array(colDimCount);
	colIsGroup = new Array(colDimCount);
	colHasSummary = new Array(colDimCount);
	colIsRowTotal = new Array(colDimCount);
	colLabelSpans = new Array(colDimCount);
	
	for (dimIndex = 0; dimIndex < colDimCount; dimIndex++) {
		colDim = colDims[dimIndex];
		// get the dimension ID from the <DimLabel> tag, only if this is the first chunk
		if (isFirstChunk) {
			colLabel = colDim.getElementsByTagName("DimLabel")[0];
			dimID = colLabel.getAttribute("id");
			M_colDimIDs[dimIndex] = parseInt(dimID.substr(3));
			if (colLabel.firstChild != null) {
				M_colDimNames[dimIndex] = colLabel.firstChild.data;
			}
			else {
				M_colDimNames[dimIndex] = " ";
			}
		}

		// get the item label and span from the <ColLabel> tag
		colItemLabels = colDim.getElementsByTagName("ColLabel");
		itemLabelCount = colItemLabels.length;
		
		dimColLabels = new Array(itemLabelCount);
		dimColIDs = new Array(itemLabelCount);
		dimColDrillLabels = new Array(itemLabelCount);
		dimColIsGroup = new Array(itemLabelCount);
		dimColHasSummary = new Array(itemLabelCount);
		dimColIsRowTotal = new Array(itemLabelCount);
		dimColLabelSpans = new Array(itemLabelCount - 1);
		
		colLabelArrays[dimIndex] = dimColLabels;
		colIDs[dimIndex] = dimColIDs;
		colIsGroup[dimIndex] = dimColIsGroup;
		colHasSummary[dimIndex] = dimColHasSummary;
		colIsRowTotal[dimIndex] = dimColIsRowTotal;
		colLabelSpans[dimIndex] = dimColLabelSpans;
		
		for (index = 0; index < itemLabelCount; index++) {
			colLabel = colItemLabels[index];
			isGroup = colLabel.getAttribute("isgroup");
			dimColIsGroup[index] = (isGroup != null && isGroup == "y");
			
			hasSummary = colLabel.getAttribute("summary");
			dimColHasSummary[index] = (hasSummary != null && hasSummary == "y");

			isTotal = colLabel.getAttribute("istotallb");
			dimColIsRowTotal[index] = (isTotal != null && isTotal == "y");

			dimColIDs[index] = colLabel.getAttribute("itemcode");
			if (colLabel.firstChild != null) {
				dimColLabels[index] = colLabel.firstChild.data;
			}
			else {
				dimColLabels[index] = "";
			}
			// get the span of nesting dimension labels
			if (dimIndex < colDimCount - 1) {
				colSpan = null;
				colSpan = colLabel.getAttribute("colspan");
				if (colSpan == null) {
					dimColLabelSpans[index] = 1;
				}
				else {
					dimColLabelSpans[index] = parseInt(colSpan);
				}
			}
		}
	}
	colCount = colLabelArrays[colDimCount - 1].length;

	// Get the footnote and missing value indicators and store them so 
	// that we can include them in the cells and pop-up the tooltips on mouseover
	var footnote;
	var pos;
	var footnoteLang;
	var footnotes = i_xmlDoc.getElementsByTagName("Footnote");
	var footnoteCount = footnotes.length;

	if (footnoteCount > 0){
		M_bFootnotes = true;
	}
	for (index = 0; index < footnoteCount; index++) {
		footnote = footnotes[index];
		
		pos = footnote.getAttribute("position");
		pos = parseInt(pos);
		// only update if it does not already exist in the array
		if (M_footnotes[pos] == null) {
			M_footnotes[pos] = new Array();
			footnoteLang = footnote.getElementsByTagName("FootnoteLang")[0];
			// When the footnote indicator is displayed in HTML format, it should be encoded.
			M_footnotes[pos][0] = footnoteLang.attributes.getNamedItem("indicator").firstChild.data;
			if (PWdsapp_bIsIE) {
				M_footnotes[pos][1] = footnoteLang.attributes.getNamedItem("indicator").firstChild.xml;
			}
			else {
				M_footnotes[pos][1] = HTMLencode(footnoteLang.attributes.getNamedItem("indicator").firstChild.data);
			}
			M_footnotes[pos][2] = footnoteLang.getAttribute("description");
		}
	}

	var missingValues;
	var missingValue;
	var missingValueLang;
	var missingValueCount;
	var pos;
	missingValues = i_xmlDoc.getElementsByTagName("MissingValue");
	missingValueCount = missingValues.length;
	if (missingValueCount > 0){
		M_bMissingValues = true;
	}
	for (index = 0; index < missingValueCount; index++) {
		missingValue = missingValues[index];

		pos = missingValue.getAttribute("position");
		pos = parseInt(pos);
		// only update if it does not already exist in the array
		if (M_missingValues[pos] == null) {
			M_missingValues[pos] = new Array();
			missingValueLang = missingValue.getElementsByTagName("MissingValueLang")[0];
			M_missingValues[pos][0] = missingValueLang.getAttribute("indicator");
			M_missingValues[pos][1] = missingValueLang.getAttribute("description");
		}
	}

	// Get the row labels and cell values.
	var rowIndex;
	var rowLabel;
	var rowLabels;
	
	var rowDims = headers.getElementsByTagName("RowDim");
	var rowDimCount = rowDims.length;
	
	var rowDim;
	var row;
	var rowSpan;
	
	var rows = data.getElementsByTagName("Row");
	var rowCount = rows.length;
	
	var rowLabelCounts = new Array(rowDimCount);
	var cells;
	var cell;
	var rowData;
	var rowNotes;
	var footIndex;
	var rowIsGroup;
	var rowHasSummary;
	var rowIsColTotal;
	var rowIDs;
	var rowDrillLabels;
	var rowLabelAttributes;
	var rowLabelCount;

	rowLabelArrays = new Array(rowDimCount);
	rowIDs = new Array(rowDimCount);
	rowIsGroup = new Array(rowDimCount);
	rowHasSummary = new Array(rowDimCount);
	rowIsColTotal = new Array(rowDimCount);
	rowLabelSpans = new Array(rowDimCount - 1);
	for (index = 0; index < rowDimCount; index++) {
		rowLabelArrays[index] = new Array();
		rowIDs[index] = new Array();
		rowIsGroup[index] = new Array();
		rowHasSummary[index] = new Array();
		rowIsColTotal[index] = new Array();
		rowLabelCounts[index] = 0;
		if (index < rowDimCount - 1) {	
			rowLabelSpans[index] = new Array();
		}		
		
		// get the dimension ID from the <RowDim> tag, only if this is the first chunk
		if (isFirstChunk) {
			dimID = rowDims[index].getAttribute("id");
			M_rowDimIDs[index] = parseInt(dimID.substr(3));
		}
	}

	var rowDataArray = new Array(rowCount);
	var rowNotesArray = new Array(rowCount);
	var rowDimLabelCount;
	
	for (rowIndex = 0; rowIndex < rowCount; rowIndex++) {
		row = rows[rowIndex];
		rowLabels = row.getElementsByTagName("RowLabel")
		rowLabelCount = rowLabels.length;
		for (index = rowLabelCount - 1, dimIndex = rowDimCount - 1; index >= 0; index--, dimIndex--) {
			// get the item label and span from the <RowLabel> tag
			rowLabel = rowLabels[index];
			rowDimLabelCount = rowLabelCounts[dimIndex];
			
			isGroup = rowLabel.getAttribute("isgroup");
			rowIsGroup[dimIndex][rowDimLabelCount] = (isGroup != null && isGroup == "y");

			hasSummary = rowLabel.getAttribute("summary");
			rowHasSummary[dimIndex][rowDimLabelCount] = (hasSummary != null && hasSummary == "y");

			isTotal = rowLabel.getAttribute("istotallb");
			rowIsColTotal[dimIndex][rowDimLabelCount] = (isTotal != null && isTotal == "y");
			
			rowIDs[dimIndex][rowDimLabelCount] = rowLabel.getAttribute("itemcode");
			if (rowLabel.firstChild != null) {
				rowLabelArrays[dimIndex][rowDimLabelCount] = rowLabel.firstChild.data;
			}
			else {
				rowLabelArrays[dimIndex][rowDimLabelCount] = "";
			}

			// get the span of nesting dimension labels
			if (dimIndex < rowDimCount - 1) {
				rowSpan = null;
				rowSpan = rowLabel.getAttribute("rowspan");
				if (rowSpan == null) {
					rowLabelSpans[dimIndex][rowDimLabelCount] = 1;
				}
				else {
					rowLabelSpans[dimIndex][rowDimLabelCount] = parseInt(rowSpan);
				}
			}
			rowLabelCounts[dimIndex]++;
		}
		
		cells = row.getElementsByTagName("C");
		cellCount = cells.length;
		rowData = new Array(cellCount);
		rowNotes = new Array(cellCount);
		rowDataArray[rowIndex] = rowData;
		rowNotesArray[rowIndex] = rowNotes;
		for (index = 0; index < cellCount; index++) {
			rowData[index] = "";  // Cell Data (Includes Footnote indicators and Data)
			rowNotes[index] = "";  // Cell Notes (Footnote and Missing Value positions for onmouseover)
			cell = cells[index];
			
			// Because of negative cell values, "nobr" is used to keep the value in one line.
			if (cell.childNodes.length > 1) {
				// There are footnotes
				footnotes = cell.getElementsByTagName("F");
				footnoteCount = footnotes.length;
				// Add them to Cell Data and Cell Notes elements of the array
				rowData[index] = "(";
				for (footIndex = 0; footIndex < footnoteCount; footIndex++) {
					pos = footnotes[footIndex].getAttribute("pos");
					pos = parseInt(pos);
					if (footIndex > 0) {
						rowData[index] = rowData[index].concat(M_footnoteSep, M_footnotes[pos][1]);
						rowNotes[index] = rowNotes[index].concat(M_footnoteSep, pos);
					}
					else {
						rowData[index] = rowData[index] + M_footnotes[pos][1];  // Indicator
						// We need the position to index the array for retrieving Identifier and Description
						rowNotes[index] = rowNotes[index] + pos;  // Position
					}
				}
				rowData[index] = rowData[index] + ")&nbsp;";
			}

			// Check to see if this is a Missing Value.  If it is we need to add it to the Cell Note array.
			mvpos = cell.getAttribute("mv");
			if (mvpos != null) {
				// Always add a semi-colon so we can tell that it is a missing value in "onmouseover"
				// We also need the position to index the array for retrieving Identifier and Description
				rowNotes[index] = rowNotes[index].concat(M_footnoteMissingValSep, mvpos);
			}

			// Add the cell data		
			if (cell.firstChild != null) {
				rowData[index] = rowData[index] + cell.firstChild.data;
			}
			else {
				rowData[index] = rowData[index] + " ";
			}
		}
	}
	rowCount = rowLabelArrays[rowDimCount - 1].length;

	// Find the range of data in this block of the table.
	var rowFirst = i_firstRow;
	var colFirst = i_firstCol;
	var rowLast  = rowFirst +  rowCount - 1;
	var colLast  = colFirst + colCount - 1;

	// Figure out which chunk has been loaded and ensure it is no longer on the list of chunks being loaded.
	delete beingLoaded["x" + Math.floor((colFirst - 1) / M_colsPerChunk) + "y" + Math.floor((rowFirst - 1) / M_rowsPerChunk)];

	// Create an object to hold all the chunk data.
	var newChunk = new TableChunk( rowFirst, rowLast, colFirst, colLast,
							rowLabelArrays, colLabelArrays, otherLabelArray,
							rowIDs, colIDs, otherIDs,
							rowDrillLabels, colDrillLabels, otherDrillLabels,
							rowIsGroup, colIsGroup, otherIsGroup,
							rowHasSummary, colHasSummary, otherHasSummary,
							rowIsColTotal, colIsRowTotal,
							rowLabelSpans, colLabelSpans, rowDataArray, rowNotesArray);

	// Add this new chunk to the chunk table.
	M_tableChunks[ M_tableChunks.length ] = newChunk;
	if (isFirstChunk) {
		setDimCountsAndClasses(rowDims.length, colDims.length);
		// If this was the first chunk of a new table, refresh the table.
		M_refreshNeeded = true;
	}
	
	if (PWdsapp_bIsIE && !PWdsapp_bActiveXEnabled) {
		M_bBusy = false;
		if (document.all("XMLDataIsland") != undefined) {
		document.all("XMLDataIsland").removeNode(true);
		}
	}

	// If a refresh was deferred awaiting the data, refresh now.
	if (M_refreshNeeded) {
		M_refreshNeeded = false;
		Resize();
	}
	if (M_isPrintableVersion){
		HidePleaseWait();
	}
	else {
		RemoveRetrievingDataMessage();
	}
}

// Sets the global count and style variables
function setDimCountsAndClasses(i_rowDimCount, i_colDimCount) {
	var dimIndex;
	
	// Set the Dim Item Counts
	M_rowDimCount = i_rowDimCount;
	M_colDimCount = i_colDimCount;
	
	M_rowSpanIndexes = new Array(M_rowDimCount - 1);
	M_rowSpanCounts = new Array(M_rowDimCount - 1);
	M_firstRowLabelIndexes = new Array(M_rowDimCount);
	M_firstColLabelIndexes = new Array(M_colDimCount);
	M_firstRowLabelIndexesOld = new Array(M_rowDimCount);
	M_firstColLabelIndexesOld = new Array(M_colDimCount);
	M_firstColLabelIndexesForSorting = new Array(M_colDimCount);
	M_rowLabelSpans = new Array(M_rowDimCount - 1);
	M_colLabelSpans = new Array(M_colDimCount - 1);
	M_oldRowCounts = new Array(M_rowDimCount);
	M_oldColCounts = new Array(M_colDimCount);
	M_doesRowLabelSpanChunks = new Array(M_rowDimCount - 1);
	M_doesColLabelSpanChunks = new Array(M_colDimCount - 1);
	
	for	(dimIndex = 0; dimIndex < M_rowDimCount; dimIndex++) {
		M_firstRowLabelIndexes[dimIndex] = 0;
	}
	for	(dimIndex = 0; dimIndex < M_colDimCount; dimIndex++) {
		M_firstColLabelIndexes[dimIndex] = 0;
		M_firstColLabelIndexesForSorting[dimIndex] = 0;
	}

	// Set the classes
	var table = document.getElementById("DataTable"); 
	M_rowHeaderClass = table.rows[M_colDimCount + 1].cells[0].getAttribute(M_className);
	M_colHeaderClass = table.rows[0].cells[1].getAttribute(M_className);
	M_colHeaderSortClass = table.rows[M_colDimCount].cells[M_rowDimCount].getAttribute(M_className);
	
	// Don't use the first cell of the table to determine the following classes, because sometimes the
	// first row can contain totals or percentages, and its cells will have a different style than
	// the cells of other rows.
	if (!M_isPrintableVersion) {
		M_dataTableClass = "DataTable";
		M_dataTotalClass = "DataTotal";
	}
	else {
		var printColour = ObjWdsForm.sWD_PrintColour.value;
		var printFontSize = ObjWdsForm.sWD_PrintFontSize.value;
		if (printColour == 0){
			if (printFontSize == 0){
				M_dataTableClass = "printableDataSmallColour";
				M_dataTotalClass = "printableDataTotalsSmallColour";
			}
			else if (printFontSize == 1) {
				M_dataTableClass = "printableDataMediumColour";
				M_dataTotalClass = "printableDataTotalsMediumColour";
			}
			else {
				M_dataTableClass = "printableDataLargeColour";
				M_dataTotalClass = "printableDataTotalsLargeColour";
			}
		}
		else {
			if (printFontSize == 0){
				M_dataTableClass = "printableDataSmallBW";
				M_dataTotalClass = "printableDataTotalsSmallBW";
			}
			else if (printFontSize == 1) {
						M_dataTableClass = "printableDataMediumBW";
						M_dataTotalClass = "printableDataTotalsMediumBW";
			}
			else {
						M_dataTableClass = "printableDataLargeBW";
						M_dataTotalClass = "printableDataTotalsLargeBW";
			}
		}
	}
	
	eval("M_totalPrctType = parseInt(ObjWdsForm.oWD_TotalPercent.value);");
}

// Sets the timeout before calling ShowCellNotes
function DisplayCellNotes(i_CellNotes, i_CurrentCell)
{
	M_cellNotes = i_CellNotes;
	M_currentCell = i_CurrentCell;

	if (M_timeoutID != 0){
		clearTimeout(M_timeoutID);
	}
	M_timeoutID = setTimeout("ShowCellNotes()", M_timeoutDelay);
}

// Displays the Cell Notes in a popup window after the timer has expired
// Cell Notes are passed to the DisplayCellNotes function as a string of comma-separated positions. 
// Footnotes and Missing Value lists are separated by semi-colons. 
// (ie) "0,4;1" would indicate footnotes (positions 0 and 4), missing value (position 1)
//      "1" would be only a footnote (position 1)
//      ";0" would be only a missing value (position 0)
// The position is used as the index into the appropriate array (M_footnotes or M_missingValues)
// for retrieving the Identifier and Description of the note
function ShowCellNotes(){

	// Reset the TimeoutID
	M_timeoutID = 0;

	// Cell Note Variables
	var cellNotes = M_cellNotes;
	var noteInd;    
	var noteDesc;
	var notePos;
	var notes = new Array();
	var noteSubstring = new Array();
	var index = 0;
	var pos = 0;
	var noteRowCounter = 0;
	var noteRow = 0;

	// Cell Size and Positioning Variables
	var currentCell = M_currentCell;
	var cellWidth = currentCell.offsetWidth;
	var cellHeight = currentCell.offsetHeight;
	var cellPosX = 0;
	var cellPosY = 0;
	var offsetX = 0;
	var offsetY = 0;
	var overflowY = 0;

	// Div variables
	var popupBox = document.getElementById("cellNotesBox");
	var divPaddingStyle;
	var divPadding;

	// Available screen size (in our document window)
	var docWidth;
	var docHeight;

	if( window.innerWidth == undefined ){
		docWidth = document.body.clientWidth - 4;	// IE 
		docHeight = document.body.clientHeight;
		divPaddingStyle = popupBox.currentStyle.paddingLeft;
		divPadding = divPaddingStyle.substr(0,divPaddingStyle.length - 2);
	}
	else {
		docWidth = window.innerWidth;	            // NN
		docHeight = window.innerHeight;
		divPaddingStyle = document.defaultView.getComputedStyle(popupBox,null);
		divPadding = parseInt(divPaddingStyle.getPropertyValue('padding-left'));
	}

	// Get the absolute position of the cell
	var currentElement = currentCell;
	while (currentElement != null) {
			cellPosX += currentElement.offsetLeft;
			cellPosY += currentElement.offsetTop;
			currentElement = currentElement.offsetParent;
	}

	//Save the Div Element here so that it can be easily reset in the HideCellNotes function
	M_copyPopupBox = popupBox.cloneNode(true);

	// We use a different style for the cell to highlight it.
	// Save the original class so that it can be reset in the HideCellNotes function
	M_copyCurrentCellClass = currentCell.getAttribute(M_className);
	currentCell.setAttribute(M_className, "CurrentCellOnPopup", 0);

	// Create the <Table> to contain the Footnote and Missing Value Data
	var cellNotesTable = document.getElementById("cellNotesTable");
	var cellNoteTR;
	var cellNoteInd;
	var cellNoteDesc;

	// Get the cell notes and create the Table of Indicators and Descriptions
	notes = cellNotes.split(M_footnoteMissingValSep);

	for (index = 0; index < notes.length; index++) {
		if (notes[index]!= ""){
				noteSubstring = notes[index].split(M_footnoteSep);
				for (pos = 0; pos < noteSubstring.length; pos++) {
					notePos = noteSubstring[pos];

					// Footnote
					if (index == 0){
						noteInd = "(" + M_footnotes[notePos][0] + ")";
						noteDesc = M_footnotes[notePos][2];
					}
					// Missing Value
					else if (index == 1){
						noteInd = M_missingValues[notePos][0];
						noteDesc = M_missingValues[notePos][1];
					}
					// Something's wrong... ignore this list. 
					else {
						noteInd = "";
						noteDesc = "";
					}

					if (noteInd != ""){
						noteRow = noteRowCounter++;
						cellNotesTable.insertRow(noteRow);

						cellNotesTable.rows[noteRow].insertCell(0);
						cellNotesTable.rows[noteRow].cells[0].appendChild(document.createTextNode(noteInd));
						cellNotesTable.rows[noteRow].cells[0].align = "left";
						cellNotesTable.rows[noteRow].cells[0].vAlign = "top";

						cellNotesTable.rows[noteRow].insertCell(1);
						cellNotesTable.rows[noteRow].cells[1].appendChild(document.createTextNode(noteDesc));
						cellNotesTable.rows[noteRow].cells[1].align = "left";
						cellNotesTable.rows[noteRow].cells[1].vAlign = "top";
					}
				}
		}
	}

	// Populate, format, and position the Popup box
	// We adjust the width first because any changes in these settings will affect the height.
	// Offsets are measured from the top/left of the current cell position
	var boxWidth = cellNotesTable.offsetWidth + 2*divPadding;
	var boxLeft;

	// Left side of screen   
	if (cellPosX <= (docWidth/2)) {   
		maxBoxWidth = docWidth - cellWidth - cellPosX - M_popupOffset - M_boundary - 2*divPadding;
		if (maxBoxWidth <= 0) {
			maxBoxWidth = 1; 
		}
		if (boxWidth > maxBoxWidth) {
			boxWidth = maxBoxWidth;
		}
		offsetX = M_popupOffset + cellWidth;
	}
	// Right side of screen
	else {
		maxBoxWidth = cellPosX - M_popupOffset - M_boundary - 2*divPadding;
		if (maxBoxWidth <= 0) {
			maxBoxWidth = 1; 
		}
		if (boxWidth > maxBoxWidth) {
			boxWidth = maxBoxWidth;
		}
		offsetX = offsetX - M_popupOffset - boxWidth - 2*divPadding;
	}

	boxLeft = cellPosX + offsetX;

	// Set the table width and position
	cellNotesTable.width = boxWidth;
	popupBox.style.left = boxLeft;

	// Allow the box to stretch vertically, if required
	// overflowY tells us how much to shift the box up or down
	// Note: There are different overflow and offset calculations based on current cell position
	var boxHeight = cellNotesTable.offsetHeight + 2*divPadding;
	var boxTop;
	var noOverflowHeight = 0;

	// Top half of available screen 
	if (cellPosY <= ((docHeight)/2)){
		noOverflowHeight = ((docHeight) - cellPosY - cellHeight - M_popupOffset - M_boundary);
		overflowY = noOverflowHeight - boxHeight; 

		// only shift the box when required
		if (overflowY >= 0){
				overflowY = 0;
		}
		offsetY = cellHeight + M_popupOffset + overflowY;
	}
	// Bottom half of available screen
	else {
		noOverflowHeight = cellPosY - M_boundary - M_popupOffset;
		overflowY = boxHeight - noOverflowHeight;

		// only shift the box when required
		if (overflowY <= 0){
				overflowY = 0;
		}
		offsetY = overflowY - M_popupOffset - boxHeight;
	}

	boxTop = cellPosY + offsetY;
	
	// Display the message from top to bottom
	if (boxTop < 0) {
	boxTop = 0;
	}
	popupBox.style.top = boxTop;

	// Display it
	popupBox.style.visibility = "visible";

}


//Hides the Popup Window used for Footnotes and Missing Values
function HideCellNotes (i_CurrentCell){

	var popupBox = document.getElementById("cellNotesBox");
	var cellNotesTable = document.getElementById("cellNotesTable");
	var currentCell = i_CurrentCell;

	// Clear the timer
	if (M_timeoutID != 0){
		clearTimeout(M_timeoutID);
		M_timeoutID = 0;
	}
	// Only reset the attributes if a cell note is currently being displayed
	else if (M_currentCell != undefined){

		// Reset the class of the cell to its original style
		currentCell.setAttribute(M_className, M_copyCurrentCellClass, 0);

		// Reset the Div
		popupBox.parentNode.replaceChild(M_copyPopupBox,popupBox);
	}

	M_currentCell = undefined;

}

// This function creates the cell note tables for the Printable Version of the table.
function createCellNoteTables(){

	var footnotesTable = document.getElementById("FootnotesTable");
	var missingValuesTable = document.getElementById("MissingValuesTable");
	var noteInd = "";
	var noteDesc = "";
	var noteRowCounter = 1;  // offset one for the section Title
	var noteRow = 0;

	// Footnotes
	for (index = 0; index < M_footnotes.length; index++) {
		if (M_footnotes[index]!= null){
				noteInd = "(" + M_footnotes[index][0] + ")";
				noteDesc = M_footnotes[index][2];
		}
		else {
				noteInd = "";
				noteDesc = "";
		}
	   
		if (noteInd != ""){
				noteRow = noteRowCounter++;
				footnotesTable.insertRow(noteRow);

				footnotesTable.rows[noteRow].insertCell(0);
				footnotesTable.rows[noteRow].cells[0].appendChild(document.createTextNode(noteInd));
				footnotesTable.rows[noteRow].cells[0].vAlign = "top";
				footnotesTable.rows[noteRow].cells[0].width = "10%";

				footnotesTable.rows[noteRow].insertCell(1);
				footnotesTable.rows[noteRow].cells[1].appendChild(document.createTextNode(noteDesc));
				footnotesTable.rows[noteRow].cells[1].vAlign = "top";
		}
	}

	// Reset the counters
	noteRowCounter = 1;
	noteRow = 0;

	// Missing Values
	for (index = 0; index < M_missingValues.length; index++) {
		if (M_missingValues[index] != null){
			noteInd = M_missingValues[index][0];
			noteDesc = M_missingValues[index][1];
		}
		else {
				noteInd = "";
				noteDesc = "";
		}

		if (noteInd != ""){
				noteRow = noteRowCounter++;
				missingValuesTable.insertRow(noteRow);

				missingValuesTable.rows[noteRow].insertCell(0);
				missingValuesTable.rows[noteRow].cells[0].appendChild(document.createTextNode(noteInd));
				missingValuesTable.rows[noteRow].cells[0].vAlign = "top";
				missingValuesTable.rows[noteRow].cells[0].width = "10%";

				missingValuesTable.rows[noteRow].insertCell(1);
				missingValuesTable.rows[noteRow].cells[1].appendChild(document.createTextNode(noteDesc));
				missingValuesTable.rows[noteRow].cells[1].vAlign = "top";
		}
	}
}


// This function creates the Printable version display of the Table.  It is called once 
// all of the data has been downloaded and put into one large HTML table.  From there,
// we determine and fix the Column Widths and Row Heights and separate the data into 
// printable pages
function createViewPrintablePages(i_maxPageHeight, i_maxPageWidth){

	var strPageWidth = i_maxPageWidth + "px";
	var strPageHeight = i_maxPageHeight + "px";
	var colWidthArray = new Array();
	var rowHeightArray = new Array();
	var footnotesTable = document.getElementById("FootnotesTable");
	var missingValuesTable = document.getElementById("MissingValuesTable");
	var pageTable = document.getElementById("pageTable");

	// Set the position of the pageTable to absolute so that it is not part of the 
	// size calculations and does not take up any screen real-estate
	pageTable.style.position = "absolute";
	pageTable.style.left = "0px";
	pageTable.style.top = "0px";
	   
	// Create Page Template that can be cloned and reused
	var pageTemplate = createPageTemplate();
	   
	// Store the column widths as they currently appear and determine how many "data" columns 
	// can fit on each page.  Use the row with the Row Dim Headers.
	var sampleRow = M_colDimCount;
	var rawDataCells = M_dataTable.rows[sampleRow].cells;
	var totalCols = rawDataCells.length;
	var pageCols = new Array();
	var currentPageDataColCount = 0;
	var currentPageWidth = 0;
	var currentColWidth = 0;
	var headerCellWidth = 0;
	var hPages = 0;
	   
	   
	// Keep track of first column number and number of columns for each page
	pageCols[0] = new Array();
	pageCols[0][0] = M_rowDimCount;

	for (col = 0; col < totalCols; col++){
		currentColWidth = rawDataCells[col].offsetWidth;
		colWidthArray[col] = currentColWidth;
		currentPageWidth = currentPageWidth + currentColWidth;
	      
		if (col < M_rowDimCount){
				headerCellWidth = headerCellWidth + currentColWidth;
				if (currentPageWidth >= i_maxPageWidth){
					// We can't display any data
					alert(resNoPrintableData);
        		window.history.back();
					return;
				}
		}
		else{
				if (currentPageWidth >= i_maxPageWidth){
					// Store the column count and initialize the next page
					pageCols[hPages][1] = currentPageDataColCount;
					if (currentPageDataColCount > 0){
						currentPageWidth = headerCellWidth + currentColWidth;
						currentPageDataColCount = 1;
						pageCols[++hPages] = new Array();
						pageCols[hPages][0] = col;
					}
					else{
						// We can't display any data
						alert(resNoPrintableData);
        			window.history.back();
						return;
					}
				}
				else{
					currentPageDataColCount++;
				}
				// Store the column count for the last horizontal page
				if (col == totalCols - 1){
					pageCols[hPages][1] = currentPageDataColCount;
				}
		}
	}
	         
	
	// Store the row heights as they currently appear and determine how many "data" rows 
	// can fit on each page. 
	   
	var rawDataRows = M_dataTable.rows;
	var pageRows = new Array();
	var currentPageDataRowCount = 0;
	var currentPageHeight = 0;
	var currentRowHeight = 0;
	var headerCellHeight = 0;
	var vPages = 0;

	   
	var totalRows = rawDataRows.length;
	   
	// Adjust the Max Height to include the Title, Other, and Source Tables
	var maxTableHeight = i_maxPageHeight - M_titleTableHeight - M_otherDimTableHeight - M_sourceInfoTableHeight;
	   
	pageRows[vPages] = new Array();
	pageRows[vPages][0] = M_colDimCount + 1;
	
	for (row = 0; row < totalRows; row++){
		// Because of the complexity of nested columns/rows and their associated spans, and since it
		// doesn't matter which cell we get the height from (as long as it doesn't have spans) then 
		// we can get the length of the row and subtract 2.  Subtracting only one may cause an issue 
		// when scroll bars are present and since the minimum number of columns is 2 (one for the row 
		// dimension and one for the data, indexing at zero in that case is OK, because there won't be
		// any spans.
		sampleColumn = rawDataRows[row].cells.length - 1; 
		currentRowHeight = rawDataRows[row].cells[sampleColumn].offsetHeight;

		rowHeightArray[row] = currentRowHeight;
		currentPageHeight = currentPageHeight + currentRowHeight;
	      
		if (row <= M_colDimCount){
				headerCellHeight = headerCellHeight + currentRowHeight;
				if (currentPageHeight >= maxTableHeight){
					// We can't display any data
					alert(resNoPrintableData);
        		window.history.back();
					return;
				}
		}
		else{
				if (currentPageHeight >= maxTableHeight){
					// Store the row count and initialize the next page
					pageRows[vPages][1] = currentPageDataRowCount;
					if (currentPageDataRowCount > 0){
						currentPageHeight = headerCellHeight + currentRowHeight;
						currentPageDataRowCount = 1;
						pageRows[++vPages] = new Array();
						pageRows[vPages][0] = row;
					}
					else {
						// We can't display any data
						alert(resNoPrintableData);
        			window.history.back();
						return;
					}
				}
				else{
					currentPageDataRowCount++;
				}

				// Store the row count for the last vertical page
				if (row == totalRows - 1){
					pageRows[vPages][1] = currentPageDataRowCount;
				}
		}
	}
	   
	// Create the pages...
	var pageArray = new Array();
	var pageNumber = 0;

	// Determine the row and column spans for nested dimensions
	if (M_colDimCount > 1){
		var colDimSpans = new Array();
		for (nestedDim = 0; nestedDim < M_colDimCount - 1; nestedDim++){
				colDimSpans[nestedDim] = rawDataRows[nestedDim].cells[1].getAttribute("colspan");
		}
	}
	if (M_rowDimCount > 1){
		var rowDimSpans = new Array();
		for (nestedDim = 0; nestedDim < M_rowDimCount - 1; nestedDim++){
				rowDimSpans[nestedDim] = rawDataRows[M_colDimCount + 1].cells[nestedDim].getAttribute("rowspan");
		}
	}
	   
	var currentPage;
	var tableWidth;
	var dataTableHead;
	var dataTableBody;
	var currentDataCol;
	var lastDataCol;
	var firstColDimHeader;
	var firstSpan;
	var remainder;
	var remainingColCount;
	var remainingHeaderCount;
	var colOffset;
	var offsetColIndex;
	var offsetColIndexCount;
	var offsetHeaderRow;
	var offsetPrevHeaderRow;

	   
	// Multiple pages caused by number rows
	for (vPage = 0; vPage < pageRows.length; vPage++){
	   
		// Multiple pages caused by number of cols
		for (hPage = 0; hPage < pageCols.length; hPage++){
				currentPage = pageTemplate.cloneNode(true);
	         
				// Set the current max table width
				currentPage.setAttribute("width", strPageWidth, 0);
	         
				// This code is commented out because enforcing a different width on the last page caused
				// the page to extend too far vertically in certain circumstances.
				/* TBD
				if (hPage == pageCols.length - 1){
					// Calculate total table width to enforce similar wrapping on the last horizontal page
					tableWidth = headerCellWidth;
					for (col = pageCols[hPage][0]; col <= pageCols[hPage][1]; col++){
						tableWidth += colWidthArray[col];
					}
					strTableWidth = tableWidth + "px";
					currentPage.rows[2].cells[0].childNodes[0].setAttribute("width", strTableWidth, 0);
				}
				*/
				dataTableHead = currentPage.rows[2].cells[0].childNodes[0].appendChild(document.createElement("Thead"));
				dataTableBody = currentPage.rows[2].cells[0].childNodes[0].appendChild(document.createElement("Tbody"));

				//Add in the Column Dim Header Rows
				for (row = 0; row < M_colDimCount; row++){
					// Column Dim Header
					dataTableHead.insertRow(row);
					rawDataCells = rawDataRows[row].cells;
					colDim = rawDataCells[0].cloneNode(true);
					strColWidth = colWidthArray[0] + "px";
					colDim.setAttribute("width", strColWidth, 0);
					dataTableHead.rows[row].appendChild(colDim);
	               
					// Column Item Headers
					colCount = 1;
					if (row == M_colDimCount - 1){
						// We don't have to worry about spans.
						while (colCount <= pageCols[hPage][1]){
								nCol = pageCols[hPage][0] + colCount - M_rowDimCount;
								colItem = rawDataCells[nCol].cloneNode(true);
								strColWidth = colWidthArray[nCol + M_rowDimCount - 1] + "px";
								colItem.setAttribute("width", strColWidth, 0);
								dataTableHead.rows[row].appendChild(colItem);
								colCount++;
						}
					}
					else {
						// Determine the first Col Item Header and how many cols it has to span
						currentDataCol = pageCols[hPage][0] - (M_rowDimCount - 1);
						lastDataCol =  currentDataCol + pageCols[hPage][1];
						firstColDimHeader = Math.ceil(currentDataCol / colDimSpans[row]);
						remainder = (currentDataCol - 1) % colDimSpans[row];
						firstSpan = colDimSpans[row] - remainder;

						// For some reason firstSpan is being treated as a string??
						currentDataCol = currentDataCol + Number(firstSpan);
	                  
						colItem = rawDataCells[firstColDimHeader].cloneNode(true);
						colItem.setAttribute("colspan", firstSpan, 0);                 
						dataTableHead.rows[row].appendChild(colItem);

						// Determine how many more Col Item headers in this dimension can be added
						remainingColCount = pageCols[hPage][1] - firstSpan;
	                  
						if (remainingColCount > 0){
								remainingHeaderCount = Math.ceil(remainingColCount / colDimSpans[row]);
								for (col = 1; col <= remainingHeaderCount; col++){
									colItem = rawDataCells[firstColDimHeader + col].cloneNode(true);
	                     
									// Set the colspan
									if (col == remainingHeaderCount){
										colItemSpan = lastDataCol - currentDataCol;
									}
									else{
										colItemSpan = colDimSpans[row];
										// colDimSpans[row] is also being treated as a string?
										currentDataCol = currentDataCol + Number(colDimSpans[row]);
									}
									colItem.setAttribute("colspan", colItemSpan, 0);
									dataTableHead.rows[row].appendChild(colItem);
								}
						}
					}
				}
				// Add in the Row Dim Headers row
				// row index was augmented to this row before exiting the previous section
				dataTableHead.insertRow(row);
				rawDataCells = rawDataRows[row].cells;
				for (col = 0; col < M_rowDimCount; col++){
					rowDim = rawDataCells[col].cloneNode(true);
					strColWidth = colWidthArray[col] + "px";
					rowDim.setAttribute("width", strColWidth, 0);
					if (col == 0){
						strRowHeight = rowHeightArray[row] + "px";
						rowDim.setAttribute("height", strRowHeight, 0);
					}
					dataTableHead.rows[row].appendChild(rowDim);
				}
				colCount = 0;
				while (colCount < pageCols[hPage][1]){
					nCol = pageCols[hPage][0] + colCount;
					colItem = rawDataCells[nCol].cloneNode(true);
					strColWidth = colWidthArray[nCol] + "px";
					colItem.setAttribute("width", strColWidth, 0);
					dataTableHead.rows[row].appendChild(colItem);
					colCount++;
				}    
	         
				// Add in Data Rows
				for (row = 0; row < pageRows[vPage][1]; row++){
					// Insert Row Items
					dataTableBody.insertRow(row);
					nRow = row + pageRows[vPage][0];

					// Insert all headers in the first row and adjust the spans
					if (row == 0){
						offsetColIndex = 0;
						offsetColIndexCount = 0;
						offsetHeaderRow = 0;
						offsetPrevHeaderRow = 0;
						for (col = 0; col < M_rowDimCount - 1; col++){
								// Determine where to get the label from
								remainder = (((nRow - M_colDimCount) - 1) % Number(rowDimSpans[col]));
								if (remainder != 0){
									// Determine the row to get the cell from
									offsetHeaderRow = nRow - remainder;
									if (offsetPrevHeaderRow == offsetHeaderRow){
										// Adjust the col index to ensure that the correct header is being retrieved
										// Note: In this implementation we assume that the nesting is consistent
										//       all the way down the table.  
										offsetColIndex = offsetColIndexCount;
										offsetColIndexCount++;
									}
									else{
										// Update header row and reset the count
										offsetPrevHeaderRow = offsetHeaderRow;
										offsetColIndexCount = 0;
									}
	                           
									// Get the header from the appropriate row, adjust the span and insert it
									rawDataCells = rawDataRows[offsetHeaderRow].cells;
									rowItem = rawDataCells[offsetColIndex].cloneNode(true);
									rowSpan = rowDimSpans[col] - remainder;
									rowItem.setAttribute("rowspan", rowSpan, 0);
									strColWidth = colWidthArray[col] + "px";
									rowItem.setAttribute("width", strColWidth, 0);
									if (col == 0){
										// We only need to set the height once per row
										strRowHeight = rowHeightArray[nRow] + "px";
										rowItem.setAttribute("height", strRowHeight, 0);
									}
									dataTableBody.rows[row].appendChild(rowItem);

								}
						}
					}
					rawDataCells = rawDataRows[nRow].cells;
					col = 0;
					rowItem = rawDataCells[col].cloneNode(true);
					while (rowItem.tagName == "TH"){
						// Since we can't be sure that the height was set, and we don't know
						// which column we are currently looking at, we always set the height
						strRowHeight = rowHeightArray[nRow] + "px";
						rowItem.setAttribute("height", strRowHeight, 0);
						dataTableBody.rows[row].appendChild(rowItem);
						rowItem = rawDataCells[++col].cloneNode(true); 
					}

					// We need to account for the "missing" cells caused by nested rows
					colOffset = 0;
					colOffset = M_rowDimCount - col;

					// Data
					colCount = 0;
					while (colCount < pageCols[hPage][1]){
						nCol = pageCols[hPage][0] + colCount - colOffset;
						rowData = rawDataCells[nCol].cloneNode(true);
						dataTableBody.rows[row].appendChild(rowData);
						colCount++;
					}
				}

				currentPage.style.visibility = "visible";
				for (x = 0; x < currentPage.rows.length; x++){
					currentPage.rows[x].cells[0].childNodes[0].style.visibility = "visible";
				}
	         
				pageArray[pageNumber++] = currentPage;
		}
	}   
	// Remove the template so it doesn't take up any screen real-estate
	temp = pageTemplate.offsetParent;
	temp.removeChild(pageTemplate);
	temp = M_otherDimTable.offsetParent;
	temp.removeChild(M_otherDimTable);
	temp = M_mainTable.offsetParent;
	temp.removeChild(M_mainTable);
	   
	// Add the printable pages to the document and display them
	currentPageDiv = document.body.appendChild(document.createElement("Div"));
	var pageBreak = document.createElement("P");
	var pb;
	pageBreak.setAttribute(M_className, "pageBreakAfter", 0);
	   
	   
	// Remove the cell note tables from the main table so they don't take up any screen real-estate
	var fn = temp.removeChild(footnotesTable);
	var mv = temp.removeChild(missingValuesTable);
	var fnAdded = false;
	var mvAdded = false;
	var footnotesOnNewPage = false;
	var clFooterTable = footerTables();
	var clElement = null;
	var footerCopy = null;

	if (M_bFootnotes){
		fn.setAttribute("width", strPageWidth, 0);
		fn.style.visibility = "visible";
	}
	if (M_bMissingValues){
		mv.setAttribute("width", strPageWidth, 0);
		mv.style.visibility = "visible";
	}


	for (nPage = 0; nPage < pageArray.length; nPage++){
		if (nPage < pageArray.length -1){
				currentPageDiv.appendChild(pageArray[nPage]);
				pb = pageBreak.cloneNode(true);
				currentPageDiv.appendChild(pb);
		}
		else {
				// This is the last page...            
				previousHeight = currentPageDiv.offsetHeight;  // top of page
				currentPageDiv.appendChild(pageArray[nPage]);  // add content
	         
				if (M_bFootnotes){
					// Try and add the footnotes
					currentPageDiv.appendChild(fn);
					totalHeight = currentPageDiv.offsetHeight;
					if (totalHeight - previousHeight < i_maxPageHeight){
						fnAdded = true;
	            
						// Try and add the missing values, too
						if (M_bMissingValues){
								currentPageDiv.appendChild(mv);
								totalHeight = currentPageDiv.offsetHeight;
								if (totalHeight - previousHeight < i_maxPageHeight){
									mvAdded = true;
								}
								else{
									//remove missing values and add a page break
									mv = currentPageDiv.removeChild(mv);
									pb = pageBreak.cloneNode(true);
									currentPageDiv.appendChild(pb);
								}
						}                
					}
					else {
						//remove footnotes and add a page break
						fn = currentPageDiv.removeChild(fn);
						pb = pageBreak.cloneNode(true);
						currentPageDiv.appendChild(pb);
					}
				}
				else if (M_bMissingValues){
					currentPageDiv.appendChild(mv);
					totalHeight = currentPageDiv.offsetHeight;
					if (totalHeight - previousHeight < i_maxPageHeight){
						mvAdded = true;
					}
					else{
						//remove missing values and add a page break
						mv = currentPageDiv.removeChild(mv);
						pb = pageBreak.cloneNode(true);
						currentPageDiv.appendChild(pb);
					}
				}
		}
	}
  
	previousHeight = currentPageDiv.offsetHeight;
	// Check if the footnotes and missing values were added
	if ((M_bFootnotes) && (!fnAdded)){
		// TBD - verify length of table to ensure that it does not span multiple pages...
		currentPageDiv.appendChild(fn);
		footnotesOnNewPage = true;
	}
	if ((M_bMissingValues) && (!mvAdded)){
		currentPageDiv.appendChild(mv);
		// If we are adding to the same page as footnotes, we need to make sure we have room
		if (footnotesOnNewPage){
				totalHeight = currentPageDiv.offsetHeight;
				if (totalHeight - previousHeight > i_maxPageHeight){
					mvCopy = currentPageDiv.removeChild(mv);
					pb = pageBreak.cloneNode(true);
					currentPageDiv.appendChild(pb);
					currentPageDiv.appendChild(mvCopy);
				}
		}
	}
	   
	// If the footer is defined, then you have to draw the footer for each page.
	// Footer is inserted before each page break.
	if (clFooterTable != null) {
		clFooterTable.setAttribute("width", strPageWidth, 0);
		clElement = currentPageDiv.firstChild;
		while (clElement != null && pb != null) {
			if (clElement.id == pb.id ) {
				footerCopy = clFooterTable.cloneNode(true);
				currentPageDiv.insertBefore(footerCopy, clElement);
				
			}
			clElement = clElement.nextSibling;
		}
	}
	
	// Clean up the view to remove any unnecessary HTML elements
	// IE5.0 will blow up if this is done immediately,
	// so delay to allow the screen to be rendered first.
	setTimeout("CleanupViewOnPrintableVersion();", 1);

}

// Create a page template to be used on all printable pages, which contains:
// tableTitle
// otherDimTable
// dataTable with just the table tags
// sourceInfoTable
function createPageTemplate(){

	var tableTitle = document.getElementById("TitleTable");
	var sourceInfoTable = document.getElementById("SourceInfoTable");
	var rowNumber = 0;

	// Get the height of the Tables that are used on each page.  
	M_titleTableHeight = tableTitle.offsetHeight;
	M_otherDimTableHeight = M_otherDimTable.offsetHeight;
	M_sourceInfoTableHeight = sourceInfoTable.offsetHeight;

	// Create a page template
	var pageTemplate = document.body.appendChild(document.createElement("Table"));
	pageTemplate.setAttribute("id", "templateTable", 0);
	   
	// Add in a copy of the Title
	pageTemplate.insertRow(rowNumber);
	pageTemplate.rows[rowNumber].insertCell(0);
	tableTitleCopy = tableTitle.cloneNode(true);
	tableTitleCopy.setAttribute("id", "templateTitle", 0);
	pageTemplate.rows[rowNumber++].cells[0].appendChild(tableTitleCopy);
	   
	// Add in a copy of the Other Dimensions
	pageTemplate.insertRow(rowNumber);
	pageTemplate.rows[rowNumber].insertCell(0);
	otherDimTableCopy = M_otherDimTable.cloneNode(true);
	otherDimTableCopy.setAttribute("id", "templateOther", 0);
	pageTemplate.rows[rowNumber++].cells[0].appendChild(otherDimTableCopy);
	   
	// Add in a copy of the frame of the Data Table
	pageTemplate.insertRow(rowNumber);
	pageTemplate.rows[rowNumber].insertCell(0);
	dataTableCopy = M_dataTable.cloneNode(false); // we only want the table itself
	dataTableCopy.setAttribute("id", "templateDataTable", 0);
	dataTableCopy.setAttribute("border", "1", 0);
	dataTableCopy.style.height = "";
	dataTableCopy.style.width = "";
	pageTemplate.rows[rowNumber++].cells[0].appendChild(dataTableCopy);
	   
	// Add in a copy of the Source Info
	pageTemplate.insertRow(rowNumber);
	pageTemplate.rows[rowNumber].insertCell(0);
	sourceInfoTableCopy = sourceInfoTable.cloneNode(true);
	dataTableCopy.setAttribute("id", "templateSourceInfoTable", 0);
	pageTemplate.rows[rowNumber].cells[0].appendChild(sourceInfoTableCopy);
	   
	return pageTemplate;
}

// This function sets the page size based on the paper size, orientation, and margins
// specified in Printable Version Options
function setPrintablePageSize(){

	var paperType = 4;
	var paperOrientation;
	var marginsArray;
	var leftMargin = 1;
	var rightMargin = 1;
	var topMargin = 1;
	var bottomMargin = 1;
	var availWidth = 0;
	var availHeight = 0;
	   
	// Get the paper type
	paperType = ObjWdsForm.sWD_PrintPaperSize.value;
	   
	// Get the margins
	marginsArray = ObjWdsForm.sWD_PrintMargins.value.split(",");
	leftMargin = marginsArray[0];
	rightMargin = marginsArray[1];
	topMargin = marginsArray[2];
	bottomMargin = marginsArray[3];
	   
	// Get the paper orientation
	paperOrientation = ObjWdsForm.sWD_PrintOrientation.value;
	
	// Portrait
	if(paperOrientation == "0"){ 
		availWidth = M_paperSizes[paperType][0] - leftMargin - rightMargin;
		availHeight = M_paperSizes[paperType][1] - topMargin - bottomMargin;
	}
	// Landscape - reverse the height and width
	else{
		availWidth = M_paperSizes[paperType][1] - leftMargin - rightMargin;
		availHeight = M_paperSizes[paperType][0] - topMargin - bottomMargin;
	}

	M_printableWidth = availWidth * M_conversionFactor * M_pixelsPerInch; 
	M_printableHeight = availHeight * M_conversionFactor * M_pixelsPerInch;

}

// This function removes all unnecessary HTML tables from the Printable Version so that 
// cutting and pasting into Word is significantly faster.  This includes the original 
// table that was used to construct the printable version, the form fields, and the 
// cover table that displays the Please Wait screen.
function CleanupViewOnPrintableVersion(){

		var pageTable = document.getElementById("pageTable");
		var cover = document.getElementById("cover");

		// Remove the original table and form fields
	var temp = pageTable.offsetParent;
		temp.removeChild(pageTable);

		// Remove the cover table
		temp = cover.offsetParent;
		temp.removeChild(cover);
}
