2 // StronglyTypedResourceBuilder.cs
5 // Atsushi Enomoto (atsushi@ximian.com)
6 // Gary Barnett (gary.barnett.mono@gmail.com)
8 // Copyright (C) 2007 Novell, Inc.
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System.CodeDom.Compiler;
34 using System.Collections;
35 using System.ComponentModel.Design;
36 using System.Reflection;
37 using System.Collections.Generic;
39 using System.Resources;
41 namespace System.Resources.Tools
43 public static class StronglyTypedResourceBuilder
46 static char [] specialChars = { ' ', '\u00A0', '.', ',', ';', '|', '~', '@', '#', '%', '^', '&',
47 '*', '+', '-', '/', '\\', '<', '>', '?', '[', ']', '(', ')', '{',
48 '}', '\"', '\'', ':', '!'};
50 static char [] specialCharsNameSpace = { ' ', '\u00A0', ',', ';', '|', '~', '@', '#', '%', '^', '&',
51 '*', '+', '-', '/', '\\', '<', '>', '?', '[', ']', '(', ')', '{',
52 '}', '\"', '\'', '!'};
54 public static CodeCompileUnit Create (string resxFile,
56 string generatedCodeNamespace,
57 CodeDomProvider codeProvider, bool internalClass,
58 out string [] unmatchable)
61 return Create (resxFile,
63 generatedCodeNamespace,
70 public static CodeCompileUnit Create (string resxFile,
72 string generatedCodeNamespace,
73 string resourcesNamespace,
74 CodeDomProvider codeProvider, bool internalClass,
75 out string [] unmatchable)
77 // unpack resfile into dictionary, pass to overload
81 throw new ArgumentNullException ("Parameter resxFile must not be null");
83 List<char> invalidPathChars = new List<char> (Path.GetInvalidPathChars ());
84 foreach (char c in resxFile.ToCharArray ()) {
85 if (invalidPathChars.Contains (c))
86 throw new ArgumentException ("Invalid character in resxFileName");
89 Dictionary<string,object> resourcesList = new Dictionary<string,object> ();
91 using (ResXResourceReader reader = new ResXResourceReader (resxFile)) {
93 foreach (DictionaryEntry d in reader)
94 resourcesList.Add ((string) d.Key, d.Value);
97 return Create (resourcesList,
99 generatedCodeNamespace,
107 public static CodeCompileUnit Create (IDictionary resourceList,
109 string generatedCodeNamespace,
110 CodeDomProvider codeProvider, bool internalClass,
111 out string [] unmatchable)
114 return Create (resourceList,
116 generatedCodeNamespace,
123 public static CodeCompileUnit Create (IDictionary resourceList,
125 string generatedCodeNamespace,
126 string resourcesNamespace,
127 CodeDomProvider codeProvider, bool internalClass,
128 out string [] unmatchable)
130 string baseNameToUse, generatedCodeNamespaceToUse;
131 string resourcesToUse;
133 // validate parameters, convert into useable form where necessary / possible
134 if (resourceList == null)
135 throw new ArgumentNullException ("Parameter resourceList must not be null");
137 if (codeProvider == null)
138 throw new ArgumentNullException ("Parameter: codeProvider must not be null");
140 if (baseName == null)
141 throw new ArgumentNullException ("Parameter: baseName must not be null");
143 baseNameToUse = VerifyResourceName (baseName, codeProvider);
145 if (baseNameToUse == null)
146 throw new ArgumentException ("Parameter: baseName is invalid");
148 if (generatedCodeNamespace == null) {
149 generatedCodeNamespaceToUse = "";
151 generatedCodeNamespaceToUse = CleanNamespaceChars (generatedCodeNamespace);
152 generatedCodeNamespaceToUse = codeProvider.CreateValidIdentifier (
153 generatedCodeNamespaceToUse);
156 if (resourcesNamespace == null)
157 resourcesToUse = generatedCodeNamespaceToUse + "." + baseNameToUse;
158 else if (resourcesNamespace == String.Empty)
159 resourcesToUse = baseNameToUse;
161 resourcesToUse = resourcesNamespace + "." + baseNameToUse;
163 // validate ResourceList IDictionary
165 Dictionary<string,ResourceItem> resourceItemDict;
166 resourceItemDict = new Dictionary<string,ResourceItem> (StringComparer.OrdinalIgnoreCase);
168 //allow ArgumentException to be raised on case insensitive dupes,InvalidCastException on key not being string
169 foreach (DictionaryEntry de in resourceList)
170 resourceItemDict.Add ((string) de.Key, new ResourceItem (de.Value));
172 ProcessResourceList (resourceItemDict, codeProvider);
175 CodeCompileUnit ccu = GenerateCodeDOMBase (baseNameToUse, generatedCodeNamespaceToUse,
176 resourcesToUse, internalClass);
178 // add properties for resources
179 unmatchable = ResourcePropertyGeneration (ccu.Namespaces [0].Types [0],
180 resourceItemDict, internalClass);
185 static string[] ResourcePropertyGeneration (CodeTypeDeclaration resType,
186 Dictionary<string, ResourceItem> resourceItemDict,
189 // either create properties for resources, ignore or add to unmatchableList
190 List<string> unmatchableList = new List<string> ();
192 foreach (KeyValuePair<string, ResourceItem> kvp in resourceItemDict) {
193 if (kvp.Value.isUnmatchable)
194 unmatchableList.Add (kvp.Key); // orig key
195 else if (!kvp.Value.toIgnore) {
196 if (kvp.Value.Resource is Stream)
197 resType.Members.Add (GenerateStreamResourceProp (kvp.Value.VerifiedKey,
200 else if (kvp.Value.Resource is String)
201 resType.Members.Add (GenerateStringResourceProp (kvp.Value.VerifiedKey,
205 resType.Members.Add (GenerateStandardResourceProp (kvp.Value.VerifiedKey,
207 kvp.Value.Resource.GetType (),
212 return unmatchableList.ToArray ();
215 static CodeCompileUnit GenerateCodeDOMBase (string baseNameToUse, string generatedCodeNamespaceToUse,
216 string resourcesToUse, bool internalClass)
218 CodeCompileUnit ccu = new CodeCompileUnit ();
219 ccu.ReferencedAssemblies.Add ("System.dll");
220 CodeNamespace nsMain = new CodeNamespace (generatedCodeNamespaceToUse);
221 ccu.Namespaces.Add (nsMain);
222 nsMain.Imports.Add (new CodeNamespaceImport ("System"));
225 CodeTypeDeclaration resType = GenerateBaseType (baseNameToUse, internalClass);
226 nsMain.Types.Add (resType);
228 GenerateFields (resType);
230 resType.Members.Add (GenerateConstructor ());
232 // Default Properties
233 resType.Members.Add (GenerateResourceManagerProp (baseNameToUse, resourcesToUse, internalClass));
234 resType.Members.Add (GenerateCultureProp (internalClass));
239 static void ProcessResourceList (Dictionary<string, ResourceItem> resourceItemDict,
240 CodeDomProvider codeProvider)
242 foreach (KeyValuePair<string, ResourceItem> kvp in resourceItemDict) {
243 //deal with ignored keys
244 if (kvp.Key.StartsWith (">>") || kvp.Key.StartsWith ("$")) {
245 kvp.Value.toIgnore = true;
248 //deal with specified invalid names (case sensitive)
249 if (kvp.Key == "ResourceManager" || kvp.Key == "Culture") {
250 kvp.Value.isUnmatchable = true;
254 kvp.Value.VerifiedKey = VerifyResourceName (kvp.Key, codeProvider);
255 // will be null if codeProvider deems invalid
256 if (kvp.Value.VerifiedKey == null) {
257 kvp.Value.isUnmatchable = true;
261 foreach (KeyValuePair<string, ResourceItem> item in resourceItemDict) {
262 // skip on encountering kvp or if VerifiedKey on object null (ie hasnt been processed yet)
263 if (Object.ReferenceEquals (item.Value, kvp.Value)
264 || item.Value.VerifiedKey == null)
266 // if case insensitive dupe found mark both
267 if (String.Equals (item.Value.VerifiedKey, kvp.Value.VerifiedKey,
268 StringComparison.OrdinalIgnoreCase)) {
269 item.Value.isUnmatchable = true;
270 kvp.Value.isUnmatchable = true;
276 static CodeTypeDeclaration GenerateBaseType (string baseNameToUse, bool internalClass)
278 CodeTypeDeclaration resType = new CodeTypeDeclaration (baseNameToUse);
279 resType.IsClass = true;
280 // set access modifier for class
282 resType.TypeAttributes = TypeAttributes.NotPublic;
284 resType.TypeAttributes = TypeAttributes.Public;
286 //class CustomAttributes
287 resType.CustomAttributes.Add (new CodeAttributeDeclaration (
288 "System.CodeDom.Compiler.GeneratedCodeAttribute",
289 new CodeAttributeArgument (
290 new CodePrimitiveExpression (
291 "System.Resources.Tools.StronglyTypedResourceBuilder")),
292 new CodeAttributeArgument (
293 new CodePrimitiveExpression ("4.0.0.0"))));
296 resType.CustomAttributes.Add (new CodeAttributeDeclaration (
297 "System.Diagnostics.DebuggerNonUserCodeAttribute"));
299 resType.CustomAttributes.Add (new CodeAttributeDeclaration (
300 "System.Runtime.CompilerServices.CompilerGeneratedAttribute"));
304 static void GenerateFields (CodeTypeDeclaration resType)
307 CodeMemberField resourceManField = new CodeMemberField ();
308 resourceManField.Attributes = (MemberAttributes.Abstract
309 | MemberAttributes.Final
310 | MemberAttributes.Assembly
311 | MemberAttributes.FamilyOrAssembly);
312 resourceManField.Name = "resourceMan";
313 resourceManField.Type = new CodeTypeReference (typeof (System.Resources.ResourceManager));
314 resType.Members.Add (resourceManField);
316 //resourceCulture field
317 CodeMemberField resourceCultureField = new CodeMemberField ();
318 resourceCultureField.Attributes = (MemberAttributes.Abstract
319 | MemberAttributes.Final
320 | MemberAttributes.Assembly
321 | MemberAttributes.FamilyOrAssembly);
322 resourceCultureField.Name = "resourceCulture";
323 resourceCultureField.Type = new CodeTypeReference (typeof (System.Globalization.CultureInfo));
324 resType.Members.Add (resourceCultureField);
327 static CodeConstructor GenerateConstructor ()
329 CodeConstructor ctor = new CodeConstructor ();
330 ctor.Attributes = MemberAttributes.FamilyAndAssembly; // always internal
332 ctor.CustomAttributes.Add (new CodeAttributeDeclaration (
333 "System.Diagnostics.CodeAnalysis.SuppressMessageAttribute",
334 new CodeAttributeArgument (
335 new CodePrimitiveExpression ("Microsoft.Performance")),
336 new CodeAttributeArgument (
337 new CodePrimitiveExpression ("CA1811:AvoidUncalledPrivateCode"))));
342 static CodeAttributeDeclaration DefaultPropertyAttribute ()
344 // CustomAttributes for ResourceManager and Culture
345 return new CodeAttributeDeclaration ("System.ComponentModel.EditorBrowsableAttribute",
346 new CodeAttributeArgument (
347 new CodeFieldReferenceExpression (
348 new CodeTypeReferenceExpression (
349 "System.ComponentModel.EditorBrowsableState"),
353 static CodeMemberProperty GenerateCultureProp (bool internalClass)
356 CodeMemberProperty cultureProp = GeneratePropertyBase ("Culture",
357 typeof (System.Globalization.CultureInfo),
362 // attributes - same as ResourceManager
363 cultureProp.CustomAttributes.Add (DefaultPropertyAttribute ());
366 cultureProp.GetStatements.Add (new CodeMethodReturnStatement (
367 new CodeFieldReferenceExpression (
368 null,"resourceCulture")));
371 cultureProp.SetStatements.Add (new CodeAssignStatement (
372 new CodeFieldReferenceExpression (
373 null,"resourceCulture"),
374 new CodePropertySetValueReferenceExpression ()));
379 static CodeMemberProperty GenerateResourceManagerProp (string baseNameToUse, string resourcesToUse,
382 // ResourceManager property
383 CodeMemberProperty resourceManagerProp = GeneratePropertyBase ("ResourceManager",
384 typeof (System.Resources.ResourceManager),
389 resourceManagerProp.CustomAttributes.Add (DefaultPropertyAttribute ());
392 // true statments for check if resourceMan null to go inside getter
393 CodeStatement [] trueStatements = new CodeStatement [2];
395 trueStatements [0] = new CodeVariableDeclarationStatement (
396 new CodeTypeReference (
397 "System.Resources.ResourceManager"),
398 "temp", new CodeObjectCreateExpression (
399 new CodeTypeReference (
400 "System.Resources.ResourceManager"),
401 new CodePrimitiveExpression (resourcesToUse),
402 new CodePropertyReferenceExpression (
403 new CodeTypeOfExpression (baseNameToUse),
406 trueStatements [1] = new CodeAssignStatement (new CodeFieldReferenceExpression (null, "resourceMan"),
407 new CodeVariableReferenceExpression ("temp"));
409 resourceManagerProp.GetStatements.Add (new CodeConditionStatement (
410 new CodeMethodInvokeExpression (
411 new CodeMethodReferenceExpression (
412 new CodeTypeReferenceExpression ("System.Object"), "Equals"),
413 new CodePrimitiveExpression(null),
414 new CodeFieldReferenceExpression (
415 null,"resourceMan")),trueStatements));
417 resourceManagerProp.GetStatements.Add (new CodeMethodReturnStatement (
418 new CodeFieldReferenceExpression ( null,"resourceMan")));
420 return resourceManagerProp;
424 static CodeMemberProperty GenerateStandardResourceProp (string propName, string resName,
425 Type propertyType, bool isInternal)
428 CodeMemberProperty prop = GeneratePropertyBase (propName, propertyType, isInternal, true, false);
430 prop.GetStatements.Add (new CodeVariableDeclarationStatement (
431 new CodeTypeReference ("System.Object"),
433 new CodeMethodInvokeExpression (
434 new CodePropertyReferenceExpression (null,"ResourceManager"),
436 new CodePrimitiveExpression (resName),
437 new CodeFieldReferenceExpression (null,"resourceCulture"))));
439 prop.GetStatements.Add (new CodeMethodReturnStatement (
440 new CodeCastExpression (
441 new CodeTypeReference (propertyType),
442 new CodeVariableReferenceExpression ("obj"))));
447 static CodeMemberProperty GenerateStringResourceProp (string propName, string resName, bool isInternal)
449 CodeMemberProperty prop = GeneratePropertyBase (propName, typeof (String), isInternal, true, false);
451 prop.GetStatements.Add (new CodeMethodReturnStatement (
452 new CodeMethodInvokeExpression (
453 new CodeMethodReferenceExpression (
454 new CodePropertyReferenceExpression (null,"ResourceManager"),
456 new CodePrimitiveExpression (resName),
457 new CodeFieldReferenceExpression (null,"resourceCulture"))));
462 static CodeMemberProperty GenerateStreamResourceProp (string propName, string resName, bool isInternal)
464 CodeMemberProperty prop = GeneratePropertyBase (propName, typeof (UnmanagedMemoryStream),
465 isInternal, true, false);
467 prop.GetStatements.Add (new CodeMethodReturnStatement (
468 new CodeMethodInvokeExpression (
469 new CodeMethodReferenceExpression (
470 new CodePropertyReferenceExpression (null,"ResourceManager"),
472 new CodePrimitiveExpression (resName),
473 new CodeFieldReferenceExpression (null,"resourceCulture"))));
478 static CodeMemberProperty GeneratePropertyBase (string name, Type propertyType, bool isInternal,
479 bool hasGet, bool hasSet)
481 CodeMemberProperty prop = new CodeMemberProperty ();
484 prop.Type = new CodeTypeReference (propertyType);
488 prop.Attributes = (MemberAttributes.Abstract
489 | MemberAttributes.Final
490 | MemberAttributes.Assembly);
492 prop.Attributes = (MemberAttributes.Abstract
493 | MemberAttributes.Final
494 | MemberAttributes.FamilyAndAssembly
495 | MemberAttributes.FamilyOrAssembly);
497 prop.HasGet = hasGet;
498 prop.HasSet = hasSet;
502 public static string VerifyResourceName (string key, CodeDomProvider provider)
509 throw new ArgumentNullException ("Parameter: key must not be null");
510 if (provider == null)
511 throw new ArgumentNullException ("Parameter: provider must not be null");
513 if (key == String.Empty) {
516 // replaces special chars
517 charKey = key.ToCharArray ();
518 for (int i = 0; i < charKey.Length; i++)
519 charKey [i] = VerifySpecialChar (charKey [i]);
520 keyToUse = new string(charKey);
522 // resolve if keyword
523 keyToUse = provider.CreateValidIdentifier (keyToUse);
524 // check if still not valid for provider
525 if (provider.IsValidIdentifier (keyToUse))
531 static char VerifySpecialChar (char ch)
533 for (int i = 0; i < specialChars.Length; i++) {
534 if (specialChars [i] == ch)
540 static string CleanNamespaceChars (string name)
542 char [] nameChars = name.ToCharArray ();
543 for (int i = 0; i < nameChars.Length ;i++) {
544 foreach (char c in specialCharsNameSpace) {
545 if (nameChars [i] == c)
549 return new string (nameChars);
553 public string VerifiedKey { get;set; }
554 public object Resource { get;set; }
555 public bool isUnmatchable { get;set; }
556 public bool toIgnore { get;set; }
558 public ResourceItem (object value)