#
/*
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; }
}
#>