<# /* T4Resx Version 0.1 Maintained by Kenneth Baltrinic http://blog.baltrinic.com Added Enum support by DotNetWise http://www.dotnetwise.com Related blog posts: http://blog.baltrinic.com/software-development/dotnet/t4-template-replace-resxfilecodegenerator The certain parts of this template were copied from the T4MVC template which is distributed under the MvcContrib license (http://mvccontrib.codeplex.com/license) This template if free for redistribution in accordance with the same license. */ #> <#@ template debug="true" hostspecific="true" #> <#@ assembly name="System.Core" #> <#@ assembly name="System.Xml" #> <#@ assembly name="Microsoft.VisualStudio.Shell.Interop.8.0" #> <#@ assembly name="EnvDTE" #> <#@ assembly name="EnvDTE80" #> <#@ assembly name="VSLangProj" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="System.IO" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Text.RegularExpressions" #> <#@ import namespace="System.Xml" #> <#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #> <#@ import namespace="EnvDTE" #> <#@ import namespace="EnvDTE80" #> <#@ import namespace="Microsoft.VisualStudio.TextTemplating" #> <# var serviceProvider = Host as IServiceProvider; if (serviceProvider != null) { Dte = serviceProvider.GetService(typeof(SDTE)) as DTE; } // Fail if we couldn't get the DTE. This can happen when trying to run in TextTransform.exe if (Dte == null) { throw new Exception("T4MVC can only execute through the Visual Studio host"); } Project = GetProjectContainingT4File(Dte); if (Project == null) { Error("Could not find the VS Project containing the T4 file."); return"XX"; } AppRoot = Path.GetDirectoryName(Project.FullName) + '\\'; RootNamespace = Project.Properties.Item("RootNamespace").Value.ToString(); #> //------------------------------------------------------------------------------ // // This code was generated by a tool. Runtime Version:4.0.30319.1 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ using System; using System.Threading; using System.Web; using System.ComponentModel.DataAnnotations; <# try{ AllEntries = new List(); FindResourceFilesRecursivlyAndRecordEntries(Project.ProjectItems, ""); AllEntries.Sort(new Comparison( (e1, e2) => (e1.Path + e1.File + e1.Name).CompareTo(e2.Path + e2.File + e2.Name))); var currentNamespace = ""; var currentClass = ""; var thisIsFirstEntryInClass = true; var names = new List(); var currentClassEntries = new List(); foreach(var entry in AllEntries) { //WriteLine("//" + entry.Path + ":" + entry.File+ ":" + entry.Name); var newNamespace = ("Resources." + entry.Path + ".").Replace(".Resources.", "."); newNamespace = RootNamespace + "." + newNamespace.Substring(0, newNamespace.Length-1); if (!string.IsNullOrEmpty(entry.CustomNamespace)) newNamespace = entry.CustomNamespace; var newClass = entry.File; bool namesapceIsChanging = newNamespace != currentNamespace; bool classIsChanging = namesapceIsChanging || newClass != currentClass; //Close out current class if class is changing and there is a current class if(classIsChanging && currentClass != "") { EmitNamesInnerClass(names); WriteLine("\t}"); } if(namesapceIsChanging) { //Close out current namespace if one exists if( currentNamespace != "" ) WriteLine("}"); currentNamespace = newNamespace; //open new namespace WriteLine(string.Format("namespace {0}", currentNamespace)); WriteLine("{"); } if(classIsChanging) { currentClassEntries.Clear(); currentClass = newClass; #> /// /// A strongly-typed resource class, for looking up localized strings, etc. /// // This class was auto-generated by the StronglyTypedResourceBuilder // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] <# WriteLine(string.Format("\tpublic class {0}", currentClass)); WriteLine("\t{"); thisIsFirstEntryInClass = true; //Emit code for the ResourceManager property and GetResourceString method for the current class #> private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal <#Write(currentClass);#>() { } /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] private static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("<#=string.Format("{0}.{1}{2}", RootNamespace, entry.Path + "." + entry.File, entry.Type) #>", typeof(<#=entry.File#>).Assembly); resourceMan = temp; } return resourceMan; } } /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] public static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } /// /// Returns the formatted resource string. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] private static string GetResourceString(string key, params string[] tokens) { var culture = Culture ?? Thread.CurrentThread.CurrentUICulture; var str = ResourceManager.GetString(key, culture); for(int i = 0; i < tokens.Length; i += 2) str = str.Replace(tokens[i], tokens[i+1]); return str; } /// /// Returns the formatted resource string. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] private static HtmlString GetResourceHtmlString(string key, params string[] tokens) { var str = GetResourceString(key, tokens); if(str.StartsWith("HTML:")) str = str.Substring(5); return new HtmlString(str); } <# } //Emit the static resource string access method for the current entry if(entry.Comment != null) { if(!thisIsFirstEntryInClass) WriteLine(""); WriteLine(string.Format("\r\n\t\t///\r\n\t\t///{0}\r\n\t\t///", entry.Comment.Replace("\r\n", "\r\n\t\t///"))); } else WriteLine(""); //Select all tokens between braces that constitute valid identifiers var tokens = Regex.Matches(entry.Value, @"{(([A-Za-z]{1}\w*?)|([A-Za-z_]{1}\w+?))?}").Cast().Select(m => m.Value); if(tokens.Any()) { var inParams = tokens.Aggregate("", (list, value) => list += ", string " + value) .Replace("{", "").Replace("}", ""); if(inParams.Length > 0 ) inParams = inParams.Substring(1); var outParams = tokens.Aggregate("", (list, value) => list += ", \"" + value +"\", " + value.Replace("{", "").Replace("}", "") ); if(entry.Value.StartsWith("HTML:")) WriteLine(string.Format("\t\tpublic static HtmlString {0}({1}) {{ return GetResourceHtmlString(\"{0}\"{2}); }}", entry.Name, inParams, outParams)); else WriteLine(string.Format("\t\tpublic static string {0}({1}) {{ return GetResourceString(\"{0}\"{2}); }}", entry.Name, inParams, outParams)); } else { if(entry.Value.StartsWith("HTML:")) WriteLine(string.Format("\t\tpublic static HtmlString {0} {{ get {{ return GetResourceHtmlString(\"{0}\"); }} }}", entry.Name)); else { WriteLine(string.Format("\t\tpublic static string {0} {{ get {{ return GetResourceString(\"{0}\"); }} }}", entry.Name)); //WriteLine(string.Format("\t\tinternal static string V{0} {{ get {{ return GetResourceString(\"{0}\"); }} }}", entry.Name)); } } names.Add(entry.Name); currentClassEntries.Add(entry); thisIsFirstEntryInClass = false; } //close out the current class when done if(currentClass != "") { EmitNamesInnerClass(names); WriteLine("\t}"); } if (currentClassEntries.Any(e=>e.ProcessEnums)) { var l = AllEntries.Select(entry=>new { s = entry.Name.Split(new char[] {'_'}, 2), entry.Comment, entry.Value }).ToLookup(n=>n.s[0],n=>new {Name = n.s.Length>1?n.s[1]:"", n.Comment, n.Value}); var d = new Dictionary(); foreach(var key in l) if (!string.IsNullOrEmpty(key.Key) && key.Any(i=>!string.IsNullOrEmpty(i.Name))) { var enumDoc = (key.Where(i=>string.IsNullOrEmpty(i.Name)).Select(i=>i.Comment).FirstOrDefault()??string.Empty).Replace("\r\n", "\r\n\t\t///"); if (!string.IsNullOrEmpty(enumDoc)) WriteLine(string.Format("\t///{0}", enumDoc)); var enumDisplay = (key.Where(i=>string.IsNullOrEmpty(i.Name)).Select(i=>i.Value).FirstOrDefault()??string.Empty).Replace("\r\n", "\r\n\t\t///"); if (!string.IsNullOrEmpty(enumDisplay)) WriteLine(string.Format("\t[System.ComponentModel.Description(\"{0}\")]", enumDisplay, currentClass)); WriteLine("\tpublic enum "+key.Key); WriteLine("\t{"); foreach(var item in key.Where(i=>!string.IsNullOrEmpty(i.Name))) { if (!string.IsNullOrEmpty(item.Comment)) WriteLine(string.Format("\t\t///{0}", item.Comment.Replace("\r\n", "\r\n\t\t///"))); if (!string.IsNullOrEmpty(item.Value)) WriteLine(string.Format("\t\t[Display(Description = \"{0}_{1}\", ResourceType = typeof({2}))]", key.Key, item.Name, currentClass)); WriteLine(string.Format("\t\t{0},",item.Name)); } WriteLine("\t}"); } } } catch(Exception ex) { Error(ex.ToString()); } #> } <#+ const string Kind_PhysicalFolder = "{6BB5F8EF-4483-11D3-8BCF-00C04F8EC28C}"; bool AlwaysKeepTemplateDirty = true; static DTE Dte; static Project Project; static string AppRoot; static string RootNamespace; static List AllEntries; void FindResourceFilesRecursivlyAndRecordEntries(ProjectItems items, string path) { foreach(ProjectItem item in items) { if(Path.GetExtension(item.Name) == ".resx") RecordEntriesInResourceFile(item, path); if(item.Kind == Kind_PhysicalFolder) FindResourceFilesRecursivlyAndRecordEntries(item.ProjectItems, path+"."+item.Name); } } void RecordEntriesInResourceFile(ProjectItem item, string path) { //skip resource files except those for the default culture if(Regex.IsMatch(item.Name, @".*\.[a-zA-z]{2}(-[a-zA-z]{2})?\.resx")) return; var filePath = (string)item.Properties.Item("FullPath").Value; var customNamespace = (string)item.Properties.Item("CustomToolNamespace").Value; var processEnums = ((string)item.Properties.Item("CustomTool").Value??string.Empty).IndexOf("enum", StringComparison.OrdinalIgnoreCase) >= 0; var xml = new XmlDocument(); xml.Load(filePath); var entries = xml.DocumentElement.SelectNodes("//data"); var parentFile = item.Name.Replace(".resx", ""); var fileType = Path.GetExtension(parentFile); if(fileType != null && fileType != "") parentFile = parentFile.Replace(fileType, ""); foreach (XmlElement entryElement in entries) { var entry = new ResourceEntry { Path = path.Substring(1), File = MakeIntoValidIdentifier(parentFile), Type = fileType, Name = MakeIntoValidIdentifier(entryElement.Attributes["name"].Value), CustomNamespace = customNamespace, ProcessEnums = processEnums, }; var valueElement = entryElement.SelectSingleNode("value"); if(valueElement != null) entry.Value = valueElement.InnerText; var commentElement = entryElement.SelectSingleNode("comment"); if(commentElement != null) entry.Comment = commentElement.InnerText; AllEntries.Add(entry); } } string MakeIntoValidIdentifier(string arbitraryString) { var validIdentifier = Regex.Replace(arbitraryString, @"[^A-Za-z0-9-._]", " "); validIdentifier = ConvertToPascalCase(validIdentifier); if (Regex.IsMatch(validIdentifier, @"^\d")) validIdentifier = "_" + validIdentifier; return validIdentifier; } string ConvertToPascalCase(string phrase) { string[] splittedPhrase = phrase.Split(' ', '-', '.'); var sb = new StringBuilder(); sb = new StringBuilder(); foreach (String s in splittedPhrase) { char[] splittedPhraseChars = s.ToCharArray(); if (splittedPhraseChars.Length > 0) { splittedPhraseChars[0] = ((new String(splittedPhraseChars[0], 1)).ToUpper().ToCharArray())[0]; } sb.Append(new String(splittedPhraseChars)); } return sb.ToString(); } void EmitNamesInnerClass(List names) { if(names.Any()) { WriteLine("\r\n\t\tpublic static class Names"); WriteLine("\t\t{"); foreach(var name in names) WriteLine(string.Format("\t\t\tpublic const string {0} = \"{0}\";", name)); WriteLine("\t\t}"); names.Clear(); } } Project GetProjectContainingT4File(DTE dte) { // Find the .tt file's ProjectItem ProjectItem projectItem = dte.Solution.FindProjectItem(Host.TemplateFile); // If the .tt file is not opened, open it if (projectItem.Document == null) projectItem.Open(Constants.vsViewKindCode); if (AlwaysKeepTemplateDirty) { // Mark the .tt file as unsaved. This way it will be saved and update itself next time the // project is built. Basically, it keeps marking itself as unsaved to make the next build work. // Note: this is certainly hacky, but is the best I could come up with so far. projectItem.Document.Saved = false; } return projectItem.ContainingProject; } struct ResourceEntry { public string Path { get; set; } public string File { get; set; } public string Type { get; set; } public string CustomNamespace { get; set; } public bool ProcessEnums { get; set; } public string Name { get; set; } public string Value { get; set; } public string Comment { get; set; } } #>