Click here to Skip to main content
15,897,032 members
Articles / Programming Languages / XSLT

Version Resource Tool Using XML/XSLT and .NET

Rate me:
Please Sign up or sign in to vote.
2.00/5 (1 vote)
24 Oct 20027 min read 65.1K   479   22  
A tool for automatically updating version resources in C++, C# and other projects
using System;
using Bytepoint.System;
using BpConsole = Bytepoint.System.Console;
using Bytepoint.IO;
using System.Collections;
using System.Diagnostics;
using System.IO;
using System.Xml;
using System.Xml.Xsl;
using System.Xml.XPath;
using System.Xml.Serialization;
using System.Reflection;

namespace Vrt
{
	public enum MessageType
	{
		Normal, 
		Warning, 
		Error
	};

	internal enum VersionPart
	{
		Major,
		Minor,
		Point,
		Build
	};

	/// <summary>
	/// Summary description for Class1.
	/// </summary>
	class MainClass
	{
		private static ArrayList outputFiles = new ArrayList();
		private static string templateDir;
		private static string inputFile;
		private static string versionString = "+0.0.0.1";
		private static bool noLogo = false;
		private static bool trace = false;
		private static ushort lang = 1033;
		private static ushort charset = 1200;
		private static string fileVersionName = String.Empty;
		private static XmlDocument document = new XmlDocument();
		private static XPathNavigator navigator = null;		

		const int OptionOut = 1;
		const int OptionNoLogo = 2;
		const int OptionSet = 3;
		const int OptionRecurse = 4;
		const int OptionTemplates = 5;
		const int OptionLang = 6;
		const int OptionCharset = 7;
		const int OptionTrace = 8;
		const int OptionName = 9;
		const int OptionHelp = 10;
		
		private static Argument[] argDefs = 
		{
			new Argument(OptionHelp, "HELP|?", ArgType.Bool, "Displays this message."),
			new Argument(OptionOut, "OUT", ArgType.String, "File to be output, including extension."),
			new Argument(OptionNoLogo, "NOLOGO", ArgType.String, "Supress banner."),
			new Argument(OptionSet, "SET", ArgType.String, "Absolute or delta value to use to modify version numbers. Can be +, - or absolute. Default is +0.0.0.1."),
			new Argument(OptionTemplates, "TEMPLATES", ArgType.String, "Template file directory. Default is program directory."),
			new Argument(OptionLang, "LANG", ArgType.Numeric, "Language to generate version resource for. Default U.S. English 0x0409 (1033)."),
			new Argument(OptionCharset, "CHARSET", ArgType.Numeric, "Character set to generate version resource for. Default is Unicode 0x04B0 (1200)."),
			new Argument(OptionTrace, "TRACE", ArgType.Bool, "Show intermediate version data after dynamic nodes are generated."),
			new Argument(OptionName, "NAME", ArgType.String, "Project/file for which to generate version data for (if template requires it).")
	};

		private static void ShowHelp()
		{
			//TODO: Copyright information

			string s = ParseArgs.FormatUsage(argDefs);
			
			BpConsole.Write(s);

			BpConsole.WriteLine(
			   /*09876543210987654321098765432109876543210987654321098765432109876543210987654321*/
				"\n" +
				"You can set the MKVER_TEMPLATES environment variable to the directory that\n" +
				"contains the XSL template files, e.g. VERSION.H.XSL, VERSION.HTML.XSL,\n" +
				"VERSION.CS.XSL, etc..\n");
		}

		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]
		static void Main(string[] args)
		{
			ParseArgs parse = new ParseArgs(args, argDefs);
			int id;
			
			while (parse.GetNext(out id))
			{
				switch (id)
				{
					case OptionOut:
						outputFiles.Add(parse.SwitchString);
						break;

					case OptionHelp:
						ShowBanner();
						ShowHelp();
						return;

					case OptionNoLogo:
						noLogo = true;
						break;
						
					case OptionTrace:
						trace = true;
						break;
						
					case OptionLang:
						lang = (ushort)parse.SwitchInt;
						break;
						
					case OptionCharset:
						charset = (ushort)parse.SwitchInt;
						break;
						
					case OptionName:
						fileVersionName = parse.SwitchString;
						break;
						
					case OptionTemplates:
						templateDir = parse.SwitchString;
						break;

					case OptionSet:
						versionString = parse.SwitchString;
						break;

					default:
						if (id < 0 || inputFile != null)
						{
							inputFile = parse.SwitchString;
							break;
						}
						else	// Invalid switch
						{
							ShowHelp();
							return;
						}
				}
			}

			ShowBanner();
			
			if (inputFile == null)
			{
				WriteMessage(MessageType.Error, "No version file specified");
				return;
			}

			if (!FindTemplateDirectory())
				return;
			
			try
			{	
				inputFile = Path.GetFullPath((string)inputFile);

				// load the version data
				document.Load(inputFile);

				// wrap it with an XmlNavigator
				navigator = document.CreateNavigator();
				
				if (!IsCorrectRevision(1))
					return;

				UpdateProductVersion();
	
				// Add dynamically generated nodes
				AddNodes();

				if (trace)
				{
					XmlTextWriter xw = new XmlTextWriter(BpConsole.Out);
					xw.Formatting = Formatting.Indented;

					document.WriteContentTo(xw);
					xw.Flush();
					BpConsole.WriteLine();
				}

				TransformDocument();				
			}
			catch (Exception e)
			{
				WriteMessage(MessageType.Error, e.Message);
			}
		}

		private static void TransformDocument()
		{
			foreach (string filePath in outputFiles)
			{
				string outputFilePath = Path.GetFullPath((string)filePath);
				string outputFileExt = Path.GetExtension(outputFilePath);
					
				// create the XslTransform object
				XslTransform transform = new XslTransform();
				
				// load it with the stylesheet
				transform.Load(templateDir + "version" + outputFileExt + ".xsl");
					
				// create the XmlWriter object
				TextWriter output = new StreamWriter(outputFilePath, false);

				// call transform - output streamed to console
				XsltArgumentList argList = new XsltArgumentList();
					
				argList.AddParam("lang", "", lang.ToString());
				argList.AddParam("charset", "", charset.ToString());
				argList.AddParam("name", "", fileVersionName);
					
				transform.Transform(navigator, argList, output);
				
				output.Close();
			}
		}
		
		private static void UpdateProductVersion()
		{
			XPathNodeIterator iter = navigator.Select("//ProductVersion");
				
			iter.MoveNext();
								
			ulong verProduct = GetVersion(iter.Current.Value);
			int sign;
			ulong verSet = GetVersion(versionString, out sign);

			if (sign == 0)
				verProduct = verSet;  // Absolute
			else
				verProduct = (ulong)((long)verProduct + ((long)verSet * sign));  // Relative

			versionString = GetVersion(verProduct);
				
			((IHasXmlNode)iter.Current).GetNode().FirstChild.Value = versionString;

			if (sign != 0 && verSet == 0)
			{
				BpConsole.WriteLine("Leaving product version at {0}.", versionString);
			}
			else
			{
				BpConsole.WriteLine("{0} product version to {1}.", 
					sign == 0 ? "Setting" : sign < 0 ? "Decrementing" : "Incrementing",
					versionString);

				// Save the document now, before we mess with it			
				document.Save(inputFile);
			}
		}

		private static bool IsCorrectRevision(int revExpected)
		{
			XPathNodeIterator iter1 = navigator.Select("/VersionData");
				
			iter1.MoveNext();
				
			string s = iter1.Current.GetAttribute("revision", navigator.NamespaceURI);
				
			int revActual = Int32.Parse(s);
				
			// Check that the version data revision is OK
			if (revActual != revExpected)
			{
				WriteMessage(MessageType.Error, 
					"Wrong data file revision.  Expected {0} and got {1}.", revActual, revExpected);
				return false;
			}
			
			return true;
		}

		public static void AppendElement(XPathNodeIterator iter, string name, string val)
		{
			XmlNode node = ((IHasXmlNode)iter.Current).GetNode();
				
			XmlElement newElem = document.CreateElement(name);
			XmlText newText = document.CreateTextNode(val);
				
			newElem.AppendChild(newText);
			
			node.AppendChild(newElem);
		}

		public static bool AddNodes()
		{
			try
			{
				XPathNodeIterator iter = navigator.Select("/VersionData");
				
				iter.MoveNext();
			
				// Add a DateTime element
				AppendElement(iter, "Date", DateTime.Now.ToLongDateString());
				AppendElement(iter, "Time", DateTime.Now.ToLongTimeString());

				iter = iter.Current.Select("ProductVersionDatum");
				
				iter.MoveNext();
				
				XPathNodeIterator iter2 = iter.Current.Select("ProductVersion");

				iter2.MoveNext();

				ulong version = GetVersion(iter2.Current.Value);
				
				string major = GetVersionPart(version, VersionPart.Major);
				string minor = GetVersionPart(version, VersionPart.Minor);
				string point = GetVersionPart(version, VersionPart.Point);
				string build = GetVersionPart(version, VersionPart.Build);

				AppendElement(iter, "ProductVerMajor", major);
				AppendElement(iter, "ProductVerMinor", minor);
				AppendElement(iter, "ProductVerPoint", point);
				AppendElement(iter, "ProductVerBuild", build);
				AppendElement(iter, "ProductVersionHex", "0x" + Convert.ToString((long)version, 16));
				AppendElement(iter, "ProductVersionCSV", major + "," + minor + "," + point + "," + build);

				iter = iter.Current.Select("//FileVersionDatum");
				
				while (iter.MoveNext())
				{
					iter2 = iter.Current.Select("FileVersion");
					iter2.MoveNext();

					version = GetVersion(iter2.Current.Value);

					major = GetVersionPart(version, VersionPart.Major);
					minor = GetVersionPart(version, VersionPart.Minor);
					point = GetVersionPart(version, VersionPart.Point);
					build = GetVersionPart(version, VersionPart.Build);

					AppendElement(iter, "FileVerMajor", major);
					AppendElement(iter, "FileVerMinor", minor);
					AppendElement(iter, "FileVerPoint", point);
					AppendElement(iter, "FileVerBuild", build);
					AppendElement(iter, "FileVersionHex", "0x" + Convert.ToString((long)version, 16));
					AppendElement(iter, "FileVersionCSV", major + "," + minor + "," + point + "," + build);
				}
			}
			catch (Exception e)
			{
				WriteMessage(MessageType.Error, "Exception '{0}'", e.Message);
				return false;
			}
			return true;
		}

		private static bool FindTemplateDirectory()
		{
			try
			{
				if (templateDir != null)
				{
					templateDir = Path.GetFullPath(templateDir);

					if (Directory.Exists(templateDir))
					{
						templateDir = PathEx.AddDirectorySeparator(templateDir);
						return true;
					}
					else
					{
						WriteMessage(MessageType.Warning, 
							"Unable to find directory '{0}' specified by command line", templateDir);
						templateDir = null;
					}
				}

				templateDir = Environment.GetEnvironmentVariable("MKVER_TEMPLATES");	

				if (templateDir != null)
				{
					templateDir = Path.GetFullPath(templateDir);

					if (Directory.Exists(templateDir))
					{
						templateDir = PathEx.AddDirectorySeparator(templateDir);
						return true;
					}
					else
					{
						WriteMessage(MessageType.Warning, 
							"Unable to find directory '{0}' specified by MKVER_TEMPLATES", templateDir);
						templateDir = null;
					}
				}

				templateDir = Environment.CurrentDirectory + @"\Templates\";
				
				if (Directory.Exists(templateDir))
					return true;

				templateDir = Path.GetPathRoot(Process.GetCurrentProcess().MainModule.FileName) + @"Templates\";
				
				if (Directory.Exists(templateDir))
					return true;
			}
			catch (Exception e)
			{
				WriteMessage(MessageType.Error, e.Message);
				return false;
			}

			WriteMessage(MessageType.Error, "Unable to find a template directory.");
			
			return false;
		}
				
		private static void ShowBanner()
		{
			if (noLogo)
				return;

			FileVersionInfo ver = FileVersionInfo.GetVersionInfo(Process.GetCurrentProcess().MainModule.FileName);
			
			BpConsole.Write(ver.FileDescription);
			BpConsole.Write(". ");
			BpConsole.Write(ver.IsDebug ? "Debug" : "Release");
			BpConsole.Write(" Version ");
			BpConsole.WriteLine(ver.FileVersion);
			BpConsole.WriteLine(ver.LegalCopyright);
			BpConsole.WriteLine();
		}

		public static void WriteMessage(MessageType s, string format, params object [] args)
		{
			switch (s)
			{
				default:
				case MessageType.Normal:
					break;
				case MessageType.Warning:
					BpConsole.Write("WARNING: ");
					break;
				case MessageType.Error:
					BpConsole.Write("ERROR: ");
					break;
			}

			BpConsole.Error.WriteLine(format, args);
		}

		public static string GetVersionPart(ulong version, VersionPart part)
		{
			return GetVersionPart(version, part, 10);
		}
		
		public static string GetVersionPart(ulong version, VersionPart part, int toBase)
			{
			string s = null;

			switch (part)
			{
				case VersionPart.Major:
					s = Convert.ToString((long)(version >> 48), toBase);
					break;

				case VersionPart.Minor:
					s = Convert.ToString((long)((version >> 32) & 0xFFFF), toBase);
					break;

				case VersionPart.Point:
					s = Convert.ToString((long)((version >> 16) & 0xFFFF), toBase);
					break;

				case VersionPart.Build:
					s = Convert.ToString((long)(version & 0xFFFF), toBase);
					break;
			}

			return s;
		}

		public static string GetVersion(ulong version)
		{
			return 
				GetVersionPart(version, VersionPart.Major) + "." +
				GetVersionPart(version, VersionPart.Minor) + "." + 
				GetVersionPart(version, VersionPart.Point) + "." + 
				GetVersionPart(version, VersionPart.Build);
		}

		public static ulong GetVersion(string version)
		{
			int sign;
			return GetVersion(version, out sign);
		}
		
		public static ulong GetVersion(string version, out int sign)
			{
			int i = 0;

			sign = 0;
			
			if (version == null || version == String.Empty)
				return 0;
			
			if (version[i] == '+')
			{
				sign = 1;
				i++;
			}
			else if (version[i] == '-')
			{
				sign = -1;
				i++;
			}

			if (!Char.IsDigit(version[i]))
				return 0;

			ulong versionNum = 0;
			ulong t = 0;
			int j = 3;

			while (true)
			{
				int length = 0;
				
				while (i + length < version.Length && Char.IsDigit(version[i + length]))
					length++;

				t = (ulong)Convert.ToUInt64(version.Substring(i, length), 10);
				versionNum |= (t << 16 * j);
				j--;
				i += length;	
				
				if (j < 0 || i >= version.Length || version[i] != '.')
					break;
					
				i++;
			}

			return versionNum;
		}
	
	} // MainClass
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.


Written By
Web Developer
United States United States
When I was 14 my physics teacher dragged a dusty Commodore PET out of a cupboard and asked, "Do you think you could do anything with this?"

Comments and Discussions