added samples
[windows-sources.git] / sdk / samples / WPFSamples / LocBaml / csharp / resourcegenerator.cs
blob800fa13da3131068ec237a5331bd353c9a1fbc37
1 //---------------------------------------------------------------------------
2 //
3 // Copyright (c) Microsoft Corporation. All rights reserved.
4 //
5 // Description: ResourceGenerator class
6 // It generates the localized baml from translations
7 //
8 //---------------------------------------------------------------------------
9 using System;
10 using System.IO;
11 using System.Windows;
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
25 /// <summary>
26 /// ResourceGenerator class
27 /// </summary>
28 internal static class ResourceGenerator
30 /// <summary>
31 /// Generates localized Baml from translations
32 /// </summary>
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)
40 case FileType.BAML :
42 // input file name
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"));
68 break;
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(
86 options, // options
87 options.Input, // resources name
88 reader, // resource reader
89 writer, // resource writer
90 dictionaries); // translations
92 reader.Close();
94 // now generate and close
95 writer.Generate();
96 writer.Close();
100 options.WriteLine(StringLoader.Get("DoneGeneratingResource", outputFileName));
101 break;
103 case FileType.EXE:
104 case FileType.DLL:
106 GenerateAssembly(options, dictionaries);
107 break;
109 default:
111 Debug.Assert(false, "Can't generate to this type");
112 break;
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(
136 input,
137 localizabilityReflector,
138 commentStream
141 // get the resources
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);
157 // update baml
158 mgr.UpdateBaml(output, translations);
160 finally
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;
189 options.Write(" ");
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
200 GenerateBamlStream(
201 (Stream) entry.Value,
202 targetStream,
203 localizations,
204 options
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];
255 int bytesRead = 1;
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(
299 moduleLocalName,
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);
323 // resource stream
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
342 else
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.
362 else
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))
382 options.Write(" ");
383 options.WriteLine(StringLoader.Get("GenerateStandaloneBaml", fullFileName));
384 GenerateBamlStream(resourceStream, output, dictionary, options);
385 options.WriteLine(StringLoader.Get("Done"));
388 else
390 // can't find localization of it, just copy it
391 GenerateStandaloneResource( fullFileName, resourceStream);
394 else
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)
428 case FileType.BAML:
430 return inputFileName;
432 case FileType.EXE:
434 inputFileName = inputFileName.Remove(inputFileName.LastIndexOf('.')) + ".resources.dll";
435 return inputFileName;
437 case FileType.DLL :
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;
461 default :
463 throw new NotSupportedException();
468 private static string GetAssemblyModuleLocalName(LocBamlOptions options, string targetAssemblyName)
470 string moduleName;
471 if (targetAssemblyName.ToLower(CultureInfo.InvariantCulture).EndsWith(".resources.dll"))
473 // we create the satellite assembly name
474 moduleName = string.Format(
475 CultureInfo.InvariantCulture,
476 "{0}.{1}.{2}",
477 targetAssemblyName.Substring(0, targetAssemblyName.Length - ".resources.dll".Length),
478 options.CultureInfo.Name,
479 "resources.dll"
482 else
484 moduleName = targetAssemblyName;
486 return moduleName;
491 // return the neutral resource name
492 private static string GetNeutralResModuleName(string resourceName, CultureInfo cultureInfo)
494 if (cultureInfo.Equals(CultureInfo.InvariantCulture))
496 return resourceName;
498 else
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");
504 if (end < 0)
506 return resourceName;
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);
521 return resourceName;
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;