Merge pull request #409 from Alkarex/patch-1
[mono.git] / mcs / class / Managed.Windows.Forms / System.Resources / ResXResourceReader.cs
index 3e093fcd53fbd7552c2f1f20228ddefd7ce472f5..084f5bd5eabf606595905e775ee7da52d3702d9f 100644 (file)
 //     Nick Drochak    ndrochak@gol.com
 //     Paolo Molaro    lupus@ximian.com
 //     Peter Bartok    pbartok@novell.com
-//
-
-// COMPLETE
+//     Gert Driesen    drieseng@users.sourceforge.net
+//     Olivier Dufour  olivier.duff@gmail.com
+//     Gary Barnett    gary.barnett.mono@gmail.com
 
 using System;
 using System.Collections;
+using System.Collections.Specialized;
 using System.ComponentModel;
 using System.ComponentModel.Design;
 using System.Globalization;
@@ -37,247 +38,406 @@ using System.IO;
 using System.Resources;
 using System.Runtime.Serialization.Formatters.Binary;
 using System.Xml;
+using System.Reflection;
+using System.Drawing;
+using System.Runtime.Serialization;
 
 namespace System.Resources
 {
-       public class ResXResourceReader : IResourceReader, IDisposable
+#if INSIDE_SYSTEM_WEB
+       internal
+#else
+       public
+#endif
+       class ResXResourceReader : IResourceReader, IDisposable
        {
                #region Local Variables
-               private Stream                  stream;
-               private XmlTextReader           reader;
-               private Hashtable               hasht;
-               private ITypeResolutionService  typeresolver;
+               private string fileName;
+               private Stream stream;
+               private TextReader reader;
+               private Hashtable hasht;
+               private ITypeResolutionService typeresolver;
+               private XmlTextReader xmlReader;
+               private string basepath;
+               private bool useResXDataNodes;
+               private AssemblyName [] assemblyNames;
+               private Hashtable hashtm;
                #endregion      // Local Variables
 
                #region Constructors & Destructor
                public ResXResourceReader (Stream stream)
                {
                        if (stream == null)
-                               throw new ArgumentNullException ("Value cannot be null.");
-                       
+                               throw new ArgumentNullException ("stream");
+
                        if (!stream.CanRead)
                                throw new ArgumentException ("Stream was not readable.");
 
                        this.stream = stream;
-                       basic_setup ();
                }
 
-               public ResXResourceReader (Stream stream, ITypeResolutionService typeresolver) : this(stream) {
-                       this.typeresolver = typeresolver;
+               public ResXResourceReader (Stream stream, ITypeResolutionService typeResolver)
+                       : this (stream)
+               {
+                       this.typeresolver = typeResolver;
                }
-               
+
                public ResXResourceReader (string fileName)
                {
-                       stream = File.OpenRead (fileName);
-                       basic_setup ();
+                       this.fileName = fileName;
                }
 
-               public ResXResourceReader (string fileName, ITypeResolutionService typeresolver) : this(fileName) {
-                       this.typeresolver = typeresolver;
+               public ResXResourceReader (string fileName, ITypeResolutionService typeResolver)
+                       : this (fileName)
+               {
+                       this.typeresolver = typeResolver;
                }
 
-               public ResXResourceReader(TextReader reader) {
-                       this.reader = new XmlTextReader(reader);
-                       this.hasht = new Hashtable();
+               public ResXResourceReader (TextReader reader)
+               {
+                       this.reader = reader;
+               }
+
+               public ResXResourceReader (TextReader reader, ITypeResolutionService typeResolver)
+                       : this (reader)
+               {
+                       this.typeresolver = typeResolver;
+               }
+
+               public ResXResourceReader (Stream stream, AssemblyName [] assemblyNames)
+                       : this (stream)
+               {
+                       this.assemblyNames = assemblyNames;
+               }
 
-                       load_data();
+               public ResXResourceReader (string fileName, AssemblyName [] assemblyNames)
+                       : this (fileName)
+               {
+                       this.assemblyNames = assemblyNames;
                }
 
-               public ResXResourceReader (TextReader reader, ITypeResolutionService typeresolver) : this(reader) {
-                       this.typeresolver = typeresolver;
+               public ResXResourceReader (TextReader reader, AssemblyName [] assemblyNames)
+                       : this (reader)
+               {
+                       this.assemblyNames = assemblyNames;
                }
 
-               ~ResXResourceReader() {
-                       Dispose(false);
+               ~ResXResourceReader ()
+               {
+                       Dispose (false);
                }
                #endregion      // Constructors & Destructor
 
+               public string BasePath {
+                       get { return basepath; }
+                       set { basepath = value; }
+               }
+
+               public bool UseResXDataNodes {
+                       get { return useResXDataNodes; }
+                       set {
+                               if (xmlReader != null)
+                                       throw new InvalidOperationException ();
+                               useResXDataNodes = value; 
+                       }
+               }
 
                #region Private Methods
-               void basic_setup () {
-                       reader = new XmlTextReader (stream);
+               private void LoadData ()
+               {
                        hasht = new Hashtable ();
+                       hashtm = new Hashtable ();
+                       if (fileName != null) {
+                               stream = File.OpenRead (fileName);
+                       }
+
+                       try {
+                               xmlReader = null;
+                               if (stream != null) {
+                                       xmlReader = new XmlTextReader (stream);
+                               } else if (reader != null) {
+                                       xmlReader = new XmlTextReader (reader);
+                               }
+
+                               if (xmlReader == null) {
+                                       throw new InvalidOperationException ("ResourceReader is closed.");
+                               }
+
+                               xmlReader.WhitespaceHandling = WhitespaceHandling.None;
 
-                       if (!IsStreamValid()){
-                               throw new ArgumentException("Stream is not a valid .resx file!  It was possibly truncated.");
+                               ResXHeader header = new ResXHeader ();
+                               try {
+                                       while (xmlReader.Read ()) {
+                                               if (xmlReader.NodeType != XmlNodeType.Element)
+                                                       continue;
+
+                                               switch (xmlReader.LocalName) {
+                                               case "resheader":
+                                                       ParseHeaderNode (header);
+                                                       break;
+                                               case "data":
+                                                       ParseDataNode (false);
+                                                       break;
+                                               case "metadata":
+                                                       ParseDataNode (true);
+                                                       break;
+                                               }
+                                       }
+                               } catch (XmlException ex) {
+                                       throw new ArgumentException ("Invalid ResX input.", ex);
+                               } catch (SerializationException ex) {
+                                       throw ex;
+                               } catch (TargetInvocationException ex) {
+                                       throw ex;
+                               } catch (Exception ex) {
+                                       XmlException xex = new XmlException (ex.Message, ex, 
+                                               xmlReader.LineNumber, xmlReader.LinePosition);
+                                       throw new ArgumentException ("Invalid ResX input.", xex);
+                               }
+                               header.Verify ();
+                       } finally {
+                               if (fileName != null) {
+                                       stream.Close ();
+                                       stream = null;
+                               }
+                               xmlReader = null;
                        }
-                       load_data ();
                }
-               
-               static string get_attr (XmlTextReader reader, string name) {
-                       if (!reader.HasAttributes)
-                               return null;
-                       for (int i = 0; i < reader.AttributeCount; i++) {
-                               reader.MoveToAttribute (i);
-                               if (String.Compare (reader.Name, name, true) == 0) {
-                                       string v = reader.Value;
-                                       reader.MoveToElement ();
-                                       return v;
-                               }
+
+               private void ParseHeaderNode (ResXHeader header)
+               {
+                       string v = GetAttribute ("name");
+                       if (v == null)
+                               return;
+
+                       if (String.Compare (v, "resmimetype", true) == 0) {
+                               header.ResMimeType = GetHeaderValue ();
+                       } else if (String.Compare (v, "reader", true) == 0) {
+                               header.Reader = GetHeaderValue ();
+                       } else if (String.Compare (v, "version", true) == 0) {
+                               header.Version = GetHeaderValue ();
+                       } else if (String.Compare (v, "writer", true) == 0) {
+                               header.Writer = GetHeaderValue ();
                        }
-                       reader.MoveToElement ();
-                       return null;
                }
 
-               static string get_value (XmlTextReader reader, string name) {
-                       bool gotelement = false;
-                       while (reader.Read ()) {
-                               if (reader.NodeType == XmlNodeType.Element && String.Compare (reader.Name, name, true) == 0) {
-                                       gotelement = true;
-                                       break;
-                               }
+               private string GetHeaderValue ()
+               {
+                       string value = null;
+                       xmlReader.ReadStartElement ();
+                       if (xmlReader.NodeType == XmlNodeType.Element) {
+                               value = xmlReader.ReadElementString ();
+                       } else {
+                               value = xmlReader.Value.Trim ();
                        }
-                       if (!gotelement)
+                       return value;
+               }
+
+               private string GetAttribute (string name)
+               {
+                       if (!xmlReader.HasAttributes)
                                return null;
-                       while (reader.Read ()) {
-                               if (reader.NodeType == XmlNodeType.Text || reader.NodeType == XmlNodeType.CDATA) {
-                                       string v = reader.Value;
-                                       return v;
-                               }
-                               else if (reader.NodeType == XmlNodeType.EndElement && reader.Value == string.Empty)
-                               {
-                                       string v = reader.Value;
+                       for (int i = 0; i < xmlReader.AttributeCount; i++) {
+                               xmlReader.MoveToAttribute (i);
+                               if (String.Compare (xmlReader.Name, name, true) == 0) {
+                                       string v = xmlReader.Value;
+                                       xmlReader.MoveToElement ();
                                        return v;
                                }
                        }
+                       xmlReader.MoveToElement ();
                        return null;
                }
 
-               private bool IsStreamValid() {
-                       bool gotroot = false;
-                       bool gotmime = false;
-                       
-                       while (reader.Read ()) {
-                               if (reader.NodeType == XmlNodeType.Element && String.Compare (reader.Name, "root", true) == 0) {
-                                       gotroot = true;
+               private string GetDataValue (bool meta, out string comment)
+               {
+                       string value = null;
+                       comment = null;
+                       while (xmlReader.Read ()) {
+                               if (xmlReader.NodeType == XmlNodeType.EndElement && xmlReader.LocalName == (meta ? "metadata" : "data"))
                                        break;
-                               }
-                       }
-                       if (!gotroot)
-                               return false;
-                       while (reader.Read ()) {
-                               if (reader.NodeType == XmlNodeType.Element && String.Compare (reader.Name, "resheader", true) == 0) {
-                                       string v = get_attr (reader, "name");
-                                       if (v != null && String.Compare (v, "resmimetype", true) == 0) {
-                                               v = get_value (reader, "value");
-                                               if (String.Compare (v, "text/microsoft-resx", true) == 0) {
-                                                       gotmime = true;
+
+                               if (xmlReader.NodeType == XmlNodeType.Element) {
+                                       if (xmlReader.Name.Equals ("value")) {
+                                               xmlReader.WhitespaceHandling = WhitespaceHandling.Significant;
+                                               value = xmlReader.ReadString ();
+                                               xmlReader.WhitespaceHandling = WhitespaceHandling.None;
+                                       } else if (xmlReader.Name.Equals ("comment")) {
+                                               xmlReader.WhitespaceHandling = WhitespaceHandling.Significant;
+                                               comment = xmlReader.ReadString ();
+                                               xmlReader.WhitespaceHandling = WhitespaceHandling.None;
+                                               if (xmlReader.NodeType == XmlNodeType.EndElement && xmlReader.LocalName == (meta ? "metadata" : "data"))
                                                        break;
-                                               }
-                                       }
-                               } else if (reader.NodeType == XmlNodeType.Element && String.Compare (reader.Name, "data", true) == 0) {
-                                       /* resheader apparently can appear anywhere, so we collect
-                                        * the data even if we haven't validated yet.
-                                        */
-                                       string n = get_attr (reader, "name");
-                                       if (n != null) {
-                                               string v = get_value (reader, "value");
-                                               hasht [n] = v;
                                        }
                                }
+                               else
+                                       value = xmlReader.Value.Trim ();
                        }
-                       return gotmime;
+                       return value;
                }
 
-               private void load_data ()
+               private void ParseDataNode (bool meta)
                {
-                       while (reader.Read ()) {
-                               if (reader.NodeType == XmlNodeType.Element && String.Compare (reader.Name, "data", true) == 0) {
-                                       string n = get_attr (reader, "name");
-                                       string t = get_attr (reader, "type");
-                                       string mt = get_attr (reader, "mimetype");
+                       Hashtable hashtable = ((meta && ! useResXDataNodes) ? hashtm : hasht);
+                       Point pos = new Point (xmlReader.LineNumber, xmlReader.LinePosition);
+                       string name = GetAttribute ("name");
+                       string type_name = GetAttribute ("type");
+                       string mime_type = GetAttribute ("mimetype");
 
-                                       Type tt = t == null ? null : Type.GetType (t);
 
-                                       if (t != null && tt == null) {
-                                               throw new SystemException ("The type `" + t +"' could not be resolved");
-                                       }
-                                       if (tt == typeof (ResXNullRef)) {
-                                               hasht [n] = null;
-                                               continue;
-                                       }
-                                       if (n != null) {
-                                               object v = null;
-                                               string val = get_value (reader, "value");
-                                               if (mt != null && tt != null) {
-                                                       TypeConverter c = TypeDescriptor.GetConverter (tt);
-                                                       v = c.ConvertFrom (Convert.FromBase64String (val));
-                                               } else if (tt != null) {
-                                                       TypeConverter c = TypeDescriptor.GetConverter (tt);
-                                                       v = c.ConvertFromString (val);
-                                               } else if (mt != null) {
-                                                       byte [] data = Convert.FromBase64String (val);
-                                                       BinaryFormatter f = new BinaryFormatter ();
-                                                       using (MemoryStream s = new MemoryStream (data)) {
-                                                               v = f.Deserialize (s);
-                                                       }
-                                               } else {
-                                                       v = val;
-                                               }
-                                               hasht [n] = v;
-                                       }
-                               }
+                       string comment = null;
+                       string value = GetDataValue (meta, out comment);
+
+                       ResXDataNode node = new ResXDataNode (name, mime_type, type_name, value, comment, pos, BasePath);
+
+                       if (useResXDataNodes) {
+                               hashtable [name] = node;
+                               return;
                        }
-               }
 
-               private Type GetType(string type) {
-                       if (typeresolver == null) {
-                               return Type.GetType(type);
-                       } else {
-                               return typeresolver.GetType(type);
+                       if (name == null)
+                               throw new ArgumentException (string.Format (CultureInfo.CurrentCulture,
+                                                       "Could not find a name for a resource. The resource value was '{0}'.",
+                                                       node.GetValue ((AssemblyName []) null).ToString()));
+
+                       // useResXDataNodes is false, add to dictionary of values
+                       if (assemblyNames != null) { 
+                               try {
+                                       hashtable [name] = node.GetValue (assemblyNames);
+                               } catch (TypeLoadException ex) {
+                                       // different error messages depending on type of resource, hacky solution
+                                       if (node.handler is TypeConverterFromResXHandler)
+                                               hashtable [name] = null;
+                                       else 
+                                               throw ex;
+                               }
+                       } else { // there is a typeresolver or its null
+                               try {
+                                       hashtable [name] = node.GetValue (typeresolver); 
+                               } catch (TypeLoadException ex) {
+                                       if (node.handler is TypeConverterFromResXHandler)
+                                               hashtable [name] = null;
+                                       else 
+                                               throw ex;
+                               }
                        }
+
                }
+
                #endregion      // Private Methods
 
                #region Public Methods
                public void Close ()
                {
-                       if (stream != null) {
-                               stream.Close ();
-                               stream = null;
-                       }
-
                        if (reader != null) {
-                               reader.Close();
+                               reader.Close ();
                                reader = null;
                        }
                }
-               
-               public IDictionaryEnumerator GetEnumerator () {
-                       if (null == stream){
-                               throw new InvalidOperationException("ResourceReader is closed.");
-                       }
-                       else {
-                               return hasht.GetEnumerator ();
+
+               public IDictionaryEnumerator GetEnumerator ()
+               {
+                       if (hasht == null) {
+                               LoadData ();
                        }
+                       return hasht.GetEnumerator ();
                }
-               
+
                IEnumerator IEnumerable.GetEnumerator ()
                {
-                       return ((IResourceReader) this).GetEnumerator();
+                       return ((IResourceReader) this).GetEnumerator ();
                }
-               
+
                void IDisposable.Dispose ()
                {
-                       Dispose(true);
-                       GC.SuppressFinalize(this);
+                       Dispose (true);
+                       GC.SuppressFinalize (this);
                }
 
-               protected virtual void Dispose(bool disposing) {
+               protected virtual void Dispose (bool disposing)
+               {
                        if (disposing) {
-                               Close();
+                               Close ();
                        }
                }
 
-               public static ResXResourceReader FromFileContents(string fileContents) {
-                       return new ResXResourceReader(new StringReader(fileContents));
+               public static ResXResourceReader FromFileContents (string fileContents)
+               {
+                       return new ResXResourceReader (new StringReader (fileContents));
                }
 
-               public static ResXResourceReader FromFileContents(string fileContents, ITypeResolutionService typeResolver) {
-                       return new ResXResourceReader(new StringReader(fileContents), typeResolver);
+               public static ResXResourceReader FromFileContents (string fileContents, ITypeResolutionService typeResolver)
+               {
+                       return new ResXResourceReader (new StringReader (fileContents), typeResolver);
+               }
+
+               public static ResXResourceReader FromFileContents (string fileContents, AssemblyName [] assemblyNames)
+               {
+                       return new ResXResourceReader (new StringReader (fileContents), assemblyNames);
+               }
+
+               public IDictionaryEnumerator GetMetadataEnumerator ()
+               {
+                       if (hashtm == null)
+                               LoadData ();
+                       return hashtm.GetEnumerator ();
                }
 
                #endregion      // Public Methods
-               
-       }  // public sealed class ResXResourceReader
-} // namespace System.Resources
+
+               #region Internal Classes
+               private class ResXHeader
+               {
+                       private string resMimeType;
+                       private string reader;
+                       private string version;
+                       private string writer;
+
+                       public string ResMimeType
+                       {
+                               get { return resMimeType; }
+                               set { resMimeType = value; }
+                       }
+
+                       public string Reader {
+                               get { return reader; }
+                               set { reader = value; }
+                       }
+
+                       public string Version {
+                               get { return version; }
+                               set { version = value; }
+                       }
+
+                       public string Writer {
+                               get { return writer; }
+                               set { writer = value; }
+                       }
+
+                       public void Verify ()
+                       {
+                               if (!IsValid)
+                                       throw new ArgumentException ("Invalid ResX input.  Could "
+                                               + "not find valid \"resheader\" tags for the ResX "
+                                               + "reader & writer type names.");
+                       }
+
+                       public bool IsValid {
+                               get {
+                                       if (string.Compare (ResMimeType, ResXResourceWriter.ResMimeType) != 0)
+                                               return false;
+                                       if (Reader == null || Writer == null)
+                                               return false;
+                                       string readerType = Reader.Split (',') [0].Trim ();
+                                       if (readerType != typeof (ResXResourceReader).FullName)
+                                               return false;
+                                       string writerType = Writer.Split (',') [0].Trim ();
+                                       if (writerType != typeof (ResXResourceWriter).FullName)
+                                               return false;
+                                       return true;
+                               }
+                       }
+               }
+               #endregion
+       }
+}