1 //---------------------------------------------------------------------------
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // Description: ResourceGenerator class
6 // It generates the localized baml from translations
8 //---------------------------------------------------------------------------
12 using System
.Globalization
;
13 using System
.Runtime
.InteropServices
;
14 using System
.Collections
;
15 using System
.Reflection
;
16 using System
.Reflection
.Emit
;
17 using System
.Diagnostics
;
18 using System
.Resources
;
19 using System
.Threading
;
20 using System
.Windows
.Threading
;
21 using System
.Windows
.Markup
.Localizer
;
23 namespace BamlLocalization
26 /// ResourceGenerator class
28 internal static class ResourceGenerator
31 /// Generates localized Baml from translations
33 /// <param name="options">LocBaml options</param>
34 /// <param name="dictionaries">the translation dictionaries</param>
35 internal static void Generate(LocBamlOptions options
, TranslationDictionariesReader dictionaries
)
37 // base on the input, we generate differently
38 switch(options
.InputType
)
43 string bamlName
= Path
.GetFileName(options
.Input
);
45 // outpuf file name is Output dir + input file name
46 string outputFileName
= GetOutputFileName(options
);
48 // construct the full path
49 string fullPathOutput
= Path
.Combine(options
.Output
, outputFileName
);
51 options
.Write(StringLoader
.Get("GenerateBaml", fullPathOutput
));
53 using (Stream input
= File
.OpenRead(options
.Input
))
55 using (Stream output
= new FileStream(fullPathOutput
, FileMode
.Create
))
57 BamlLocalizationDictionary dictionary
= dictionaries
[bamlName
];
59 // if it is null, just create an empty dictionary.
60 if (dictionary
== null)
61 dictionary
= new BamlLocalizationDictionary();
63 GenerateBamlStream(input
, output
, dictionary
, options
);
67 options
.WriteLine(StringLoader
.Get("Done"));
70 case FileType
.RESOURCES
:
72 string outputFileName
= GetOutputFileName(options
);
73 string fullPathOutput
= Path
.Combine(options
.Output
, outputFileName
);
75 using (Stream input
= File
.OpenRead(options
.Input
))
77 using (Stream output
= File
.OpenWrite(fullPathOutput
))
79 // create a Resource reader on the input;
80 IResourceReader reader
= new ResourceReader(input
);
82 // create a writer on the output;
83 IResourceWriter writer
= new ResourceWriter(output
);
85 GenerateResourceStream(
87 options
.Input
, // resources name
88 reader
, // resource reader
89 writer
, // resource writer
90 dictionaries
); // translations
94 // now generate and close
100 options
.WriteLine(StringLoader
.Get("DoneGeneratingResource", outputFileName
));
106 GenerateAssembly(options
, dictionaries
);
111 Debug
.Assert(false, "Can't generate to this type");
118 private static void GenerateBamlStream(Stream input
, Stream output
, BamlLocalizationDictionary dictionary
, LocBamlOptions options
)
120 string commentFile
= Path
.ChangeExtension(options
.Input
, "loc");
121 TextReader commentStream
= null;
125 if (File
.Exists(commentFile
))
127 commentStream
= new StreamReader(commentFile
);
130 // create a localizabilty resolver based on reflection
131 BamlLocalizabilityByReflection localizabilityReflector
=
132 new BamlLocalizabilityByReflection(options
.Assemblies
);
134 // create baml localizer
135 BamlLocalizer mgr
= new BamlLocalizer(
137 localizabilityReflector
,
142 BamlLocalizationDictionary source
= mgr
.ExtractResources();
143 BamlLocalizationDictionary translations
= new BamlLocalizationDictionary();
145 foreach (DictionaryEntry entry
in dictionary
)
147 BamlLocalizableResourceKey key
= (BamlLocalizableResourceKey
) entry
.Key
;
148 // filter out unchanged items
149 if (!source
.Contains(key
)
150 || entry
.Value
== null
151 || source
[key
].Content
!= ((BamlLocalizableResource
)entry
.Value
).Content
)
153 translations
.Add(key
, (BamlLocalizableResource
)entry
.Value
);
158 mgr
.UpdateBaml(output
, translations
);
162 if (commentStream
!= null)
164 commentStream
.Close();
169 private static void GenerateResourceStream(
170 LocBamlOptions options
, // options from the command line
171 string resourceName
, // the name of the .resources file
172 IResourceReader reader
, // the reader for the .resources
173 IResourceWriter writer
, // the writer for the output .resources
174 TranslationDictionariesReader dictionaries
// the translations
178 options
.WriteLine(StringLoader
.Get("GenerateResource", resourceName
));
179 // enumerate through each resource and generate it
180 foreach(DictionaryEntry entry
in reader
)
182 string name
= entry
.Key
as string;
183 object resourceValue
= null;
185 // See if it looks like a Baml resource
186 if (BamlStream
.IsResourceEntryBamlStream(name
, entry
.Value
))
188 Stream targetStream
= null;
190 options
.Write(StringLoader
.Get("GenerateBaml", name
));
192 // grab the localizations available for this Baml
193 string bamlName
= BamlStream
.CombineBamlStreamName(resourceName
, name
);
194 BamlLocalizationDictionary localizations
= dictionaries
[bamlName
];
195 if (localizations
!= null)
197 targetStream
= new MemoryStream();
199 // generate into a new Baml stream
201 (Stream
) entry
.Value
,
207 options
.WriteLine(StringLoader
.Get("Done"));
209 // sets the generated object to be the generated baml stream
210 resourceValue
= targetStream
;
213 if (resourceValue
== null)
216 // The stream is not localized as Baml yet, so we will make a copy of this item into
217 // the localized resources
220 // We will add the value as is if it is serializable. Otherwise, make a copy
221 resourceValue
= entry
.Value
;
223 object[] serializableAttributes
= resourceValue
.GetType().GetCustomAttributes(typeof(SerializableAttribute
), true);
224 if (serializableAttributes
.Length
== 0)
226 // The item returned from resource reader is not serializable
227 // If it is Stream, we can wrap all the values in a MemoryStream and
228 // add to the resource. Otherwise, we had to skip this resource.
229 Stream resourceStream
= resourceValue
as Stream
;
230 if (resourceStream
!= null)
232 Stream targetStream
= new MemoryStream();
233 byte[] buffer
= new byte[resourceStream
.Length
];
234 resourceStream
.Read(buffer
, 0, buffer
.Length
);
235 targetStream
= new MemoryStream(buffer
);
236 resourceValue
= targetStream
;
241 if (resourceValue
!= null)
243 writer
.AddResource(name
, resourceValue
);
248 private static void GenerateStandaloneResource(string fullPathName
, Stream resourceStream
)
250 // simply do a copy for the stream
251 using (FileStream file
= new FileStream(fullPathName
, FileMode
.Create
, FileAccess
.Write
))
253 const int BUFFER_SIZE
= 4096;
254 byte[] buffer
= new byte[BUFFER_SIZE
];
256 while (bytesRead
> 0)
258 bytesRead
= resourceStream
.Read(buffer
, 0, BUFFER_SIZE
);
259 file
.Write(buffer
, 0, bytesRead
);
264 //--------------------------------------------------
265 // The function follows Managed code parser
266 // implementation. in the future, maybe they should
267 // share the same code
268 //--------------------------------------------------
269 private static void GenerateAssembly(LocBamlOptions options
, TranslationDictionariesReader dictionaries
)
271 // there are many names to be used when generating an assembly
272 string sourceAssemblyFullName
= options
.Input
; // source assembly full path
273 string outputAssemblyDir
= options
.Output
; // output assembly directory
274 string outputAssemblyLocalName
= GetOutputFileName(options
); // output assembly name
275 string moduleLocalName
= GetAssemblyModuleLocalName(options
, outputAssemblyLocalName
); // the module name within the assmbly
277 // get the source assembly
278 Assembly srcAsm
= Assembly
.LoadFrom(sourceAssemblyFullName
);
280 // obtain the assembly name
281 AssemblyName targetAssemblyNameObj
= srcAsm
.GetName();
283 // store the culture info of the source assembly
284 CultureInfo srcCultureInfo
= targetAssemblyNameObj
.CultureInfo
;
286 // update it to use it for target assembly
287 targetAssemblyNameObj
.Name
= Path
.GetFileNameWithoutExtension(outputAssemblyLocalName
);
288 targetAssemblyNameObj
.CultureInfo
= options
.CultureInfo
;
290 // we get a assembly builder
291 AssemblyBuilder targetAssemblyBuilder
= Thread
.GetDomain().DefineDynamicAssembly(
292 targetAssemblyNameObj
, // name of the assembly
293 AssemblyBuilderAccess
.RunAndSave
, // access rights
294 outputAssemblyDir
// storage dir
297 // we create a module builder for embeded resource modules
298 ModuleBuilder moduleBuilder
= targetAssemblyBuilder
.DefineDynamicModule(
300 outputAssemblyLocalName
303 options
.WriteLine(StringLoader
.Get("GenerateAssembly"));
305 // now for each resource in the assembly
306 foreach (string resourceName
in srcAsm
.GetManifestResourceNames())
308 // get the resource location for the resource
309 ResourceLocation resourceLocation
= srcAsm
.GetManifestResourceInfo(resourceName
).ResourceLocation
;
311 // if this resource is in another assemlby, we will skip it
312 if ((resourceLocation
& ResourceLocation
.ContainedInAnotherAssembly
) != 0)
314 continue; // in resource assembly, we don't have resource that is contained in another assembly
317 // gets the neutral resource name, giving it the source culture info
318 string neutralResourceName
= GetNeutralResModuleName(resourceName
, srcCultureInfo
);
320 // gets the target resource name, by giving it the target culture info
321 string targetResourceName
= GetCultureSpecificResourceName(neutralResourceName
, options
.CultureInfo
);
324 Stream resourceStream
= srcAsm
.GetManifestResourceStream(resourceName
);
326 // see if it is a .resources
327 if (neutralResourceName
.ToLower(CultureInfo
.InvariantCulture
).EndsWith(".resources"))
329 // now we think we have resource stream
330 // get the resource writer
331 IResourceWriter writer
;
332 // check if it is a embeded assembly
333 if ((resourceLocation
& ResourceLocation
.Embedded
) != 0)
335 // gets the resource writer from the module builder
336 writer
= moduleBuilder
.DefineResource(
337 targetResourceName
, // resource name
338 targetResourceName
, // resource description
339 ResourceAttributes
.Public
// visibilty of this resource to other assembly
344 // it is a standalone resource, we get the resource writer from the assembly builder
345 writer
= targetAssemblyBuilder
.DefineResource(
346 targetResourceName
, // resource name
347 targetResourceName
, // description
348 targetResourceName
, // file name to save to
349 ResourceAttributes
.Public
// visibility of this resource to other assembly
353 // get the resource reader
354 IResourceReader reader
= new ResourceReader(resourceStream
);
356 // generate the resources
357 GenerateResourceStream(options
, resourceName
, reader
, writer
, dictionaries
);
359 // we don't call writer.Generate() or writer.Close() here
360 // because the AssemblyBuilder will call them when we call Save() on it.
364 // else it is a stand alone untyped manifest resources.
365 string extension
= Path
.GetExtension(targetResourceName
);
367 string fullFileName
= Path
.Combine(outputAssemblyDir
, targetResourceName
);
369 // check if it is a .baml, case-insensitive
370 if (string.Compare(extension
, ".baml", true, CultureInfo
.InvariantCulture
) == 0)
372 // try to localized the the baml
373 // find the resource dictionary
374 BamlLocalizationDictionary dictionary
= dictionaries
[resourceName
];
376 // if it is null, just create an empty dictionary.
377 if (dictionary
!= null)
379 // it is a baml stream
380 using (Stream output
= File
.OpenWrite(fullFileName
))
383 options
.WriteLine(StringLoader
.Get("GenerateStandaloneBaml", fullFileName
));
384 GenerateBamlStream(resourceStream
, output
, dictionary
, options
);
385 options
.WriteLine(StringLoader
.Get("Done"));
390 // can't find localization of it, just copy it
391 GenerateStandaloneResource( fullFileName
, resourceStream
);
396 // it is an untyped resource stream, just copy it
397 GenerateStandaloneResource( fullFileName
, resourceStream
);
400 // now add this resource file into the assembly
401 targetAssemblyBuilder
.AddResourceFile(
402 targetResourceName
, // resource name
403 targetResourceName
, // file name
404 ResourceAttributes
.Public
// visibility of the resource to other assembly
410 // at the end, generate the assembly
411 targetAssemblyBuilder
.Save(outputAssemblyLocalName
);
412 options
.WriteLine(StringLoader
.Get("DoneGeneratingAssembly"));
416 //-----------------------------------------
417 // private function dealing with naming
418 //-----------------------------------------
420 // return the local output file name, i.e. without directory
421 private static string GetOutputFileName(LocBamlOptions options
)
423 string outputFileName
;
424 string inputFileName
= Path
.GetFileName(options
.Input
);
426 switch(options
.InputType
)
430 return inputFileName
;
434 inputFileName
= inputFileName
.Remove(inputFileName
.LastIndexOf('.')) + ".resources.dll";
435 return inputFileName
;
439 return inputFileName
;
441 case FileType
.RESOURCES
:
443 // get the output file name
444 outputFileName
= inputFileName
;
446 // get to the last dot seperating filename and extension
447 int lastDot
= outputFileName
.LastIndexOf('.');
448 int secondLastDot
= outputFileName
.LastIndexOf('.', lastDot
- 1);
449 if (secondLastDot
> 0)
451 string cultureName
= outputFileName
.Substring(secondLastDot
+ 1, lastDot
- secondLastDot
- 1);
452 if (LocBamlConst
.IsValidCultureName(cultureName
))
454 string extension
= outputFileName
.Substring(lastDot
);
455 string frontPart
= outputFileName
.Substring(0, secondLastDot
+ 1);
456 outputFileName
= frontPart
+ options
.CultureInfo
.Name
+ extension
;
459 return outputFileName
;
463 throw new NotSupportedException();
468 private static string GetAssemblyModuleLocalName(LocBamlOptions options
, string targetAssemblyName
)
471 if (targetAssemblyName
.ToLower(CultureInfo
.InvariantCulture
).EndsWith(".resources.dll"))
473 // we create the satellite assembly name
474 moduleName
= string.Format(
475 CultureInfo
.InvariantCulture
,
477 targetAssemblyName
.Substring(0, targetAssemblyName
.Length
- ".resources.dll".Length
),
478 options
.CultureInfo
.Name
,
484 moduleName
= targetAssemblyName
;
491 // return the neutral resource name
492 private static string GetNeutralResModuleName(string resourceName
, CultureInfo cultureInfo
)
494 if (cultureInfo
.Equals(CultureInfo
.InvariantCulture
))
500 // if it is an satellite assembly, we need to strip out the culture name
501 string normalizedName
= resourceName
.ToLower(CultureInfo
.InvariantCulture
);
502 int end
= normalizedName
.LastIndexOf(".resources");
509 int start
= normalizedName
.LastIndexOf('.', end
- 1);
511 if (start
> 0 && end
- start
> 0)
513 string cultureStr
= resourceName
.Substring( start
+ 1, end
- start
- 1);
515 if (string.Compare(cultureStr
, cultureInfo
.Name
, true) == 0)
517 // it has the correct culture name, so we can take it out
518 return resourceName
.Remove(start
, end
- start
);
525 private static string GetCultureSpecificResourceName(string neutralResourceName
, CultureInfo culture
)
527 // gets the extension
528 string extension
= Path
.GetExtension(neutralResourceName
);
530 // swap in culture name
531 string cultureName
= Path
.ChangeExtension(neutralResourceName
, culture
.Name
);
533 // return the new name with the same extension
534 return cultureName
+ extension
;