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;
}
}
}