Click here to Skip to main content
15,884,099 members
Articles / Programming Languages / Visual Basic

VS.NET CodeDOM-Based Custom Tool for String Resource Management

Rate me:
Please Sign up or sign in to vote.
4.52/5 (12 votes)
22 Jun 200413 min read 123.4K   1.1K   48  
A VS.NET custom tool, created with the help of CodeDOM and EnvDTE, used to facilitate management of resource strings via IntelliSense and error checking in VS.NET environment.
using System;
using System.Reflection;
using System.Resources;
using System.IO;
using System.Windows.Forms;
using System.Collections;
using System.Collections.Specialized;
using System.Text.RegularExpressions;
using System.CodeDom;
using System.CodeDom.Compiler;

namespace CustomTools
{
	/// <summary>
	/// Summary description for CodeDomClassGenerator.
	/// </summary>
	internal class CodeDomResourceClassGenerator
	{
		private CodeDomResourceClassGenerator()
		{
			//
		}

		public static byte[] GenerateCode(CodeDomProvider provider, 
										  string fileName, 
										  string fileContents, 
										  string baseName, 
										  string classNameSpace)
		{
			CodeCompileUnit compileUnit = BuildGraph(fileName, fileContents, baseName, classNameSpace);
			ICodeGenerator gen = provider.CreateGenerator();

			using(StringWriter writer = new StringWriter())
			using(IndentedTextWriter tw = new IndentedTextWriter(writer))
			{
				gen.GenerateCodeFromCompileUnit(compileUnit, tw, new CodeGeneratorOptions());
				string code = writer.ToString().Trim();
				return System.Text.Encoding.ASCII.GetBytes(code);
			}
		}

		private static CodeCompileUnit BuildGraph(string fileName, string fileContents, string baseName, string classNameSpace)
		{
			CodeCompileUnit compileUnit = new CodeCompileUnit();

			//Just for VB.NET
			compileUnit.UserData.Add("AllowLateBound", false);
			compileUnit.UserData.Add("RequireVariableDeclaration", true);

			//Dummy namespace, so the Import statements would appear above the main namespace declaration
			CodeNamespace dummyNamespace = new CodeNamespace("");
			compileUnit.Namespaces.Add(dummyNamespace);

			//Namespace Import
			dummyNamespace.Imports.Add(new CodeNamespaceImport("System"));
			dummyNamespace.Imports.Add(new CodeNamespaceImport("System.Resources"));
			dummyNamespace.Imports.Add(new CodeNamespaceImport("System.Reflection"));

			//Namespace
			CodeNamespace nSpace = new CodeNamespace(classNameSpace);
			compileUnit.Namespaces.Add(nSpace);

			//Namespace comments
			nSpace.Comments.Add(new CodeCommentStatement("-----------------------------------------------------------------------------"));
			nSpace.Comments.Add(new CodeCommentStatement(" <autogeneratedinfo>"));
			nSpace.Comments.Add(new CodeCommentStatement("     This code was generated by:"));
			nSpace.Comments.Add(new CodeCommentStatement("       ResourceClassGenerator custom tool for VS.NET"));
			nSpace.Comments.Add(new CodeCommentStatement(""));
			nSpace.Comments.Add(new CodeCommentStatement("     It contains classes defined from the contents of the resource file:"));
			nSpace.Comments.Add(new CodeCommentStatement("       " + fileName));
			nSpace.Comments.Add(new CodeCommentStatement(""));
			nSpace.Comments.Add(new CodeCommentStatement("     Generated: " + DateTime.Now.ToString("f", new System.Globalization.CultureInfo("en-US"))));
			nSpace.Comments.Add(new CodeCommentStatement(" </autogeneratedinfo>"));
			nSpace.Comments.Add(new CodeCommentStatement("-----------------------------------------------------------------------------"));

			//Class ResourceFormatter
			CodeTypeDeclaration cResourceFormatter = new CodeTypeDeclaration("ResourceFormatter");
			//This is automatically internal, only nested classes can be private
			cResourceFormatter.TypeAttributes = System.Reflection.TypeAttributes.NotPublic;
			//ResourceFormatter Comments
			cResourceFormatter.Comments.Add(new CodeCommentStatement("<summary>", true));
			cResourceFormatter.Comments.Add(new CodeCommentStatement("Provides access to an assembly's string resources", true));
			cResourceFormatter.Comments.Add(new CodeCommentStatement("</summary>", true));
			nSpace.Types.Add(cResourceFormatter);
			
			//Field _ResourceManager
			CodeMemberField fResourceManager = new CodeMemberField(typeof(System.Resources.ResourceManager), "_ResourceManager");
			fResourceManager.Attributes = (MemberAttributes)((int)MemberAttributes.Static + (int)MemberAttributes.Private);
			cResourceFormatter.Members.Add(fResourceManager);

			//Property ResourceManager
			CodeMemberProperty pResourceManager = new CodeMemberProperty();
			pResourceManager.Name = "ResourceManager";
			pResourceManager.Type = new CodeTypeReference(typeof(System.Resources.ResourceManager));
			pResourceManager.Attributes = (MemberAttributes)((int)MemberAttributes.Static + (int)MemberAttributes.Private);
			//It is read-only property
			pResourceManager.HasSet = false;
			//ResourceManager Comments
			pResourceManager.Comments.Add(new CodeCommentStatement("<summary>", true));
			pResourceManager.Comments.Add(new CodeCommentStatement("ResourceManager property with lazy initialization", true));
			pResourceManager.Comments.Add(new CodeCommentStatement("</summary>", true));
			pResourceManager.Comments.Add(new CodeCommentStatement("<value>An instance of the ResourceManager class.</value>", true));

			CodeVariableReferenceExpression _ResourceManager = new CodeVariableReferenceExpression("_ResourceManager");
			
			//ResourceManager assignment line inside the if block
			CodeAssignStatement assignResourceManager = new CodeAssignStatement
			(
				_ResourceManager,
				new CodeObjectCreateExpression
				(
					typeof(System.Resources.ResourceManager), 
					new CodeExpression[]
					{
						new CodePrimitiveExpression(baseName),
						new CodeMethodInvokeExpression
						(
							new CodeTypeReferenceExpression(typeof(System.Reflection.Assembly)),
							"GetExecutingAssembly",
							new CodeExpression[] {}
						)							
					}
				)
			);

			//ResourceManager if block - for lazy initialization
			CodeConditionStatement ifBlock = new CodeConditionStatement
			(
				new CodeBinaryOperatorExpression
				(
					_ResourceManager,
					CodeBinaryOperatorType.IdentityEquality,
					new CodePrimitiveExpression(null)
				),
				new CodeStatement[]	
				{
					assignResourceManager
				}
			);
			
			pResourceManager.GetStatements.Add(ifBlock);
			pResourceManager.GetStatements.Add(new CodeMethodReturnStatement(_ResourceManager));
			cResourceFormatter.Members.Add(pResourceManager);

			//GetString method
			CodeMemberMethod mGetString = new CodeMemberMethod();
			mGetString.Name="GetString";
			mGetString.Attributes=(MemberAttributes)((int)MemberAttributes.Static + (int)MemberAttributes.Public);
			mGetString.ReturnType= new CodeTypeReference(typeof(string));
			mGetString.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string),"resourceId"));
			//GetString method comments
			mGetString.Comments.Add(new CodeCommentStatement("<summary>", true));
			mGetString.Comments.Add(new CodeCommentStatement("Loads an unformatted string", true));
			mGetString.Comments.Add(new CodeCommentStatement("</summary>", true));
			mGetString.Comments.Add(new CodeCommentStatement("<param name=\"resourceId\">Identifier of string resource</param>", true));
			mGetString.Comments.Add(new CodeCommentStatement("<returns>string</returns>", true));
			//GetString method statements
			CodePropertyReferenceExpression propExp = new CodePropertyReferenceExpression(new CodeTypeReferenceExpression("ResourceFormatter"), "ResourceManager");
			CodeMethodInvokeExpression invokeResourceManager = new CodeMethodInvokeExpression(propExp, "GetString", new CodeExpression[] {new CodeArgumentReferenceExpression("resourceId")});					
			mGetString.Statements.Add(new CodeMethodReturnStatement(invokeResourceManager));
			cResourceFormatter.Members.Add(mGetString);

			//The second overloaded GetString method
			mGetString = new CodeMemberMethod();
			mGetString.Name="GetString";
			mGetString.Attributes=(MemberAttributes)((int)MemberAttributes.Static + (int)MemberAttributes.Public);
			mGetString.ReturnType= new CodeTypeReference(typeof(string));
			mGetString.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "resourceId"));
			CodeParameterDeclarationExpression objects = new CodeParameterDeclarationExpression(typeof(object[]), "args");
			mGetString.Parameters.Add(objects);
			//The second GetString method comments
			mGetString.Comments.Add(new CodeCommentStatement("<summary>", true));
			mGetString.Comments.Add(new CodeCommentStatement("Loads a formatted string", true));
			mGetString.Comments.Add(new CodeCommentStatement("</summary>", true));
			mGetString.Comments.Add(new CodeCommentStatement("<param name=\"resourceId\">Identifier of string resource</param>", true));
			mGetString.Comments.Add(new CodeCommentStatement("<param name=\"objects\">Array of objects to be passed to string.Format</param>", true));
			mGetString.Comments.Add(new CodeCommentStatement("<returns>string</returns>", true));
			//The second GetString method statements
			mGetString.Statements.Add(new CodeVariableDeclarationStatement(typeof(string), "format", invokeResourceManager));
			CodeMethodInvokeExpression invokeStringFormat = new CodeMethodInvokeExpression
			(
				new CodeTypeReferenceExpression(typeof(string)), 
				"Format", 
				new CodeExpression[] 
				{
					new CodeArgumentReferenceExpression("format"),
					new CodeArgumentReferenceExpression("args")
				} 
			);
			mGetString.Statements.Add(new CodeMethodReturnStatement(invokeStringFormat));
			cResourceFormatter.Members.Add(mGetString);

			//Iterate through every resource and create the static class with the appropriate GetString method
			using(Stream inputStream = new MemoryStream(System.Text.Encoding.UTF8.GetBytes(fileContents)))
			using(ResXResourceReader resReader = new ResXResourceReader(inputStream))
			{
				IDictionaryEnumerator resEnumerator = resReader.GetEnumerator();
				SortedList sList = new SortedList();
				while(resEnumerator.MoveNext())
				{
					sList.Add(resEnumerator.Key, resEnumerator.Value);
				}
				resEnumerator = sList.GetEnumerator();

				// Create a class definition for each string entry  
				while(resEnumerator.MoveNext())
				{
					string resKey = (string)resEnumerator.Key;

					//Resource class
					CodeTypeDeclaration cResource = new CodeTypeDeclaration(resKey);
					//Class is automatically internal, only nested classes can be private
					cResource.TypeAttributes = System.Reflection.TypeAttributes.NotPublic;
					//Resource class comments
					cResource.Comments.Add(new CodeCommentStatement("<summary>", true));
					cResource.Comments.Add(new CodeCommentStatement("Access to resource identifier " + resKey, true));
					cResource.Comments.Add(new CodeCommentStatement("</summary>", true));
					nSpace.Types.Add(cResource);

					mGetString = new CodeMemberMethod();
					mGetString.Name="GetString";
					mGetString.Attributes=(MemberAttributes)((int)MemberAttributes.Static + (int)MemberAttributes.Public);
					mGetString.ReturnType= new CodeTypeReference(typeof(string));

					//It is necessary to know how many replaceable parameters the string has
					string ParameterMatchExpression = @"(\{[^\}\{]+\})";
					MatchCollection mc = Regex.Matches(resEnumerator.Value.ToString(), ParameterMatchExpression);
					//Don't include duplicates in count as String.Format argument can be specified
					//more than once, ie: "First: {0}, Second: {1}, First again: {0}" 
					StringCollection parameters = new StringCollection();
					foreach(Match match in mc)
					{
						if(!parameters.Contains(match.Value))
						{
							parameters.Add(match.Value);
						}
					}
					
					CodeExpression[] getStringParams;
					if (parameters.Count>0)
					{
						CodeExpression[] args = new CodeExpression[parameters.Count];
						//Create the argument lists
						for(int i = 0; i < parameters.Count; i++)
						{
							mGetString.Parameters.Add(new CodeParameterDeclarationExpression(typeof(object), "arg" + i.ToString()));
							args[i] = new CodeArgumentReferenceExpression("arg" + i.ToString());
						}
						getStringParams = new CodeExpression[2];
						getStringParams[1] = new CodeArrayCreateExpression(typeof(object), args);
					}
					else
					{
						getStringParams = new CodeExpression[1];
					}
					//The first parameter(key) is allways the same regardless of
					//whether additional args exist or not
					getStringParams[0] = new CodePrimitiveExpression(resKey);

					//Resource class statements
					CodeMethodInvokeExpression invokeGetString = new CodeMethodInvokeExpression
					(
						new CodeTypeReferenceExpression("ResourceFormatter"),
						"GetString",
						getStringParams
					);
					mGetString.Statements.Add(new CodeMethodReturnStatement(invokeGetString));
					cResource.Members.Add(mGetString);
				}
			}

			return compileUnit;
		}

	}
}

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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer Mono Ltd
Croatia Croatia
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions