The Labs \ Source Viewer \ SSCLI \ System.Tools \ ResourceClassOptions

  1. // ==++==
  2. //
  3. //
  4. // Copyright (c) 2006 Microsoft Corporation. All rights reserved.
  5. //
  6. // The use and distribution terms for this software are contained in the file
  7. // named license.txt, which can be found in the root of this distribution.
  8. // By using this software in any fashion, you are agreeing to be bound by the
  9. // terms of this license.
  10. //
  11. // You must not remove this notice, or any other, from this software.
  12. //
  13. //
  14. // ==--==
  15. namespace System.Tools
  16. {
  17.     using System;
  18.     using System.IO;
  19.     using System.Collections;
  20.     using System.Collections.Generic;
  21.     using System.Resources;
  22.     using System.Text;
  23.     using System.Diagnostics;
  24.     using System.Globalization;
  25.     using System.CodeDom;
  26.     using System.CodeDom.Compiler;
  27.     using System.Xml;
  28.     using System.Reflection;
  29.     using System.Runtime.InteropServices;
  30.    
  31.     // .NET Development Platform Resource file Generator
  32.     //
  33.     // This program will read in text files or ResX files of name-value pairs and
  34.     // produces a .NET .resources file. Additionally ResGen can change data from
  35.     // any of these three formats to any of the other formats (though text files
  36.     // only support strings).
  37.     //
  38.     // The text files must have lines of the form name=value, and comments are
  39.     // allowed ('#' at the beginning of the line).
  40.     //
  41.     /// <include file='doc\ResGen.uex' path='docs/doc[@for="ResGen"]/*' />
  42.     /// <devdoc>
  43.     /// <para>[To be supplied.]</para>
  44.     /// </devdoc>
  45.     public sealed class ResGen
  46.     {
  47.         private const int errorCode = -1;
  48.        
  49.         private static int errors = 0;
  50.         private static int warnings = 0;
  51.        
  52.         // We use a list to preserve the resource ordering (primarily for easier testing),
  53.         // but also use a hash table to check for duplicate names.
  54.         private static ArrayList resources = new ArrayList();
  55.         private static Hashtable resourcesHashTable = new Hashtable(StringComparer.OrdinalIgnoreCase);
  56.        
  57.         private static List<AssemblyName> assemblyList;
  58.        
  59.         internal sealed class ResourceClassOptions
  60.         {
  61.             private string _language;
  62.             private string _nameSpace;
  63.             private string _className;
  64.             private string _outputFileName;
  65.             private bool _internalClass;
  66.            
  67.             internal ResourceClassOptions(string language, string nameSpace, string className, string outputFileName, bool isClassInternal)
  68.             {
  69.                 _language = language;
  70.                 _nameSpace = nameSpace;
  71.                 _className = className;
  72.                 _outputFileName = outputFileName;
  73.                 _internalClass = isClassInternal;
  74.             }
  75.            
  76.             internal string Language {
  77.                 get { return _language; }
  78.             }
  79.            
  80.             internal string NameSpace {
  81.                 get { return _nameSpace; }
  82.             }
  83.            
  84.             internal string ClassName {
  85.                 get { return _className; }
  86.             }
  87.            
  88.             internal string OutputFileName {
  89.                 get { return _outputFileName; }
  90.             }
  91.            
  92.             internal bool InternalClass {
  93.                 get { return _internalClass; }
  94.                 set { _internalClass = value; }
  95.             }
  96.         }
  97.        
  98.         internal sealed class LineNumberStreamReader : StreamReader
  99.         {
  100.             // Line numbers start from 1, as well as line position.
  101.             // For better error reporting, set line number to 1 and col to 0.
  102.             int _lineNumber;
  103.             int _col;
  104.            
  105.             internal LineNumberStreamReader(string fileName, Encoding encoding, bool detectEncoding) : base(fileName, encoding, detectEncoding)
  106.             {
  107.                 _lineNumber = 1;
  108.                 _col = 0;
  109.             }
  110.            
  111.             internal LineNumberStreamReader(Stream stream) : base(stream)
  112.             {
  113.                 _lineNumber = 1;
  114.                 _col = 0;
  115.             }
  116.            
  117.             public override int Read()
  118.             {
  119.                 int ch = base.Read();
  120.                 if (ch != -1) {
  121.                     _col++;
  122.                     if (ch == '\n') {
  123.                         _lineNumber++;
  124.                         _col = 0;
  125.                     }
  126.                 }
  127.                 return ch;
  128.             }
  129.            
  130.             public override int Read(            [In(), Out()]
  131. char[] chars, int index, int count)
  132.             {
  133.                 int r = base.Read(chars, index, count);
  134.                 for (int i = 0; i < r; i++) {
  135.                     if (chars[i + index] == '\n') {
  136.                         _lineNumber++;
  137.                         _col = 0;
  138.                     }
  139.                     else
  140.                         _col++;
  141.                 }
  142.                 return r;
  143.             }
  144.            
  145.             public override string ReadLine()
  146.             {
  147.                 string s = base.ReadLine();
  148.                 if (s != null) {
  149.                     _lineNumber++;
  150.                     _col = 0;
  151.                 }
  152.                 return s;
  153.             }
  154.            
  155.             public override string ReadToEnd()
  156.             {
  157.                 throw new NotImplementedException("NYI");
  158.             }
  159.            
  160.             internal int LineNumber {
  161.                 get { return _lineNumber; }
  162.             }
  163.            
  164.             internal int LinePosition {
  165.                 get { return _col; }
  166.             }
  167.         }
  168.        
  169.         // For flow of control & passing sufficient error context back
  170.         // from ReadTextResources
  171.         internal sealed class TextFileException : Exception
  172.         {
  173.             private string _fileName;
  174.             private int _lineNumber;
  175.             private int _column;
  176.            
  177.             internal TextFileException(string message, string fileName, int lineNumber, int linePosition) : base(message)
  178.             {
  179.                 _fileName = fileName;
  180.                 _lineNumber = lineNumber;
  181.                 _column = linePosition;
  182.             }
  183.            
  184.             internal string FileName {
  185.                 get { return _fileName; }
  186.             }
  187.            
  188.             internal int LineNumber {
  189.                 get { return _lineNumber; }
  190.             }
  191.            
  192.             internal int LinePosition {
  193.                 get { return _column; }
  194.             }
  195.         }
  196.        
  197.         private static void AddResource(string name, object value, string inputFileName, int lineNumber, int linePosition)
  198.         {
  199.             Entry entry = new Entry(name, value);
  200.            
  201.             if (resourcesHashTable.ContainsKey(name)) {
  202.                 Warning(SR.GetString(SR.DuplicateResourceKey, name), inputFileName, lineNumber, linePosition);
  203.                 return;
  204.             }
  205.            
  206.             resources.Add(entry);
  207.             resourcesHashTable.Add(name, value);
  208.         }
  209.        
  210.         private static void AddResource(string name, object value, string inputFileName)
  211.         {
  212.             Entry entry = new Entry(name, value);
  213.            
  214.             if (resourcesHashTable.ContainsKey(name)) {
  215.                 Warning(SR.GetString(SR.DuplicateResourceKey, name), inputFileName);
  216.                 return;
  217.             }
  218.            
  219.             resources.Add(entry);
  220.             resourcesHashTable.Add(name, value);
  221.         }
  222.        
  223.        
  224.        
  225.        
  226.         private static void Error(string message)
  227.         {
  228.             Error(message, 0);
  229.         }
  230.        
  231.         // Use this for general resgen errors with no specific file info
  232.         private static void Error(string message, int errorNumber)
  233.         {
  234.             string errorFormat = "ResGen : error RG{1:0000}: {0}";
  235.             Console.Error.WriteLine(errorFormat, message, errorNumber);
  236.             errors++;
  237.         }
  238.        
  239.         // Use this for a general error w.r.t. a file, like a missing file.
  240.         private static void Error(string message, string fileName)
  241.         {
  242.             Error(message, fileName, 0);
  243.         }
  244.        
  245.         // Use this for a general error w.r.t. a file, like a missing file.
  246.         private static void Error(string message, string fileName, int errorNumber)
  247.         {
  248.             string errorFormat = "{0} : error RG{1:0000}: {2}";
  249.             Console.Error.WriteLine(errorFormat, fileName, errorNumber, message);
  250.             errors++;
  251.         }
  252.        
  253.         // For specific errors about the contents of a file and you know where
  254.         // the error occurred.
  255.         private static void Error(string message, string fileName, int line, int column)
  256.         {
  257.             Error(message, fileName, line, column, 0);
  258.         }
  259.        
  260.         // For specific errors about the contents of a file and you know where
  261.         // the error occurred.
  262.         private static void Error(string message, string fileName, int line, int column, int errorNumber)
  263.         {
  264.             string errorFormat = "{0}({1},{2}): error RG{3:0000}: {4}";
  265.             Console.Error.WriteLine(errorFormat, fileName, line, column, errorNumber, message);
  266.             errors++;
  267.         }
  268.        
  269.         // General warnings
  270.         private static void Warning(string message)
  271.         {
  272.             string warningFormat = "ResGen : warning RG0000 : {0}";
  273.             Console.Error.WriteLine(warningFormat, message);
  274.             warnings++;
  275.         }
  276.        
  277.         // Warnings in a particular file, but we don't have line number info
  278.         private static void Warning(string message, string fileName)
  279.         {
  280.             Warning(message, fileName, 0);
  281.         }
  282.        
  283.         // Warnings in a particular file, but we don't have line number info
  284.         private static void Warning(string message, string fileName, int warningNumber)
  285.         {
  286.             string warningFormat = "{0} : warning RG{1:0000}: {2}";
  287.             Console.Error.WriteLine(warningFormat, fileName, warningNumber, message);
  288.             warnings++;
  289.         }
  290.        
  291.         // Warnings in a file on a particular line and character
  292.         private static void Warning(string message, string fileName, int line, int column)
  293.         {
  294.             Warning(message, fileName, line, column, 0);
  295.         }
  296.        
  297.         // Warnings in a file on a particular line and character
  298.         private static void Warning(string message, string fileName, int line, int column, int warningNumber)
  299.         {
  300.             string warningFormat = "{0}({1},{2}): warning RG{3:0000}: {4}";
  301.             Console.Error.WriteLine(warningFormat, fileName, line, column, warningNumber, message);
  302.             warnings++;
  303.         }
  304.        
  305.         private static Format GetFormat(string filename)
  306.         {
  307.             string extension = Path.GetExtension(filename);
  308.             if (String.Compare(extension, ".txt", true, CultureInfo.InvariantCulture) == 0 || String.Compare(extension, ".restext", true, CultureInfo.InvariantCulture) == 0)
  309.                 return Format.Text;
  310.             else if (String.Compare(extension, ".resources", true, CultureInfo.InvariantCulture) == 0)
  311.                 return Format.Binary;
  312.             else {
  313.                 Error(SR.GetString(SR.UnknownFileExtension, extension, filename));
  314.                 Environment.Exit(errorCode);
  315.                 return Format.Text;
  316.                 // never reached
  317.             }
  318.         }
  319.        
  320.         /// <include file='doc\ResGen.uex' path='docs/doc[@for="ResGen.Main"]/*' />
  321.         /// <devdoc>
  322.         /// <para>[To be supplied.]</para>
  323.         /// </devdoc>
  324.         public static void Main(string[] args)
  325.         {
  326.             // Tell build we had an error, then set this to 0 if we complete successfully.
  327.             Environment.ExitCode = errorCode;
  328.             if (args.Length < 1 || args[0].Equals("-h") || args[0].Equals("-?") || args[0].Equals("/h") || args[0].Equals("/?")) {
  329.                 Usage();
  330.                 return;
  331.             }
  332.            
  333.             string[] inFiles = null;
  334.             string[] outFiles = null;
  335.             // Default resource class options for all classes
  336.             ResourceClassOptions resourceClassOptions = null;
  337.             int argIndex = 0;
  338.             bool setSimpleInputFile = false;
  339.             // For resgen a.resources a.resx
  340.             bool gotOutputFileName = false;
  341.             // For resgen a.txt a.resources b.txt
  342.             bool useSourcePath = false;
  343.             bool isClassInternal = true;
  344.            
  345.             while (argIndex < args.Length && errors == 0) {
  346.                 if (args[argIndex].Equals("/compile")) {
  347.                     inFiles = new string[args.Length - argIndex - 1];
  348.                     outFiles = new string[args.Length - argIndex - 1];
  349.                     for (int i = 0; i < inFiles.Length; i++) {
  350.                         inFiles[i] = args[argIndex + 1];
  351.                         int index = inFiles[i].IndexOf(",");
  352.                         if (index != -1) {
  353.                             string tmp = inFiles[i];
  354.                             inFiles[i] = tmp.Substring(0, index);
  355.                             if (!ValidResourceFileName(inFiles[i])) {
  356.                                 Error(SR.GetString(SR.BadFileExtension, inFiles[i]));
  357.                                 break;
  358.                             }
  359.                             if (index == tmp.Length - 1) {
  360.                                 Error(SR.GetString(SR.MalformedCompileString, tmp));
  361.                                 inFiles = new string[0];
  362.                                 break;
  363.                             }
  364.                             outFiles[i] = tmp.Substring(index + 1);
  365.                             if (!ValidResourceFileName(outFiles[i])) {
  366.                                 Error(SR.GetString(SR.BadFileExtension, outFiles[i]));
  367.                                 break;
  368.                             }
  369.                         }
  370.                         else {
  371.                             if (!ValidResourceFileName(inFiles[i])) {
  372.                                 if (inFiles[i][0] == '/' || inFiles[i][0] == '-') {
  373.                                     Error(SR.GetString(SR.InvalidCommandLineSyntax, "/compile", inFiles[i]));
  374.                                 }
  375.                                 else
  376.                                     Error(SR.GetString(SR.BadFileExtension, inFiles[i]));
  377.                                 break;
  378.                             }
  379.                            
  380.                             string resourceFileName = GetResourceFileName(inFiles[i]);
  381.                             Debug.Assert(resourceFileName != null, "Unexpected null file name!");
  382.                             outFiles[i] = resourceFileName;
  383.                         }
  384.                         argIndex++;
  385.                     }
  386.                 }
  387.                 else if (args[argIndex].StartsWith("/r:") || args[argIndex].StartsWith("-r:")) {
  388.                     // assembly names syntax /r:c:\system\System.Drawing.dll
  389.                     string s = args[argIndex];
  390.                     s = s.Substring(3);
  391.                     // Skip over "/r:"
  392.                     if (assemblyList == null) {
  393.                         assemblyList = new List<AssemblyName>();
  394.                     }
  395.                     try {
  396.                         assemblyList.Add(AssemblyName.GetAssemblyName(s));
  397.                     }
  398.                     catch (Exception e) {
  399.                         Error(SR.GetString(SR.CantLoadAssembly, s, e.GetType().Name, e.Message));
  400.                     }
  401.                 }
  402.                 else if (args[argIndex].ToLower(CultureInfo.InvariantCulture).Equals("/usesourcepath") || args[argIndex].ToLower(CultureInfo.InvariantCulture).Equals("-usesourcepath")) {
  403.                     useSourcePath = true;
  404.                 }
  405.                 else if (args[argIndex].ToLower(CultureInfo.InvariantCulture).Equals("/publicclass") || args[argIndex].ToLower(CultureInfo.InvariantCulture).Equals("-publicclass")) {
  406.                     isClassInternal = false;
  407.                 }
  408.                 else {
  409.                     if (ValidResourceFileName(args[argIndex])) {
  410.                         if (!setSimpleInputFile) {
  411.                             inFiles = new string[1];
  412.                             inFiles[0] = args[argIndex];
  413.                             outFiles = new string[1];
  414.                             outFiles[0] = GetResourceFileName(inFiles[0]);
  415.                             setSimpleInputFile = true;
  416.                         }
  417.                         else {
  418.                             if (!gotOutputFileName) {
  419.                                 outFiles[0] = args[argIndex];
  420.                                 gotOutputFileName = true;
  421.                             }
  422.                             else {
  423.                                 Error(SR.GetString(SR.InvalidCommandLineSyntax, "<none>", args[argIndex]));
  424.                                 break;
  425.                             }
  426.                         }
  427.                     }
  428.                     else {
  429.                         if (args[argIndex][0] == '/' || args[argIndex][0] == '-') {
  430.                             Error(SR.GetString(SR.BadCommandLineOption, args[argIndex]));
  431.                         }
  432.                         else
  433.                             Error(SR.GetString(SR.BadFileExtension, args[argIndex]));
  434.                         return;
  435.                     }
  436.                 }
  437.                 argIndex++;
  438.             }
  439.            
  440.             if ((inFiles == null || inFiles.Length == 0) && errors == 0) {
  441.                 Usage();
  442.                 return;
  443.             }
  444.            
  445.             if (resourceClassOptions != null) {
  446.                 resourceClassOptions.InternalClass = isClassInternal;
  447.                
  448.                 // Verify we don't produce two identically named resource classes,
  449.                 // or write different classes to the same file when using the
  450.                 // /compile option.
  451.                 if (inFiles.Length > 1) {
  452.                     if (resourceClassOptions.ClassName != null || resourceClassOptions.OutputFileName != null) {
  453.                         Error(SR.GetString(SR.CompileAndSTRDontMix));
  454.                     }
  455.                 }
  456.             }
  457.            
  458.             // Do all the work.
  459.             if (errors == 0) {
  460.                 for (int i = 0; i < inFiles.Length; i++) {
  461.                     ProcessFile(inFiles[i], outFiles[i], resourceClassOptions, useSourcePath);
  462.                 }
  463.             }
  464.            
  465.             // Quit & report errors, if necessary.
  466.             if (warnings != 0)
  467.                 Console.Error.WriteLine(SR.GetString(SR.WarningCount, warnings));
  468.            
  469.             if (errors != 0) {
  470.                 Console.Error.WriteLine(SR.GetString(SR.ErrorCount, errors));
  471.                 Debug.Assert(Environment.ExitCode != 0);
  472.                 // Now delete all the output files, ensuring the build won't
  473.                 // continue using half-generated output files. This is a
  474.                 // backstop for other errors up above.
  475.                 if (outFiles != null) {
  476.                     foreach (string outFile in outFiles) {
  477.                         if (File.Exists(outFile)) {
  478.                             try {
  479.                                 File.Delete(outFile);
  480.                             }
  481.                             catch {
  482.                             }
  483.                         }
  484.                     }
  485.                 }
  486.             }
  487.             else {
  488.                 // Tell build we succeeded.
  489.                 Environment.ExitCode = 0;
  490.             }
  491.         }
  492.        
  493.         private static string GetResourceFileName(string inFile)
  494.         {
  495.             if (inFile == null) {
  496.                 return null;
  497.             }
  498.            
  499.             // Note that the naming scheme is basename.[en-US.]resources
  500.             int end = inFile.LastIndexOf('.');
  501.             if (end == -1) {
  502.                 return null;
  503.             }
  504.             return inFile.Substring(0, end) + ".resources";
  505.         }
  506.        
  507.         private static bool ValidResourceFileName(string inFile)
  508.         {
  509.             if (inFile == null)
  510.                 return false;
  511.            
  512.             CompareInfo comp = CultureInfo.InvariantCulture.CompareInfo;
  513.             if (comp.IsSuffix(inFile, ".resx", CompareOptions.IgnoreCase) || comp.IsSuffix(inFile, ".txt", CompareOptions.IgnoreCase) || comp.IsSuffix(inFile, ".restext", CompareOptions.IgnoreCase) || comp.IsSuffix(inFile, ".resources", CompareOptions.IgnoreCase))
  514.                 return true;
  515.             return false;
  516.         }
  517.        
  518.        
  519.         private static void ProcessFile(string inFile, string outFile, ResourceClassOptions resourceClassOptions, bool useSourcePath)
  520.         {
  521.             //Console.WriteLine("Processing {0} --> {1}", inFile, outFile);
  522.             // Reset state
  523.             resources.Clear();
  524.             resourcesHashTable.Clear();
  525.            
  526.             try {
  527.                 // Explicitly handle missing input files here - don't catch a
  528.                 // FileNotFoundException since we can get them from the loader
  529.                 // if we try loading an assembly version we can't find.
  530.                 if (!File.Exists(inFile)) {
  531.                     Error(SR.GetString(SR.FileNotFound, inFile));
  532.                     return;
  533.                 }
  534.                
  535.                 ReadResources(inFile, useSourcePath);
  536.             }
  537.             catch (ArgumentException ae) {
  538.                 if (ae.InnerException is XmlException) {
  539.                     XmlException xe = (XmlException)ae.InnerException;
  540.                     Error(xe.Message, inFile, xe.LineNumber, xe.LinePosition);
  541.                 }
  542.                 else {
  543.                     Error(ae.Message, inFile);
  544.                 }
  545.                 return;
  546.             }
  547.             catch (TextFileException tfe) {
  548.                 // Used to pass back error context from ReadTextResources to here.
  549.                 Error(tfe.Message, tfe.FileName, tfe.LineNumber, tfe.LinePosition);
  550.                 return;
  551.             }
  552.             catch (Exception e) {
  553.                 Error(e.Message, inFile);
  554.                 // We need to give meaningful error messages to the user.
  555.                 // Note that ResXResourceReader wraps any exception it gets
  556.                 // in an ArgumentException with the message "Invalid ResX input."
  557.                 // If you don't look at the InnerException, you have to attach
  558.                 // a debugger to find the problem.
  559.                 if (e.InnerException != null) {
  560.                     Exception inner = e.InnerException;
  561.                     StringBuilder sb = new StringBuilder(200);
  562.                     sb.Append(e.Message);
  563.                     while (inner != null) {
  564.                         sb.Append(" ---> ");
  565.                         sb.Append(inner.GetType().Name);
  566.                         sb.Append(": ");
  567.                         sb.Append(inner.Message);
  568.                         inner = inner.InnerException;
  569.                     }
  570.                     Error(SR.GetString(SR.SpecificError, e.InnerException.GetType().Name, sb.ToString()), inFile);
  571.                 }
  572.                 return;
  573.             }
  574.            
  575.             try {
  576.                 WriteResources(outFile);
  577.             }
  578.             catch (IOException io) {
  579.                 Error(SR.GetString(SR.WriteError, outFile), outFile);
  580.                 if (io.Message != null)
  581.                     Error(SR.GetString(SR.SpecificError, io.GetType().Name, io.Message), outFile);
  582.                 if (File.Exists(outFile)) {
  583.                     Error(SR.GetString(SR.CorruptOutput, outFile));
  584.                     try {
  585.                         File.Delete(outFile);
  586.                     }
  587.                     catch (Exception) {
  588.                         Error(SR.GetString(SR.DeleteOutputFileFailed, outFile));
  589.                     }
  590.                 }
  591.                 return;
  592.             }
  593.             catch (Exception e) {
  594.                 Error(SR.GetString(SR.GenericWriteError, outFile));
  595.                 if (e.Message != null)
  596.                     Error(SR.GetString(SR.SpecificError, e.GetType().Name, e.Message));
  597.             }
  598.         }
  599.        
  600.        
  601.        
  602.         // <doc>
  603.         // <desc>
  604.         // Reads the resources out of the specified file and populates the
  605.         // resources hashtable.
  606.         // </desc>
  607.         // <param term='filename'>
  608.         // Filename to load.
  609.         // </param>
  610.         // </doc>
  611.         private static void ReadResources(string filename, bool useSourcePath)
  612.         {
  613.             Format format = GetFormat(filename);
  614.             switch (format) {
  615.                 case Format.Text:
  616.                     ReadTextResources(filename);
  617.                     break;
  618.                 case Format.Binary:
  619.                    
  620.                    
  621.                     ReadResources(new ResourceReader(filename), filename);
  622.                     // closes reader for us
  623.                     break;
  624.                 default:
  625.                    
  626.                     Debug.Fail("Unknown format " + format.ToString());
  627.                     break;
  628.             }
  629.             Console.WriteLine(SR.GetString(SR.ReadIn, resources.Count, filename));
  630.         }
  631.        
  632.         // closes reader when done. File name is for error reporting.
  633.         private static void ReadResources(IResourceReader reader, string fileName)
  634.         {
  635.             using (reader) {
  636.                 IDictionaryEnumerator resEnum = reader.GetEnumerator();
  637.                 while (resEnum.MoveNext()) {
  638.                     string name = (string)resEnum.Key;
  639.                     object value = resEnum.Value;
  640.                     AddResource(name, value, fileName);
  641.                 }
  642.             }
  643.         }
  644.        
  645.         private static void ReadTextResources(string fileName)
  646.         {
  647.             // Check for byte order marks in the beginning of the input file, but
  648.             // default to UTF-8.
  649.             using (LineNumberStreamReader sr = new LineNumberStreamReader(fileName, new UTF8Encoding(true), true)) {
  650.                 StringBuilder name = new StringBuilder(255);
  651.                 StringBuilder value = new StringBuilder(2048);
  652.                
  653.                 int ch = sr.Read();
  654.                 while (ch != -1) {
  655.                     if (ch == '\n' || ch == '\r') {
  656.                         ch = sr.Read();
  657.                         continue;
  658.                     }
  659.                    
  660.                     // Skip over commented lines or ones starting with whitespace.
  661.                     // Support LocStudio INF format's comment char, ';'
  662.                     if (ch == '#' || ch == '\t' || ch == ' ' || ch == ';') {
  663.                         // comment char (or blank line) - skip line.
  664.                         sr.ReadLine();
  665.                         ch = sr.Read();
  666.                         continue;
  667.                     }
  668.                     // Note that in Beta we recommended users should put a [strings]
  669.                     // section in their file. Now it's completely unnecessary and can
  670.                     // only cause bugs. We will not parse anything using '[' stuff now
  671.                     // and we should give a warning about seeing [strings] stuff.
  672.                     // In V1.1 or V2, we can rip this out completely, I hope.
  673.                     if (ch == '[') {
  674.                         string skip = sr.ReadLine();
  675.                         if (skip.Equals("strings]"))
  676.                             Warning(SR.GetString(SR.StringsTagObsolete), fileName, sr.LineNumber - 1, 1);
  677.                         else
  678.                             throw new TextFileException(SR.GetString(SR.INFFileBracket, skip), fileName, sr.LineNumber - 1, 1);
  679.                         ch = sr.Read();
  680.                         continue;
  681.                     }
  682.                    
  683.                     // Read in name
  684.                     name.Length = 0;
  685.                     while (ch != '=') {
  686.                         if (ch == '\r' || ch == '\n')
  687.                             throw new TextFileException(SR.GetString(SR.NoEqualsWithNewLine, name.Length, name), fileName, sr.LineNumber, sr.LinePosition);
  688.                        
  689.                         name.Append((char)ch);
  690.                         ch = sr.Read();
  691.                         if (ch == -1)
  692.                             break;
  693.                     }
  694.                     if (name.Length == 0)
  695.                         throw new TextFileException(SR.GetString(SR.NoEquals), fileName, sr.LineNumber, sr.LinePosition);
  696.                    
  697.                     // For the INF file, we must allow a space on both sides of the equals
  698.                     // sign. Deal with it.
  699.                     if (name[name.Length - 1] == ' ') {
  700.                         name.Length = name.Length - 1;
  701.                     }
  702.                     ch = sr.Read();
  703.                     // move past =
  704.                     // If it exists, move past the first space after the equals sign.
  705.                     if (ch == ' ')
  706.                         ch = sr.Read();
  707.                    
  708.                     // Read in value
  709.                     value.Length = 0;
  710.                    
  711.                     while (ch != -1) {
  712.                         // Did we read @"\r" or @"\n"?
  713.                         bool quotedNewLine = false;
  714.                         if (ch == '\\') {
  715.                             ch = sr.Read();
  716.                             switch (ch) {
  717.                                 case '\\':
  718.                                     // nothing needed
  719.                                     break;
  720.                                 case 'n':
  721.                                     ch = '\n';
  722.                                     quotedNewLine = true;
  723.                                     break;
  724.                                 case 'r':
  725.                                     ch = '\r';
  726.                                     quotedNewLine = true;
  727.                                     break;
  728.                                 case 't':
  729.                                     ch = '\t';
  730.                                     break;
  731.                                 case '"':
  732.                                     ch = '"';
  733.                                     break;
  734.                                 case 'u':
  735.                                     char[] hex = new char[4];
  736.                                     int numChars = 4;
  737.                                     int index = 0;
  738.                                     while (numChars > 0) {
  739.                                         int n = sr.Read(hex, index, numChars);
  740.                                         if (n == 0)
  741.                                             throw new TextFileException(SR.GetString(SR.BadEscape, (char)ch, name.ToString()), fileName, sr.LineNumber, sr.LinePosition);
  742.                                         index += n;
  743.                                         numChars -= n;
  744.                                     }
  745.                                     ch = (char)UInt16.Parse(new string(hex), NumberStyles.HexNumber, CultureInfo.InvariantCulture);
  746.                                     quotedNewLine = (ch == '\n' || ch == '\r');
  747.                                     break;
  748.                                 default:
  749.                                    
  750.                                     throw new TextFileException(SR.GetString(SR.BadEscape, (char)ch, name.ToString()), fileName, sr.LineNumber, sr.LinePosition);
  751.                                     break;
  752.                             }
  753.                         }
  754.                        
  755.                         // Consume endline...
  756.                         // Endline can be \r\n or \n. But do not treat a
  757.                         // quoted newline (ie, @"\r" or @"\n" in text) as a
  758.                         // real new line. They aren't the end of a line.
  759.                         if (!quotedNewLine) {
  760.                             if (ch == '\r') {
  761.                                 ch = sr.Read();
  762.                                 if (ch == -1) {
  763.                                     break;
  764.                                 }
  765.                                 else if (ch == '\n') {
  766.                                     ch = sr.Read();
  767.                                     break;
  768.                                 }
  769.                             }
  770.                             else if (ch == '\n') {
  771.                                 ch = sr.Read();
  772.                                 break;
  773.                             }
  774.                         }
  775.                        
  776.                         value.Append((char)ch);
  777.                         ch = sr.Read();
  778.                     }
  779.                    
  780.                     // Note that value can be an empty string
  781.                    
  782.                     AddResource(name.ToString(), value.ToString(), fileName, sr.LineNumber, sr.LinePosition);
  783.                 }
  784.             }
  785.         }
  786.        
  787.         private static void WriteResources(string filename)
  788.         {
  789.             Format format = GetFormat(filename);
  790.             switch (format) {
  791.                 case Format.Text:
  792.                     WriteTextResources(filename);
  793.                     break;
  794.                 case Format.Binary:
  795.                    
  796.                    
  797.                     WriteResources(new ResourceWriter(filename));
  798.                     // closes writer for us
  799.                     break;
  800.                 default:
  801.                    
  802.                     Debug.Fail("Unknown format " + format.ToString());
  803.                     break;
  804.             }
  805.         }
  806.        
  807.         // closes writer automatically
  808.         private static void WriteResources(IResourceWriter writer)
  809.         {
  810.             foreach (Entry entry in resources) {
  811.                 string key = entry.name;
  812.                 object value = entry.value;
  813.                 writer.AddResource(key, value);
  814.             }
  815.             Console.Write(SR.GetString(SR.BeginWriting));
  816.             writer.Close();
  817.             Console.WriteLine(SR.GetString(SR.DoneDot));
  818.         }
  819.        
  820.         private static void WriteTextResources(string fileName)
  821.         {
  822.             using (StreamWriter writer = new StreamWriter(fileName, false, Encoding.UTF8)) {
  823.                 foreach (Entry entry in resources) {
  824.                     string key = entry.name;
  825.                     object v = entry.value;
  826.                     string value = v as string;
  827.                     if (value == null) {
  828.                         Error(SR.GetString(SR.OnlyString, key, v.GetType().FullName), fileName);
  829.                     }
  830.                    
  831.                     // Escape any special characters in the String.
  832.                     value = value.Replace("\\", "\\\\");
  833.                     value = value.Replace("\n", "\\n");
  834.                     value = value.Replace("\r", "\\r");
  835.                     value = value.Replace("\t", "\\t");
  836.                    
  837.                     writer.WriteLine("{0}={1}", key, value);
  838.                 }
  839.             }
  840.         }
  841.        
  842.         private static void Usage()
  843.         {
  844.             Console.WriteLine(SR.GetString(SR.Usage, Environment.Version, CommonResStrings.CopyrightForCmdLine));
  845.            
  846.             Console.WriteLine(SR.GetString(SR.ValidLanguages));
  847.            
  848.             CompilerInfo[] compilerInfos = CodeDomProvider.GetAllCompilerInfo();
  849.             for (int i = 0; i < compilerInfos.Length; i++) {
  850.                 string[] languages = compilerInfos[i].GetLanguages();
  851.                 if (i != 0)
  852.                     Console.Write(", ");
  853.                
  854.                 for (int j = 0; j < languages.Length; j++) {
  855.                     if (j != 0)
  856.                         Console.Write(", ");
  857.                     Console.Write(languages[j]);
  858.                 }
  859.             }
  860.             Console.WriteLine();
  861.         }
  862.        
  863.         // Text files are just name/value pairs. ResText is the same format
  864.         // with a unique extension to work around some ambiguities with MSBuild
  865.         // ResX is our existing XML format from V1.
  866.         private enum Format
  867.         {
  868.             Text,
  869.             // .txt or .restext
  870.             Binary
  871.             // .resources
  872.         }
  873.        
  874.         // name/value pair
  875.         private class Entry
  876.         {
  877.             public Entry(string name, object value)
  878.             {
  879.                 this.name = name;
  880.                 this.value = value;
  881.             }
  882.            
  883.             public string name;
  884.             public object value;
  885.         }
  886.     }
  887. }

Developer Fusion