/* =======================================Script Library created by Kenneth Haggman November 2006.Description: When Domino generates HTML for views, it really does a pretty good job.There are 4 major things wrong with the HTML:1. There is no correct setting of column widths.2. Each cell (almost) has the 'nowrap' attribute.3. FONT-tags are used.4. The COLSPAN-attribute used for response-columsn are in the wrong place.This script fixes all of those problems.It works like this:Domino generates the HTML and sends it to the browser, where it is received by a $$ViewBody fieldin a $$ViewTemplate or simply as an embedded view.We let Domino send all the HTML but we hide it in the browser. Once loaded in the browser,we run this script that fixes the HTML, puts it in another DIV-tag and presents it. Simple.======================================= */// Globals.var bHasHeader = true;var fontSpecifics = new Array();		/* =======================================Code invoked from the 'onload'-event of the form containing the view.======================================= */function onLoader() {	if (amendDominoHtml()) {		// No errors caught when amending the HTML, show the amended HTML.		document.getElementById("viewbodyamended").style.display = "block";	}	else {		// Error caught when amending HTML, show the original Domino-generated HTML.		document.getElementById("viewbody").style.display = "block";	}}/* =======================================Calls a set of functions to fix Domino-generated view HTML.======================================= */function amendDominoHtml() {	try {		// Copy Domino-generated HTML into <div>-tag.		document.getElementById("viewbodyamended").innerHTML = document.getElementById("viewbody").innerHTML;				// Get the div-tag where the Domino-generated HTML is contained.		var divAmended = document.getElementById('viewbodyamended');				// Domino will always generate the view as a table, get the first table.		// Essentially, we could have just gotten the very first element since this will definitely be a <table>-tag.		var objMainTable = divAmended.getElementsByTagName('table')[0];		if (!objMainTable) {			// We may have a 'no documents found'. Stop further execution.			return false;		}		// Fix some attributes for the table itself.		if (!setTableAttributes(objMainTable)) {					// Something went wrong.			return false;		}		// Fix the header.		// Doing multiple things in this function.		if (!fixHeader(objMainTable)) {					return false;		}		// Insert a new <tr> as the very first <tr> in the table.		// This <tr> will have 'width' attributes in all table cells (except the last one in some cases).		// This will be the only <tr> in the whole table where the <td> tags have widths.		// By having this <tr> first in the table, all other <tr>'s in the table will abide to the widths set.		if (!insertFirstRow(objMainTable)) {			return false;		}		// Remove all 'nowrap' attributes. We really do want the cells to wrap.		if (!removeNoWrap(objMainTable)) {			return false;		}		// Build an array containing font specifics from the style sheet.		getFontSpecifics();				// This function will have the biggest impact on the presentation.		// All those <font> tags are replaced by <span> tags with classes.		if (!fixFontTags(objMainTable)) {			return false;		}				// Make sure there are no empty cells.		if (!fillEmptyCells(objMainTable)) {			return false;		}		// Get rid of certain 'ecblank' images.		if (!removeCertainBlanks(objMainTable)) {			return false;		}				// Set overline attribute on last total column.		//if (!setOverline(objMainTable)) {		//	return false;		//}				// Finally, take care of the incorrectly created response-rows.		if (!fixResponseRows(objMainTable)) {			return false;		}				// Everything is fine, return true.		return true;	}	catch (err) {		alert("Error in function: amendDominoHtml\nError: " + err.description);		return false;	}	}/* =======================================Function to manipulate attributes in the table.======================================= */function setTableAttributes(objMainTable) {	try {		if (objMainTable.getAttribute('cellpadding')) {			var atts = objMainTable.attributes;			for (var ix=0; ix<atts.length; ix++) {				if (atts[ix].nodeName.toLowerCase() == 'cellpadding') {					objMainTable.setAttribute(atts[ix].nodeName, "1");					ix = atts.length;				}			}		}		// Set an id on the table so that we can change it via CSS.		objMainTable.id = "viewmaintable";				return true;	}	catch (err) {		alert("Error in function: setTableAttributes\nError: " + err.description);		return false;	}	}/* =======================================Function to get rid of 'colspan' in the table header.Will also replace <th> tags with <td> tags.======================================= */function fixHeader(objMainTable) {	try {		// Get the very first <tr> in the table.		var objTrFirst = objMainTable.getElementsByTagName('tr')[0];				// If this is the header, we will have <th> tags.		if (objTrFirst.innerHTML.toLowerCase().indexOf('<th') == -1) {			bHasHeader = false;			return true;		}				// Set an id on the row so that we can change it via CSS.		//objTrFirst.setAttribute('class', 'viewheadrow');		objTrFirst.id = 'viewheadrow';				// Run through all the <th> tags and replace 'colspan'-ed cells with separate, single cells.		// This is needed to get the sizes correct and the actual headings in the right place.		// Why? Because Domino will generate and left-align colspan-ed cells that shouldn't be left-aligned.		var objCells = objTrFirst.getElementsByTagName('th');		for (var ix=0; ix<objCells.length; ix++) {			if (objCells[ix].getAttribute('colspan')) {				var iNumCells = parseInt(objCells[ix].getAttribute('colspan'), 10);				if (iNumCells > 1) {					// This <th> has colspan.					// Split into multiple cells instead.					for (var jx=0; jx<(iNumCells - 1); jx++) {						// Create a new <th> and insert before current.						var thNew = document.createElement('th');						thNew.innerHTML = '<img src="/icons/ecblank.gif" height=1 border=0 alt="">';						objTrFirst.insertBefore(thNew, objCells[ix]);						// Since we add cells, we must increase the outer index.						ix++;					}					// Remove the 'colspan' attribute from the current cell.					var atts = objCells[ix].attributes;					for (var kx=0; kx<atts.length; kx++) {						if (atts[kx].nodeName.toLowerCase() == 'colspan') {							objCells[ix].removeAttribute(atts[kx].nodeName);							kx = atts.length;						}					}				}			}		}					// Since we may have altered the <tr> by adding more <th>'s, it is safer to re-instantiate		// so that we really get the correct object.		objTrFirst = objMainTable.getElementsByTagName('tr')[0];				// Copy the existing <th> tags as <td> tags and append to the end of the <tr>.		var objCells = objTrFirst.getElementsByTagName('th');		for (ix=0; ix<objCells.length; ix++) {			// Copy all attributes of the <th> to the new <td>.			var tdNew = document.createElement('td');			var atts = objCells[ix].attributes;			for (var jx=0; jx<atts.length; jx++) {				if (atts[jx].nodeValue != "" && atts[jx].nodeValue != null && atts[jx].nodeValue != 'undefined') {					if (atts[jx].nodeName.toLowerCase() == 'colspan' && atts[jx].nodeValue == "1") {						// NOP					}					else if (atts[jx].nodeName.toLowerCase() == 'rowspan' && atts[jx].nodeValue == "1") {						// NOP					}					else {						// Copy this attribute.						tdNew.setAttribute(atts[jx].nodeName, atts[jx].nodeValue);					}				}			}			// Copy the inner html.			tdNew.innerHTML = objCells[ix].innerHTML;			// Append the new <td> to the <tr>.			objTrFirst.appendChild(tdNew);		}				// Now remove all the <th>'s.		objTrFirst = objMainTable.getElementsByTagName('tr')[0];		var objCells = objTrFirst.getElementsByTagName('th');		while (objCells.length > 0) {			objTrFirst.removeChild(objCells[0]);		}				// We now seem to be left with a lot of CRLF.		// This has no impact on the display.		return true;	}	catch (err) {		alert("Error in function: fixHeader\nError: " + err.description);		return false;	}	}/* =======================================Function to insert a new <tr> in the table.This <tr> will have 'width' attributes in the <td>'s.======================================= */function insertFirstRow(objMainTable) {	try {		// The stylesheet should contain certain items that we will read to get the column widths.		var widthArray = new Array();		populateWidthArray(widthArray);		// Get the very first <tr> in the table.		var objTrFirst = objMainTable.getElementsByTagName('tr')[0];				// Get all <td> tags.		var objCells = objTrFirst.getElementsByTagName('td');				// Count the cells, since we may have 'colspan' we can't simply use objCells.length.		var iCellCount = 0;		for (var ix=0; ix<objCells.length; ix++) {			var objCell = objCells[ix];			if (objCell.getAttribute('colspan') != '' && objCell.getAttribute('colspan') != null) {				iCellCount += parseInt(objCell.getAttribute('colspan'), 10);			}			else {				iCellCount++;			}		}		// Check if the DOM has the <thead> and/or the <tbody> tag.		// If they exist, they will be immediate childs of the <table>.		var iHeadTag = -1;		var iBodyTag = -1;		for (ix=0; ix<objMainTable.childNodes.length; ix++) {			if (objMainTable.childNodes[ix].nodeName.toLowerCase() == 'thead') {				iHeadTag = ix;			}			if (objMainTable.childNodes[ix].nodeName.toLowerCase() == 'tbody') {				iBodyTag = ix;			}		}				// Create a new <tr>.		var trNew = document.createElement('tr');				// Loop and create <td> nodes with correct width.		for (ix=0; ix<iCellCount; ix++) {			var cellNew = document.createElement('td');			if (ix < widthArray.length) {				// Neither createAttribute nor setAttribute seems to work properly across browsers.				// So we are using the simple version here.				cellNew.width = widthArray[ix];			}			// Each cell will have a 1-pixel high transparent gif as content.			// This will ensure that all browsers really will make the tag the correct width			// as well as ensuring that the actual height of the <tr> will be small enough to not matter.			cellNew.innerHTML = '<img src="/icons/ecblank.gif" height=1 border=0 alt="">';			trNew.appendChild(cellNew);		}				// Insert the new <tr> in the correct place.		if (iHeadTag > -1) {			//alert("Inside thead !!!");		}		else {			// We have no head-tag.			// Do not create one, simply insert the new <tr> before the first existing <tr>.			if (iBodyTag > -1) {				// If we have a <tbody>, we must insert before the first <tr> within the <tbody>.				var objBody = objMainTable.childNodes[iBodyTag];				ix = 0;				while (objBody.childNodes[ix].nodeName.toLowerCase() != 'tr') {					ix++;				}				objBody.insertBefore(trNew, objBody.childNodes[ix]);			}			else {				// No <tbody> node here.				// Find first <tr> and insert new node before it.				ix = 0;				while (objMainTable.childNodes[ix].nodeName.toLowerCase() != 'tr') {					ix++;				}				objMainTable.insertBefore(trNew,objMainTable.childNodes[ix]);			}		}				return true;	}	catch (err) {		alert("Error in function: insertFirstRow\nError: " + err.description);		return false;	}	}/* =======================================Implemented as a separate function so as to have it's own error handling.Some browsers will throw errors that we need to ignore.======================================= */function populateWidthArray(widthArray) {	// Find the stylesheet titled 'viewbodystyles'	for (var ix=0; ix<document.styleSheets.length; ix++) {		try {			// This will generate an error in some browsers if no title is present.			if (document.styleSheets[ix].title == "viewbodystyles") {				var mySheet=document.styleSheets[ix];				// Find all the css entries that we will use as width-indicators.				var myRules = mySheet.cssRules? mySheet.cssRules: mySheet.rules				for (var jx=0; jx<myRules.length; jx++) {					var thisSelector = myRules[jx].selectorText.toLowerCase();					if (thisSelector.substring(0, 8) == ".tdwidth") {						widthArray[widthArray.length] = replaceSubstring(myRules[jx].style.width, 'px', '');					}				}			}		}		catch (err) {			// Don't do anything, just continue the loop.		}		}}/* =======================================Will remove the 'nowrap' attributes from <td>'s.======================================= */function removeNoWrap(objMainTable) {	try {		// Local variable for the attribute name.		// We only want to loop once to get the proper spelling.		var strAttrName = '';				// Get all <tr>'s in the table.		var objTrs = objMainTable.getElementsByTagName('tr');				// Check each <td> in the <tr>.		for (var ix=0; ix<objTrs.length; ix++) {			var objCells = objTrs[ix].getElementsByTagName('td');			for (var jx=0; jx<objCells.length; jx++) {				if (objCells[jx].getAttribute('nowrap')) {					// Unfortunately, we can't do a simple removeAttribute('nowrap').					// Despite the fact the getAttribute seems to be case-insensitive, removeAttribute is not.					// So we create a list of all attributes (may be quite a few) and loop until we find a match in lowercase.					if (strAttrName == '') {						var atts = objCells[jx].attributes;						for (var kx=0; kx<atts.length; kx++) {							if (atts[kx].nodeName.toLowerCase() == 'nowrap') {								strAttrName = atts[kx].nodeName;								kx = atts.length;							}						}					}					objCells[jx].removeAttribute(strAttrName);				}			}		}		return true;	}	catch (err) {		alert("Error in function: removeNoWrap\nError: " + err.description);		return false;	}	}/* =======================================Will populate an array with font specifics from the style sheet.======================================= */function getFontSpecifics() {	// Find the stylesheet titled 'viewbodystyles'	for (var ix=0; ix<document.styleSheets.length; ix++) {		try {			// This will generate an error in some browsers if no title is present.			if (document.styleSheets[ix].title == "viewbodystyles") {				var mySheet=document.styleSheets[ix];				// Find all the css entries that we will use as width-indicators.				var myRules = mySheet.cssRules? mySheet.cssRules: mySheet.rules				for (var jx=0; jx<myRules.length; jx++) {					var thisSelector = myRules[jx].selectorText.toLowerCase();					if (thisSelector.substring(0, 8) == ".thstyle") {						var strFontString = thisSelector + '^';						strFontString += myRules[jx].style.fontFamily + '^';						strFontString += myRules[jx].style.fontSize + '^';						strFontString += myRules[jx].style.color;						fontSpecifics[fontSpecifics.length] = strFontString;					}					else if (thisSelector.substring(0, 8) == ".tdstyle") {						var strFontString = thisSelector + '^';						strFontString += myRules[jx].style.fontFamily + '^';						strFontString += myRules[jx].style.fontSize + '^';						strFontString += myRules[jx].style.color;						fontSpecifics[fontSpecifics.length] = strFontString;					}					else if (thisSelector.substring(0, 11) == ".tdcategory") {						var strFontString = thisSelector + '^';						strFontString += myRules[jx].style.fontFamily + '^';						strFontString += myRules[jx].style.fontSize + '^';						strFontString += myRules[jx].style.color;						fontSpecifics[fontSpecifics.length] = strFontString;					}					else if (thisSelector.substring(0, 11) == ".tdresponse") {						var strFontString = thisSelector + '^';						strFontString += myRules[jx].style.fontFamily + '^';						strFontString += myRules[jx].style.fontSize + '^';						strFontString += myRules[jx].style.color;						fontSpecifics[fontSpecifics.length] = strFontString;					}				}			}		}		catch (err) {			// Don't do anything, just continue the loop.		}		}}/* =======================================Will remove all <font> tags.Will replace them with <span> tags with inline styles for font and color.I can't get this to work across browsers using classes in the <span> tags, which is a shame.======================================= */function fixFontTags(objMainTable) {	var strSubMsg = '';		try {		// Get all <font> tags.		var allFontTags = objMainTable.getElementsByTagName('font');		while (allFontTags.length > 0) {			// We always get the first one.			// Since we are replacing the <font> node at the end of the while-block, the list will automatically adjust itself.			strSubMsg = 'get first <font> tag';			var thisFontTag = allFontTags[0];						// Get the column number for this <font> tag.			// The function will take 'colspan' into account.			strSubMsg = 'get column number';			var iCellCount = getColumnNbr(thisFontTag);						// Determine the type of column we are in.			// The 'types' are one of these:			// 1. A column in the header line.			// 2. A response column.			// 3. A category column.			// 4. A normal column (may also be a totals column).			var spanNew;			strSubMsg = 'check if header row';			if (isHeaderRow(thisFontTag)) {				strSubMsg = 'create new <span> tag for header';				spanNew = constructSpanTag(thisFontTag, 1, iCellCount);			}			else {				strSubMsg = 'check if response row';				if (isResponseRow(thisFontTag)) {					strSubMsg = 'create new <span> tag for response';					spanNew = constructSpanTag(thisFontTag, 2, iCellCount);				}				else {					strSubMsg = 'check if category row';					if (isCategoryRow(iCellCount)) {						strSubMsg = 'create new <span> tag for category';						spanNew = constructSpanTag(thisFontTag, 3, iCellCount);					}					else {						strSubMsg = 'create new <span> tag for normal row';						spanNew = constructSpanTag(thisFontTag, 4, iCellCount);					}				}			}						// We need to insert the new <span> tag in the correct place.			// Most browsers won't support the very handy 'replaceNode' method,			// so we test before attempting. The alternative 'replaceChild' is a bit more clunky but works OK.			strSubMsg = 'replacing <font> tag with <span> tag.';			document.replaceNode ? thisFontTag.replaceNode(spanNew) : thisFontTag.parentNode.replaceChild(spanNew,thisFontTag);						// Do we need to re-get the font-tags or is that automatic?			//strSubMsg = 'get all <font> tags again';			//allFontTags = objMainTable.getElementsByTagName('font');		}		return true;	}	catch (err) {		alert("Error in function: fixFontTags, " + strSubMsg + "\nError: " + err.description);		return false;	}	}/* =======================================Determines the correct column number for the <font> node.Counts <td>'s that are immediate childs of the <tr> that is an immediate child of <tbody>.Returns an integer value. 1 for column 1, 2 for column 2, etc.======================================= */function getColumnNbr(curNode) {	var strSubMsg = '';	try {		// Get the correct <tr> for this node.		var nodeTr = getCorrectTr(curNode);			// Using the current <font> node as starting point, loop upwards in the hierarchy		// until we find a <td> that is an immediate child of nodeTr.		strSubMsg = 'finding correct <td> node';		var nodeTd = curNode.parentNode;		while (nodeTd.parentNode != nodeTr) {			nodeTd = nodeTd.parentNode;		}			// Set return value to 1.		// Then loop and count the number of previous siblings.		// For each one found, add to return value.		var iReturn = 1;		var nodeSibling = getSpecificSibling(nodeTd, -1);		while ((nodeSibling != null) && (nodeSibling.nodeName.toLowerCase() == 'td')) {			// Previous columns may have 'colspan'.			var addThis = 1;			if (nodeSibling.getAttribute('colspan') != null) {				if (nodeSibling.getAttribute('colspan') != '') {					addThis = parseInt(nodeSibling.getAttribute('colspan'), 10);				}			}			iReturn += addThis;			nodeSibling = getSpecificSibling(nodeSibling, -1);		}		return iReturn;	}	catch (err) {		alert('Error in function: getColumnNbr, ' + strSubMsg + '\nError name: ' + err.name + '\nError description: ' + err.description);		return 0;	}	}/* =======================================Function to get a sibling of a nodeUsage: getSpecificSibling(node,dir)where node is the node you want to find the sibling for,and dir is the direction: 1 (next) or -1 (previous)======================================= */function getSpecificSibling(node, dir) {	try {		if (dir == -1) {			if (node.previousSibling) {				return node.previousSibling;			}			else {				return null;			}		}		else {			if (node.nextSibling) {				return node.nextSibling;			}			else {				return null;			}		}	}	catch (err) {		alert("Error in function: getSpecificSibling\nError: " + err.description);		return false;	}	} /* =======================================Determines whether the current node is part of the header-row.Returns true or false.======================================= */function isHeaderRow(curNode) {	if (!bHasHeader) {		// We have no header row in this view.		return false;	}		// Get the correct <tr> for this node.	var nodeTr = getCorrectTr(curNode);	if (nodeTr.getAttribute('id') == 'viewheadrow') {		// If this <tr> has the header-id, we are indeed in a node that is part of the header row.		return true;	}		// Node is not part of the header row.	return false;}/* =======================================Determines whether the current node is part of a response-row.Returns true or false.======================================= */function isResponseRow(curNode) {		// Get the correct <tr> for this node.	var nodeTr = getCorrectTr(curNode);	// Get the innerHTML of the <tr>. We can check that to determine is this is a response row or not.	// Response rows will have (at least) one table and a 'colspan' attribute in the <td> after the table.	var strHtml = nodeTr.innerHTML.toLowerCase()	var iLastColspan = strHtml.lastIndexOf(" colspan=");	if (iLastColspan == -1) {		// No colspan in the row, this is not a response.		return false;	}	var iLastTable = strHtml.lastIndexOf("<table");	if (iLastTable == -1) {		// No table in the row, this is not a response.		return false;	}	if (iLastColspan > iLastTable) {		// The last colspan is AFTER the table.		// This is typical for a response-row.		return true;	}	return false;}/* =======================================Determines whether the current node is part of a category-row.Returns true or false.======================================= */function isCategoryRow(colNbr) {	// Loop in the 'fontSpecifics' array.	// If we find a font with a name starting with '.tdcategory' and ending with the same number as passed in,	// we are indeed in a column that is part of a category row.	try {		for (var ix=0; ix < fontSpecifics.length; ix++) {			if (fontSpecifics[ix].substring(0, 11) == '.tdcategory') {				var iStyleColNbr = parseInt(fontSpecifics[ix].substring(11, 13));				if (iStyleColNbr == colNbr) {					// This is a category column.					return true;				}			}		}		// Didn't find a category font for this column.		return false;	}	catch (err) {		alert("Error in function: isCategoryRow\nError: " + err.description);		return false;	}	}/* =======================================Builds a <span> tag based on the fonts we have inthe 'fontSpecifics' array.Returns the <span> tag as an object.======================================= */function constructSpanTag(thisFontTag, iColumnType, iCellCount) {	try {		// Construct class name string.		var strClassNumber = '';		var strClassName = '';		switch (iColumnType) {			case 1:				// We are in the header.				strClassNumber = '0' + iCellCount.toString();				strClassName = '.thstyle' + strClassNumber.substring((strClassNumber.length - 2), strClassNumber.length);				break;			case 2:				// This is a response column.				strClassNumber = '0' + (iCellCount - 1).toString();				strClassName = '.tdresponse' + strClassNumber.substring((strClassNumber.length - 2), strClassNumber.length);				break;			case 3:				// This is the category column.				strClassNumber = '0' + iCellCount.toString();				strClassName = '.tdcategory' + strClassNumber.substring((strClassNumber.length - 2), strClassNumber.length);				break;			case 4:				// A normal column.				strClassNumber = '0' + iCellCount.toString();				strClassName = '.tdstyle' + strClassNumber.substring((strClassNumber.length - 2), strClassNumber.length);		}				// Extract the 'color' attribute from the <font> tag, if there is one.		var strColor = "";		if (thisFontTag.getAttribute('color') != '' && thisFontTag.getAttribute('color') != null && thisFontTag.getAttribute('color') != 'undefined') {			strColor = thisFontTag.getAttribute('color');		}		// Also get the value of the node.		var strNodeValue = thisFontTag.innerHTML;		// The <td> may contain other tags like <b>, <u> and <i> which means the <font> may very well		// not be an immediate child of the <td>.		// Create a new <span> node and set it's attributes and value.		var spanNew = document.createElement('span');		// Loop in the array 'fontSpecifics' until we have a match with the classname we built above.		for (var ix=0; ix < fontSpecifics.length; ix++) {			var splitTag = fontSpecifics[ix].split('^');			if (splitTag[0] == strClassName) {				spanNew.style.fontFamily = splitTag[1];				spanNew.style.fontSize = splitTag[2];				// If Domino has generated a color in the <font> tag, use it.				// It may be a result of a color column.				if (strColor != '') {					spanNew.style.color = strColor;				}				else {					spanNew.style.color = splitTag[3];				}				// Break the loop.				ix = fontSpecifics.length;			}		}		// Copy the actual value of the <font> tag to the new <span> tag.		spanNew.innerHTML = strNodeValue;		// If the new <span> tag contains a link, set the same attributes on the <a> tag.		objLinks = spanNew.getElementsByTagName('a');		if (objLinks.length > 0) {			objLinks[0].style.fontFamily = spanNew.style.fontFamily;			objLinks[0].style.fontSize = spanNew.style.fontSize;			objLinks[0].style.color = spanNew.style.color;		}				// Return the <span> tag.		return spanNew;	}	catch (err) {		alert("Error in function: constructSpanTag\nError: " + err.description);		return null;	}	}/* =======================================Domino may have inserted blank images with a width of 1 and a height of 16.These images make very little use. In fact, they make the table look a bit 'uneven' at times.So we simply remove them.======================================= */function removeCertainBlanks(objMainTable) {	try {		// Get all images.		var imgs = objMainTable.getElementsByTagName('img');		for (var ix=0; ix<imgs.length; ix++) {			if (imgs[ix].getAttribute('src').toLowerCase().indexOf('/icons/ecblank.gif') != -1) {				if (imgs[ix].getAttribute('height') == '16') {					if (imgs[ix].getAttribute('width') == '1') {						imgs[ix].parentNode.removeChild(imgs[ix]);					}				}			}		}		return true;	}	catch (err) {		alert("Error in function: removeHeights\nError: " + err.description);		return false;	}	}/* =======================================Will make sure there are no <td>'s without content.In some browsers, empty cells will make the table ugly.======================================= */function fillEmptyCells(objMainTable) {	try {		// Get all <tr>'s in the table.		var objTrs = objMainTable.getElementsByTagName('tr');				// Check each  <td> in the <tr>.		for (var ix=0; ix<objTrs.length; ix++) {			var objCells = objTrs[ix].getElementsByTagName('th');			if (objCells.length == 0) {				objCells = objTrs[ix].getElementsByTagName('td');			}			for (var jx=0; jx<objCells.length; jx++) {				if (objCells[jx].innerHTML == '') {					objCells[jx].innerHTML = '<img src="/icons/ecblank.gif" height=1 border=0 alt="">';				}			}		}		return true;	}	catch (err) {		alert("Error in function: fillEmptyCells\nError: " + err.description);		return false;	}	}/* =======================================Domino generates incorrect HTML for response-rows.The table that contains the actual response-data has no 'colspan'-attributebut the following column has.We will attempt to move the 'colspan'-attribute from one <td> to another.======================================= */function fixResponseRows(objMainTable) {	try {		// Get all <tr>'s in the table.		var objTrs = objMainTable.getElementsByTagName('tr');				// Loop amongst the <tr>'s.		for (var ix=0; ix<objTrs.length; ix++) {			if (objTrs[ix].parentNode.nodeName.toLowerCase() == 'tbody') {				// This <tr> is an immediate child of <tbody>.				if (isResponseRow(objTrs[ix])) {					// This is a response row.					// We will create a list of <td>'s that are immediate child nodes of the <tr>.					var myTdList = new Array();					// Get a list of all the <td> tags in this <tr>.					var objCells = objTrs[ix].getElementsByTagName('td');					for (var jx=0; jx<objCells.length; jx++) {						// Make sure we are only checking immediate childs of the <tr>.						if (objCells[jx].parentNode == objTrs[ix]) {							myTdList[myTdList.length] = objCells[jx];						}					}										// We now have a list of all the immediate <td>'s.					// Check our array of <td>'s.					for (var kx=0; kx<myTdList.length; kx++) {						// If the current <td> contains a <table> and the next <td> has a colspan-attribute, we have a candidate for fixing.						var lx = kx + 1;						if (lx < myTdList.length) {							var strHtml = myTdList[kx].innerHTML.toLowerCase();							if (strHtml.indexOf('<table') > -1) {								// This <td> contains a <table>.								if (myTdList[kx].getAttribute('colspan') == '' || myTdList[kx].getAttribute('colspan') == null || myTdList[kx].getAttribute('colspan') == '1') {									// This <td> does not have a 'colspan'-attribute (or one with a value of '1').									if (myTdList[lx].getAttribute('colspan') != '' && myTdList[lx].getAttribute('colspan') != null) {										// The next <td> does have a 'colspan'-attribute.										if (parseInt(myTdList[lx].getAttribute('colspan'), 10) > 1) {											// And now we would like to simply do a 'setAttribute' and a 'removeAttribute' but we can't											// because certain browsers are case-sensitive here.											// We have to loop through all attributes until we get a lowercase match and then remove the attribute.											var atts = myTdList[lx].attributes;											for (var mx=0; mx<atts.length; mx++) {												if (atts[mx].nodeName.toLowerCase() == 'colspan') {													// Copy the colspan attribute that is in the 'lx' <td> to the 'kx' <td>.													myTdList[kx].setAttribute(atts[mx].nodeName, myTdList[lx].getAttribute('colspan'));													myTdList[lx].removeAttribute(atts[mx].nodeName);													mx = atts.length;												}											}												}									}								}							}						}					}					}			}		}						return true;	}	catch (err) {		alert("Error in function: fixResponseRows\nError: " + err.description);		return false;	}	}/* =======================================Function to set the 'overline' attribute on total columns.We have to be careful that we only do this on the appropriate lines.======================================= */function setOverline(objMainTable) {	try {		// Create a local array and populate with total columns from the stylesheet.		var totalArray = new Array();		populateTotalArray(totalArray);		if (totalArray.length == 0) {			// No totals. Nothing wrong. Just exit.			return true;		}				// Get the last <tr> in the table.		var trs = objMainTable.getElementsByTagName('tr');		var lastTr = trs[trs.length - 1];				// Get the <td>'s of the <tr>.		var iColNbr = 0;		var candidateArray = new Array();		var tds = lastTr.getElementsByTagName('td');		for (var ix=0; ix<tds.length; ix++) {			// Is this <td> an immediate child of the last <tr>?			if (tds[ix].parentNode == lastTr) {				// Calculate the current column number.				var addThis = 1;				if (tds[ix].getAttribute('colspan') != null) {					addThis = parseInt(tds[ix].getAttribute('colspan'), 10);				}				iColNbr += addThis;				// Is this column a totals column or not?				var isTotal = false;				for (var jx=0; jx<totalArray.length; jx++) {					if (totalArray[jx] == iColNbr.toString()) {						isTotal = true;					}				}				if (!isTotal) {					// This is a non-totals column.					// If we have a <span> tag in there, we will definitely not want any overlines in this row.					//if (tds[ix].innerHTML.toLowerCase().indexOf('<span') > -1) {					if (tds[ix].innerText != '') {						return true;					}				}				else {					// This is a totals column.					// If we have a <span> tag in there, we store the <td> in our candidate array.					if (tds[ix].innerHTML.toLowerCase().indexOf('<span') > -1) {						candidateArray[candidateArray.length] = tds[ix];					}				}			}		}				// Loop through candidate array and set overline-attributes.		for (ix=0; ix<candidateArray.length; ix++) {			var firstSpan = candidateArray[ix].getElementsByTagName('span')[0];			if (firstSpan) {				firstSpan.style.textDecoration = 'overline';					}		}				return true;	}	catch (err) {		alert("Error in function: setOverline\nError: " + err.description);		return false;	}	}/* =======================================Implemented as a separate function so as to have it's own error handling.Some browsers will throw errors that we need to ignore.======================================= */function populateTotalArray(totalArray) {	// Find the stylesheet titled 'viewbodystyles'	for (var ix=0; ix<document.styleSheets.length; ix++) {		try {			// This will generate an error in some browsers if no title is present.			if (document.styleSheets[ix].title == "viewbodystyles") {				var mySheet=document.styleSheets[ix];				// Find all the css entries that we will use as width-indicators.				var myRules = mySheet.cssRules? mySheet.cssRules: mySheet.rules				for (var jx=0; jx<myRules.length; jx++) {					var thisSelector = myRules[jx].selectorText.toLowerCase();					if (thisSelector.substring(0, 9) == ".totalcol") {						totalArray[totalArray.length] = replaceSubstring(myRules[jx].style.width, 'px', '');					}				}			}		}		catch (err) {			// Don't do anything, just continue the loop.		}		}}/* =================================================Common function to loop upwards in the hierarchy until we are ona <tr> node that is a grand-child of the main view table.================================================= */function getCorrectTr(node) {	// Loop until we on a <tr> where the 'grandparent'  is the main view table.	var nodeTr = node;	var nodeMainTable = nodeTr.parentNode.parentNode;	while (nodeMainTable.getAttribute('id') != 'viewmaintable') {		nodeTr = nodeTr.parentNode;		nodeMainTable = nodeTr.parentNode.parentNode;	}	return nodeTr;}/* =================================================Mimics the @ReplaceSubstring function in @Formulas================================================= */function replaceSubstring(inputString, fromString, toString) {	// Goes through the inputString and replaces every occurrence of fromString with toString	var temp = inputString;	if (fromString == "") {		return inputString;	}	if (toString.indexOf(fromString) == -1) { // If the string being replaced is not a part of the replacement string (normal situation)		while (temp.indexOf(fromString) != -1) {			var toTheLeft = temp.substring(0, temp.indexOf(fromString));			var toTheRight = temp.substring(temp.indexOf(fromString)+fromString.length, temp.length);			temp = toTheLeft + toString + toTheRight;		}	} 	else { // String being replaced is part of replacement string (like "+" being replaced with "++") - prevent an infinite loop		var midStrings = new Array("~", "`", "_", "^", "#");		var midStringLen = 1;		var midString = "";		// Find a string that doesn't exist in the inputString to be used		// as an "inbetween" string		while (midString == "") {			for (var i=0; i < midStrings.length; i++) {				var tempMidString = "";				for (var j=0; j < midStringLen; j++) { 					tempMidString += midStrings[i]; 				}				if (fromString.indexOf(tempMidString) == -1) {					midString = tempMidString;					i = midStrings.length + 1;				}			}		} // Keep on going until we build an "inbetween" string that doesn't exist		// Now go through and do two replaces - first, replace the "fromString" with the "inbetween" string		while (temp.indexOf(fromString) != -1) {			var toTheLeft = temp.substring(0, temp.indexOf(fromString));			var toTheRight = temp.substring(temp.indexOf(fromString)+fromString.length, temp.length);			temp = toTheLeft + midString + toTheRight;		}		// Next, replace the "inbetween" string with the "toString"		while (temp.indexOf(midString) != -1) {			var toTheLeft = temp.substring(0, temp.indexOf(midString));			var toTheRight = temp.substring(temp.indexOf(midString)+midString.length, temp.length);			temp = toTheLeft + toString + toTheRight;		}	} // Ends the check to see if the string being replaced is part of the replacement string or not	return temp; // Send the updated string back to the user} // Ends the "replaceSubstring" function 