//<%
// this line to enable the script to be used client and server side - matched at end of this file
// copyright and  IPR of York Developement and Research Limited
// author Martin Baker 22 August 2004, and as revised since

// this contains a mix of functions some only usable at the server and some at both server and client

try {Response.Clear();} catch (e) {}; // gets rid of the // at the top line in server side use

// contains constructor ydrCcy(value,currency,decimals)
//              static          ydrUtils()
//              prototypes for String

var delimEntry=unescape("%01");  // general purpose default delimiter for structured strings

var ydrNull = new Object(); // also used in ydrDate marks a real value of null as distinct to a parameter to a function being null

// constructor of an object that has common properties across all value types 
// these objects are found in ydrGenericScripts
/* all objects must have 
	x.user(v) read or write a user visible value (note null is -- here)
	x.xml(v) read or write an xml nodeValue (and which sets / returns == ydrNull is the value is null)
	x.value(v) read or write a data value for use in code
	x.clone(vo) creates a copy of me and my value  or vo if given vo is a value of another 
	x.isNull() returns true if is null 
	
	x.qualifier
*/

var ydrValue = function(fmt)
{
	this.fmt = fmt;
	var object;
	
	this.clone= function(value) 
	{ 
		var v= new ydrValue(this.fmt);
		v.qualifier(object.qualifier);
		if (value) v.xml(value);
		return v;
	};
	this.qualifier =  function (value) 
	{ 
		if (value) 
			object.qualifier=value 
		else 
			return object.qualifier;	
	};
	this.user = function(value) { return object.user(value)};
	this.xml = function(value) { return object.xml(value)};
	this.value = function(value) { return object.value(value)};
	this.isNull = function() { return object.isNull()};
	
    switch (fmt)
    {
		case "date" :
			object = new ydrDate(); break;
		
		case "currency":
			object=new ydrCcy(); break;
		
		case "counter":
		case "int" :
		case "long" :
		case "0decimals" :
			object = new ydrInt(); 	break;
		
		case "1decimals" :
		case "2decimals" :
		case "3decimals" :
		case "4decimals" :
		case "5decimals" :
		case "anydecimals" :
			object = new ydrFloat(); 	break;
			object.qualifier = parseInt(fmt);
			break;
		
		case "reference" : // needs to change to a reference object
			object = new ydrString(); /* temp bodge for testing actions*/ this.fmt="string"; break; 
			
		case "string" :
			object = new ydrString(); break;
		
		default :
			throw new Error("ydrValue found unrecognised fmt " + fmt);
    };
}

var ydrCcy = function(value,currency,decimals)
{
	var thisThis =  this;
	this.fmt = "currency";
	this.qualifier = null;
	
	function setDecimals(decimals)
	{
		decimals=parseInt(decimals);
		if (decimals == undefined || isNaN(decimals))
			thisThis.decimals = 2
		else
			thisThis.decimals = Math.max(0,Math.min(6, parseInt(decimals)));
	};	
	
	function setCurrency(ccy)
	{
		if (ccy == undefined || ccy == "")
			thisThis.currency="GBP"
		else
			thisThis.currency=ccy.toString().substr(0,3).toUpperCase();	
	}
	
	function setValue(value)
	{
		if (value == ydrNull || isNaN(value))
			thisThis.value = ydrNull
		else
		{
			var fact = Math.pow(10,thisThis.decimals);
			
			if (typeof(value) == "number")
			{			
				try 
				{ thisThis.value =  Math.round(value * fact) / fact; }
				catch (exception)
				{ thisThis.value = ydrNull };
			}
			else
			{
				try 
				{ thisThis.value = Math.round(parseFloat(value) * fact) / fact; }
				catch (exception)
				{ thisThis.value = ydrNull };
			};
		};
	};

	this.clone=function() 	{ return new ydrCcy(thisThis.value,thisThis.currency,thisThis.decimals);	}
	
	this.minus = function(newvalue)
	{
		// takes newvalue from the current currency object
		// if new value is missing then negates the current value	!Note the difference!
		
		if (thisThis.value != ydrNull)	
		{
			if (thisThis.value == Number.NaN)
				thisThis.value = newvalue
			else if (newvalue == undefined)
				thisThis.value=-thisThis.value
			else if (newvalue.value == undefined)
				setValue(thisThis.value - parseFloat(newvalue))
			else if (newvalue.currency == thisThis.currency && newvalue.decimals == thisThis.decimals)
				thisThis.value= thisThis.value + newvalue.value
			else
				thisThis.value = ydrNull;
		};		
		return thisThis;
	}

	this.plus = function(newvalue)
	{
		// takes newvalue from the current currency object
		// if new value is missing then negates the current value	!Note the difference!
		
		if (thisThis.value != ydrNull)	
		{
			if (thisThis.value == Number.NaN)
				thisThis.value = newvalue
			else if (newvalue != undefined)
			{
				if (newvalue.value == undefined)
					setValue(thisThis.value + parseFloat(newvalue))
				else if (newvalue.currency == thisThis.currency && newvalue.decimals == thisThis.decimals)
					thisThis.value= thisThis.value + newvalue.value 
				else
					thisThis.value = ydrNull;
			};
		};		
		return thisThis;
	}
	
	this.xml = function(value)
	{
		if (value)
		{
			if (value=="--" || value == ydrNull)
				thisThis.value = ydrNull
			else if (value.indexOf(":")>0)
			{
				// legacy format
				value = value.split(":");
				setDecimals(value[2]);
				setCurrency(value[1]);
				setValue(value[0]);
			}
			else
			{
				value = value.split(" ");
				setDecimals(value[0].split(/\./)[1].length);
				setCurrency(value[1]);
				setValue(value[0]);
			};
		}
		else
		{
			if (thisThis.value == ydrNull)	
				return "--"
			else			
			{
				// note this is not the same as this.user() because here we always use a . for the decimal place and no (in the future) currency specifics	
				//return thisThis.value+":"+thisThis.currency+":"+thisThis.decimals;
				return this.user().replace(/\,/,".");
			};
		};
	}

	this.user = function(value)
	{
		if (value)
		{
			if (value == "--" || value == ydrNull)
				thisThis.value = ydrNull
			else
			{
				value = value.trim().toUpperCase().replace(/\s+/g, "")
				var posn = value.search(/[A-Z]/);
				if (posn>-1)
				{
					setCurrency(value.substr(posn));
					setValue(value.substr(0,posn).trim());
				}
				else
					setValue(value);
			};
		}
		else
		{
			var text;

			if (thisThis.value == ydrNull || isNaN(thisThis.value))
				return ("--");

			try
			{
				switch (thisThis.decimals)
				{
					case 0:
						text = parseInt(thisThis.value).toString();
						break;
					case 1:
						text=parseInt(thisThis.value * 10).toString()	;	
						if (text.length < 2)  
							text= "0.0".substring(0,3-text.length)+text
						else
							text= text.substr(0,text.length-1) + "." + text.substr(text.length-1);
						break;
					case 2:
						text=parseInt(thisThis.value * 100).toString();
						if (text.length < 3)  
							text= "0.00".substring(0,4-text.length)+text
						else
							text= text.substr(0,text.length-2) + "." + text.substr(text.length-2);
						break;
					case 3:
						text=parseInt(thisThis.value * 1000).toString();
						if (text.length < 4)  
							text= "0.000".substring(0,5-text.length)+text
						else
							text= text.substr(0,text.length-3) + "." + text.substr(text.length-3);
						break;
					case 4:	
						text=parseInt(thisThis.value * 10000).toString();
						if (text.length < 5)  
							text= "0.0000".substring(0,6-text.length)+text
						else
							text= text.substr(0,text.length-4) + "." + text.substr(text.length-4);
						break;
					default:
						text = thisThis.value
				};
			} catch  (exception)  { text = thisThis.value };
			
			return text+ " " + thisThis.currency;
		};
	}

	// constructor - start with default current
	this.decimals=2;
	this.currency="GBP";
	this.value=0;

	if (value != null)
	{
		if (value.constructor == ydrCcy)
		{
			this.decimals = value.decimals;
			this.currency = value.currency;
			this.value = value.value;
		}
		else if (typeof value == "string")
		{
			this.xml(value)
		}
		else
		{
			if (decimals) setDecimals(decimals);
			if (currency) setCurrency(currency);
			setValue(value);
		}
	};			
}

// testers
/*
		var a;
		a=new ydrDate().user();
		a=new ydrDate().user();
		a=new ydrDate().xml();
		a=new ydrDate().xml("2008-10-25T10:55:47")
		a=new ydrDate().xml("2008-10-25T10:55:47").xml()
		a=new ydrDate().xml("2008-10-25T10:55:47").xml("zero")
		a=new ydrDate().xml("2008-10-25T10:55:47").user()
		a=new ydrDate().xml("2008-10-25T10:55:47").user()
		a=new ydrDate("2008-10-25T10:55:47").user()
		a=new ydrDate(new Date()).user()
		a=new ydrDate().user("22-dec-2010 10:10").xml()
		a=new ydrDate().user("22-dec-2010 10:10").user()
		a=new ydrDate().changeBy(4,"hours").user();
*/

// new ydrDate(jsdateObject)
// new ydrDate(xmlDateString)
// new ydrDate()
// new ydrDate().user(userString)
// ydrDate.jsDate - set or let property containing null or the jscript equuivalent date

// ydrDate.xml(xmlDateString) set or get (para = null) the XML string (returns this)
// xmlDateString = ydrDate.XML()
// xmlDateString = ydrDate.XML("zero") // returns an XML dateTime string in Zero timezoneoffset but without the timezone element - useful for log file names etc and logtimestamps

// ydrDate.user(userString) setsobject to user's view of a date - fairly tolerant of formats, if months in words must be months of the current machine
	// returns this
// userString=ydrDate.user()

// ydrDate.changeBy(amount,"units") changes date by this amount hours minutes, seconds, days months years
// note that server side this returns server language months and client side this returns client side language months
// hence all transfers server to client to server must be in XML data time format
var ydrDateMonths;
var ydrDate = function(newDateJSorXML)
{
	var m_this=this;
	this.fmt = "date";
	this.qualifier = null;
	this.jsDate;  // the wrapped javascript date

	// provide a GS to convert month names to/from numeric - month names are the ones of the locale (client or server)
	function months()
	{
		if (ydrDateMonths==undefined)
		{
			// fish for the field that contains the month - sometimes second and sometimes third [index 2] field
			var monthIndex=1;  
			var localeString = new Date().toLocaleString().split(" ");
			if (isNaN(parseInt(localeString[0])) && ! isNaN(parseInt(localeString[1])) )
				monthIndex =2;
				
			ydrDateMonths = new ydrGenericScripts.gString("'","^");
			for (var i=1; i<13; i++)
				ydrDateMonths.add(i.toString(),new Date(2000,i-1,1).toLocaleString().split(" ")[monthIndex].substr(0,3));
				
				//1^Jan'2^Feb'3^Mar'4^Apr'5^May'6^Jun'7^Jul'8^Aug'9^Sep'10^Oct'11^Nov'12^Dec
		};
		return ydrDateMonths;
	}
	
	this.isNull = function() { return m_this.jsDate == ydrNull }
	this.clone=function() 	{ return new ydrDate(m_this) }
	
	// numerical form used for sort in xpath
	// compatible with number(translate("-T:Z+")) for all xml() generated dates or dateTimes provided
	// the two forms are not mixed in the same value set
	// readonly
	this.xmlN=function(dateVar, mayHaveNull)
	{
		var a=1;
		
		return this.xml(dateVar, mayHaveNull)
			.replace(/-/g,"0")
			.replace(/T/,"0")
			.replace(/:/g,"0")
			.replace(/Z/,".")
			.replace(/\+/,".");
	};
	
	this.xmlDate = function() { return this.xml().substring(0,10) };
	
	this.xml = function(dateVar, mayHaveNull)
	{	
		if ((! mayHaveNull ) && (dateVar == undefined || dateVar == "zero" || dateVar.length==0 ))
			return getXML(dateVar)

		else if (!dateVar && mayHaveNull)
			m_this.jsDate = ydrNull
			
		else
		{
			var zone=0;
			var posn=dateVar.indexOf("Z");
			
			if (posn>0)
				zone=parseInt("0"+dateVar.substr(posn+1))  // straight hours if any or zero
			else 
			{
				posn=dateVar.indexOf("+",11) // can be date not time but time offset
				if (posn>0)
					zone=parseFloat(dateVar.substr(posn+1,2))+parseFloat(dateVar.substr(posn+4,2))/60
				else
				{
					posn=dateVar.indexOf("-",11);
					if (posn>0)
						zone= - parseFloat(dateVar.substr(posn+1,2))+parseFloat(dateVar.substr(posn+4,2))/60
				};
			};
					
			if (dateVar.length==8) // plain date YYYYMMDD
				m_this.jsDate=new Date(dateVar.substr(0,4),dateVar.substr(4,2)-1,dateVar.substr(6,2))
			else if (dateVar.length==10) // XML date YYYY-MM-DD
				m_this.jsDate=new Date(dateVar.substr(0,4),dateVar.substr(5,2)-1,dateVar.substr(8,2))
			else if (dateVar.length==13) //YYYYMMDDTHHMM
				m_this.jsDate=new Date(dateVar.substr(0,4),dateVar.substr(4,2)-1,dateVar.substr(6,2),dateVar.substr(9,2), dateVar.substr(11,2))
			else if (dateVar.length==15) // YYYYMMDDTHHMMSS
				m_this.jsDate=new Date(dateVar.substr(0,4),dateVar.substr(4,2)-1,dateVar.substr(6,2),dateVar.substr(9,2), dateVar.substr(11,2), dateVar.substr(13,2))
			else if (dateVar.length==17) // YYYY-MM-DDTHH:MM
				m_this.jsDate=new Date(dateVar.substr(0,4),dateVar.substr(5,2)-1,dateVar.substr(8,2),dateVar.substr(11,2), dateVar.substr(14,2))
			else if (dateVar.length>=19) // YYYY-MM-DDTHH:MM:SS
				m_this.jsDate=new Date(dateVar.substr(0,4),dateVar.substr(5,2)-1,dateVar.substr(8,2),dateVar.substr(11,2), dateVar.substr(14,2), dateVar.substr(17,2))
			else
			{
				m_this.jsDate = ydrNull;
				return;
			};
				
			if (zone != 0 )
				m_this.jsDate = new Date(m_this.jsDate.getVarDate()+zone*60000) 
					+ m_this.jsDate.getTimezoneOffset()
			else
				m_this.jsDate = new Date(m_this.jsDate.valueOf() - m_this.jsDate.getTimezoneOffset()*60000);
				
			return m_this;
		};
	}

	// like user except that the date stored is strictly the GMT date and time regardless of local offsets
	// ie even in summer time 10/7/2010 si the xml 10/7/2010 and not 2010-07-09T23:00:00
	this.userUTC = function(userString)
	{
		if (userString == "--" || userString == ydrNull)
		{
			m_this.jsDate =ydrNull;
			return this;
		}		
		else if (userString)
		{
			this.user(userString);
			if (m_this.jsDate != ydrNull)
				m_this.jsDate = new Date(m_this.jsDate.valueOf()-m_this.jsDate.getTimezoneOffset()*60000);
			return this;
		}
		else
		{
			with (new ydrDate(this))
			{
				if (m_this.jsDate != ydrNull)
					jsDate = new Date(m_this.jsDate.valueOf()+m_this.jsDate.getTimezoneOffset()*60000);
				return user();
			}		
		};
	}

	function getXML(mode)
	{	
		if (m_this.jsDate == ydrNull)
			return null		
		else if (mode=="zero")
			dateVar =new Date(m_this.jsDate.valueOf()-m_this.jsDate.getTimezoneOffset()*60000)
		else
			dateVar=m_this.jsDate;

		with (dateVar)
		{
			return getUTCFullYear() 
				+"-"
				+ ydrGenericScripts.two(getUTCMonth()+1) 
				+"-"
				+ ydrGenericScripts.two(getUTCDate()) 
				+ "T" 
				+ ydrGenericScripts.two(getUTCHours()) 
				+":"
				+ ydrGenericScripts.two(getUTCMinutes())
				+":"
				+ ydrGenericScripts.two(getUTCSeconds());
		};
	}

	this.user = function(userString)
	{
		if (userString == undefined || userString.length ==0)
		{
			if (m_this.jsDate == ydrNull)
				return "--"
			else
				with (m_this.jsDate)
				{
					var result= ydrGenericScripts.two(getDate()) + "-"
							+ months().search(getMonth()+1)+"-"
							+ getFullYear() +" "
							+ ydrGenericScripts.two(getHours()) +":"
							+ ydrGenericScripts.two(getMinutes())+":"
							+ ydrGenericScripts.two(getSeconds());
							
					if (result.indexOf("00:00:00")>=0 || m_this.qualifier == "date")
						return result.substr(0,11)
					else
						return result;
				};
		}
		else	
		{		
			if (userString == "--" || userString == ydrNull)
				m_this.jsDate =ydrNull
				
			else if (userString.indexOf("UTC")>0)
				m_this.jsDate = new Date( userString); // assume is a javascript date;
				
			else
			{
				var entries = (userString.trim().replace(/-/g," ").replace(/:/g," ").replace(/\//g," ")+" 0 0 0 0 0 0").split(/\s+/);
				if (isNaN(parseInt(entries[1])))
				{
					entries[1]=months().assoc(entries[1]);
				};
				
				// adjust for two year dates assumed same century unless tens and units more than 10 bigger than current year
				try {
					if (entries[2].length<=2)
					{
						// unbelievable parseInt("09") gives the value 0 while parseFloat("09") gives the value 9 
						// this is because only one of them implements the octal prefix 0 not Oh
						if (entries[2] == "0")
							entries[2] = new Date().getFullYear()
						else
						{	
							entries[2] = parseFloat(entries[2]);
							entries[2] = entries[2]  
								+ ( (entries[2] > (parseFloat(new Date().getFullYear().toString().substr(2,2)) +10 ))
									? 1900 : 2000);
						};
					};
				}						
				catch (e) {};
			
				// if date only then remove any spurious hours minutes and seconds
				if (m_this.qualifier == "date")
					entries = entries.splice(0,3);
					
				try 
				{
					m_this.jsDate = new Date(entries[2],entries[1]-1,entries[0],entries[3],entries[4],entries[5])
				}
				catch (e) { throw new Error("ydrGenericScripts:ydrDate.user-set invalid date format in "+userString) };		
				if (isNaN(m_this.jsDate))
					m_this.jsDate =ydrNull; 
			};
			
			return m_this;
		};
	}

	this.changeBy = function(amount, units)
	{
		if (typeof amount == "string")
			amount = parseFloat(amount);
			
		with (m_this.jsDate)
		{
			var daylightJiggle= getTimezoneOffset(); // to enable same time of day as times pass daylight saving changes
			// noting that for year changes the date may be summer time one year and winter time the next -
			// the day and above intervals always retain the same local time of day regardless of daylight saving
			
			switch (units)
			{
				case "seconds" : 	setSeconds(getSeconds() + amount);	daylightJiggle=null; break;
				case "minutes" :		setMinutes(getMinutes() + amount); daylightJiggle=null;		break;
				case "hours" :		setHours(getHours() + amount); daylightJiggle=null;			break;
				case "days" :			setDate(getDate()+ amount); 				break;
				case "months" :		setMonth(getMonth() + amount); 			break;
				case "years" :		setFullYear(getFullYear() + amount); 		break;
				default :
					ydrGenericScripts.gAlert ("ydrGenericScripts.ydrDate.changeBy: Unknown units")
			};
			
			if (daylightJiggle != null)
				m_this.jsDate = new Date(valueOf() +( daylightJiggle - getTimezoneOffset())*60000);
		};
		return m_this;
	}

	// returns start of the calendar day, local time of client or server (note these are not necessailry the same)
	// or with ("UTC") the UTC start of day
	this.startOfDay = function(asUTC)
	{
		var newDate= new Date(86400000*Math.floor((m_this.jsDate)/86400000));//= 24*60*60*1000 = milliseconds in a day
		var offset = newDate.getTimezoneOffset();
		if (offset == 0 || asUTC)
			return new ydrDate(newDate)
		else
			return new ydrDate(newDate.valueOf()+offset*60000);
	}
	
	this.untilNow = function()
	{
		return new ydrDate(new Date().valueOf() - m_this.jsDate.valueOf()    )
	}
	
	this.duration = function()
	{
		return Math.floor(m_this.jsDate.valueOf()/1000)
			+"."
			+(m_this.jsDate.getUTCMilliseconds()+1000).toString().substr(1,3);		
	}
	
	this.value =  function(v)
	{
		if (value == ydrNull)
			m_this.jsDate = ydrNull 
		else if (v != null)
			m_this.jsDate = new Date(v)
		else
			return m_this.jsDate.valueOf;
	};

	
	// constructor ending
	if (newDateJSorXML == undefined)
		this.jsDate = new Date()
	else if (newDateJSorXML == ydrNull)
		this.jsDate =  ydrNull
	else	
		switch (newDateJSorXML.constructor )
		{
			case Number :
				this.jsDate =  new Date( newDateJSorXML);
				break; 
				
			case String : 
				if (newDateJSorXML.indexOf("UTC")>0)
					m_this.jsDate = new Date( newDateJSorXML); // assume is a javascript date
				else	
				{
					this.xml(newDateJSorXML);
					if (m_this.jsDate == ydrNull)
					{
						this.user(newDateJSorXML);
						if (m_this.jsDate == ydrNull)
							ydrGenericScripts.gAlert ("ydrGenericScripts.ydrDate.constructor: user date found when XML date expected")
					};
				}
				break;
				
			case Date : 
				this.jsDate = new Date(newDateJSorXML);	
				break;
		
			case ydrDate :
				this.jsDate = new Date(newDateJSorXML.jsDate);
				break;
			
			default : 
				ydrGenericScripts.gAlert ("ydrGenericScripts.ydrDate.constructor: unrecognised initial value " + newDateJSorXML.toString())
		};
}
var ydrDateStatic = new ydrDate(); // used for static date object as date manipulator - do not use remembered value other than in current statement

var ydrString = function(s)
{
	thisThis = this;
	this.fmt = "string";
	this.qualifier = null;
	var value;	

	this.clone = function(value)
	{
		var result = new ydrString(value)
		result.qualifier = thisThis.qualifier;
		return result;
	};
	
	this.isNull = function() { return value == null};

	this.xml = function(v)
	{
		if (v == ydrNull)
			value = null
		else if (v != null)
			value = v.trim()
		else
			return value;
	};
	
	this.user =  function(v)
	{
		if (value == "--" || value == ydrNull)
			value = null
		else if (v != null)
			value = v.trim()
		else
			return (value==null ? "--" : value);
	};
	
	this.value =  function(v)
	{
		return thisThis.xml(v);
	};
	
	this.value(s);
};

var ydrReference = function(s)
{
	thisThis = this;
	this.fmt = "string";
	this.qualifier = null;
	var value;	

	this.clone = function(value)
	{
		var result = new ydrString(value)
		result.qualifier = thisThis.qualifier;
		return result;
	};
	
	this.isNull = function() { return value == null};

	this.xml = function(v)
	{
		if (v == ydrNull)
			value = null
		else if (v != null)
			value = v.trim()
		else
			return value;
	};
	
	this.user =  function(v)
	{
		if (value == "--" || value == ydrNull)
			value = null
		else if (v != null)
			value = v.trim()
		else
			return (value==null ? "--" : value);
	};
	
	this.value =  function(v)
	{
		return thisThis.xml(v);
	};
	
	this.value(s);
};

var ydrInt= function(s)
{
	thisThis = this;
	this.fmt = "int";
	this.qualifier = null;
	var value;	

	this.clone = function(value)
	{
		var result = new ydrInt(value)
		result.qualifier = thisThis.qualifier;
		return result;
	};
	
	this.isNull = function() { return value == null};

	this.xml = function(v)
	{
		if (v == ydrNull)
			value = null
		else if (v != null)
			value = parseInt(v)
		else
			return value;
	};
	
	this.user =  function(v)
	{
		if (value == "--" || value == ydrNull)
			value = null
		else if (v != null)
			value = parseInt(v)
		else
			return (value==null ? "--" : value.toString());
	};
	
	this.value =  function(v)
	{
		if (value == ydrNull)
			value = null
		else if (v != null)
			value = parseInt(v)
		else
			return value;
	};
	
	this.value(s);
};

var ydrFloat= function(s)
{
	thisThis = this;
	this.fmt = "float";
	this.qualifier = null;
	var value;	

	this.clone = function(value)
	{
		var result = new ydrFloat(value)
		result.qualifier = thisThis.qualifier;
		return result;
	};
	
	this.isNull = function() { return value == null};

	this.xml = function(v)
	{
		if (v == ydrNull)
			value = null
		else if (v != null)
			value = parseFloat(v)
		else
			return value;
	};
	
	this.user =  function(v)
	{
		if (value == "--" || value == ydrNull)
			value = null
		else if (v != null)
			value = parseFloat(v)
		else
			return (value==null ? "--" : value.toString());
	};
	
	this.value =  function(v)
	{
		if (value == ydrNull)
			value = null
		else if (v != null)
			value = parseFloat(v)
		else
			return value;
	};
	
	this.value(s);
};

//////////

var ydrUtils = new function()
{
	var m_this=this;	
	
	// put in the sandbox any object or function that must not be called or called  safely
	var sandbox=new function()
	{
		this.Application;
		this.Session;
		this.Server;
		this.Request;
		this.Response;
		this.ActiveXObject;
		this.VBArray;
		this.window;
		this.eval = function(script)
		{
			 if (! ydrGenericScripts.isAtServer())
				return eval(script)
			else
				return null; 
		}
	}
	this.exclaimEval = function(script, istrusted) // evaluate an exclaim validation computation script
	{	
		try
		{
			if (istrusted == true || ! ydrGenericScripts.isAtServer())
				return eval(script)
			else
				with (sandbox())
				{
					return eval(script)
				};
			
		} catch (e) { ydrGenericScripts.gAlert ("ydrGenericScripts.ydrUtils.exclaimEval: illegal script\n\n"+script+"\n\n"+e.message); return script}
	};
	function now() { return new ydrDate() };
	function today() 	{ 	return new ydrDate().startOfDay() }     
};

var ydrGenericScripts = new function ()
{
	// since these can run at both client and server, there are minor differences between actoins
	// use this to tell where we are at run time
	
		// used elsewgere

	// js.sort() functions for ascending and descending order - acts on plain strings
	this.plainSortAsc =function(myLeft,myRight)
	{
		return ((myLeft == myRight) ? 0 : (myLeft > myRight) ? 1 : -1) ;			
	};
	this.plainSortDesc =function(myLeft,myRight)
	{
		return ((myLeft == myRight) ? 0 : (myLeft > myRight) ? -1 : 1) ;			
	};
	this.numericSortAsc =function(myLeft,myRight)
	{
		myLeft = parseFloat(myLeft);
		myRight =  parseFloat(myRight)
		return ((myLeft == myRight) ? 0 : (myLeft > myRight) ? 1 : -1) ;			
	};
	
	this.collectionSort = function(collection, ruleFunction)
	{
		try 
		{
			return collection.sort(ruleFunction);
		} catch (e) {};
		
		var arr = new Array();
		for (var i=0; i<collection.length; i++)
			arr[i] = collection[i];
		
		return arr.sort(ruleFunction);
	};	
	
	this.two = function(n) 
	{
		n= parseInt(n);
		if (n<0 || n>9)
			return n.toString();
		else
			return "0"+n;
	}
	
	this.notNullString = function(s)
	{
		return (s == null) ? "" : s;
	}
	
	this.XHTMLneutralise = function(content, noAlert)
	{
		function selectNodes(xpath)
		{
			if (ydrGenericScripts.isAtServer())
				return content.selectNodes(xpath)
		else	
				return ydrBrowser.xml.selectNodes(content, xpath)	
		}
		
		// remove or replace attributes in XHTML so that it becomes inert
		var items;
		
		with (items = selectNodes( "//@onclick | //@onblur | //@ondblclick | //@onfocus | //@onkeydown | //@onkeypress | //@onkeyup | //@onmousedown | //@onmousemove | //@onmouseout | //@onmouseover | //@onmouseup | //@href | //@id")	)
		{
			for (var i=0; i<length; i++)
			{
				with (items[i])
				{
					switch (nodeName)
					{
						case  "id" :
							nodeValue="zq"+i ; 
							break;
						case "onclick" :
						case "ondblclick" :
							if (! noAlert)
							{
								nodeValue = "ydrClientPane.printAlert()" ;
								break;
							};
							// = else drop though
							
						default :
							if (nodeName.substr(0,2)=="on")
								nodeValue="a=1"
							else
								nodeValue="";
					};
				};
			};
		};

		with (items = selectNodes( "//@link | //script | //head"))
		{
			for (var i=0; i<length; i++)
				items[i].parentNode.removeChild(items[i]);
		}
	}
	
	this.proper = function(text, throughout)
	{
		if (text && text.length > 0)
		{
			if (throughout == "throughout")	
			{
				var parts = text.split(/ /);
				for (var i=0; i< parts.length; i++)
					parts[i] = parts[i].charAt(0).toUpperCase()+parts[i].substr(1);
				return parts.join(" ");
			}
			else
				return text.charAt(0).toUpperCase()+text.substr(1);
		}
		else
			return text;
	}
	
	this.styleToObject = function(text)
	{
		// takes text in the form as given in a css style list and renders a javascript object that has the values as names
		var result = new Object();
		
		var entries = text.split(/;/);
		var entry;
		for (var i=0; i<entries.length; i++)
		{
			entry=entries[i].split(/:/);
			result[entry[0].trim()] = entry[1].trim();	
		};
		return result;
	}
	
	this.simpleHTMLtoPlain = function(html)
	{
		return html
			.replace(/<\/p>[\s]*<p>/g,"\n\n")
			.replace(/<br \/>/g,"\n")
			.replace(/<p>/,"")
			.replace(/<\/p>/g,"")
			.replace(/<p>/g,"\n")
			.replace(/<h[0-9r][\s\S]*?>/,"\n")
			.replace(/\/<h[0-9r]>/,"")
			.replace(/<.*?>/g,"")
			.replace(/<!\-\-[\s\S]*?-->/g,"");
	}
	
	function genericXMLdocument()
	{	
		var result;
		if (ydrGenericScripts.isAtServer())
			result = ydrServerScripts.domDocument()
			
		else
			result = ydrBrowser.xml.document() ;		
		
		return result;
	}
	
	// converts v to a value that can be saved as a manually created XML string within an xml or HTML attribute
	// or as a quote value in an xpath expression
	// eg if you want to create myvalue="someotherText" where someothertext is not yet xml en
	this.xmlEncode = function(v)
	{
		if (v == null)	
			return "";
			
		var fiddlerDoc = genericXMLdocument();
		
		if (ydrGenericScripts.isAtServer())
		{
			with (ydrServerScripts.domDocument())
			{
				loadXML("<x x='' />");
				with (documentElement.attributes.getNamedItem("x"))
				{
					nodeValue=v;
					result=xml;
					result=result.substr(3, result.length-4);
				};
			};
		}
		else
		{
			var x =ydrBrowser.xml.document("<x x=''/>").documentElement.attributes.getNamedItem("x")
			
			// warning serialiser in Chrome does not return XML for an attribute - simply the attributes value
			// whereas Micrsoft returns the xml for the attribute
			
			x.nodeValue = v;
			result = ydrBrowser.xml.xml(x.ownerDocument.documentElement).replace(/\<x x=\"|\".*/g, "");   
		}
			
		return result.replace(/'/g, "&#39;").replace(/\\/,"&#92;");	
	}
	
	this.HTMLtextEncode = function(string)
	{
		// takes a string as xml/xhtml and extracts the text from it then xpath encodes it
		if (this.isAtServer())
		{
			with (ydrServerScripts.domDocument())
			{			
				loadXML(string);
				return ydrGenericScripts.xmlEncode(text);
			}
		}
		else
		{
			var node =document.createElement("div");
			node.innerHTML = string;
			return ydrGenericScripts.xmlEncode(ydrBrowser.html.text(node));
		};		
	}	
	
	var fiddlerDoc;
	this.snippetToAttribute = function(snippet, freeThreaded)
	{
		// takes a string that is like an attribute declaration (no namespaces)
		// and returns just the attribute node created from is
		// eg this.snippetToAttribute("xxx='yyy'")
		if (this.isAtServer())
		{
			if (freeThreaded)
			{
				with (ydrServerScripts.domDocumentFreeThreaded())
				{
					loadXML("<a "+snippet +" />")
					return documentElement.attributes[0];
				};
			}
			else
			{ 
				if (fiddlerDoc == null)
					fiddlerDoc = ydrServerScripts.domDocument();
					
				fiddlerDoc.loadXML("<a "+snippet +" />")
				return fiddlerDoc.documentElement.attributes[0];
			};
		}
		else
		{
			stop();
		};
	}

	//generate a random permutation
	this.permutation = function(n)
	{
		var intermediate = new Array();
		for (var i=0; i<n; i++)
			intermediate[i] = i;
			
		var result = new Array();
		while (intermediate.length > 0)
			result.push(intermediate.splice(Math.floor(intermediate.length*Math.random()),1)[0]);
		
		return result;
	};
	
	this.XMLsnippetValue = function (value)
	{
		if (fiddlerDoc == null)
			fiddlerDoc = ydrServerScripts.domDocument();
		
		fiddlerDoc.loadXML("<value />");
		fiddlerDoc.documentElement.setAttribute("value",value);
		return fiddlerDoc.xml
	};
	
	this.isAtServer = function()
	{
		try
		{
			return (Session != undefined)
		}
		catch (e)
		{ 
			return false
		};
	}
		
	this.gAlert = function(s)
	{
		if (ydrGenericScripts.isAtServer())
			throw new Error (s)
		else
		{
			if (s.charAt(0) == "<")
				window.alert(this.simpleHTMLtoPlain(s))
			else
				window.alert(s);
		}
	}
	
	this.dictionary=function()
	//simple look alike to microsoft scripting dictionary but which is not available in firefox
	{
		var o_compareMode="text";
		
		this.value = new Object();	
		this.length=0;		
		this.compareMode = function (_compareMode) 
		{
			switch (_compareMode)
			{
				case "binary" :
					o_compareMode="binary";
					break;
				case "text" :
					o_compareMode="text";
					break;
				default:
					throw "invalid comparemode";
			};
		};
		
		this.add=function(name,value)
		{
			if (o_compareMode == "text")
				this.value[name.toLowerCase()]=value
			else
				this.value[name]=value;
			this.length=this.value.length;
		};
		
		this.item=function(name)
		{
			if (o_compareMode == "text")
				return this.value[name.toLowerCase()]
			else
				return this.value[name];
		};
	}

	this.joinNodes = function(nodes)
	{
		var result = "";
		if (this.isAtServer())
		{
			for (var i=0; i<nodes.length; i++)
				result += nodes[i].xml
			return result;
		}
		else
		{
			//TODO needs browser sensitivity
			for (var i=0; i<nodes.length; i++)
				result += nodes[i].xml
			return result;
		};
	}
	
	this.gString = function(text,sep)
	{
		var m_value;
		var m_delim=delimEntry;
		var m_delimInner;
		var m_sep;
		
		this.length=0;	

		// determines a value from a text list 
		// not yet operational
		this.maskListValue = function(text)
		{
			m_value = text.assocValidate()
		}
		
		// sets the gstring to a set of values where each key is a number binary power of the position of the item in the text
		// standard delim list  eg |date1|date2|date3|  give rise to say '1^date1'2^date2'4^date3'8^date4'

		this.setValue = function(text,sep)
		{
			// takes a plain text string and delivers a structure internally that can be 
			m_value = text.assocValidate().assocSplit();
			
			m_sep=sep;
			m_delim=text.charAt(0);
			
			if (m_sep != undefined)
			{
				var i;
				for (i=0; i<m_value.length; i++)
				{
					m_value[i]=m_value[i].split(sep);
				};		
			};		
			this.length=m_value.length;
		} 
		if (text != undefined)	
			this.setValue(text,sep);
		
		this.getValue = function()
		{
			var result = m_delim;
			
			if (m_sep == undefined)
			{
				for (i=0; i<m_value.length; i++)
				{
					result += m_value[i] + m_delim;
				};
			}
			else
			{
				for (i=0; i<m_value.length; i++)
				{
					result += m_value[i][0] + m_sep + m_value[i][1] + m_delim;
				};
			};
			return result;
		};	
		
		this.item = function(index)
		{
			return m_value[index][0];
		};
		
		this.add = function (key,data)
		{
			m_value[this.length] = new Array(key,data);
			this.length=this.length+1;
		}
		
		this.key = function(index)
		{
			return m_value[index][1];
		};
			
		this.search = function(key)
		{
			for (i=0; i<m_value.length; i++)
			{
				if (m_value[i][0]==key)
					return m_value[i][1];
			};		
		};	
		
		this.assocMulti = function(data, defaultData)
		{
			if (m_value.length >0 || data == null || data.length == 0)
			{
				if (m_delimInner == null)
					m_delimInner = m_value[0][1].charAt(0);
			
				data = m_delimInner + data + m_delimInner;
						
				for (i=0; i<m_value.length; i++)
					if (m_value[i][1].indexOf(data)>=0)
						return m_value[i][0];		
						
				if (defaultData)
					return this.assocMulti(defaultData);
			};
		}
		
		this.assoc = function(data)
		{
			for (i=0; i<m_value.length; i++)
			{
				if (m_value[i][1]==data)
					return m_value[i][0];
			};		
			//oh found nowt try case insensitive
			var data2=data.toUpperCase();
			for (i=0; i<m_value.length; i++)
			{
				if (m_value[i][1].toUpperCase()==data2)
					return m_value[i][0];
			};	
			
			//oh found nowt try case insensitive matching only string start
			for (i=0; i<m_value.length; i++)
			{
				if (m_value[i][1].substr(0,data2.length).toUpperCase()==data2)
					return m_value[i][0];
			};	
			
			//oh found nowt try caps
			for (i=0; i<m_value.length; i++)
			{
				if (capsOf(m_value[i][1])==capsOf(data))
					return m_value[i][0];
			};	
		};	
		// actually is does the first letter of each word if captilised and always the first character
		function capsOf(str)
		{
			var ch;
			var result = "";
			var list=str.split(/\s/);
			for (var i=0; i<list.length; i++)
			{
				ch=list[i].charAt(0);
				if (ch == ch.toUpperCase())
					result += ch;
			};
			if (result.length == 0)
				return str.charAt(0)
			else
				return result;
		};
		
		// creates HTML buttons with a close event on the named object for creation buttons on an HTML
		// page to be used by a client script - note buttons must be free of quotation marks unless escape encoded
		this.buttonsHTML = function(handlerObject)
		{
			var result = "";
			
			for (var i=0; i<this.length; i++)
			{
				result += ("<p class='button'><input type='button' ydr:fmt='none' value='%value%'"
					+" onclick='"+handlerObject+".close(%index%,this)' /></p>")
					.replace(/%value%/,m_value[i][1])
					.replace(/%index%/,m_value[i][0])				
			};
			
			return result;
		}		
		
		// given an XML element add a series of xhtml option nodes to it in respect of this gstring
		// if optional value is given then marks that one as selected
		// two versions - obe for psoting into XML doc and one for writing hand cranked string
		// the hand-cranked string IS NOT SAFE for html markup
		this.optionElementsString = function(optionalValue)
		{
			var result="";
			if (m_sep == undefined)
			{
				for (i=0; i<m_value.length; i++)
				{
					result += "<option value='" + m_value[i];
					if (m_value[i] == optionalValue)
						result += "' selected='selected";
					result += "'>" + m_value[i] + "</option>";
				};
			}
			else
			{
				for (i=0; i<m_value.length; i++)
				{
					result += "<option value='" + m_value[i][0];
					if (m_value[i][0] == optionalValue)
						result += "' selected='selected";
					result += "'>" + m_value[i][1] + "</option>";
				};
			};
			return result;	
		}
		
		this.optionElements = function(element, optionalValue)
		{
			// delivers an XML or an HTML document element structure for options indicated by the value of this instance of gString, clientside or serverside
			// according to the type of the element given into which the option list is to be placed
			var node;
			if (m_sep == undefined)
			{
				for (i=0; i<m_value.length; i++)
				{
					with (node = element.appendChild(element.ownerDocument.createElement("option")))
					{ 
						setAttribute("value", m_value[i] );
						if (m_value[i] == optionalValue)
							setAttribute("selected", "selected");
						
						if (ydrGenericScripts.isAtServer())
							node.text = m_value[i]
						else if (element.innerHTML == null)
							ydrBrowser.xml.text(node , m_value[i])
						else
							ydrBrowser.html.text(node , m_value[i]);
					};
				};
			}
			else
			{
				for (i=0; i<m_value.length; i++)
				{
					with (node = element.appendChild(element.ownerDocument.createElement("option")))
					{ 
						setAttribute("value", m_value[i][0] );
						if (m_value[i][0] == optionalValue)
							setAttribute("selected", "selected");
							
						if (ydrGenericScripts.isAtServer())
							node.text = m_value[i][1]
						else if (element.innerHTML == null)
							ydrBrowser.xml.text(node, m_value[i][1])
						else
							ydrBrowser.html.text(node, m_value[i][1]);
					};
				};
			};
			return result;	
		};
	};
};
	

// implements the following string prototype manipulators
	// new String()./^()  ' as per vb trim$
String.prototype.trim =function()
{	return this.replace(/^\s*|\s*$/g,"")}


Array.prototype.permute = function(permutation)
{
	if (permutation == null)
		permutation = ydrGenericScripts.permutation(this.length);
		
	var result = new Array();
	for ( var i=0; i<this.length; i++)
		result.push( this[permutation[i]]);
		
	return result;
}
				
Array.prototype.pushUnique = function(value)
{
	if (typeof value == "string")
	{
		for (var i=0; i<this.length; i++)
			if (this[i] == value)
				return i;
	}
	else
	{
		for (var i=0; i<this.length; i++)
			if (this[i].key == value.key)
			{
				this[i] = value; // while the key may be the same the rest of the object might not be
				return i;		
			};
	};
	this.push(value);
	return this.length-1; // returns the index of the item just saved
};	
				
function ydrBase0IndexCollection(object, action)
{
	// the whole reason for this is that sometimes the same object appears to be zero based server side and 1 based client side or 
	// versions of that object - we always work zero based so adjust when we get a 1 based collection
	var optionBase=1;
	var newAction;
	
	// make sure strings are really strings (not a string object) and numbers are really numbers
	
	if (typeof(action) == "object")
		action = action+"";

	try 
	{	
		newAction=parseInt(action);
		if (! isNAN(newAction))
			action=newAction
		else
			action = action + "";
	} catch (e) {};
		
	if (typeof(action) == "string")
	{
		// does not alter iterate position or at end setting
		try { optionBase = (object.item(0) != undefined) ? 0 : 1} catch (e) {};
		
		try 
		{
			if (action == "last")
				return object.item(object.length-1 + optionBase) 
			else if (action == "first")
				return object.item(optionBase)
			else
				return null;	
		} catch(e) {  throw(e) };
	}		 
	else if (typeof(action) == "number")
	{
		// does not alter iterate position or at end setting
		// always return a result as a zero based equivalent of the object even if the object has a 1 base
		// we find that some objects have different 1/0 option base depenedent on client or server and object version
		// this regularises it 
		// -1 means go to before the first entry
		// 0 is the first
		
		try { optionBase = (object.item(0) != undefined) ? 0 : 1} catch (e) {};
		
		if (object.length == 0)
			return null;
			
		try 
		{
			return object.item(action+optionBase)
		} catch (e) {return null};		
	}
	
	else
		throw("ydrEnumeratorCollection: invalid action " + action + typeof(action));
}

function ydrEnumeratorCollectionCreate(object)
{
		var result = new Object();
		result.object=object
		result.index=-1;
		try {result.atEnd = (object.length  == 0)} catch (e) {result.atEnd = false};
		return result;
};

function ydrEnumeratorCollection(action)
{
	// the whole reason for this is that sometimes the same object appears to be zero based server side and 1 based client side or 
	// versions of that object - we always work zero based so adjust when we get a 1 based collection
	var optionBase=1;
	var newAction;
	
	// make sure strings are really strings (not a string object) and numbers are really numbers
	
	
	//try
	//{
		if (typeof(action) == "object")
		{
			
			action = action+"";
		//throw ("fxxxxound object "+action +typeof(action));
		};
	//}
	
	try 
	{	
		newAction=parseInt(action);
		if (! isNAN(newAction))
			action=newAction
		else
			action = action + "";
	} catch (e) {};
		
		
	if (typeof(action) == "string")
	{
		// does not alter iterate position or at end setting
		try { optionBase = (this.object.item(0) != undefined) ? 0 : 1} catch (e) {};
		
		try 
		{
			if (action == "last")
				return this.object.item(this.object.length-1 + optionBase) 
			else if (action == "first")
				return this.object.item(optionBase)
			else
				return null;	
		} catch(e) {  throw(e) };
	}		 
	else if (typeof(action) == "number")
	{
		// does not alter iterate position or at end setting
		// always return a result as a zero based equivalent of the object even if the object has a 1 base
		// we find that some objects have different 1/0 option base depenedent on client or server and object version
		// this regularises it 
		// -1 means go to before the first entry
		// 0 is the first
		
		try { optionBase = (this.object.item(0) != undefined) ? 0 : 1} catch (e) {};
		
		if (this.object.length == 0)
			return null;
			
		try 
		{
			return this.object.item(action+optionBase)
		} catch (e) {return null};		
	}
	else if (action == undefined)
	{
		// returns the next one if any		
		this.index++;				

		// set at end for the next call
		try {this.atEnd = (this.object.item(this.index+1) == undefined )} catch (e) {this.atEnd =true}
		
		try 
		{
			return this.object.item(this.index);
		}
		catch (e)
		{
			if (this.index == 0)
				// just in case was a 1 based collection
				return this.ydrEnumeratorCollection();
			else
			{
				this.atEnd = true;
				return null;
			}
		};		
	}		
	else
		throw("ydrEnumeratorCollection: invalid action " + action + typeof(action));
}
Object.prototype.ydrEnumeratorCollection = ydrEnumeratorCollection;	

///////////


///////////

	

function tagYDRA(script)
{
	
	/* finds multiple <!--YDRA ..>..</YDRA> tag pairs and replaces them with permit or denied tags if the user
		has the required right being a single keyword as the first entry within the <YDRA.. tag>
		and which must appear in  a ' delimited string
		if not present then replaces with the deniedopen and deniedClose tags
		
		xxxOpen replaces the <YDRA codeword and leaves the remainder of the tag
		xxxClose replaces the entire </YDRA> tag
		
		NOTE ADDITIONAL EMBEDDED SPACES	are not supported
		NOTE all tags MUST BE UPPERCASE
		
		ALSO finds any non comment <YDRSEARCH tags and removes them
	*/
	
	if (ydrGenericScripts.isAtServer())
	{
		var rights = ydrServerScripts.sessionGet("rights")				// 'one'two'three'  the rights given to this user, one of which must be present to 
														// permit the reference
		
		var posn;										// start of open tag
		var posnEnd;									// start of right in open tag
		var posnStart;									// start of the right in open tag	
		var requiredRight;								// the required right
		var posnCloseTag;								// start of the close tag
		var posnCloseTagEnd;                       // end of the close tag
	//deal with YDRA
		do 
		{  
			posn=script.search(/<!-*\s*YDRA /i)
			if (posn < 0)
				break;
			
			posnStart = script.indexOf(" ",posn+6)+1;
			
			while (script.charAt(posnStart) == " ")
			  posnStart++ ;
		
			posnEnd = script.indexOf(" ", posnStart);
			if (posnEnd < 0)
				break;
			
			posnCloseTag = script.search(/<!-*\s*\/YDRA\s*-*>/i, posnEnd);
			if (posnCloseTag <=0)
				break;
			posnCloseTagEnd=script.indexOf(">",posnCloseTag);
			
			requiredRight = "'"+script.substring(posnStart,posnEnd).toUpperCase()+"'";
			
			globalContext.add("RequiredRight", requiredRight.substring(1,requiredRight.length-1));
			
			if (Session("rights").indexOf(requiredRight)>=0)
				script=script.substring(0,posn)
					+tagYDRGLOBAL(
						 Session("permitOpen").replace("-->",">")	
						+ script.substring(posnEnd, posnCloseTag) 
						+  Session("permitClose")
					)
					+ script.substring(posnCloseTagEnd+1);
			else
				script=script.substring(0,posn)
					+tagYDRGLOBAL(
						Session("denyOpen").replace("-->",">")	
						+ script.substring(posnEnd, posnCloseTag)
						+ Session("denyClose")			
					)
					+ script.substring(posnCloseTagEnd+1);
			globalContext.Remove("RequiredRight");
					
		} while (true);

	// also deal with YDRERROR and YDRUSER and YDRURLBASE
		if (Session("user") == undefined)
			return script
				.replace(/<!-*\s*YDRERROR\s*-*>/ig, "<p>")
				.replace(/<!-*\s*\/YDRERROR\s*-*>/ig, "</p>");
		else	
			return tagYDRGLOBAL(script
				.replace(/<!-*\s*YDRERROR\s*-*>/ig, Session("errorOpen"))
				.replace(/<!-*\s*\/YDRERROR\s*-*>/ig, Session("errorClose"))
				.replace(/<!-*\s*YDRUSER\s*nosearchpage\s*-*>/ig, Session("userDisplay").replace(/<YDRSEARCHPAGE>/, ""))
				.replace(/<!-*\s*YDRUSER\s*-*>/ig, Session("userDisplay").replace(/<YDRSEARCHPAGE>/, Session("searchPage")))
				
				.replace(/<!-*\s*YDRURLBASE\s*-*>/ig, Session("clientUrlBase"))
				);
	}	
}

function tagYDRGLOBAL(script)
{
	var globalSearch=new RegExp("<!-*\s*YDRGLOBAL ","i");
	while (true)
	{
		var globalName;		
		var globalIndex =script.search(globalSearch);
		
		if (globalIndex<0)
			break;
		
		globalIndexEnd = script.indexOf(">",globalSearch.lastIndex+1);
		if (globalIndexEnd < 0)
			break;
		
		globalName = script.substring(globalSearch.lastIndex, globalIndexEnd).trim();
		
		if (globalName.substring(globalName.length-3) == "--")
			globalName=globalName.substring(0,globalName.length-2).trim();
		
		// see also tagGet
		
		script = script.substring(0,globalIndex-1)
			+getGlobal(globalName)
			+script.substring(globalIndexEnd+1);
	};

	return script;
}


var tagOffset=0;
function tagGet(source,tagName, namesAsSessionVariables)
// gets the first HTMLtag starting from tagOffset that has the name tagname and returns a dictionary of
// the results and updates tagOffset - always set tagoffset to 0 on first call

// if namesAsSessionVariables is present then any value for an entry in namesAsSessionVariables that
// is not a quoted string will be treated as a lookup to Session if any entry can be found therein

{
	var result=new ydrGenericScripts.dictionary();
	result.compareMode("text");
	
	var reg = new RegExp("<!-*\\s*"+tagName+" ","i");
	var posn=source.substring(tagOffset).search(reg);
	
	if (posn >= 0)
	{
		posn=posn+tagOffset;
		var posnEnd= source.indexOf(">",posn);

		if (posnEnd<1)
			tagOffset=0;
		else
		{
			var details=source.substring(reg.lastIndex+tagOffset,posnEnd)
				.replace(/\n/g," ")
				.replace(/\t/g," ")
				.replace(/\s+/g," ")		//naughty since this replaces multiple spaces inside quoted strings!
				.replace(/-->/,">")
				;
			tagOffset=posnEnd+1;
			// we now expect a succession of key = value or key = '..' or key = "value" 
			posn =-1;
			var finished=false;
			
			var key;
			var value;
			var posnNext;
			var posnNextSPace;
			
			try {
				while (! finished)
				{
					posnNext = details.indexOf("=",posn+1);
					if (posnNext>0)
						{
							details=details.substring(0,posn)+details.substring(posn).replace(/ *= */,"=")
							posnNext = details.indexOf("=",posn+1);
						};
					
					posnNextSpace = details.indexOf(" ",posn+1);
					
					if (posnNextSpace >= 0 && ( posnNextSpace < posnNext || posnNext <0))
					{
						// name only without value - treat as boolean true
						key = details.substring(posn+1,posnNextSpace);
						try {result.add(key,"true");} catch(e){};
						
						posn=posnNextSpace;
					}
					
					else if (posnNext < 0)
						finished = true;
					
					else
					{
						key = details.substring(posn+1,posnNext);

						posn= posnNext+1;							
						if (details.charAt(posn) == "'" )
						{
							posnNext = details.indexOf("'",posn+1);
							
							if (posnNext < 0)
								finished =  true;
							else
								try {result.add(key, details.substring(posn+1, posnNext));} catch(e){}; 
							
							posn = posnNext+1;
								
						}
						else if (details.charAt(posn) == "\"")
						{
							posnNext = details.indexOf("\"",posn + 1);
							
							if (posnNext < 0 )
								finished =  true;
							else
							
							try {result.add(key, details.substring(posn+1, posnNext)); } catch (e){}; 
								
							posn = posnNext+1;
						}
						else
						{	
							// this is a plain identifier
							posnNext=details.indexOf(" ",posn+1);
							if (posnNext < 0)
							{
								result.add(key, details.substring(posn));
								finished = true;
							}
							else
							{
								value = details.substring(posn, posnNext);
								
								// this allows reference to application!variablename and session!variableName	
								
								if (namesAsSessionVariables != undefined)
								try
								{
									// see also <YDRGLOBAL>
									if (namesAsSessionVariables.search(new RegExp(key,"i"))>=0 || namesAsSessionVariables == "*")
										if (value.substring(0,8) == "Session!")
											value=Session(value.substring(8));
										else if (value.substring(0,12) == "Application!")
											value = Application(value.substring(12));
										else if (value.indexOf("!")>=0)
											value = "Unrecognised reference: check capitalisation, Application!.. or Session!.. expected"+value;
										else
											value = "Unrecognised reference: "+value;
								}
								catch (exception) {value="Unrecognised object: check container name and spelling: "+value+ ": "+exception.description};
								
								try {result.add(key, value);} catch (e) {}; // ignore duplicates
							}
							posn = posnNext;
						};
					};
				};
			} catch (e) {};		
		};
	};
	
	if ((tagName == "YDREXCLAIMFIELDDEF") && ydrGenericScripts.isAtServer())
	{
		// in this special case we keep a permanent record of the last one of these retrieved
		// so that if a web page returns having provided a value we can refer again to the
		// input field's properties
		
		var objectname = result.item("OBJECTNAME");
		
		if (Session("fields")== undefined)
			Session("fields") = new ActiveXObject("Scripting.Dictionary");

		if (Session("fields").Exists(objectname))
			Session("fields").Remove(objectname);
			
		Session("fields").Add(objectname, result.value)
	};
	
	return result;
}

function tagYDRexclaimFieldDef(tag, labelStyles, inputStyles)
{
	// renders into HTML a button using the parameters exactly as defined for a YDREXCLAIMFIELDDEF
	// result is rendered into two columns, the prompt column and the value column 
	// all values are text/user side not data side eg "0" not 0, "ftString" not 10
	
	//EXCEPTION for associative fields the Assoc and AssocIndex properties are inoperative as we have access
	// to the database to look them up, use instead AssocString.  If used Assoc looks up the named property in Session
	
	// we use Session  == undefined to confirm if we are client side
	
	tagOffset=0;
	var tagDetails;
	if (ydrGenericScripts.isAtServer())
		tagDetails = tagGet(tag,"YDREXCLAIMFIELDDEF","'session!default'session!assoc'")
	else
		tagDetails = tagGet(tag,"YDREXCLAIMFIELDDEF","''");
	
	var prompt=tagDetails.item("prompt"); 
		if (prompt == undefined) prompt = "&#160;";
		
	var id=tagDetails.item("objectname");  
		if (id == undefined) id="unknown";
	
	var lengthData= tagDetails.item("lengthData"); 
		if (lengthData == undefined ) lengthData = "0";
	
	var defaultValue=tagDetails.item("default");
		if (defaultValue == undefined) defaultValue="";
	
	var promptPosition=tagDetails.item("promptPosition");
		if  (promptPosition == undefined) promptPosition = "Left";

	var fmt=tagDetails.item("fmt");
		if (fmt == undefined) fmt="ftstring"; else fmt=fmt.toLowerCase();

	prompt = "\n<label "+labelStyles+ " for='"+id+"'>"+prompt+"</label>";
	
	var resultProfile =" id='"+id+"' name='"+id+"' "
		+ inputStyles
		+ ( lengthData == "0" ? "" : " size='"+lengthData+"'");
	
	var errorStage="Format";
	try {
		switch (fmt)
		{
			case "ftstring":
				result = "\n<input"
					+ resultProfile
					+ ( defaultValue == undefined ? "" : " value=\"" + defaultValue.replace(/\"/g,"&quot;") +"\"")
					+ " onblur = 'return ydrValidate(document.getElementById(\""+id     +"\"), \""     +fmt+"\",\"\");'"
					+ " type='text' />";
				break;
			
			case "ft0decimals":
				result = "\n<input"
					+ resultProfile
					+ ( defaultValue == undefined ? "" : " value=\"" + defaultValue +"\"")
					+ " onblur = 'return ydrValidate(document.getElementById(\""+id+"\"), \""+fmt+"\",\"\");'"
					+ " type='text' />";
				break;
				
			case "ftassociative":				
				var assoc=tagDetails.item("Assoc").assocSplit();
					
				var mask=tagDetails.item("Mask");  
					if (mask == undefined) mask = "false";
				
				if (mask == "true")
				{
					// need a collection of checkboxes
					errorStage="Assoc:mask:true" +defaultValue;
					
					result="";	
					if (defaultValue == undefined)
						defaultValue=0;
					else
					{
						errorStage="Assoc:mask:true aa1 "+defaultValue;
						defaultValue=parseInt(defaultValue);
						
						if (isNaN(defaultValue))
							defaultValue = 0; 
					};
							
					if (assoc.length>3)
						result += "\n<br /><button type='button' class='button' onclick='ydrClientScriptsExclaim.setProperty(\""+id+"\",\"checked\", true);'>All</button>"
							+ " <button type='button' class='button' onclick='ydrClientScriptsExclaim.setProperty(\""+id+"\",\"checked\", false);'>None</button><br />";
							
					if (assoc.length<2)
						disabled = "disabled='disabled' ";
					else
						disabled="";
										
					thisBit=1;
					var value;
					for (var i=0; i<assoc.length; i++)
					{
						entry=assoc[i];
						errorStage="Assoc:mask:true aa2 "+entry.length;
						
						if (entry.length >0)
						{
							posn=entry.indexOf("^");
							var errorStage="Assoc:mask:true b";
							if (posn>0)
							{
								value= parseInt(entry.substring(0,posn));
								checked = (( value & defaultValue) == value
									? " checked='checked' " : "");
								entry=entry.substring(posn+1);
							}
							else
							{	
								value = thisBit;
								checked = ((thisBit & defaultValue) > 0
									? " checked='checked'" : "");							
							};
							
							var errorStage="Assoc:mask:true c";
							result += "\n<br /><input"
								+ resultProfile
								+ " type='checkbox'"
								+ " value='"+value+"'"
								+ checked
								+ disabled 
								+" />"
								+ entry.replace(/\"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;")
								;
							
							thisBit <<= 1;
						};
						
					};	
				}
				else
				{
					// need a SELECT combo list
					var errorStage="Assoc:mask:false";
					result ="\n<select"
						+resultProfile
						+">";
						
					for (var i=0; i<assoc.length; i++)
					{
						entry=assoc[i];
					
						if (entry.length >0)
						{
							posn=entry.indexOf("^");
						
							if (posn > 0)
								result += "\n<option value='" 
									+ Server.HTMLEncode(entry.substring(0,posn)) + "'"
									+ (entry.substring(0,posn) == defaultValue ? " selected='selected'" : "")
									+"/>"
									+Server.HTMLEncode(entry.substring(posn+1))
									;
							else
							{
								entry=Server.HTMLEncode(entry);
								result += "\n<option value='"
									+entry +"'"
									+ (entry == defaultValue ? " selected='selected'" : "")
									+"/>"
									+entry
									;
							}
						}
					};	
										
					result += "\n</select>"
				};
			
				break;
				
			default:
				result = "Unrecognised FMT in YDRexclaimFieldDef: "+errorStage+" " +tagDetails.item("fmt");	
		}	
	}
	catch (exception)
	{
		result = "Syntax error in YDRexclaimFieldDef: " +errorStage + " " + exception.description;
	};	
	
	if (prompt.length > 0) 
		switch (promptPosition.toLowerCase())
		{
			case "top":
				return "\n<td colspan = 2"+labelStyles+">"
					+prompt
					+ "<br />"
					+ (inputStyles.length >0 ? "<span " +inputStyles+">" : "")
					+ result
					+ (inputStyles.length >0 ? "</span>" : "")
					+ "</td>";	
			
			case "bottom":
				return "\n<td colspan = 2"+labelStyles+">"
					+ (inputStyles.length >0 ? "<span " +inputStyles+">" : "")
					+ result
					+ (inputStyles.length >0 ? "</span>" : "")
					+ "<br />"
					+ prompt
					+ "</td>";	
			
			case "right":
				return "\n<td "+inputStyles+">"+result+"</td><td "+labelStyles+">"+prompt+"</td>";
			
			case "left":
			default:
				return "\n<td "+labelStyles+">"+prompt+"</td><td "+inputStyles+">"+result+"</td>";
		
		}
	else
		return "\n<td colspan = 2 "+ labelStyles+">"+result+"</td>";
}
function ydrElementAssocStr(fieldName)
{
	try
	{
		return Session("fields")(fieldName).assoc.assocSplit();
	}
	catch (e)
	{ return "'&lt;"+fieldName+" does not exist or is not of type ydrFieldAssociative&gt;'";};
}
function ydrElementAssocNum2String(fieldName, givenValue)
{
	// fieldName is the name of a ydrFieldAssociative field that is already held 
	// in Session("fields")
	// long being the bits set
	// makes a ' delimited assoc string without values having just the named entries
		
	var assoc = ydrElementAssocStr(fieldName); 
	
	var value;
	var result="'";
	var entry;				
	var thisBit=1;

	for (var i=0; i<assoc.length; i++)
	{
		entry=assoc[i];
		
		
		if (entry !=null && entry.length >0)
		{
			posn=entry.indexOf("^");
			var errorStage="Assoc:mask:true b";
			if (posn>0)
			{
				value= parseInt(entry.substring(0,posn));
				if (( value & givenValue) == value)
					result += entry.substring(posn+1)+"'";
			}
			else
			{	
				value = thisBit;
				
				//result += "BB"+value+"cc"+givenValue+"dd"+((thisBit & givenValue)+"ee"+assoc.length);
				if ((thisBit & givenValue) > 0)
					result += entry +"'";							
			};
			
			thisBit <<= 1;
		};
		
	};
	
	return result;	
}

function ydrElement(object, index)
{
	if (object.length == undefined)
		if (index == 0)
			return object;
		else
			return undefined;
	else
		return object(index);
}

function ydrElementCount(object)
{
	if (object == undefined)
		return 0;
	else if (object.length == undefined)
		return 1;
	else
		return object.length;
}

function ydrElementAssocCondense(elements)
{
	// element values are numeric binary bits - merge them
	if (elements == undefined)
		return 0;
	else if (elements.count == undefined)
		return parseInt(elements);
	else
	{
		var result=0;

		for (var i=1; i<=elements.count; i++)
		{
			result =  result | elements(i);
		}
		return result;
	};
}

// standard methods
function String_assocSplit()
{
	if (this == undefined || this.length<1)
	{
		Response.Write(log("error","ydrGenericScripts.assocSplit",1,"<br />ydrGenericScripts.assocSplit: Illegal assocstr2 - void"+ Server.HTMLEncode(escape(this.substring(0,500)))));

		return undefined;
	}
	else 
	{
		var delim=this.charAt(0);
		var temp = this;
		// look for application or setting referenced strings
		if (delim == "A" || delim == "S")
		{
			temp = getGlobal(this);
			delim = temp.charAt(0);	
		};
		
		if ((delim < " " ) && (delim == temp.substring(temp.length-1)))
			return temp.substring(1,temp.length-1).split(temp.charAt(0));

		else if (delim == "&" || delim == "'" || delim == '"')
		{
			// these are text based and any crlftab stuff is irrelevant
			ass0cStr=temp.replace(/[\f\n\r\t\v]/g, "");
			
			if ((delim == temp.substring(temp.length-1)))
			{
				// this two part code here is necesasry because some browsers have bugs that dont do split properly 
					//on an empty string that is created as a substr of another
				if (temp.length >1)
					return temp.substring(1,temp.length-1).split(temp.charAt(0));
				else
					return new Array();
			}
		}
		
		// So elsewise it is bad	

		try {
			log("error","ydrGenericScripts.js",1
				,"<br />ydrGenericScripts.assocSplit: Illegal assocstr - structure"
				+ "<br /><br />"+Server.HTMLEncode(escape(delim))
				+ "&#160;" +Server.HTMLEncode(escape(temp.substring(temp.length-1) ))
				+ "<br /><br />"
				+"<br />"+ Server.HTMLEncode(escape(temp.substring(0,500)))
				+"<br /><br />"+ Server.HTMLEncode(escape(temp.substring(temp.length-500)))
			);
		} catch (exception) {};
		
		Response.Write(errorPage("<h2>Sorry: There has been an internal error</h2><p>The internal error message is: <br /><br /><I>ydrGenericScripts.assocSplit: Illegal data content: assocstr - structure</I>"
			+"<br /><br />This problem has been reported to the administrator in the log"));
	
		return null;
		
		//==ydrServerScripts.responseEnd();
	};	
};	
String.prototype.assocSplit = String_assocSplit;

function String_assocValidate(caller, delim)
{
	// validates the string and returns it if ok else throws an error
	if (this==undefined || this.length<1)
		throw new Error(1, "AssocValidate failure for "+caller+"<br /><br />Invalid string length <1");
	else if (this.charAt(0) == "<")
		throw new Error (4, "AssocValidate failure for "+caller
			+"<br /><br />Error found when creating AssocString .. See Below"
			+"<br /><br />"+this);
	else if (this.charAt(0) != this.charAt(this.length - 1))
		throw new Error(2, "AssocValidate failure for "+caller+"<br /><br />Opening delim is not equal to closing delim"
			+"<br /><br />Opener = "+Server.HTMLEncode(escape(this.charAt(0)))
			+"<br />Closer = "+Server.HTMLEncode(escape(this.charAt(this.length-1)))
			+"<br />Length = "+ this.length);
	else if (delim != undefined)
	{
		if (this.charAt(0) != delim)
			throw new Error(3, "AssocValidate failure for "+caller+"<br /><br />Opening delim is not equal to closing delim"
				+"<br /><br />Opener = "+Server.HTMLEncode(escape(this.charAt(0)))
				+"<br />Expected = "+Server.HTMLEncode(escape(delim))
				+"<br />Length = "+ this.length);
	};
	
	return this;
};

String.prototype.assocValidate = String_assocValidate;


// used on a server.asp page to retrieve in text form the of the selected content of the XML
// if you want the whole XML to a client then use yfRemoteXMLfetch (same parameters)
// if you want the whole XML on the server just use MSXML direct

// can be stuff to be referenced as <YDRGLOBAL Context!dictionaryname>	
// note that it is required that all references to this object are prevented from working client side
// since the scripting dictionary is not an available object outside IE
var globalContext;
if (ydrGenericScripts.isAtServer())
{
	globalContext = new ActiveXObject("Scripting.Dictionary"); 
};

// %>

