/*~********************************************************************

File:	SSV2XML.js, v1.1

Script to convert TSV, CSV, or similar string separated values into XML

Charles Macfarlane Harrison, jj@javajive.macfh.co.uk, December 2009

***********************************************************************

Charles Harrison's Work is licensed under a Creative Commons Licence -
Attribution-Non-Commercial-No Derivative Works 2.0 UK: England & Wales
http://creativecommons.org/licenses/by-nc-nd/2.0/uk/

For further details, please see:
http://www.macfh.co.uk/JavaJive/JavaJive.html

***********************************************************************

This script expects to be run by Windows Scripting Host, CScript.exe
This may not be installed by default on a Windows machine.  To install
WSH (you will likely need administrator rights), see ...
http://www.microsoft.com/downloads/details.aspx?FamilyId=C717D943-7E4B-4622-86EB-95A22B832CAA

For other OSs or interpreters, search for comments which begin ...
Non WSH use:
... currently there are 13, and make appropriate alterations.

(For best layout Tabs expected every 4 columns)


***********************************************************************

Updates:
13/06/2010	Safer Array for() loops if the Array.prototype is altered.

**********************************************************************/

//	Non WSH use: delete or change appropriately these 6 lines
//	Create normal WSH support objects for the wider purpose of the program
//	var	wshNetwork	= new ActiveXObject( "WScript.Network" );
	var	wshShell	= new ActiveXObject( "WScript.Shell" );
	var	wshFileSys	= new ActiveXObject( "Scripting.FileSystemObject" );
	var interpreter	= WScript.FullName;			//	The name of the script interpreter
	var	fullScript	= WScript.ScriptFullName;	//	The full path name of the script
	var thisScript	= WScript.ScriptName;		//	The name of the script

//	Script Data

	var	xmlHeader	= "<?xml version=\"1.0\" encoding=\"iso-8859-1\"?>";
	var	separator	= "\\t";
	var	comment		= "Created by: SSV2XML.js, " + Date();
	var	logExt		= ".log";
	var	xmlExt		= ".xml";
	var	CComBegRE	= new RegExp( "/\\*" );
	var	CComEndRE	= new RegExp( "\\*/" );
	var	MComBeg		= "<!--";
	var	MComEnd		= "-->";
	var	MComBegRE	= new RegExp( MComBeg );
	var	MComEndRE	= new RegExp( MComEnd );
	var	fileTag		= null;
	var	rowTag		= null;
	var	attributes	= null;
	var	exclusions	= null;
	var	comments	= null;
	var	commentsRE	= null;
	var	srcFileName	= null;
	var	dstFileName	= null;
	var	logFileName	= null;

//	Global defaults
	var	debug		= false;
	var	overwrite	= false;

//	File data
	var	forRead		= 1;
	var	forWrite	= 2;
	var	forAppend	= 8;

//	==========================================================================
//	Start of processing
//	==========================================================================

//	Process command-line
	// Initialise the object heirarchy
	initObjects();

	var	fTagPar		= new KeyStrPar( "F", "-F:<s>\tXML tag to contain whole file (required)", true );
	var	rTagPar		= new KeyStrPar( "R", "-R:<s>\tXML tag to contain each row (required)", true );
	var	attPar		= new KeyStrPar( "A", "-A:<l>\tCS list of columns to make attributes of each row tag (optional)" );
	var	sepPar		= new KeyStrPar( "S", "-S:<s>\tCharacter or string to separate data (default <TAB>)" );
	var	excPar		= new KeyStrPar( "X", "-X:<l>\tComma-separated list of column numbers to be excluded (optional)" );
	var	comsPar		= new KeyStrPar( "C", "-C:<c>\tRecognise source comments (optional <char>, C, or RE)" );
	var	comPar		= new KeyStrPar( "M", "-M:<s>\tInclude comment (-=none, default 'Created by: SSV2XML.js, <Date>')" );

	var	srcPar		= new FileSpecPar( "<src>\tName of well formed string separated source file (required)", true );
	var	dstPar		= new FileSpecPar( "<dest>\tDestination XML file (default <src filename>" + xmlExt + ")" );

	var	debPar		= new KeyTogPar( "D", "-D\tDebug (creates log file with additional output)", false, debug );
	var	ovrPar		= new KeyTogPar( "O", "-O\tOverwrite existing output file without warning (default off)", false, overwrite );

	var	hlpPar		= new KeyPar	( "{0,2}(\\?|H|Help)?", "?\tShow Help", null, null, null, KeyPar.prototype.tags + "?" );

	var	cmdLine		= new CmdLine(
									[ fTagPar, rTagPar, attPar, sepPar, excPar, comsPar, comPar, debPar, ovrPar, hlpPar, srcPar, dstPar ],
									true,
									null,
									"SSV2XML.js",
									"Converts TSV, CSV, or similar string separated data into XML\n"
										+ "(expects there to be a header row and no separators in quoted strings)\n",
									"Syntax:\n"
										+ "\t<interpreter> SSV2XML.js /f:<tag> /r:<tag> [options] <src> [<dest>]\n",
									"Example:\n"
										+ "\tcscript SSV2XML.js /s:, /a:1,4 /f:widgets /r:widget /o data.csv data.xml\n"
								);

//	Non WSH use: change appropriately WScript.Arguments
	if( cmdLine.scan(WScript.Arguments) && !hlpPar.getScand() )
		{
		fileTag			= fTagPar.getValue();
		rowTag			= rTagPar.getValue();

		if( attPar.getScand() )
			attributes	= attPar.getValue().split(",");

		if( comsPar.getScand() )
			{
			comments = comsPar.getValue();
			switch( comments )
				{
				case "c":	comments = comments.toUpperCase();
				case "C":	commentsRE = new RegExp( "^\\s*//\\s*(.*)\\s*$" );
							break;
				default:	if( comments.length == 1 )
								commentsRE	= new RegExp( "^\\s*" + comments + "\\s*(.*)\\s*$" );
							else
								commentsRE	= new RegExp( comments );
							break;
				}
			}

		if( comPar.getScand() )
			comment	= comPar.getValue();
		if( comment == "-" )
			comment = false;

		if( sepPar.getScand() )
			separator	= sepPar.getValue();

		if( excPar.getScand() )
			exclusions	= excPar.getValue().split(",");

		if( debPar.getScand() )
			debug		= debPar.getValue();
		if( ovrPar.getScand() )
			overwrite	= ovrPar.getValue();

//	Non WSH use: change appropriately wshShell.ExpandEnvironmentStrings
		srcFileName = wshShell.ExpandEnvironmentStrings( srcPar.getValue() );
		if( dstPar.getScand() )
			dstFileName = wshShell.ExpandEnvironmentStrings( dstPar.getValue() );
		else
			dstFileName = srcFileName.substr( 0, srcFileName.lastIndexOf(".") ) + xmlExt;

		//	Open the log file
//	Non WSH use: change appropriately wshFileSys.CreateTextFile
		var	logFile = null;
		if( debug)
			{
			logFileName = dstFileName.substr( 0, dstFileName.lastIndexOf(".") ) + logExt;
			logFile = wshFileSys.CreateTextFile( logFileName, true );
			}

		log( CmdLine.prototype.tUndl, logFile );
		log( interpreter + " " + fullScript + " " + cmdLine.getGood(), logFile );
		log( CmdLine.prototype.tUndl + "\n", logFile );

		//	Open the source file
//	Non WSH use: change appropriately wshFileSys.OpenTextFile
		log( "Opening the source file ...", logFile );
		log( "\t\"" + srcFileName + "\"\n", logFile );
		var	srcFile = wshFileSys.OpenTextFile( srcFileName, forRead );

		var	colHeaders, inner, inCComment = false, inMComment = false;

		//	Create the destination file
//	Non WSH use: change appropriately wshFileSys.* (3), wshShell.Popup
		log( "Creating the destination file ...", logFile );
		log( "\t\"" + dstFileName + "\"\n", logFile );
		var	dstFile = null;
		if( wshFileSys.FileExists(dstFileName) && ( overwrite || (wshShell.Popup("Overwrite \"" + dstFileName + "\"?", 0, "File exists!", 33) == 1) ) )
			dstFile = wshFileSys.OpenTextFile( dstFileName, forWrite);
		else
			dstFile = wshFileSys.CreateTextFile( dstFileName, false );

		//	Set up the separator RE
		separator = new RegExp( separator == "\\t" ? "(?: *" + separator + " *){1}" : "\\s*" + separator + "\\s*", "g" );
		if( debug )
			log( "Separator: " + separator + "\n", logFile );

		//	Write XML header
		writeFile( xmlHeader + "\n", dstFile );
		if( comment )
			writeFile( MComBeg + " " + comment + " " + MComEnd + "\n", dstFile );

//	Non WSH use: change appropriately TextStream.* (2)
		//	Convert the source file
		writeFile( "<" + fileTag + ">", dstFile );
		while( !srcFile.AtEndOfStream )
			{
			var	line	= srcFile.ReadLine().trim();
			if( debug )
				log( line, logFile );
			if( line.length > 0 )
				{
				if( inCComment )
					{
					if( line.search(CComEndRE) >= 0 )
						{
						line = line.replace( CComEndRE, MComEnd );
						inCComment = false;
						}
					writeFile( line, dstFile );
					}
				else if( (comments == "C") && (line.search(CComBegRE) >= 0) )
					{
					line = line.replace( CComBegRE, MComBeg );
					if( line.search(CComEndRE) >= 0 )
						line = line.replace( CComEndRE, MComEnd );
					else
						inCComment = true;
					writeFile( line, dstFile );
					}
				else if( inMComment )
					{
					if( line.search(MComEndRE) >= 0 )
						inMComment = false;
					writeFile( line, dstFile );
					}
				else if( comments && (line.search(MComBegRE) >= 0) )
					{
					if( line.search(MComEndRE) < 0 )
						inMComment = true;
					writeFile( line, dstFile );
					}
				else if( line.search(commentsRE) >= 0 )
					{
					var	rep = commentsRE.exec(line);
					if( rep && rep[1] )
						{
						line = line.replace( commentsRE, MComBeg + " " + rep[1].trim() + " " + MComEnd );
						writeFile( line, dstFile );
						}
					}
				else
					{
					var	i, I, j, J, atts = "";
					inner = "";
//	Seems to give surer results, especially when the separator is a tab
//					line = line.split( separator );
					line = line.replace( separator, "\n" ).split( "\n" );
					if( debug )
						log( line, logFile );
					if( !colHeaders )
						{
						colHeaders = line;
						for( i = 0, I = colHeaders.length; i < I; i++ )
							colHeaders[i] = colHeaders[i].trim().replace( new RegExp("(&|<|>|\\s)+", "g"), "_" );
						}
					else
						{
						for( i = 0, I = line.length; i < I; i++ )
							{
							var	found = false;
							if( exclusions != null )
								for( j = 0, J = exclusions.length; (j < J) && !found; j++ )
									if( i == exclusions[j] - 1 )
										found = true;
							if( (line[i] != "") && !found )
								{
								line[i] = line[i].replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
								found = false;
								if( attributes != null )
									for( j = 0, J = attributes.length; (j < J) && !found; j++ )
										if( i == attributes[j] - 1 )
											found = true;
								if( found )
									atts += " " + colHeaders[i] + "=\"" + line[i] + "\"";
								else
									inner += "\t\t<" + colHeaders[i] + ">" + line[i] + "</" + colHeaders[i] + ">\n";
								}
							}
						if( inner )
							writeFile( "\t<" + rowTag + atts + ">\n" + inner + "\t</" + rowTag + ">", dstFile );
						else
							{
							if( atts )
								writeFile( "\t<" + rowTag + atts + " />", dstFile );
							}
						}
					}
				}
			}
		writeFile( "</" + fileTag + ">\n", dstFile );

//	Non WSH use: change appropriately TextStream.Close (3)
		//	Close the destination file
		log( "Closing the destination file ...", logFile );
		dstFile.Close();

		//	Close the source file
		log( "Closing the source file ...", logFile );
		srcFile.Close();

		//	Close the log file
		if( logFile )
			logFile.Close();

		}
	else
		log	(
			cmdLine.getHelp()
				+ (cmdLine.getGood() != "" ? "\nCommand-line successfully scanned:\n" + cmdLine.getGood() : "")
				+ (cmdLine.getBad() != "" ? "\nCommand-line not scanned:\n" + cmdLine.getBad() : ""),
			logFile
			);

//	Non WSH use: change appropriately WScript.Quit
//	Quit
	WScript.Quit();

//	==========================================================================
//	End of processing
//	==========================================================================


//	==========================================================================
//	Start of functions and objects
//	==========================================================================

//	Non WSH use: change appropriately wshFileSys.GetFileName
//	Initialise the command-line object heirarchy
//
//	Note:	MUST be called before creating any *Par (parameter) or CmdLine (command-line) objects.
//
function initObjects()
	{
//	Extend the Array object
	Array.prototype.toString = arrString;

//	Extend the String object
	String.prototype.trim = strTrim;

	FileSpecPar.prototype	= new Param;
	KeyPar.prototype		= new Param;
	KeyPar.prototype.tags	= "-";				//	Windows users may prefer "-/"
	KeyPar.prototype.cases	= false;
	KeyStrPar.prototype		= new KeyPar;
	KeyLstPar.prototype		= new KeyStrPar;
	KeyTogPar.prototype		= new KeyStrPar;
	KeyTogPar.prototype.vals = "-+01FT";
	KeyIntPar.prototype		= new KeyStrPar;
	KeyFltPar.prototype		= new KeyIntPar;
	CmdLine.prototype.tTitl	= thisScript;
	CmdLine.prototype.tSyn	= "Syntax:\n" + wshFileSys.GetFileName(interpreter) + " " + thisScript + " <parameter(s)>\n";
	CmdLine.prototype.tErr	= "Command-Line Error - Required parameter(s) missing or wrong syntax or value:\n";
	CmdLine.prototype.tUndl = "============================================================================";
	CmdLine.prototype.tPars = "Parameters:";
	}

//	A set of command-line parameters
//
//	Parameters:
//
//		aPars	An array of objects each derived from Param and holding one recognised command-line parameter
//		bHelp	Boolean for whether to give help when errors are encountered scanning script arguments
//		sHelp	Either: A full (console) page of help for the user constructed by the programmer ...
//		... 	Or:		Null, in which case console help will be constructed from the parameters' help and:
//		STitle	Program title
//		sDesc	Program description
//		sSyntax	A line describing the command-line syntax
//		sEg		An example command line
//		sPars	An optional header for the list of help-lines for the individual command-line parameters
//
function CmdLine( aPars, bHelp, sHelp, sTitle, sDesc, sSyntax, sEg, sPars )
	{
	this.pars		= aPars ? aPars : null;
	this.bHelpful	= bHelp ? bHelp : false;
	this.help		= sHelp ? sHelp : null;
	if(	sTitle )
		this.tTitl	= sTitle;
	this.desc		= sDesc ? sDesc : null;
	if(	sSyntax )
		this.tSyn	= sSyntax;
	this.eg			= sEg;
	if(	sPars )
		this.tPars	= sPars;
	this.goodScans	= "";
	this.badScans	= "";
	this.getGood	= gGood;
	this.getBad		= gBad;
	this.scan		= scanCmdLine;
	this.getHelp	= fHelp;
	}

function scanCmdLine( args )
	{
	var	result	= true;
	var	subResult;
	var	msg		= this.tErr;
	for( var a = 0, A = args.length; a < A; a++ )
		{
//	Non WSH use: change appropriately to args[a]
		thisArg	= args(a);
		subResult = false;
		for( var p = 0, P = this.pars.length; p < P; p++ )
			{
			if( debug )
				log( "Parameter " + p + ": " + (this.pars[p].getHelp() ? this.pars[p].getHelp() : "") );
			if( this.pars[p].scan( thisArg ) )
				{
				subResult = true;
				this.goodScans += (this.goodScans != "" ? " " : "") + thisArg;
				if( debug )
					log( "Argument $" + a  + " " + thisArg + " scanned as " + this.pars[p].getValue() );
				break;
				}
			}
		if( (!subResult) && (this.bHelpful || debug) )
			{
			this.badScans += (this.badScans != "" ? " " : "") + thisArg;
			if( debug )
				log( "Argument $" + (a+1)  + " " + thisArg + " not scanned" );
			}
		result &= subResult;
		}
	subResult = true;
	for( p = 0, P = this.pars.length; p < P; p++ )
		if( this.pars[p].getReqd() && !this.pars[p].getScand() )
			{
			msg += this.pars[p].getHelp() + "\n";
			subResult = false;
			}
	if( (!subResult) && (this.bHelpful || debug) )
		log( msg + "\n" );
	result &= subResult;
	return result;
	}

function fHelp()
	{
	var	result = null;
	if( this.help != null )
		result = this.help;
	else
		{
		result = ( this.tTitl ? this.tTitl + "\n" + this.tUndl.substr(0, this.tTitl.length) + "\n\n" : "" )
					+ ( this.desc ? this.desc + "\n" : "" )
					+ ( this.tSyn ? this.tSyn + "\n" : "" )
					+ ( this.eg ? this.eg + "\n" : "" )
					+ ( this.tPars && this.pars && this.pars.length ? this.tPars + "\n" : "" );
		for( var p = 0, P = this.pars.length; p < P; p++ )
			if( this.pars[p].getHelp() )
				result += this.pars[p].getHelp() + "\n";
		}
	return result;
	}

function gGood()
	{
	return this.goodScans;
	}

function gBad()
	{
	return this.badScans;
	}

//	Base object for all command-line parameters
//
//	Parameters:
//
//		sHelp	Help text (preferably < 79 characters) to prompt user
//		bReqd	Boolean for whether the parameter must be present, default is false
//		oValue	The initial value, defaults to null
//
function Param( sHelp, bReqd, oValue )
	{
	this.help		= sHelp ? sHelp : null;
	this.reqd		= bReqd ? true : false;
	this.scand		= false;
	this.value		= oValue ? oValue : null;
	this.getHelp	= gHelp;
	this.getReqd	= gReqd;
	this.getScand	= gScand;
	this.getValue	= gVal;
	this.setValue	= sVal;
	this.addListen	= aList;
	this.delListen	= dList;
	this.listeners	= new Array(0);
	}

function gHelp()
	{
	return this.help;
	}

function gReqd()
	{
	return this.reqd;
	}

function gScand()
	{
	return this.scand;
	}

function gVal()
	{
	return this.value;
	}

function sVal( nValue )
	{
	var	oValue = this.value;
	this.value = nValue;
	if( nValue != oValue )
		for( var l = 0, L = this.listeners.length; l < L; l++ )
			this.listeners[l]( nValue, oValue );
	return oValue;
	}

function aList( fListener )
	{
	this.listeners.push( fListener );
	}

function dList( fListener )
	{
	for( var l = this.listeners.length - 1; l >= 0; l-- )
		if( this.listeners[l] == fListener )
			this.listeners.splice( l , 1 );
	}

//	A file spec command-line parameter object
//
//	Format:	<filespec>
//
//	Note:	Untagged/unkeyed string parameters such as FileSpecPars must follow all other parameter types
//			in the aPars array passed to the CmdLine constructor.
//
//	Parameters:
//
//		sHelp	Help text (preferably < 79 characters) to prompt user
//		bReqd	Boolean for whether the parameter must be present, default is false
//		sValue	The initial value, defaults to null
//		bStrict	Whether the parser should perform strict scanning, default is false
//				(would accept legal filenames that look like other parameters, such as -A)
//
function FileSpecPar( sHelp, bReqd, sValue, bStrict )
	{
	//	Object inheritance
	this.base	= Param;
	this.base( sHelp, bReqd, sValue );

	this.strict	= bStrict ? true : false;
	this.scan	= scanFileSpec;
	}

function scanFileSpec( aValue )
	{
	var	result 	= false;
	var	re 		= new RegExp( "[" + KeyPar.prototype.tags + "]" );
	if( !this.scand )
		{
		if( this.strict || !re.test(aValue.substr(0, 1)) )
			{
			this.setValue( aValue );
			this.scand	= true;
			result = true;
			}
		}
	return result;
	}

//	A keyed command-line parameter to toggle (switch or logically NOT) a boolean value
//
//	Format: [-]<key>
//
//	Parameters:
//
//		sKey	The string, usually a single character but can be a regular expression, to use as key
//		sHelp	Help text (preferably < 79 characters) to prompt user
//		bReqd	Boolean for whether the parameter must be present, default is false
//		bValue	The initial value, defaults to false
//		bCase	Boolean for whether the key is case-sensitive, default is false
//		sTag	String of allowable tag characters, default is "-"
//
function KeyPar( sKey, sHelp, bReqd, bValue, bCase, sTag )
	{
	//	Object inheritance
	this.base	= Param;
	this.base( sHelp, bReqd, bValue ? bValue : false );

	this.key	= sKey;
	if( bCase )
		this.cases = true;
	if( sTag )
		this.tags	= sTag;
	this.scan	= scanKey;
	this.re		= new RegExp("^[" + this.tags + "]" + this.key + "$", this.cases ? "g" : "ig" );
	}

function scanKey( aValue )
	{
	var	result = false;
	if( (!this.scand) && this.re.test(aValue) )
		{
		this.setValue( !this.getValue() );
		this.scand	= true;
		result = true;
		}
	return result;
	}

//	A keyed command-line string parameter
//
//	Format: [-]<key>:<string>
//
//	Parameters:
//
//		sKey	The string, usually a single character but can be a regular expression, to use as key
//		sHelp	Help text (preferably < 79 characters) to prompt user
//		bReqd	Boolean for whether the parameter must be present, default is false
//		sValue	The initial value, defaults to null
//		bCase	Boolean for whether the key is case-sensitive, default is false
//		sTag	String of allowable tag characters, default is "-"
//
function KeyStrPar( sKey, sHelp, bReqd, sValue, bCase, sTag )
	{
	//	Object inheritance
	this.base	= KeyPar;
	this.base( sKey, sHelp, bReqd, sValue ? sValue : null, bCase, sTag );

	this.scan	= scanKeyStr;
	this.re		= new RegExp("^[" + this.tags + "]" + this.key + ":?(.+)$", this.cases ? "g" : "ig" );
	}

function scanKeyStr( aValue )
	{
	var	result = false;
	if( !this.scand )
		{
		try
			{
			this.setValue( this.re.exec(aValue)[1] );
			this.scand	= true;
			result = true;
			}
		catch(e)
			{
			if( debug )
				log( "scanKeyStr - exception caught: 0x" + ((e.number+0xFFFFFFFF)%0xFFFFFFFF).toString(16).toUpperCase() + "\n\t" + e.description );
			}
		}
	return result;
	}

//	A keyed command-line parameter to set a boolean value
//
//	Format: [-]<key>:<-+01>
//
//	Parameters:
//
//		sKey	The string, usually a single character but can be a regular expression, to use as key
//		sHelp	Help text (preferably < 79 characters) to prompt user
//		bReqd	Boolean for whether the parameter must be present, default is false
//		bValue	The initial value, defaults to false
//		bCase	Boolean for whether the key and the toggle characters are case-sensitive, default is false
//		sTag	String of allowable tag characters, default is "-"
//		sVals	String of allowable toggle characters, should be in pairs "<False><True>", as in default "-+01FT"
//
function KeyTogPar( sKey, sHelp, bReqd, bValue, bCase, sTag, sVals )
	{
	//	Object inheritance
	this.base	= KeyStrPar;
	this.base( sKey, sHelp, bReqd, bValue ? bValue : false, bCase, sTag );

	if( sVals )
		this.vals = sVals;
	this.scan	= scanKeyTog;
	this.re		= new RegExp("^[" + this.tags + "]" + this.key + ":?([" + this.vals + "])?$", this.cases ? "g" : "ig" );
	}

function scanKeyTog( aValue )
	{
	var	result = false;
	if( !this.scand )
		{
		try
			{
			var	tog = this.re.exec(aValue);
			if( tog != null )
				{
				tog = tog[tog.length - 1];
				switch( tog ? this.vals.indexOf( this.cases ? tog : tog.toUpperCase() ) % 2 : -1 )
					{
					case -1:	this.setValue( !this.getValue() );
								this.scand	= true;
								result = true;
								break;

					case 0:		this.setValue( false );
								this.scand	= true;
								result = true;
								break;

					case 1:		this.setValue( true );
								this.scand	= true;
								result = true;
								break;

					default:	break;
					}
				}
			}
		catch(e)
			{
			if( debug )
				log( "scanKeyTog - exception caught: 0x" + ((e.number+0xFFFFFFFF)%0xFFFFFFFF).toString(16).toUpperCase() + "\n\t" + e.description );
			}
		}
	return result;
	}

//	A keyed command-line parameter to choose from a list of allowed strings
//
//	Format: [-]<key>:<string>
//
//	Parameters:
//
//		sKey	The string, usually a single character but can be a regular expression, to use as key
//		sHelp	Help text (preferably < 79 characters) to prompt user
//		sVals	Array of allowed string choices
//		bReqd	Boolean for whether the parameter must be present, default is false
//		bValue	The initial value, defaults to false
//		bCase	Boolean for whether the key and the string values are case-sensitive, default is false
//		sTag	String of allowable tag characters, default is "-"
//
function KeyLstPar( sKey, sHelp, sVals, bReqd, bValue, bCase, sTag )
	{
	//	Object inheritance
	this.base	= KeyStrPar;
	this.base( sKey, sHelp, bReqd, bValue ? bValue : false, bCase, sTag );

	if( sVals )
		this.vals = sVals;
	this.scan	= scanKeyLst;
	this.re		= new RegExp("^[" + this.tags + "]" + this.key + ":?(" + this.vals.join("|") + ")$", this.cases ? "g" : "ig" );
	}

function scanKeyLst( aValue )
	{
	var	result = false;
	if( !this.scand )
		{
		try
			{
			this.setValue( this.re.exec(aValue)[1] );
			this.scand	= true;
			result = true;
			}
		catch(e)
			{
			if( debug )
				log( "scanKeyStr - exception caught: 0x" + ((e.number+0xFFFFFFFF)%0xFFFFFFFF).toString(16).toUpperCase() + "\n\t" + e.description );
			}
		}
	return result;
	}

//	A keyed command-line bounded integer parameter
//
//	Format: [-]<key>:<decimal>|B<binary>|O<octal>|H<hexadecimal>
//
//	Parameters:
//
//		sKey	The string, usually a single character but can be a regular expression, to use as key
//		iLower	The lower bound, defaults to javascript's smallest integer -2^52
//		iUpper	The upper bound, defaults to javascript's largest integer +2^52
//		iValue	The initial value, defaults to null
//		sHelp	Help text (preferably < 79 characters) to prompt user
//		bReqd	Boolean for whether the parameter must be present, default is false
//		bCase	Boolean for whether the key, B, O, H are case-sensitive, default is false
//		sTag	String of allowable tag characters, default is "-"
//
function KeyIntPar( sKey, iLower, iUpper, iValue, sHelp, bReqd, bCase, sTag )
	{
	//	Object inheritance
	this.base	= KeyStrPar;
	this.base( sKey, sHelp, bReqd, iValue, bCase, sTag );

	this.lower		= iLower != null ? iLower : -Math.pow(2,52);
	this.upper		= iUpper != null ? iUpper : Math.pow(2,52);
	this.getLower	= gLower;
	this.setLower	= sLower;
	this.getUpper	= gUpper;
	this.setUpper	= sUpper;
	this.scan		= scanKeyInt;
	this.re			= new RegExp("^[" + this.tags + "]" + this.key + ":?(([-+]?[0-9]+)|(B[-+]?[0-1]+)|(O[-+]?[0-7]+)|(H[-+]?[0-9A-F]+))$", this.cases ? "g" : "ig" );
	}

function scanKeyInt( aValue )
	{
	var	result = false;
	if( !this.scand )
		{
		try
			{
			var	base;
			var	numb = this.re.exec(aValue)[1];
			switch( numb.charAt(0).toUpperCase() )
				{
				default:	base = 10;
							break;

				case "B":	numb = numb.substr( 1 );
							base = 2;
							break;

				case "O":	numb = numb.substr( 1 );
							base = 8;
							break;

				case "H":	numb = numb.substr( 1 );
							base = 16;
							break;
				}
			numb = parseInt( numb, base );
			if( (numb >= this.lower) && (numb <= this.upper) )
				{
				this.setValue( numb );
				this.scand	= true;
				result = true;
				}
			}
		catch(e)
			{
			if( debug )
				log( "scanKeyInt - exception caught: 0x" + ((e.number+0xFFFFFFFF)%0xFFFFFFFF).toString(16).toUpperCase() + "\n\t" + e.description );
			}
		}
	return result;
	}

function gLower()
	{
	return this.lower;
	}

function sLower( nLower )
	{
	var	oLower = this.lower;
	this.lower = nLower;
	return oLower;
	}

function gUpper()
	{
	return this.upper;
	}

function sUpper( nUpper )
	{
	var	oUpper = this.upper;
	this.upper = nUpper;
	return oUpper;
	}

//	A keyed command-line bounded floating point parameter accepting a number in decimal or scientific format
//
//	Format: [-]<key>:<fp decimal>
//
//	Parameters:
//
//		sKey	The string, usually a single character but can be a regular expression, to use as key
//		iLower	The lower bound, defaults to javascript's smallest number -2^52
//		iUpper	The upper bound, defaults to javascript's largest number +2^52
//		iValue	The initial value, defaults to null
//		sHelp	Help text (preferably < 79 characters) to prompt user
//		bReqd	Boolean for whether the parameter must be present, default is false
//		bCase	Boolean for whether the key and the E denoting an exponent are case-sensitive, default is false
//		sTag	String of allowable tag characters, default is "-"
//
function KeyFltPar( sKey, iLower, iUpper, iValue, sHelp, bReqd, bCase, sTag )
	{
	//	Object inheritance
	this.base	= KeyIntPar;
	this.base( sKey, iLower, iUpper, iValue, sHelp, bReqd, bCase, sTag );

	this.scan		= scanKeyFlt;
	this.re			= new RegExp("^[" + this.tags + "]" + this.key + ":?([-+]?[.0-9]+)(?:E([-+]?[.0-9]+))?$", this.cases ? "g" : "ig" );
	}

function scanKeyFlt( aValue )
	{
	var	result = false;
	if( !this.scand )
		{
		try
			{
			var	comps = this.re.exec(aValue);
			var	numb = comps[2] ? parseFloat( comps[1] )*Math.pow( 10, parseFloat(comps[2]) ) : parseFloat( comps[1] );
			if( (numb >= this.lower) && (numb <= this.upper) )
				{
				this.setValue( numb );
				this.scand	= true;
				result = true;
				}
			}
		catch(e)
			{
			if( debug )
				log( "scanKeyFlt - exception caught: 0x" + ((e.number+0xFFFFFFFF)%0xFFFFFFFF).toString(16).toUpperCase() + "\n\t" + e.description );
			}
		}
	return result;
	}

//	Convert Array to string
//	(redefinition for Array class)
function arrString()
	{
	var	result = "";
	for( var i = 0, I = this.length; i < I; i++ )
		result += (i == 0 ? "{ " : ", " ) + i + ": " + this[i];
	result += " }";
	return result;
	}

//	Strip leading and trailing spaces
//	(extension to String class)
function strTrim()
	{
    return this.replace(/(^\s+)|(\s+$)/g, "");
	}

//	Non WSH use: change appropriately TextStream.WriteLine
//	Output to destination file
function writeFile( _msg, _dstfile )
	{
	if( _dstfile != null )
		_dstfile.WriteLine( _msg );
	return;
	}

//	Non WSH use: change appropriately WScript.Echo, TextStream.WriteLine
//	Output logging messages
function log( _msg, _logfile )
	{
	WScript.Echo( _msg );
	if( _logfile != null )
		_logfile.WriteLine( _msg );
	return;
	}

//	==========================================================================
//	End of functions and objects
//	==========================================================================

