/*------------------------------------------------------------------------------
	XDown.js
	SDK scripting tool for converting InfoPath solutions into form viewable using regular web 
	browsers (such as Internet Explorer version 5 and higher). For quick help on usage, type:

	cscript xdown.js /help (or /?)

	For more information, please refer to SDK documentation.
---------------------------------------------------------------------------- -*/

/*------------------------------------------------------------------------------
	Initialize global "constants"
---------------------------------------------------------------------------- -*/
var gc_iVer = '1';				// tool major version
var gc_iRev = '17';				// tool minor version
var gc_SystemFolder = 1;		// For FileSystemObject.GetSpecialFolder
var gc_TemporaryFolder = 2;		// For FileSystemObject.GetSpecialFolder
var gc_ForReading = 1;			// For FileSystemObject.Open
var gc_ForWriting = 2;			// For FileSystemObject.Open
var gc_ForAppending = 8;		// For FileSystemObject.Open
var gc_ModeUnknown = 0;			// Tool mode: none set / determined
var gc_ModeHelp = 0;			// Tool mode: just display short usage info
var gc_ModeListViews = 1;		// Tool mode: display information about views contained in a solution
//var gc_ModeListFiles = 2;		// Tool mode: display solution files and downlevel action on them (without doing it) 
var gc_ModeDownlevel = 3;		// Tool mode: convert InfoPath solution to downlevel
var gc_ModeUpdate = 4;			// Tool mode: update InfoPath solution to generate downlevel view
var gc_ModePatchDocument = 5;	// Tool mode: convert InfoPath XML document to downlevel
var gc_WarningNamespace = [	'http://schemas.microsoft.com/office/infopath/2003/xslt/xDocument',
							'http://schemas.microsoft.com/office/infopath/2003/xslt/extension',
							'http://schemas.microsoft.com/office/infopath/2003/xslt/solution']; // Generate warning if any of these is used in XSL

/*------------------------------------------------------------------------------
	Initialize global objects. Abort script if any of these cannot be created
---------------------------------------------------------------------------- -*/
var g_oOut = new Output(100);	// Output object with high verbosity level (to display any messages until until user-specified verbosity level is set)
if (g_oOut == null)
	{
	WScript.Echo('Cannot create console output object');
	WScript.Quit(127);
	};
var g_oFS = EnsureValue(new ActiveXObject("Scripting.FileSystemObject"), 'Cannot create FileSystem object');
var g_oShell = EnsureValue(WScript.CreateObject("WScript.Shell"), 'Cannot create Scripting Shell object');
var g_oTempFolder = EnsureValue(g_oFS.GetSpecialFolder(gc_TemporaryFolder), 'Temporary folder not set in environment.');

/*------------------------------------------------------------------------------
	Initialize global variables
---------------------------------------------------------------------------- -*/
var g_iCopied = 0;		// Count copied files
var g_iTransformed = 0;	// Count XSL converted to downlevel
var g_iModified = 0;	// Count XML with downlevel stylesheet added
var g_iCreated = 0;		// Count XSNs (CABs) created
var g_iSkipped = 0;		// Count skipped files in the source solution
var g_iWarned = 0;		// Count namespace warnings
var g_sTransformXSLName = WScript.ScriptFullName.replace(/.js/gi,'.xsl');
var g_sScriptName = WScript.ScriptName.replace(/.js/gi,'');

/*------------------------------------------------------------------------------
	Main tool entry point
---------------------------------------------------------------------------- -*/
PrintBanner();
var oCommandLine = new CommandLine(WScript.Arguments);
g_oOut = null;
g_oOut = new Output(oCommandLine.VerbosityLevel);
RunToolMode(oCommandLine);

/*------------------------------------------------------------------------------
	RunToolMode - execute one of four tool modes
	Input:	oCommandLine - object containing user's command line parameters
---------------------------------------------------------------------------- -*/
function RunToolMode(oCommandLine)
{
	switch (oCommandLine.Mode)
	{
	// List view mode: display information about views contained in a solution
	case gc_ModeListViews:
		EnsureValue(oCommandLine.SrcSolution, 'Missing source solution');
		if (oCommandLine.DstSolution != '')
			g_oOut.Error(1, 'Destination solution not allowed in list view mode.');
		var oSolution = EnsureValue(new Solution(oCommandLine), 'Could not create object for ' + oCommandLine.SrcSolution + ' solution.');
		for (i = 0; i < oSolution.Views.length; i++)
			{
			g_oOut.Echo('------------------------------');
			var oView = oSolution.Views[i];
			g_oOut.Echo('View #   : ' + (i+1));
			g_oOut.Echo('Name     : ' + oView.Name);
			g_oOut.Echo('Caption  : ' + oView.Caption);
			g_oOut.Echo('Transform: ' + oView.Transform);
			g_oOut.Echo('Default  : ' + (oView.IsDefault ? 'Yes' : 'No'));
			g_oOut.Echo('Current  : ' + (oView.IsCurrent ? 'Yes' : 'No'));
			}
		g_oOut.Echo('------------------------------');
		oSolution.Cleanup(false);
		break;
	// Solution mode: convert InfoPath solution to downlevel
	case gc_ModeDownlevel:
	case gc_ModeUpdate:
		EnsureValue(oCommandLine.SrcSolution, 'Missing source solution');
		EnsureValue(oCommandLine.DstSolution, 'Missing destination solution');
		if (oCommandLine.SrcSolution.toUpperCase() == oCommandLine.DstSolution.toUpperCase())
			g_oOut.Error(1, 'Solution source and destination cannot be same.');
		EnsureFile(g_sTransformXSLName, 'XSL Transformation file ');
		if (oCommandLine.Mode == gc_ModeUpdate)
			{
			EnsureValue(oCommandLine.DownlevelXSL, 'Missing downlevel transformation (/x option)');
			}
		else
			{
			if (oCommandLine.DownlevelXSL != '')
				g_oOut.Error(1, 'Cannot specify /x with downlevel mode.');
			if (GetExtension(oCommandLine.DstSolution) == '.XSN')
				g_oOut.Error(1, 'Downlevel mode requires folder as a destination');
			}
		var oSolution = EnsureValue(new Solution(oCommandLine), 'Could not create object for ' + oCommandLine.SrcSolution + ' solution.');
		var sDstTemplateXML = '';
		for (i = 0; i < oSolution.Files.length; i++)
			{
			var oFile = oSolution.Files[i];
			if (oFile.IsTemplate)
				sDstTemplateXML = oFile.FullDstPath;
			var fPreserveDst = (!oCommandLine.UpdateDestination && g_oFS.FileExists(oFile.FullDstPath));
			oFile.Process(i, oCommandLine.UpdateDestination, oCommandLine.JustListFilesAndActions);
			}
		var fKeepDst = false; // Destination temporary folder may need to be preserved if XML is being viewed from there
		if (oCommandLine.FinalOpen && !oCommandLine.JustListFilesAndActions)
			{
			EnsureValue(sDstTemplateXML, 'No template XML found in the solution.');
			ViewFile(sDstTemplateXML);
			fKeepDst = (g_oFS.GetParentFolderName(sDstTemplateXML).toUpperCase() == oSolution.DstFolder.toUpperCase()); // Don't delete destination 
			}
		oSolution.Cleanup(fKeepDst);
		if (CountAndReportTouchedFiles() == 0)
			g_oOut.Echo('WARNING: No files changed. Destination exists and no /! command line was used.');
		break;
	// Document mode: convert InfoPath XML document to downlevel
	case gc_ModePatchDocument:
		EnsureValue(oCommandLine.SrcDocument, 'Missing source document');
		EnsureValue(oCommandLine.DstDocument, 'Missing destination document');
		if (oCommandLine.SrcDocument.toUpperCase() == oCommandLine.DstDocument.toUpperCase())
			g_oOut.Error(1, 'Document source and destination cannot be same.');
		EnsureValue(oCommandLine.DownlevelXSL, 'Missing downlevel transformation (/x option)');
		var oDocument = new SolutionFile(oCommandLine.SrcDocument, oCommandLine.DstDocument, true, oCommandLine.CommentOutAppAndExtTags, oCommandLine.DownlevelXSL);		
		oDocument.UpdateVersion = false;
		oDocument.Process(0, oCommandLine.UpdateDestination, oCommandLine.JustListFilesAndActions);
		if (oCommandLine.FinalOpen  && !oCommandLine.JustListFilesAndActions)
			{
			ViewFile(oCommandLine.DstDocument);
			}
		if (CountAndReportTouchedFiles() == 0)
			g_oOut.Echo('WARNING: No downlevel document was generated. Destination exists and no /! command line was used.');
		break;
	// Help mode: display short usage info
	case gc_ModeHelp:
		PrintHelp();
		break;
	// Unknown mode
	default:
		g_oOut.Error(1, 'Invalid run mode. Please type ' + g_sScriptName + ' /help for usage information.');
		break;
	}
}


/*------------------------------------------------------------------------------
	CommandLine object
	constructor	build a representation of the source solution with properties and methods 
				required by the tool
	Input:		oCmdLineArgs - command line arguments object from WSH engine
---------------------------------------------------------------------------- -*/
function CommandLine(oCmdLineArgs)
{
	// object properties
	this.Mode = gc_ModeUnknown;		// default mode
	this.JustListFilesAndActions = false;	// do the downlevel conversion, don't just give info about it
	this.UpdateDestination = false;	// don't update destination unless /! is specified
	this.CopyAllFiles = false;		// don't copy all files unless /a is specified
	this.VerbosityLevel = 100;		// set highest verbosity level
	this.TransformFileName = '';	// file name used for transformation (name of view XSL)
	this.TransformName = '';		// transformation name (name of view)
	this.CommentOutAppAndExtTags = false;   // don't comment out InfoPath tags in XML unless /c is specified
	this.FinalOpen = false;				// don't open document or template XML at the end unless /o is specified
	this.SrcSolution = '';				// full path to source solution (solution mode)
	this.DstSolution = '';				// full path to destination solution (solution mode)
	this.SrcDocument = '';				// full path to source document (document mode)
	this.DstDocument = '';				// full path to destination document (document mode)
	this.DownlevelXSL = '';				// full path to downlevel XSL (document mode)
	// Just print help if invoked without command line parameters
	if (oCmdLineArgs.length == 0)
		return;
	var iParamCount = -1;
	// Scan command line parameters
	for (var i = 0; i < oCmdLineArgs.length; i++)
		{
		var sArg = oCmdLineArgs(i);
		if ((sArg.charAt(0) == '/') || (sArg.charAt(0) == '-'))
			{
			var sArgName = sArg.substr(1);
			var sArgVal = '';
			var iColonPos = sArgName.indexOf(':');
			if (iColonPos >= 0)
				{
				sArgVal = sArgName.substr(iColonPos+1);
				sArgName = sArgName.substr(0, iColonPos);
				}
			switch (sArgName.charAt(0))
			{
			// MODE SELECTION FLAGS
			// turn on list view mode
			case 'l':
				if (this.Mode != gc_ModeUnknown)
					g_oOut.Error(1, 'Mode can only be specified once.');
				this.Mode = gc_ModeListViews;
				break;
			// turn on document (patch) mode
			case 'p':
				if (this.Mode != gc_ModeUnknown)
					g_oOut.Error(1, 'Mode can only be specified once.');
				this.Mode = gc_ModePatchDocument;
				break;
			// turn on update mode
			case 'u':
				if (this.Mode != gc_ModeUnknown)
					g_oOut.Error(1, 'Mode can only be specified once.');
				this.Mode = gc_ModeUpdate;
				this.CopyAllFiles = true;
				break;
			// turn on dowlevel mode
			case 'd':
				if (this.Mode != gc_ModeUnknown)
					g_oOut.Error(1, 'Mode can only be specified once.');
				this.Mode = gc_ModeDownlevel;
				break;
			// turn on help mode
			case '?':	
			case 'h':
				this.Mode = gc_ModeHelp;
				return;
			// PARAMETER SELECTION FLAGS
			// set verbosity level
			case 'v':
				this.VerbosityLevel = parseInt(sArgVal);
				if (this.VerbosityLevel > 99)
					g_oOut.Error(1,'Verbosity level exceeds 99.');
				break;
			// force destination update even if destination files exist
			case '!':
				this.UpdateDestination = true;
				break;
			// specify transform file in manifest to use (override use of default view)
			case 't':
				if (this.DownlevelXSL != '')
					g_oOut.Error(1, '/t and /x cannot be used together.');
				var sCurrentTransform = EnsureValue(sArgVal, 'Transform (file) name missing or invalid');
				if (GetExtension(sCurrentTransform) == '.XSL')
					this.TransformFileName = sCurrentTransform;
				else
					this.TransformName = sCurrentTransform;
				break;
			// comment out InfoPath' Application and Extension tags
			case 'c':
				this.CommentOutAppAndExtTags = true;
				break;
			// flag for just viewing files and their actions without doing them
			case 'f':
				this.JustListFilesAndActions = true;
				break;
			// copy all files to destination
			case 'a':
				this.CopyAllFiles = true;
				break;
			// specify opening of an XML file (document or template) at the end of downlevel conversion 
			case 'o': 
				this.FinalOpen = true;
				break;
			// downlevel transformation URL
			case 'x':
				if ((this.TransformFileName != '') || (this.TransformName != ''))
					g_oOut.Error(1, '/t and /x cannot be used together.');
				this.DownlevelXSL = EnsureValue(sArgVal, 'Downlevel transformation URL missing or invalid.');
				// if starts with a dot, just leave it assuming it's a relative path that will work
				if (this.DownlevelXSL.charAt(0) != '.')
					{
					if (g_oFS.FileExists(this.DownlevelXSL))
						this.DownlevelXSL = GetFullFilePathAndName(this.DownlevelXSL, true);
					else	
						this.DownlevelXSL = EnsureURL(this.DownlevelXSL);
					}
				break;
			// all other command line switches are not recognized and result in an error
			default:
				g_oOut.Error(2, "Invalid command line switch '" + sArgName + "'.");
			} 
		}
		else
		{
			iParamCount++;
			switch (iParamCount)
			{
			// first parameter is the source
			case 0:
				switch (GetExtension(sArg))
				{
				// source is a cabbed solution, must be in solution mode
				case '.XSN':
					this.SrcSolution = GetFullFilePathAndName(sArg, true);
					break;
				// source is a XML document, must be in document mode	
				case '.XML':
					this.SrcDocument = GetFullFilePathAndName(sArg, true);
					break;
				// source is folder containing InfoPath solution, it must exist
				default:
					this.SrcSolution = GetFullFolderPathAndName(sArg, false, false);
					break;
				}
				break;
			// second parameter is the destination
			case 1:
				switch (GetExtension(sArg))
				{
				// destination is a cabbed solution, valid only in solution mode
				case '.XSN':
					// Check for valid destination by trying to create a TMP file there
					try
						{
						var sTestXSN = sArg.replace(/.xsn/gi,'.tmp');
						var oTestXSN = g_oFS.OpenTextFile(sTestXSN, gc_ForWriting, true);
						this.DstSolution = g_oFS.GetFile(sTestXSN).Path;
						oTestXSN.Close();
						g_oFS.DeleteFile(this.DstSolution);
						this.DstSolution = this.DstSolution.replace(/.tmp/gi, '.xsn');
						}
					catch (e)
						{
						g_oOut.Error(1, 'Unable to create ' + sArg + ' (Make sure destination folder exists)');
						}
					break;
				// destination is a XML document, valid only in document mode
				case '.XML':
					this.DstDocument = GetFullFilePathAndName(sArg, false);
					break;
				// destination is a folder containing downlevel solution, valid only in solution mode
				default:
					this.DstSolution = GetFullFolderPathAndName(sArg, false, true);
					break;
				}
				break;
			// only up to 2 parameters allowed
			default:
				g_oOut.Error(1, 'Too many parameters. Use double quotes around parameters containing spaces.');
				break;
			}
		}
	  }
}

/*------------------------------------------------------------------------------
	Solution object 
	constructor	build a representation of the source solution with properties and methods 
				required by the tool
	Input: 		oParam - command line object
---------------------------------------------------------------------------- -*/
function Solution(oParam)
{
	// object properties
	this.SrcCab = '';		// full path to solution XSN 
	this.SrcFolder = '';	// full path to solution folder
	this.DstCab = '';		// full path to destination solution XSN
	this.DstFolder = '';	// full path to destination solution XSN
	this.Views = new Array();	// collection of View objects defined in solution
	this.Files = new Array();	// collection of File objects defined in solution
	this.Cleanup = SolutionCleanup;// method to remove temporary folders and files
	this.DDFName = '';			// full path to DDF file
	// local variables	
	var oDDF = null;	// file object for DDF file (if destination solution is XSN)
	var fCopyXSL;		// flag to enable just copying of XSL files, not their transformation
	var sHrefToXSL;		// value of href attribute in XSL PI inserted into document or template XML
	var fCommentOut;	// flag to enable commenting out InfoPath tags in document or template XML
	// Source solution can be either in a flat folder or cabbed up in a XSN
	if (GetExtension(oParam.SrcSolution) == '.XSN')
		{
		this.SrcCab = oParam.SrcSolution;
		this.SrcFolder = g_oTempFolder.Path + '\\' + g_oFS.GetBaseName(this.SrcCab);
		CreateFolder(this.SrcFolder);
		RunCommand('expand.exe "' + this.SrcCab + '" -F:* "' + this.SrcFolder+'"', true, "");
		}
	else
		{
		this.SrcFolder = oParam.SrcSolution;
		}
	// Destination (downlevel) solution can be either in a flat folder or cabbed up into a XSN
	if (GetExtension(oParam.DstSolution) == '.XSN')
		{
		this.DstCab = oParam.DstSolution;
		this.DstFolder = g_oTempFolder.Path + '\\' + g_sScriptName + '\\' + g_oFS.GetBaseName(this.DstCab);
		RemoveFolder(this.DstFolder);
		CreateFolder(this.DstFolder);
		this.DDFName = g_oTempFolder.Path + '\\' + g_sScriptName + '\\' + g_oFS.GetBaseName(this.DstCab) + '.ddf';
		oDDF = OpenFile(this.DDFName, gc_ForWriting, true);
		oDDF.WriteLine('.Set CabinetName1 = ' + g_oFS.GetFileName(this.DstCab));
		oDDF.WriteLine('.Set DiskDirectory1 = ' + g_oFS.GetParentFolderName(this.DstCab));
		oDDF.WriteLine('.Set SourceDir = ' + this.DstFolder);
		}
	else
		{
		this.DstFolder = oParam.DstSolution;
		}
	// Each solution must have a manifest.xsf which describes it
	var sManifestFile = this.SrcFolder + '\\manifest.xsf';
	var oManifest = LoadXMLFile(sManifestFile);
	// Get the views from the manifest
	var oViews = EnsureValue(oManifest.documentElement.selectNodes("//xsf:views"), 'xsf:views element missing in ' + sManifestFile);
	var sDefaultViewName = oViews.item(0).getAttributeNode("default").value;
	EnsureValue(sDefaultViewName, 'Solution has no default view set.');
	var oViewList = EnsureValue(oManifest.documentElement.selectNodes("//xsf:view"), 'No views found in ' + sManifestFile);
	for (var i = 0; i < oViewList.length; i++)
		this.Views[i] = new SolutionView(this.SrcFolder, oViewList.item(i), sDefaultViewName, oParam.TransformName, oParam.TransformFileName);		
	if (oParam.DownlevelXSL != '')
		{
		// /update: XSLs will be just copied over, template.xml will have XSL PI added pointing to downlevel XSL (which can be anywhere)
		fCopyXSL = true;
		sHrefToXSL = oParam.DownlevelXSL;
		fCommentOut = false;
		}
	else
		{
		// /downlevel: XSLs will be converted to downlevel, template.xml will have XSL PI pointing to current downlevel XSL (assumed to be in the same folder)
		var iCurrentViewIndex = -1;
		for (var i = 0; i < oViewList.length; i++)
			{
			if (this.Views[i].IsCurrent)
				{
				iCurrentViewIndex = i;
				break;
				}
			}
		if (iCurrentViewIndex < 0)
			g_oOut.Error(3, 'View  ' + oParam.TransformName + oParam.TransformFileName + ' not found in solution.');
		fCopyXSL = false;
		sHrefToXSL = this.Views[iCurrentViewIndex].Transform;
		fCommentOut = oParam.CommentOutAppAndExtTags;
		}	
	// Get the files from the manifest
	var oFileList = EnsureValue(oManifest.documentElement.selectNodes("//xsf:file"), 'No files found in ' + sManifestFile);
	for (var i = 0; i < oFileList.length; i++)
		{
		var sName = EnsureValue(oFileList.item(i).getAttributeNode("name").value, 'Empty or missing file name in solution.');
		this.Files[i] = new SolutionFile(this.SrcFolder + '\\' + sName, this.DstFolder + '\\' + sName, oParam.CopyAllFiles, fCommentOut, sHrefToXSL, oDDF, fCopyXSL);		
		}
	// manifest.xsf itself isn't listed, so add to file list 
	this.Files[oFileList.length] = new SolutionFile(sManifestFile, this.DstFolder + '\\manifest.xsf', oParam.CopyAllFiles, fCommentOut, '', oDDF, false);
	// if destination needs to be cabbed, add entry for .DDF
	if (this.DstCab != '')
		this.Files[oFileList.length+1] = new SolutionFile(this.DDFName, this.DstCab, oParam.CopyAllFiles, fCommentOut, '', oDDF, false);
}

/*------------------------------------------------------------------------------
	Solution object 
	SolutionCleanup method - remove temporary files and folders which were needed for cabbing
---------------------------------------------------------------------------- -*/
function SolutionCleanup(fKeepDstFolder)
{
	if (this.SrcCab != '')
		RemoveFolder(this.SrcFolder);
	if (this.DstCab != '')
		{
		if (fKeepDstFolder)
			g_oOut.Warning(this.DstFolder + ' may need to be removed manually.');
		else
			RemoveFolder(this.DstFolder);
		}
	if (g_oFS.FileExists(this.DDFName))
		{
		try 
			{
			g_oFS.DeleteFile(this.DDFName, true);
			g_oOut.Message(3, 'Deleted ' + this.DDFName);
			}
		catch (e)
			{
			g_oOut.Warning('Failed to delete ' + this.DDFName);
			}
		}
}

/*------------------------------------------------------------------------------
	SolutionView object
	constructor	build an object representing a view within the solution. Solution object contains
				an array (of at least one) of these
	Input:		sSolutionFolder - full path to folder containing source solution (may be temporary)
				oItem - XML node object (from solution's manifest.xsf) describing a view
				sDefaultName - name of default view
				sCurrentTransformName - name of current (selected) tranformation
				sCurrentTransformFileName - XSL file associated with current (selected) transformation
---------------------------------------------------------------------------- -*/
function SolutionView(sSolutionFolder, oItem, sDefaultName, sCurrentTransformName, sCurrentTransformFileName)
{
	// object properties
	this.Name = oItem.getAttributeNode("name").value;							// view name
	this.Caption = oItem.getAttributeNode("caption").value;						// view caption
	this.Transform = oItem.selectSingleNode("xsf:mainpane/@transform").value; 	// view transform (name.XSL)
	this.FullTransformPath = sSolutionFolder + '\\' + this.Transform;			// full path to transform XSL
	this.IsDefault = (sDefaultName.toUpperCase() == this.Name.toUpperCase());	// true if this transform is the default one
	this.IsCurrent = false;														// true if this transform is the selected one
	EnsureFile(this.FullTransformPath, 'Manifest specified view transform');
	if (sCurrentTransformName != '')
		{
		this.IsCurrent = (sCurrentTransformName.toUpperCase() == this.Name.toUpperCase());
		}
	else
		{
		if (sCurrentTransformFileName != '')
			this.IsCurrent = (sCurrentTransformFileName.toUpperCase() == this.Transform.toUpperCase());
		else
			this.IsCurrent = this.IsDefault;
		}
}

/*------------------------------------------------------------------------------
	SolutionFile object
	constructor	build an object representing a file within the source solution. Solution object
				contains an array of these. Additionaly, a SolutioFile object is created for
				manifest.xsf and <dst>.ddf (if the destination is to be cabbed)
	Input:		sFullSrcPath - full path to source solution (can be in %TEMP% if source is XSN)
				sFullDstPath - full path to destination solution (can be in %TEMP% if destination is XSN)
				fCopyAllFiles - reflects the state of /a command line switch
				fCommentOut - reflects the state of /c command line switch
				sTransformFile - full path to XSL file to be used for downlevel transformation
				oDDF - text file object (<dst>.DDF) if the destination is a XSN
				fCopyXSL - true if view XSLs just need to be copied, false if they need to be transformed
---------------------------------------------------------------------------- -*/
function SolutionFile(sFullSrcPath, sFullDstPath, fCopyAllFiles, fCommentOut, sTransformFile, oDDF, fCopyXSL)
{
	var oSrcFile = EnsureFile(sFullSrcPath, 'Manifest specified file ');
	// object properties
	this.Name = oSrcFile.Name;			// file name (with extension)
	this.FullSrcPath = sFullSrcPath;	// full path to file
	this.TransformFile = '';			// full path to transform 
	this.FullDstPath = sFullDstPath;	// full path to downlevel version of this file
	this.IsTemplate = false;			// true if this is TEMPLATE.XML
	this.CommentOut = fCommentOut;		// true if InfoPath specific tags are to be commented out
	this.DDF = oDDF;					// DDF file object
	this.ManifestListed = true;			// file is listed in MANIFEST.XSF
	this.Process = SolutionFileProcess;	// All files have same processing flow
	this.UpdateVersion = true;			// Always fix version number, unless turned off
	// Assign Downgrade function (method)
	switch (GetExtension(this.Name))
	{
	// XML files have to be modified for downlevel (but only if they contain any InfoPath specific tags)
	case '.XML':
		this.TransformFile = sTransformFile;
		this.IsTemplate = (this.Name.toUpperCase() == 'TEMPLATE.XML');
		this.Downgrade = SolutionFileModify;
		break;
	// XSL files are either transformed using XDown.XSL or copied verbatim
	case '.XSL':
		this.TransformFile = g_sTransformXSLName;
		this.Downgrade = (fCopyXSL ? SolutionFileCopy : SolutionFileTransform);
		break;
	// XSF is usually skipped, unless /a command line switch was specified, manifest.xsf in not listed in itself!
	case '.XSF':
		this.ManifestListed = false;
		this.Downgrade = (fCopyAllFiles ? SolutionFileModifyVersion : SolutionFileSkip);
		break;
	// JS are usually skipped, unless /a command line switch was specified
	case '.JS':
		this.Downgrade = (fCopyAllFiles ? SolutionFileCopy : SolutionFileSkip);
		break;
	// if tool has added a DDF file to solution file list, it means that the destination folder has to be cabbed into a XSN
	case '.DDF':
		this.ManifestListed = false;
		this.Downgrade = SolutionFileMakeCab;
		break;
	// all other files are copied verbatim to destination folder
	default:
		this.Downgrade = SolutionFileCopy;
		break;
	}
}

/*------------------------------------------------------------------------------
	Solution File object
	SolutionFileMakeCab method - create a XSN file (using description in <dst>.DDF)
	Input:	fExplainOnly - false to execute action, true to just display the information
			what would be done (used with /f command line switch)
---------------------------------------------------------------------------- -*/
function SolutionFileMakeCab(fExplainOnly)
{
	this.DDF.Close();
	this.DDF = null;
	if (fExplainOnly)
		return 'CREATE ' + this.FullDstPath + ' from ' + this.FullSrcPath;

	RunCommand('makecab.exe /f "' + this.FullSrcPath + '"', true, "");
	if (g_oFS.FileExists(this.FullDstPath))
		g_iCreated++;
}

/*------------------------------------------------------------------------------
	Solution File object
	SolutionFileModify method - used for XML files. If InfoPath constructs are found in the XML,
			they may be commented out (/c command line switch) or left intact (default).
			If valid InfoPath document version number is found, its least significant part is 
			incremented by 1
	Input:	fExplainOnly - false to execute action, true to just display the information
			what would be done (used with /f command line switch)
---------------------------------------------------------------------------- -*/
function SolutionFileModify(fExplainOnly)
{
	if (fExplainOnly)
		{
		if (this.IsTemplate)
			return 'MODIFY (Version' + (this.UpdateVersion ? ' ' : ' not ') + 'incremented, ' + (this.CommentOut ? 'InfoPath tags commented out,' : 'InfoPath tags intact,') + ' XSL PI to ' + this.TransformFile + ' added) [or COPY]';
		else
			return 'COPY [or MODIFY (Version' + (this.UpdateVersion ? ' ' : ' not ') + 'incremented, ' + (this.CommentOut ? 'InfoPath tags commented out,' : 'InfoPath tags intact,') + ' XSL PI to ' + this.TransformFile + ' added)]';
		}

	var fModified = false;
	var oSolVer = new SolutionVersion('solutionVersion="');// object will contain information about version update

	if (this.DDF != null)
		this.DDF.WriteLine(g_oFS.GetFileName(this.FullDstPath) + ' ' + g_oFS.GetFileName(this.FullDstPath));

	var oXML = LoadXMLFile(this.FullSrcPath);
	var oTag = oXML.firstChild;
	var oApplicationTag = null;
	var oExtensionTag = null;
	var oStyleSheetTag = null;
	while (oTag != null)
		{
		switch (oTag.nodeName)
			{
			case "mso-infoPathSolution":
				oExtensionTag = oTag;
				break;
			case "mso-application":
				oApplicationTag = oTag;
				break;
			case "xml-stylesheet":
				oStyleSheetTag = oTag;
				break;
			default:
				break;
			}
		oTag = oTag.nextSibling;
		}
	if (oExtensionTag != null)
		{
		if (this.UpdateVersion)
			{
			if (oSolVer.Update(oExtensionTag.text))
				{
				// Replace the original tag with the one with updated version number
				var oUpdatedExtensionTag = oXML.createProcessingInstruction("mso-infoPathSolution", oSolVer.sUpdatedLine);
				oXML.insertBefore(oUpdatedExtensionTag, oExtensionTag);
				oXML.removeChild(oExtensionTag);
				oExtensionTag = oUpdatedExtensionTag;
				fModified = true;
				}
			}
		if (this.CommentOut)
			{
			var oExtensionCommentTag = oXML.createComment("?mso-infoPathSolution " + oExtensionTag.text);
			oXML.insertBefore(oExtensionCommentTag, oExtensionTag);
			oXML.removeChild(oExtensionTag);
			fModified = true;
			}
		}
	if (oApplicationTag != null)
		{
		var oXSLPITag = oXML.createProcessingInstruction("xml-stylesheet", 'type="text/xsl" href="'+this.TransformFile+'"');
		if (oStyleSheetTag == null)
			{
			oXML.insertBefore(oXSLPITag, oApplicationTag.nextSibling);
			fModified = true;
			}
		else
			{
			// Update the XSL PI only if href is different!
			var sTagText = oStyleSheetTag.text;
			var sOldHref = sTagText.substr(sTagText.indexOf('href="'));
			if (sOldHref != ('href="'+this.TransformFile)+'"')
				{
				oXML.insertBefore(oXSLPITag, oStyleSheetTag);
				oXML.removeChild(oStyleSheetTag);
				fModified = true;
				}
			}
		if (this.CommentOut)
			{
			var oApplicationCommentTag = oXML.createComment("?mso-application " + oApplicationTag.text);
			oXML.insertBefore(oApplicationCommentTag, oApplicationTag);
			oXML.removeChild(oApplicationTag);
			fModified = true;
			}
		}
	oXML.save(this.FullDstPath);
	if (fModified)
		{
		g_oOut.Message(3, this.FullSrcPath + '['+oSolVer.sOldNumber + '] modified to ' + this.FullDstPath + '['+oSolVer.sNewNumber + ']');
		g_iModified++;
		}
	else
		{
		g_oOut.Message(3, this.FullSrcPath + '['+oSolVer.sOldNumber + '] copied to ' + this.FullDstPath + '[' + oSolVer.sNewNumber + ']');
		g_iCopied++;
		}
}

/*------------------------------------------------------------------------------
	Solution File object
	SolutionFileModifyVersion method - used for XSF files. 
			If valid InfoPath solution version number is found, its least significant part is 
			incremented by 1
	Input:	fExplainOnly - false to execute action, true to just display the information
			what would be done (used with /f command line switch)
---------------------------------------------------------------------------- -*/
function SolutionFileModifyVersion(fExplainOnly)
{
	if (fExplainOnly)
		return 'COPY OR MODIFY (Version incremented)';

	var fModified = false;
	var oSolVer = new SolutionVersion('solutionVersion="');// object will contain information about version update

	if (this.DDF != null)
		this.DDF.WriteLine(g_oFS.GetFileName(this.FullDstPath) + ' ' + g_oFS.GetFileName(this.FullDstPath));

	var oXSF = LoadXMLFile(this.FullSrcPath);
	var oTag = oXSF.firstChild;
	var oDocumentClassTag = null;
	while ((oTag != null) && (oDocumentClassTag == null))
		{
		if (oTag.nodeName == "xsf:xDocumentClass")
			oDocumentClassTag = oTag;
		oTag = oTag.nextSibling;
		}
	if (oDocumentClassTag != null)
		{
		var oAttrib = oDocumentClassTag.selectSingleNode("@solutionVersion");
		if (oAttrib != null)
			{
			if (oSolVer.Update('solutionVersion="'+oAttrib.text+'"'));
				{
				fModified = true;
				oAttrib.text = oSolVer.sNewNumber;
				}
			}
		}
	oXSF.save(this.FullDstPath);
	if (fModified)
		{
		g_oOut.Message(3, this.FullSrcPath + '[' + oSolVer.sOldNumber + '] modified to ' + this.FullDstPath + '[' + oSolVer.sNewNumber + ']');
		g_iModified++;
		}
	else
		{
		g_oOut.Message(3, this.FullSrcPath + '[' + oSolVer.sOldNumber + '] copied to ' + this.FullDstPath + '[' + oSolVer.sNewNumber + ']');
		g_iCopied++;
		}
}

/*------------------------------------------------------------------------------
	Solution File object
	SolutionFileTransform method - used only for solution view XSLs in solution mode (without
			/d parameter). InfoPath view XSL is treated as XML data on which XSL transform
			(using XDown.XSL) is executed. The resulting XML becomes the "downlevel view
			XSL". This process (and therefore the solution mode requires MSXML 5.0 installed
			on computer running the tool)
	Input:	fExplainOnly - false to execute action, true to just display the information
			what would be done (used with /f command line switch)
---------------------------------------------------------------------------- -*/
function SolutionFileTransform(fExplainOnly)
{
	var oXml = CreateMSXMLObject("MSXML2.DOMDocument.5.0");	// uplevel InfoPath solution view XSL
	oXml.async = false;
	oXml.validateOnParse = false;
	oXml.load(this.FullSrcPath);
	if (oXml.parseError.errorCode != 0)
		g_oOut.Error(10, ReportParseError(oXml.parseError));

	if (fExplainOnly)
		{
		return 'TRANSFORM (' + CountAndPrintNamespaceWarnings(false, oXml.documentElement.attributes, this.Name, this.FullSrcPath) + ' downlevel view warning(s))';
		}

	if (this.DDF != null)
		this.DDF.WriteLine(g_oFS.GetFileName(this.FullDstPath) + ' ' + g_oFS.GetFileName(this.FullDstPath));


	var oXsl = CreateMSXMLObject("MSXML2.FreeThreadedDOMDocument.5.0");	// XDown.XSL
	oXsl.async = false;
	oXsl.validateOnParse = false;
	oXsl.load(this.TransformFile);
	if (oXsl.parseError.errorCode != 0)
		g_oOut.Error(10, ReportParseError(oXsl.parseError));

	var oOut = CreateMSXMLObject("MSXML2.DOMDocument.5.0");	// downlevel InfoPath solution view XSL
	try
		{ 
		var oXSLT = CreateMSXMLObject("MSXML2.XSLTemplate.5.0");
		oXSLT.stylesheet = oXsl;
		var oXSLProc = oXSLT.createProcessor();
		oXSLProc.input = oXml;
		oXSLProc.output = oOut;
		oXSLProc.transform();
		}
	catch (e) 
		{
		g_oOut.Error(10, 'XSL runtime', e);
		} 
	try 
		{
		oOut.save(this.FullDstPath);
		g_oOut.Message(3, this.FullSrcPath + ' transformed to ' + this.FullDstPath);
		g_iWarned += CountAndPrintNamespaceWarnings(true, oXml.documentElement.attributes, this.Name, this.FullSrcPath);
		g_iTransformed++;
		}
	catch (e)
		{
		g_oOut.Error(2, 'Cannot write ' + this.FullDstPath, e);
		}	
}

/*------------------------------------------------------------------------------
	Solution File object
	SolutionFileCopy method - copy any solution file from source to destination folder, keeping the
			name intact
	Input:	fExplainOnly - false to execute action, true to just display the information
			what would be done (used with /f command line switch)
---------------------------------------------------------------------------- -*/
function SolutionFileCopy(fExplainOnly)
{
	if (fExplainOnly)
		return 'COPY';

	if (this.DDF != null)
		this.DDF.WriteLine(g_oFS.GetFileName(this.FullDstPath) + ' ' + g_oFS.GetFileName(this.FullDstPath));

	try 
		{
		if (g_oFS.FileExists(this.FullDstPath))
			g_oFS.DeleteFile(this.FullDstPath, true);
		g_oFS.CopyFile(this.FullSrcPath, this.FullDstPath, true);
		g_iCopied++;
		g_oOut.Message(3, this.FullSrcPath + ' copied to ' + this.FullDstPath);
		}
	catch (e)
		{
		g_oOut.Error(2, ' - cannot copy ' + this.FullSrcPath + ' to ' + this.FullDstPath, e);
		}
}

/*------------------------------------------------------------------------------
	Solution File object
	SolutionFileSkip method - don't do anything with the solution file, except count skipping 
	Input:	fExplainOnly - false to execute action, true to just display the information
			what would be done (used with /f command line switch)
---------------------------------------------------------------------------- -*/
function SolutionFileSkip(fExplainOnly)
{
	if (fExplainOnly)
		return 'SKIP';
	g_iSkipped++;
	g_oOut.Message(3, 'Skipping ' + this.FullSrcPath);
}

/*------------------------------------------------------------------------------
	Solution File object
	SolutionFileProcess method
	Input:	i - number of file being processed
			fForceUpdate - state of /! flag
			fExplainOnly - false to execute action, true to just display the information
			what would be done (used with /f command line switch)
---------------------------------------------------------------------------- -*/
function SolutionFileProcess(i, fForceUpdate, fExplainOnly)
{
	var fPreserveDst = (!fForceUpdate && g_oFS.FileExists(this.FullDstPath));
	if (fExplainOnly)
		{
		g_oOut.Echo('------------------------------');
		g_oOut.Echo('File #: ' + (i+1) + (this.ManifestListed ? '' : ' (not listed in manifest.xsn)'));
		g_oOut.Echo('Src   : ' + this.FullSrcPath);
		g_oOut.Echo('Dst   : ' + this.FullDstPath);
		g_oOut.Echo('Action: ' + (fPreserveDst ? 'None (destination exists and no update specified)' : this.Downgrade(true)));
		}
	else
		{
		if (fPreserveDst)
			g_oOut.Warning('Destination ' + this.FullDstPath + ' already exists.');
		else
			this.Downgrade(false);
		}
}

/*------------------------------------------------------------------------------
	Output object
	constructor	build an object useful for outputting console messages (and breaking tool
				execution in case of a fatal error)
	Input:		iVerbosityLevel - value set by /v command line option (higher numbers
				result in more output text written while running the tool)
---------------------------------------------------------------------------- -*/
function Output(iVerbosityLevel)
{
	this.Verbosity = iVerbosityLevel;
	this.Error = OutputError;
	this.Message = OutputMessage;
	this.Warning = OutputWarning;
	this.Echo = OutputEcho;
}

/*------------------------------------------------------------------------------
	Output object
	OutputMessage method - print on the console only if message importance is higher then set
				verbosity level
	Input:		iVLev - message importance
				sMsg - message to be displayed
---------------------------------------------------------------------------- -*/
function OutputMessage(iVLev,sMsg)
{
	if (this.Verbosity >= iVLev) 
		this.Echo(sMsg);
}

/*------------------------------------------------------------------------------
	Output object
	OutputError method - print error message and terminate tool with a set error level
	Input	iErrCode - error level to return to tool caller (console execution environment)
			sErrMsg - error message to display
			oException - if not null, assumed to be JScript exception object and information
				about it will be displayed along with sErrMsg
---------------------------------------------------------------------------- -*/
function OutputError(iErrCode, sErrMsg, oException)
{
	if (oException == undefined)
		{
		this.Echo('ERROR: ' + sErrMsg); 
		}
	else
		{
		var iErrCode = (oException.number & 0xFFFF);
		var iErrFacility = ((oException.number >> 16) & 0x7FF);
		this.Echo('ERROR: ' + oException.description + ' ' + sErrMsg + " (Facility: " + iErrFacility + " Code: " + iErrCode + ")");
		}
	WScript.Quit(iErrCode);		
}

/*------------------------------------------------------------------------------
	Output object
	OutputWarning method - always print a warning message to console
	Input	sWarningMsg - warning message to display
---------------------------------------------------------------------------- -*/
function OutputWarning(sWarningMsg)
{
	this.Echo('WARNING: ' + sWarningMsg); 
}

/*------------------------------------------------------------------------------
	Output object
	OutputWarning method - wrapper around WScript.Echo()
---------------------------------------------------------------------------- -*/
function OutputEcho(sMsg)
{
	WScript.Echo(sMsg);
}


/*------------------------------------------------------------------------------
	SolutionVersion object 
	constructor	build a representation of the source solution version. See member descriptions below
	Input: 		sVersionAttribName - name of the attribute containing version number
---------------------------------------------------------------------------- -*/

function SolutionVersion(sVersionAttribName)
{
	this.sAttribName = sVersionAttribName;	// attribute name which contains the version in format x.y.z.u
	this.sUpdatedLine = '';					// updated line (or same as original if contains no version attribute)
	this.sOldNumber = '';					// old (x.y.z.u)
	this.sNewNumber = '';					// new (x.y.x.u+1)
	this.fUpdated = false;					// update accomplished
	this.Update = SolutionVersionUpdate;	// worker method
}

/*------------------------------------------------------------------------------
	SolutionVersion object 
	SolutionVersionUpdate method - search for version attribute in string and update if found
	Input:	sLine - line which may contain version attribute
	Output:	true if version was found in sLine and updated correctly
---------------------------------------------------------------------------- -*/
function SolutionVersionUpdate(sLine)
{
	this.fUpdated = false;	
	this.sUpdatedLine = sLine;
	var iVersionPos = sLine.indexOf(this.sAttribName);
	if (iVersionPos >= 0)
		{
		this.sOldNumber = sLine.substring(iVersionPos + this.sAttribName.length, sLine.indexOf('"',iVersionPos + this.sAttribName.length));
		var aVersion = this.sOldNumber.split('.')
		if (aVersion.length == 4)
			{
			aVersion[3] = parseInt(aVersion[3])+1;
			this.sNewNumber = aVersion.join('.');
			this.sUpdatedLine = sLine.replace(this.sAttribName + this.sOldNumber + '"', this.sAttribName + this.sNewNumber + '"');
			this.fUpdated = true;
			}
		else
			{
			g_oOut.Warning('No version update - attribute not in expected format.');
			}
		}
	return this.fUpdated;
}

/*------------------------------------------------------------------------------
	CreateMSXMLObject helper function
	Input:	sAutomationName - object's ProgID
	Output:	MXSML object, or error if could not be created
---------------------------------------------------------------------------- -*/
function CreateMSXMLObject(sAutomationName)
{
	var oMSXML = null;
	try
		{
		oMSXML = new ActiveXObject(sAutomationName);
		}
	catch (e)
		{
		g_oOut.Error(10, ' - cannot create ' + sAutomationName + '. InfoPath SDK is may not be correctly installed.', e);
		}
	return oMSXML;
}

/*------------------------------------------------------------------------------
	ReportParseError helper function
	Input:	error - exception object
	Output:	string containing information about the error
---------------------------------------------------------------------------- -*/
function ReportParseError(error)
{
	var sCaretSpace = "";
	var sMsg = "XML Error loading '" + error.url + "\n" + error.reason + "\n";
	for (var i = 1; i < error.linepos; i++) 
		sCaretSpace += " ";
	if (error.line > 0)
	    sMsg += "at line " + error.line + ", character " + error.linepos + "\n" + error.srcText + "\n" + sCaretSpace + "^";
	return sMsg;
}

/*------------------------------------------------------------------------------
	GetExtension helper function
	Input:	sFileName - file name or full path
	Output:	file extension including the dot, upper cased (used for detecting file types)
---------------------------------------------------------------------------- -*/
function GetExtension(sFileName)
{
	var sExt = '.' + g_oFS.GetExtensionName(sFileName);
	return sExt.toUpperCase();
}

/*------------------------------------------------------------------------------
	RunCommand helper function - execute an arbitrary console-launched command
	Input:	sCmdLine - command line to be executed 
			fWaitToReturn - if true, wait for the command to complete execution before continuing
				tool execution
			sWarningMsg - warning to display if command fails. If empty, fail if command was
				unsuccessful
---------------------------------------------------------------------------- -*/
function RunCommand(sCmdLine, fWaitToReturn, sWarningMsg)
{
	g_oOut.Message(1,'EXECUTING: ' + sCmdLine);
	try
		{
		var iRetCode = g_oShell.Run("cmd.exe /c " + sCmdLine,7,fWaitToReturn);
		if (iRetCode != 0)
			g_oOut.Error(128 + iRetCode, "Command failed with return code " + iRetCode + ".");
		}
	catch(e)
		{
		if (sWarningMsg != '')
			g_oOut.Warning(sWarningMsg);
		else
			g_oOut.Error(128, "Executing '" + sCmdLine + "' failed.");
		}
}

/*------------------------------------------------------------------------------
 	RemoveFolder helper function - delete any folder (if permitted by file locking and security)
 	Input:	sDirName - relative or apsolute path to folder to be removed
---------------------------------------------------------------------------- -*/
function RemoveFolder(sDirName)
{
	if (g_oFS.FolderExists(sDirName))
		RunCommand('cmd.exe /c rd /s /q "' + sDirName + '"',true, "");
}

/*------------------------------------------------------------------------------
	CreateFolder helper function
 	Input:	sDirName - relative or apsolute path to folder to be created (will create intermediate
 				folders too, command shell extensions are enabled for that purpose)
---------------------------------------------------------------------------- -*/
function CreateFolder(sDirName)
{		
	if (!g_oFS.FolderExists(sDirName))
		RunCommand('cmd.exe /e:on /c md "' + sDirName + '"', true, '');
	return (g_oFS.FolderExists(sDirName));
}

/*------------------------------------------------------------------------------
 	PrintBanner helper function - display short information about this tool
---------------------------------------------------------------------------- -*/
function PrintBanner()
{
	g_oOut.Echo("----------------------------------------------");
	g_oOut.Echo(" InfoPath Downlevel document creation tool V" + gc_iVer + "." + gc_iRev);
	g_oOut.Echo("         (c) Microsoft Corp., 2002.                ");
	g_oOut.Echo("----------------------------------------------");	
}

/*------------------------------------------------------------------------------
 CountAndReportTouchedFiles helper function - display short report about how many files have 
 	been created / overwritten by the tool
 	Output:	total count of changed files
---------------------------------------------------------------------------- -*/
function CountAndReportTouchedFiles()
{
	g_oOut.Echo('\n' + g_iCopied + ' copied, ' + g_iTransformed + ' transformed (with ' + g_iWarned + ' warning(s)), ' + g_iModified+' modified, ' + g_iSkipped + ' skipped, ' + g_iCreated + ' created.');
	return (g_iCopied+g_iTransformed+g_iModified+g_iCreated);
}

/*------------------------------------------------------------------------------
 	PrintHelp helper function - display short information about tool usage
---------------------------------------------------------------------------- -*/
function PrintHelp()
{
	g_oOut.Echo("Downlevel mode:");
	g_oOut.Echo("   XDown.js /d[ownlevel] srcsol dstdir [/o] [/c] [/!] [/f] [/a] [/t:transform] [/v:level]");
	g_oOut.Echo("Update mode:");
	g_oOut.Echo("   XDown.js /u[pdate] srcsol dstsol /x:url|path [/o] [/!] [/f] [/v:level]");
	g_oOut.Echo("List views mode:");
	g_oOut.Echo("   XDown.js /l[ist] srcsol [/v:level] [/t:transform]");
	g_oOut.Echo("Patch document mode:");
	g_oOut.Echo("   XDown.js /p[atch] srcdoc dstdoc /x:url|path [/o] [/c] [/!] [/f] [/v:level] ");
	g_oOut.Echo("");
	g_oOut.Echo("srcsol    Path to solution's source folder/XSN containing manifest.xsf");
	g_oOut.Echo("dstdir    Path to destination folder that will contain downlevel files");	
	g_oOut.Echo("dstsol    Path to destination folder/XSN that will contain updated files");	
	g_oOut.Echo("srcdoc    Path to full view (source) document file");
	g_oOut.Echo("dstdoc    Path to downlevel (destination) document file");	
	g_oOut.Echo("/o        Open downlevel xml after conversion (dstsol\\template.xml or dstdoc)");
	g_oOut.Echo("/c        Comment out extension and application tags in downlevel XMLs");
	g_oOut.Echo("          (To enable viewing downlevel XML on machines with InfoPath installed)");
	g_oOut.Echo("/!        Force destination update");
	g_oOut.Echo("/f        Just list file information and potential downlevel action for each");
	g_oOut.Echo("/a        Copy all solution files to destination (do not skip .JS and .XSF)");
	g_oOut.Echo("/v        Verbosity level: 0 (tacit) - 4 (eloquent)");
	g_oOut.Echo("/x        URL or path to downlevel transform XSL file");	
	g_oOut.Echo("/t        Downlevel transform file (.XSL) or name (default if omitted)");	
}

/*------------------------------------------------------------------------------
	GenerateNamespace warnings - display a warning for each problematic namespace
			actually used (not just defined) in the view XSL
	Input:	fPrint - true to print warning, in addition of counting them
			oXslDocAttrib - object representing a collection of attributes on the XSL's document tag
			sXslName - name of the view XSL file
			sXslFullPath - full path to the view XSL file
	Output:	number of namespaces used which may cause problems in downlevel
---------------------------------------------------------------------------- -*/
function CountAndPrintNamespaceWarnings(fPrint, oXslDocAttrib, sXslName, sXslFullPath)
{
	var iCntWarnings = 0;	// warnings counter
	var sXslText = '';		// contents of XSL file - read it only when necessary (see below)
	for (var i = 0; i < oXslDocAttrib.length; i++)
		{
		var sName = oXslDocAttrib(i).name;
		var sValue = oXslDocAttrib(i).value;
		// process only xmlns:foo attributes on XSL's document element
		if (sName.indexOf('xmlns:') == 0)
			{
			// check namespace against the list of known namespaces which may cause problems
			var fIsWarningNamespace = false;
			for (var j = 0; j < gc_WarningNamespace.length; j++)
				{
				if (sValue == gc_WarningNamespace[j])
					{
					fIsWarningNamespace = true;
					break;
					}
				}
			// if questionable namespace found, see if it is used anywhere in XSL
			if (fIsWarningNamespace)
				{
				sName = sName.substr(6);
				// lazy read of XSL file contents
				if (sXslText == '')
					{
					var oXslFile = OpenFile(sXslFullPath, gc_ForReading);
					sXslText = oXslFile.ReadAll();
					oXslFile.Close();
					}
				if (sXslText.indexOf(sName+':') >= 0)
					{
					iCntWarnings ++;
					if (fPrint)
						g_oOut.Warning('Downlevel may not display correctly! (' + sXslName + ' uses xmlns:' + sName + '="' + sValue + '")');
					}
				}
			}
		}
	return iCntWarnings;
}

/*------------------------------------------------------------------------------
	EnsureFile helper function - check if a file exists
	Input:	sFilename - relative of absolute path to a file
			sErrorMsg - message to display if file not found
	Output:	file object if file found, error (and tool termination) otherwise
---------------------------------------------------------------------------- -*/
function EnsureFile(sFilename, sErrorMsg)
{
	var sFileN = g_oFS.GetAbsolutePathName(sFilename);
	if (!g_oFS.FileExists(sFileN))
		g_oOut.Error(1, sErrorMsg + sFileN + ' not found.');
	return g_oFS.GetFile(sFileN);
}

/*------------------------------------------------------------------------------
	EnsureValue helper function - check if there is a valid object or non-empty string
	Input:	val - object or string to be checked
			sErrorMsg - message to display if val is not acceptable
	Output:	val if acceptable, error (and tool termination) otherwise
---------------------------------------------------------------------------- -*/
function EnsureValue(val, sErrorMsg)
{
//	WScript.Echo('['+typeof val+']['+val+']');
	if ((val == null) || (val == undefined) || (typeof val == 'string' && val == ''))
		g_oOut.Error(2, sErrorMsg)
	return val;
}

/*------------------------------------------------------------------------------
	LoadXMLFile helper function - create a XML DOM object from XML file on disk
 	Input:	sFileName - relative or absolute path to a XML file
 	Output:	XML DOM object if successful, error and tool termination otherwise
---------------------------------------------------------------------------- -*/
function LoadXMLFile(sFileName)
{
	EnsureFile(sFileName, 'Required file ');
	var oXML = CreateMSXMLObject("Microsoft.XMLDOM");
	if (oXML.load(sFileName))
		g_oOut.Message(4,'Contents of ' + sFileName + ' loaded into XML DOM.');
	else
		g_oOut.Error(4, 'Loading global data from ' + sFileName);
	return oXML;
}

/*------------------------------------------------------------------------------
 	EnsureURL helper function - check if an URL has valid syntax
 	Input:	sURL - URL string to be checked
 	Output:	sURL if valid, error and tool termination otherwise
---------------------------------------------------------------------------- -*/
function EnsureURL(sRawURL)
{
	var sError = '';
	var rgBackslash = /\\/gi;
	var sURL = sRawURL.replace(rgBackslash,'/'); // Replace all \ with /
	WScript.Echo(sRawURL+'='+sURL);
	var aURLParts = sURL.split('/');
	for (var i = 0; i < aURLParts.length; i++)
	{
		var sPart = aURLParts[i];
		switch (i)
		{
		// valid protocol name must end with a colon
		case 0:
			if ((sPart == '') || (sPart.charAt(sPart.length-1) != ':'))
				sError='Invalid base url (protocol).';
			break;
		// two forward slashes expected after protocol name
		case 1:	
			if (sPart != '')
				sError = 'Invalid base url (// missing).';
			break;
		// server file name must have at least one character before the dot
		case aURLParts.length-1:
			if (!(((sPart.length > 4) && (GetExtension(sPart) == '.XSL')) || ((sPart.length > 5) && (GetExtension(sPart) == '.XSLT'))))
				sError='Invalid or missing XSL name.';
			break;
		// server (folder) names cannot be blank
		default:
			if (sPart == '')
				sError = 'Invalid base url (address or folder).';
			break;
		}
		if (sError != '')
			g_oOut.Error(1, sError);
	}
	if (i < 4)
		g_oOut.Error(1, 'Incomplete URL.');
	return sURL;
}

/*------------------------------------------------------------------------------
	GeFullFolderPathAndName helper function - make absolute folder path and create it if needed
	Input:	sFolderName - relative or absolute folder path
			fClear - if true, clean the folder (remove and recreate)
			fCreate - if true, create the requested folder
	Output:	full path to folder if it exists (or was created)
---------------------------------------------------------------------------- -*/
function GetFullFolderPathAndName(sFoldername, fClear, fCreate)
{
	if (g_oFS.FolderExists(sFoldername))
		{
		if (fClear)
			{
			RemoveFolder(sFoldername);
			CreateFolder(sFoldername);
			}
		}
	else
		{
		if (fCreate)
			CreateFolder(sFoldername);
		else
			g_oOut.Error(2, 'Folder ' + sFoldername + ' not found.');
		}  
	var of = g_oFS.GetFolder(sFoldername);
	return(of.Path.toUpperCase());
}

/*------------------------------------------------------------------------------
	ViewFile helper function - invoke the standard shell action ("Open") on file
	Input:	sFileName - relative / absolute path to file to be viewed. Files of that type must
				be registered with some application for shell to be able to view the file
---------------------------------------------------------------------------- -*/
function ViewFile(sFileName)
{
	if (g_oFS.FileExists(sFileName))
		RunCommand('"' + sFileName + '"', false, 'Could not open ' + sFileName + '. Probably no application associated with this file type.');
	else
		g_oOut.Warning('File ' + sFileName + ' not found for opening.');
}

/*------------------------------------------------------------------------------
	OpenFile helper function - return an object representing a text file to be read from or 
		written into
	Input:	sFileName - relative / absolute path to file to be opened
			Mode - one of: gc_ForReading, gc_ForWriting, gc_ForAppending
			fOverwrite - if true and Mode == gc_ForWriting, create a new empty file
---------------------------------------------------------------------------- -*/
function OpenFile(sFileName, Mode, fOverwrite)
{
	var oFile = null;
	try
		{
		oFile = g_oFS.OpenTextFile(sFileName, Mode, fOverwrite);
		}
	catch (e)
		{
		g_oOut.Error(4, '- cannot open ' + sFileName, e);
		}
	return oFile;
}

/*------------------------------------------------------------------------------
 helper function
---------------------------------------------------------------------------- -*/
function GetFullFilePathAndName(sFilename, fRequire)
{
	var sFullPath = g_oFS.GetAbsolutePathName(sFilename);
	if (!g_oFS.FileExists(sFullPath) && fRequire)
		g_oOut.Error(1, 'File ' + sFullPath + ' not found.');
	return sFullPath;
}


// SIG // Begin signature block
// SIG // MIIaJAYJKoZIhvcNAQcCoIIaFTCCGhECAQExCzAJBgUr
// SIG // DgMCGgUAMGcGCisGAQQBgjcCAQSgWTBXMDIGCisGAQQB
// SIG // gjcCAR4wJAIBAQQQEODJBs441BGiowAQS9NQkAIBAAIB
// SIG // AAIBAAIBAAIBADAhMAkGBSsOAwIaBQAEFMn3VfbAKHiq
// SIG // b8OqjJ/ebG5JyQTaoIIUvDCCArwwggIlAhBKGdI4jIJZ
// SIG // HKVdc18VXdyjMA0GCSqGSIb3DQEBBAUAMIGeMR8wHQYD
// SIG // VQQKExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMRcwFQYD
// SIG // VQQLEw5WZXJpU2lnbiwgSW5jLjEsMCoGA1UECxMjVmVy
// SIG // aVNpZ24gVGltZSBTdGFtcGluZyBTZXJ2aWNlIFJvb3Qx
// SIG // NDAyBgNVBAsTK05PIExJQUJJTElUWSBBQ0NFUFRFRCwg
// SIG // KGMpOTcgVmVyaVNpZ24sIEluYy4wHhcNOTcwNTEyMDAw
// SIG // MDAwWhcNMDQwMTA3MjM1OTU5WjCBnjEfMB0GA1UEChMW
// SIG // VmVyaVNpZ24gVHJ1c3QgTmV0d29yazEXMBUGA1UECxMO
// SIG // VmVyaVNpZ24sIEluYy4xLDAqBgNVBAsTI1ZlcmlTaWdu
// SIG // IFRpbWUgU3RhbXBpbmcgU2VydmljZSBSb290MTQwMgYD
// SIG // VQQLEytOTyBMSUFCSUxJVFkgQUNDRVBURUQsIChjKTk3
// SIG // IFZlcmlTaWduLCBJbmMuMIGfMA0GCSqGSIb3DQEBAQUA
// SIG // A4GNADCBiQKBgQDTLiDwaHwsLS6BHLEGsqcLtxENV9pT
// SIG // 2HXjyTMqstT2CVs08+mQ/gkM0NsbWrnN5/aIsZ3AhyXr
// SIG // fVgQc2p4y3EV/cZY9imrWF6WBP0tYhFYgRzKcZTVIlgv
// SIG // 1cwUBYQ2upSqtE1K6e47Iq1WmX4hnGyGwEpHl2q0pjbV
// SIG // /Akt07Q5mwIDAQABMA0GCSqGSIb3DQEBBAUAA4GBAGFV
// SIG // Dj57x5ISfhEQjiLM1LMTK1voROQLeJ6kfvOnB3Ie4lnv
// SIG // zITjiZRM205h77Ok+0Y9UDQLn3BW9o4qfxfO5WO/eWkH
// SIG // cy6wlSiK9e2qqdJdzQrKEAmPzrOvKJbEeSmEktz/umdC
// SIG // SKaQEOS/YficU+WT0XM/+P2dT4SsVdH9EWNjMIIEAjCC
// SIG // A2ugAwIBAgIQCHptXG9ik0+6xP1D4RQYnTANBgkqhkiG
// SIG // 9w0BAQQFADCBnjEfMB0GA1UEChMWVmVyaVNpZ24gVHJ1
// SIG // c3QgTmV0d29yazEXMBUGA1UECxMOVmVyaVNpZ24sIElu
// SIG // Yy4xLDAqBgNVBAsTI1ZlcmlTaWduIFRpbWUgU3RhbXBp
// SIG // bmcgU2VydmljZSBSb290MTQwMgYDVQQLEytOTyBMSUFC
// SIG // SUxJVFkgQUNDRVBURUQsIChjKTk3IFZlcmlTaWduLCBJ
// SIG // bmMuMB4XDTAxMDIyODAwMDAwMFoXDTA0MDEwNjIzNTk1
// SIG // OVowgaAxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8w
// SIG // HQYDVQQLExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTsw
// SIG // OQYDVQQLEzJUZXJtcyBvZiB1c2UgYXQgaHR0cHM6Ly93
// SIG // d3cudmVyaXNpZ24uY29tL3JwYSAoYykwMTEnMCUGA1UE
// SIG // AxMeVmVyaVNpZ24gVGltZSBTdGFtcGluZyBTZXJ2aWNl
// SIG // MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA
// SIG // wHphh+uypwNjGysaYd6AtxUdoIuQPbsnkoQUOeuFzimS
// SIG // BmZIpANPjehPp/CvXtEvGceR8bWee5Ehzun/407w/K+V
// SIG // WLhjLeaO9ikYzXCOUMPtlrtA274l6EJV1vaF8gbni5kc
// SIG // MfMDD9RMnCQq3Bsbj4LzsO+nTeMUp+CP1sdowmFYqXLU
// SIG // +DBIT9kvb2Mg2YnKgnvCS7woxYFo5+aCQKxGOqD5PzbN
// SIG // TLtUQlp6ZXv+hOTHR1SsuT3sgMca98QzgYHJKpX7f146
// SIG // h5AU28wudfLva+Y9qWC+QgGqT6pbqD8iMZ8SFflzoR6C
// SIG // iwQr6kYCTG2PH1AulUsqeAaEdD2RjyxHMQIDAQABo4G4
// SIG // MIG1MEAGCCsGAQUFBwEBBDQwMjAwBggrBgEFBQcwAYYk
// SIG // aHR0cDovL29jc3AudmVyaXNpZ24uY29tL29jc3Avc3Rh
// SIG // dHVzMAkGA1UdEwQCMAAwRAYDVR0gBD0wOzA5BgtghkgB
// SIG // hvhFAQcBATAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3
// SIG // dy52ZXJpc2lnbi5jb20vcnBhMBMGA1UdJQQMMAoGCCsG
// SIG // AQUFBwMIMAsGA1UdDwQEAwIGwDANBgkqhkiG9w0BAQQF
// SIG // AAOBgQAt809jYCwY2vUkD1KzDOuzvGeFwiPtj0YNzxpN
// SIG // vvN8eiAwMhhoi5K7Mpnwk7g7FQYnez4CBgCkIZKEEwrF
// SIG // mOVAV8UFJeivrxFqqeU7y+kj9pQpXUBV86VTncg2Ojll
// SIG // CHNzpDLSr6y/xwU8/0Xsw+jaJNHOY64Jp/viG+P9QQpq
// SIG // ljCCBBIwggL6oAMCAQICDwDBAIs8PIgR0T72Y+zfQDAN
// SIG // BgkqhkiG9w0BAQQFADBwMSswKQYDVQQLEyJDb3B5cmln
// SIG // aHQgKGMpIDE5OTcgTWljcm9zb2Z0IENvcnAuMR4wHAYD
// SIG // VQQLExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xITAfBgNV
// SIG // BAMTGE1pY3Jvc29mdCBSb290IEF1dGhvcml0eTAeFw05
// SIG // NzAxMTAwNzAwMDBaFw0yMDEyMzEwNzAwMDBaMHAxKzAp
// SIG // BgNVBAsTIkNvcHlyaWdodCAoYykgMTk5NyBNaWNyb3Nv
// SIG // ZnQgQ29ycC4xHjAcBgNVBAsTFU1pY3Jvc29mdCBDb3Jw
// SIG // b3JhdGlvbjEhMB8GA1UEAxMYTWljcm9zb2Z0IFJvb3Qg
// SIG // QXV0aG9yaXR5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
// SIG // MIIBCgKCAQEAqQK9wXDmO/JOGyifl3heMOqiqY0lX/j+
// SIG // lUyjt/6doiA+fFGim6KPYDJr0UJkee6sdslU2vLrnIYc
// SIG // j5+EZrPFa3piI9YdPN4PAZLolsS/LWaammgmmdA6LL8M
// SIG // tVgmwUbnCj44liypKDmo7EmDQuOED7uabFVhrIJ8oWAt
// SIG // d0zpmbRkO5pQHDEIJBSfqeeRKxjmPZhjFGBYBWWfHTdS
// SIG // h/en75QCxhvTv1VFs4mAvzrsVJROrv2nem10Tq8YzJYJ
// SIG // KCEAV5BgaTe7SxIHPFb/W/ukZgoIptKBVlfvtjteFoF3
// SIG // BNr2vq6Alf6wzX/WpxpyXDzKvPAIoyIwswaFybMgdxOF
// SIG // 3wIDAQABo4GoMIGlMIGiBgNVHQEEgZowgZeAEFvQcO9p
// SIG // cp4jUX4Usk2O/8uhcjBwMSswKQYDVQQLEyJDb3B5cmln
// SIG // aHQgKGMpIDE5OTcgTWljcm9zb2Z0IENvcnAuMR4wHAYD
// SIG // VQQLExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xITAfBgNV
// SIG // BAMTGE1pY3Jvc29mdCBSb290IEF1dGhvcml0eYIPAMEA
// SIG // izw8iBHRPvZj7N9AMA0GCSqGSIb3DQEBBAUAA4IBAQCV
// SIG // 6AvAjfOXGDXtuAEk2HcR81xgMp+eC8s+BZGIj8k65iHy
// SIG // 8FeTLLWgR8hi7/zXzDs7Wqk2VGn+JG0/ycyq3gV83TGN
// SIG // PZ8QcGq7/hJPGGnA/NBD4xFaIE/qYnuvqhnIKzclLb5l
// SIG // oRKKJQ9jo/dUHPkhydYV81KsbkMyB/2CF/jlZ2wNUfa9
// SIG // 8VLHvefEMPwgMQmIHZUpGk3VHQKl8YDgA7Rb9LHdyFfu
// SIG // ZUnHUlS2tAMoEv+Q1vAIj364l8WrNyzkeuSod+N2oADQ
// SIG // aj/B0jaK4EESqDVqG2rbNeHUHATkqEUEyFozOG5NHA1i
// SIG // twqijNPVVD9GzRxVpnDbEjqHk3Wfp9KgMIIEyTCCA7Gg
// SIG // AwIBAgIQaguZT8AA3qoR1NhAmqi+5jANBgkqhkiG9w0B
// SIG // AQQFADBwMSswKQYDVQQLEyJDb3B5cmlnaHQgKGMpIDE5
// SIG // OTcgTWljcm9zb2Z0IENvcnAuMR4wHAYDVQQLExVNaWNy
// SIG // b3NvZnQgQ29ycG9yYXRpb24xITAfBgNVBAMTGE1pY3Jv
// SIG // c29mdCBSb290IEF1dGhvcml0eTAeFw0wMDEyMTAwODAw
// SIG // MDBaFw0wNTExMTIwODAwMDBaMIGmMQswCQYDVQQGEwJV
// SIG // UzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH
// SIG // UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBv
// SIG // cmF0aW9uMSswKQYDVQQLEyJDb3B5cmlnaHQgKGMpIDIw
// SIG // MDAgTWljcm9zb2Z0IENvcnAuMSMwIQYDVQQDExpNaWNy
// SIG // b3NvZnQgQ29kZSBTaWduaW5nIFBDQTCCASAwDQYJKoZI
// SIG // hvcNAQEBBQADggENADCCAQgCggEBAKKEFVPYCzAONJX/
// SIG // OhvC8y97bTcjTfPSjOX9r/3FAjQfJMflodxU7H4CdEer
// SIG // 2zJYFhRRKTjxfrK0jDpHtTlOblTCMQw6bfvNzctQnBuu
// SIG // p9jZSiY/tcXLj5biSfJt2OmWPt4Fz/CmVTetL2DNgGFC
// SIG // oUlUSg8Yt0vZk5kwWkd1ZLTTu922qwydT7hzOxg6qrSH
// SIG // jLCIsE1PH04RtTOA3w06ZG9ExzS9SpObvKYd+QUjTmAp
// SIG // j8wq8oSama2o2wpwe9Y0QZClt2bHXBsdozMOm1QDGj+Y
// SIG // kLjM5z0EdEMcj/c55rOsSHprKg5iAWE5dm79PpgHSxTx
// SIG // AUb9FQDgR9pP5AXkgCUCAQOjggEoMIIBJDATBgNVHSUE
// SIG // DDAKBggrBgEFBQcDAzCBogYDVR0BBIGaMIGXgBBb0HDv
// SIG // aXKeI1F+FLJNjv/LoXIwcDErMCkGA1UECxMiQ29weXJp
// SIG // Z2h0IChjKSAxOTk3IE1pY3Jvc29mdCBDb3JwLjEeMBwG
// SIG // A1UECxMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSEwHwYD
// SIG // VQQDExhNaWNyb3NvZnQgUm9vdCBBdXRob3JpdHmCDwDB
// SIG // AIs8PIgR0T72Y+zfQDAQBgkrBgEEAYI3FQEEAwIBADAd
// SIG // BgNVHQ4EFgQUKVy5G7bNM+67nll99+XKLsQNNCgwGQYJ
// SIG // KwYBBAGCNxQCBAweCgBTAHUAYgBDAEEwCwYDVR0PBAQD
// SIG // AgFGMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEE
// SIG // BQADggEBAEVY4ppBf/ydv0h3d66M2eYZxVe0Gr20uV8C
// SIG // oUVqOVn5uSecLU2e/KLkOIo4ZCJC37kvKs+31gbK6yq/
// SIG // 4BqFfNtRCD30ItPUwG2IgRVEX2SDZMSplCyK25A3Sg+3
// SIG // 6NRhj3Z24dkl/ySElY0EVlSUoRw6PoK87qWHjByMS3lf
// SIG // tUn6XjJpOh9UrXVN32TnMDzbZElE+/vEHEJx5qA9Re5r
// SIG // AJ+sQr26EbNW5PvVoiqB2B9OolW+J49wpqJsG/9UioK8
// SIG // gUumobFmeqkXp8sGwEfrprPpMRVTPSoEv/9zSNyLJ0P8
// SIG // Y+juJIdbvjbR6DH1Mtle33l6ujCsaYZK+4wRvxuNVFkw
// SIG // ggUPMIID96ADAgECAgphBxFDAAAAAAA0MA0GCSqGSIb3
// SIG // DQEBBQUAMIGmMQswCQYDVQQGEwJVUzETMBEGA1UECBMK
// SIG // V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwG
// SIG // A1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSswKQYD
// SIG // VQQLEyJDb3B5cmlnaHQgKGMpIDIwMDAgTWljcm9zb2Z0
// SIG // IENvcnAuMSMwIQYDVQQDExpNaWNyb3NvZnQgQ29kZSBT
// SIG // aWduaW5nIFBDQTAeFw0wMjA1MjUwMDU1NDhaFw0wMzEx
// SIG // MjUwMTA1NDhaMIGhMQswCQYDVQQGEwJVUzETMBEGA1UE
// SIG // CBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEe
// SIG // MBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSsw
// SIG // KQYDVQQLEyJDb3B5cmlnaHQgKGMpIDIwMDIgTWljcm9z
// SIG // b2Z0IENvcnAuMR4wHAYDVQQDExVNaWNyb3NvZnQgQ29y
// SIG // cG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
// SIG // ggEKAoIBAQCqmb05qBgn9Cs9C0w/fHcup8u10YwNwjp0
// SIG // 15O14KBLP1lezkVPmnkp8UnMGkfuVcIIPhIg+FXy7l/T
// SIG // 4MqWvDDe/ljIJzLQhVTo8JEQu/MrvhnlA5sLhh3zsDmM
// SIG // uP0LHTxzJqxXK8opohWQghXid6NAUgOLncJwuh/pNPbz
// SIG // NZJOVYP42jC2IN5XBrVaQgbeWcvy36a9FUdxGSUj0stv
// SIG // mxl532pb8XYFeSn8w1bKj0QIhVWKy8gPRktVy4yWd0qH
// SIG // 6KlBBsf/DeloV2Nyw2lXtEPPMjow3Bvp1UMmKnn+ldsi
// SIG // ZyTJL9A04+b7UUmGuDzQJV/W7J4DYYepaEDH+OID5s8F
// SIG // AgMBAAGjggFAMIIBPDAOBgNVHQ8BAf8EBAMCBsAwEwYD
// SIG // VR0lBAwwCgYIKwYBBQUHAwMwHQYDVR0OBBYEFGvIxlEg
// SIG // 8LQv06C2rn9eJrK4h1IpMIGpBgNVHSMEgaEwgZ6AFClc
// SIG // uRu2zTPuu55Zffflyi7EDTQooXSkcjBwMSswKQYDVQQL
// SIG // EyJDb3B5cmlnaHQgKGMpIDE5OTcgTWljcm9zb2Z0IENv
// SIG // cnAuMR4wHAYDVQQLExVNaWNyb3NvZnQgQ29ycG9yYXRp
// SIG // b24xITAfBgNVBAMTGE1pY3Jvc29mdCBSb290IEF1dGhv
// SIG // cml0eYIQaguZT8AA3qoR1NhAmqi+5jBKBgNVHR8EQzBB
// SIG // MD+gPaA7hjlodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v
// SIG // cGtpL2NybC9wcm9kdWN0cy9Db2RlU2lnblBDQS5jcmww
// SIG // DQYJKoZIhvcNAQEFBQADggEBADUj/RNU/Onc8N0MFHr6
// SIG // p7PO/ac6yLrl5/YD+1Pbp5mpoJs2nAPrgkccIb0Uy+dn
// SIG // QAnHFpECVc5DQrTNG12w8zIEPRLlHacHp4+jfkVVdhuW
// SIG // lZFp8N0480iJ73BAt9u1VYDAA8QutijcCoIOx0Pjekhd
// SIG // uAaJkkBsbsXc+JrvC74hCowvOrXtp85xh2gj4bPkGH24
// SIG // RwGlK8RYy7KJbF/90yzEb7gjsg3/PPIRRXTyCQaZGN1v
// SIG // wIYBGBIdKxavVu9lM6HqZ070S4Kr6Q/cAfrfYH9mR13L
// SIG // LHDMe07ZBrhujAz+Yh5C+ZN8oqsKntAjEK5NeyeRbya+
// SIG // aPqmP58j68idu4cxggTUMIIE0AIBATCBtTCBpjELMAkG
// SIG // A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAO
// SIG // BgNVBAcTB1JlZG1vbmQxHjAcBgNVBAoTFU1pY3Jvc29m
// SIG // dCBDb3Jwb3JhdGlvbjErMCkGA1UECxMiQ29weXJpZ2h0
// SIG // IChjKSAyMDAwIE1pY3Jvc29mdCBDb3JwLjEjMCEGA1UE
// SIG // AxMaTWljcm9zb2Z0IENvZGUgU2lnbmluZyBQQ0ECCmEH
// SIG // EUMAAAAAADQwCQYFKw4DAhoFAKCBpDAZBgkqhkiG9w0B
// SIG // CQMxDAYKKwYBBAGCNwIBBDAcBgorBgEEAYI3AgELMQ4w
// SIG // DAYKKwYBBAGCNwIBFTAjBgkqhkiG9w0BCQQxFgQUW0+w
// SIG // zTqn3XTFKcypXQjku/kyMa4wRAYKKwYBBAGCNwIBDDE2
// SIG // MDSgEoAQAFgARABvAHcAbgAuAGoAc6EegBxodHRwOi8v
// SIG // b2ZmaWNlLm1pY3Jvc29mdC5jb20gMA0GCSqGSIb3DQEB
// SIG // AQUABIIBABDvr1AOh6NRHP3h3g1Ech5qewrxWEHDQrc0
// SIG // 8+YHkILf9WJK3CC64cgDB+Z/W7zBSBU4TPEnsfOAXsVq
// SIG // GLYrgQEJsLaN+tRHo0ZMr2S5j4h4vy5E5xh0o1nzR9n5
// SIG // dStwQTOQfr1yjcprpjCOjtRa0iiJiLDqmgetU3BTXSrT
// SIG // fGn0CFoYuL3K4R/QbPYHtOfVPAVuJqJ70H2/HtW2+v9C
// SIG // 8a7t6RdHqE70qYIB2hKGZZEZzPaqLA/dQQNtUnscH2AE
// SIG // tr2XvtcF09y/0UEMDWlFZMTDZVF0D0y4uSkNzSDRY/Sk
// SIG // WPzg49QOm2X6VtlCL8D37h0ZfUfFZohXxiKNI4+HQMmh
// SIG // ggJMMIICSAYJKoZIhvcNAQkGMYICOTCCAjUCAQEwgbMw
// SIG // gZ4xHzAdBgNVBAoTFlZlcmlTaWduIFRydXN0IE5ldHdv
// SIG // cmsxFzAVBgNVBAsTDlZlcmlTaWduLCBJbmMuMSwwKgYD
// SIG // VQQLEyNWZXJpU2lnbiBUaW1lIFN0YW1waW5nIFNlcnZp
// SIG // Y2UgUm9vdDE0MDIGA1UECxMrTk8gTElBQklMSVRZIEFD
// SIG // Q0VQVEVELCAoYyk5NyBWZXJpU2lnbiwgSW5jLgIQCHpt
// SIG // XG9ik0+6xP1D4RQYnTAMBggqhkiG9w0CBQUAoFkwGAYJ
// SIG // KoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0B
// SIG // CQUxDxcNMDMwOTE5MDM1OTM1WjAfBgkqhkiG9w0BCQQx
// SIG // EgQQQ6yNUMfddOVy+GsL3oZ57jANBgkqhkiG9w0BAQEF
// SIG // AASCAQC7YjmUBPxJB727/Cgj12C2mC+PcXNlQLaPlX6R
// SIG // br1KMoqdW1xwd+owYsYlVUpIUGfF9Y4mlJXG93PpKo2J
// SIG // HnRPtMS6nTo6Gb8+U2G1LjNGx3Myw/2YtqcUKMQxHip0
// SIG // Z85y3Sp3Lnz0geRYljcOYUQgpi472+71IAiAp+HY4f5R
// SIG // dDrAeFZkbkYJ8bHOSgJchLEMLBH2wxC11jUOzOwWCdtv
// SIG // BIDKH0Pv66dlyEOLFSX6bH3C/HjKC8LJmaUpvyTYMg6N
// SIG // x0F6v+ItWPiU26KNQyA6eaWmk+JwtBF4QGxY4exjxJm8
// SIG // CbqlR9a04eLHFV0f5lgx2pcmvBMFdDQSrf2NNpsS
// SIG // End signature block
