Refactored, cleaned up
[mono.git] / mcs / class / Managed.Windows.Forms / System.Resources / ResXResourceReader.cs
1 // Permission is hereby granted, free of charge, to any person obtaining
2 // a copy of this software and associated documentation files (the
3 // "Software"), to deal in the Software without restriction, including
4 // without limitation the rights to use, copy, modify, merge, publish,
5 // distribute, sublicense, and/or sell copies of the Software, and to
6 // permit persons to whom the Software is furnished to do so, subject to
7 // the following conditions:
8 // 
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
11 // 
12 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
13 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
14 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
16 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
17 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
18 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
19 //
20 // Copyright (c) 2004-2005 Novell, Inc.
21 //
22 // Authors:
23 //      Duncan Mak      duncan@ximian.com
24 //      Nick Drochak    ndrochak@gol.com
25 //      Paolo Molaro    lupus@ximian.com
26 //      Peter Bartok    pbartok@novell.com
27 //      Gert Driesen    drieseng@users.sourceforge.net
28 //      Olivier Dufour  olivier.duff@gmail.com
29 //      Gary Barnett    
30
31 using System;
32 using System.Collections;
33 using System.Collections.Specialized;
34 using System.ComponentModel;
35 using System.ComponentModel.Design;
36 using System.Globalization;
37 using System.IO;
38 using System.Resources;
39 using System.Runtime.Serialization.Formatters.Binary;
40 using System.Xml;
41 using System.Reflection;
42 using System.Drawing;
43 using System.Runtime.Serialization;
44
45 namespace System.Resources
46 {
47 #if INSIDE_SYSTEM_WEB
48         internal
49 #else
50         public
51 #endif
52         class ResXResourceReader : IResourceReader, IDisposable
53         {
54                 #region Local Variables
55                 private string fileName;
56                 private Stream stream;
57                 private TextReader reader;
58                 private Hashtable hasht;
59                 private ITypeResolutionService typeresolver;
60                 private XmlTextReader xmlReader;
61                 private string basepath;
62                 private bool useResXDataNodes;
63                 private AssemblyName [] assemblyNames;
64                 private Hashtable hashtm;
65                 #endregion      // Local Variables
66
67                 #region Constructors & Destructor
68                 public ResXResourceReader (Stream stream)
69                 {
70                         if (stream == null)
71                                 throw new ArgumentNullException ("stream");
72
73                         if (!stream.CanRead)
74                                 throw new ArgumentException ("Stream was not readable.");
75
76                         this.stream = stream;
77                 }
78
79                 public ResXResourceReader (Stream stream, ITypeResolutionService typeResolver)
80                         : this (stream)
81                 {
82                         this.typeresolver = typeResolver;
83                 }
84
85                 public ResXResourceReader (string fileName)
86                 {
87                         this.fileName = fileName;
88                 }
89
90                 public ResXResourceReader (string fileName, ITypeResolutionService typeResolver)
91                         : this (fileName)
92                 {
93                         this.typeresolver = typeResolver;
94                 }
95
96                 public ResXResourceReader (TextReader reader)
97                 {
98                         this.reader = reader;
99                 }
100
101                 public ResXResourceReader (TextReader reader, ITypeResolutionService typeResolver)
102                         : this (reader)
103                 {
104                         this.typeresolver = typeResolver;
105                 }
106
107                 public ResXResourceReader (Stream stream, AssemblyName [] assemblyNames)
108                         : this (stream)
109                 {
110                         this.assemblyNames = assemblyNames;
111                 }
112
113                 public ResXResourceReader (string fileName, AssemblyName [] assemblyNames)
114                         : this (fileName)
115                 {
116                         this.assemblyNames = assemblyNames;
117                 }
118
119                 public ResXResourceReader (TextReader reader, AssemblyName [] assemblyNames)
120                         : this (reader)
121                 {
122                         this.assemblyNames = assemblyNames;
123                 }
124
125                 ~ResXResourceReader ()
126                 {
127                         Dispose (false);
128                 }
129                 #endregion      // Constructors & Destructor
130
131                 public string BasePath {
132                         get { return basepath; }
133                         set { basepath = value; }
134                 }
135
136                 public bool UseResXDataNodes {
137                         get { return useResXDataNodes; }
138                         set {
139                                 if (xmlReader != null)
140                                         throw new InvalidOperationException ();
141                                 useResXDataNodes = value; 
142                         }
143                 }
144
145                 #region Private Methods
146                 private void LoadData ()
147                 {
148                         hasht = new Hashtable ();
149                         hashtm = new Hashtable ();
150                         if (fileName != null) {
151                                 stream = File.OpenRead (fileName);
152                         }
153
154                         try {
155                                 xmlReader = null;
156                                 if (stream != null) {
157                                         xmlReader = new XmlTextReader (stream);
158                                 } else if (reader != null) {
159                                         xmlReader = new XmlTextReader (reader);
160                                 }
161
162                                 if (xmlReader == null) {
163                                         throw new InvalidOperationException ("ResourceReader is closed.");
164                                 }
165
166                                 xmlReader.WhitespaceHandling = WhitespaceHandling.None;
167
168                                 ResXHeader header = new ResXHeader ();
169                                 try {
170                                         while (xmlReader.Read ()) {
171                                                 if (xmlReader.NodeType != XmlNodeType.Element)
172                                                         continue;
173
174                                                 switch (xmlReader.LocalName) {
175                                                 case "resheader":
176                                                         ParseHeaderNode (header);
177                                                         break;
178                                                 case "data":
179                                                         ParseDataNode (false);
180                                                         break;
181                                                 case "metadata":
182                                                         ParseDataNode (true);
183                                                         break;
184                                                 }
185                                         }
186                                 } catch (XmlException ex) {
187                                         throw new ArgumentException ("Invalid ResX input.", ex);
188                                 } catch (SerializationException ex) {
189                                         throw ex;
190                                 } catch (TargetInvocationException ex) {
191                                         throw ex;
192                                 } catch (Exception ex) {
193                                         XmlException xex = new XmlException (ex.Message, ex, 
194                                                 xmlReader.LineNumber, xmlReader.LinePosition);
195                                         throw new ArgumentException ("Invalid ResX input.", xex);
196                                 }
197                                 header.Verify ();
198                         } finally {
199                                 if (fileName != null) {
200                                         stream.Close ();
201                                         stream = null;
202                                 }
203                                 xmlReader = null;
204                         }
205                 }
206
207                 private void ParseHeaderNode (ResXHeader header)
208                 {
209                         string v = GetAttribute ("name");
210                         if (v == null)
211                                 return;
212
213                         if (String.Compare (v, "resmimetype", true) == 0) {
214                                 header.ResMimeType = GetHeaderValue ();
215                         } else if (String.Compare (v, "reader", true) == 0) {
216                                 header.Reader = GetHeaderValue ();
217                         } else if (String.Compare (v, "version", true) == 0) {
218                                 header.Version = GetHeaderValue ();
219                         } else if (String.Compare (v, "writer", true) == 0) {
220                                 header.Writer = GetHeaderValue ();
221                         }
222                 }
223
224                 private string GetHeaderValue ()
225                 {
226                         string value = null;
227                         xmlReader.ReadStartElement ();
228                         if (xmlReader.NodeType == XmlNodeType.Element) {
229                                 value = xmlReader.ReadElementString ();
230                         } else {
231                                 value = xmlReader.Value.Trim ();
232                         }
233                         return value;
234                 }
235
236                 private string GetAttribute (string name)
237                 {
238                         if (!xmlReader.HasAttributes)
239                                 return null;
240                         for (int i = 0; i < xmlReader.AttributeCount; i++) {
241                                 xmlReader.MoveToAttribute (i);
242                                 if (String.Compare (xmlReader.Name, name, true) == 0) {
243                                         string v = xmlReader.Value;
244                                         xmlReader.MoveToElement ();
245                                         return v;
246                                 }
247                         }
248                         xmlReader.MoveToElement ();
249                         return null;
250                 }
251
252                 private string GetDataValue (bool meta, out string comment)
253                 {
254                         string value = null;
255                         comment = null;
256                         while (xmlReader.Read ()) {
257                                 if (xmlReader.NodeType == XmlNodeType.EndElement && xmlReader.LocalName == (meta ? "metadata" : "data"))
258                                         break;
259
260                                 if (xmlReader.NodeType == XmlNodeType.Element) {
261                                         if (xmlReader.Name.Equals ("value")) {
262                                                 xmlReader.WhitespaceHandling = WhitespaceHandling.Significant;
263                                                 value = xmlReader.ReadString ();
264                                                 xmlReader.WhitespaceHandling = WhitespaceHandling.None;
265                                         } else if (xmlReader.Name.Equals ("comment")) {
266                                                 xmlReader.WhitespaceHandling = WhitespaceHandling.Significant;
267                                                 comment = xmlReader.ReadString ();
268                                                 xmlReader.WhitespaceHandling = WhitespaceHandling.None;
269                                                 if (xmlReader.NodeType == XmlNodeType.EndElement && xmlReader.LocalName == (meta ? "metadata" : "data"))
270                                                         break;
271                                         }
272                                 }
273                                 else
274                                         value = xmlReader.Value.Trim ();
275                         }
276                         return value;
277                 }
278
279                 private void ParseDataNode (bool meta)
280                 {
281                         Hashtable hashtable = ((meta && ! useResXDataNodes) ? hashtm : hasht);
282                         Point pos = new Point (xmlReader.LineNumber, xmlReader.LinePosition);
283                         string name = GetAttribute ("name");
284                         string type_name = GetAttribute ("type");
285                         string mime_type = GetAttribute ("mimetype");
286
287
288                         string comment = null;
289                         string value = GetDataValue (meta, out comment);
290
291                         ResXDataNode node = new ResXDataNode (name, mime_type, type_name, value, comment, pos, BasePath);
292
293                         if (useResXDataNodes) {
294                                 hashtable [name] = node;
295                                 return;
296                         }
297
298                         if (name == null)
299                                 throw new ArgumentException (string.Format (CultureInfo.CurrentCulture,
300                                                         "Could not find a name for a resource. The resource value was '{0}'.",
301                                                         node.GetValue ((AssemblyName []) null).ToString()));
302
303                         // useResXDataNodes is false, add to dictionary of values
304                         if (assemblyNames != null) { 
305                                 try {
306                                         hashtable [name] = node.GetValue (assemblyNames);
307                                 } catch (TypeLoadException ex) {
308                                         // different error messages depending on type of resource, hacky solution
309                                         if (node.handler is TypeConverterFromResXHandler)
310                                                 hashtable [name] = null;
311                                         else 
312                                                 throw ex;
313                                 }
314                         } else { // there is a typeresolver or its null
315                                 try {
316                                         hashtable [name] = node.GetValue (typeresolver); 
317                                 } catch (TypeLoadException ex) {
318                                         if (node.handler is TypeConverterFromResXHandler)
319                                                 hashtable [name] = null;
320                                         else 
321                                                 throw ex;
322                                 }
323                         }
324
325                 }
326
327                 #endregion      // Private Methods
328
329                 #region Public Methods
330                 public void Close ()
331                 {
332                         if (reader != null) {
333                                 reader.Close ();
334                                 reader = null;
335                         }
336                 }
337
338                 public IDictionaryEnumerator GetEnumerator ()
339                 {
340                         if (hasht == null) {
341                                 LoadData ();
342                         }
343                         return hasht.GetEnumerator ();
344                 }
345
346                 IEnumerator IEnumerable.GetEnumerator ()
347                 {
348                         return ((IResourceReader) this).GetEnumerator ();
349                 }
350
351                 void IDisposable.Dispose ()
352                 {
353                         Dispose (true);
354                         GC.SuppressFinalize (this);
355                 }
356
357                 protected virtual void Dispose (bool disposing)
358                 {
359                         if (disposing) {
360                                 Close ();
361                         }
362                 }
363
364                 public static ResXResourceReader FromFileContents (string fileContents)
365                 {
366                         return new ResXResourceReader (new StringReader (fileContents));
367                 }
368
369                 public static ResXResourceReader FromFileContents (string fileContents, ITypeResolutionService typeResolver)
370                 {
371                         return new ResXResourceReader (new StringReader (fileContents), typeResolver);
372                 }
373
374                 public static ResXResourceReader FromFileContents (string fileContents, AssemblyName [] assemblyNames)
375                 {
376                         return new ResXResourceReader (new StringReader (fileContents), assemblyNames);
377                 }
378
379                 public IDictionaryEnumerator GetMetadataEnumerator ()
380                 {
381                         if (hashtm == null)
382                                 LoadData ();
383                         return hashtm.GetEnumerator ();
384                 }
385
386                 #endregion      // Public Methods
387
388                 #region Internal Classes
389                 private class ResXHeader
390                 {
391                         private string resMimeType;
392                         private string reader;
393                         private string version;
394                         private string writer;
395
396                         public string ResMimeType
397                         {
398                                 get { return resMimeType; }
399                                 set { resMimeType = value; }
400                         }
401
402                         public string Reader {
403                                 get { return reader; }
404                                 set { reader = value; }
405                         }
406
407                         public string Version {
408                                 get { return version; }
409                                 set { version = value; }
410                         }
411
412                         public string Writer {
413                                 get { return writer; }
414                                 set { writer = value; }
415                         }
416
417                         public void Verify ()
418                         {
419                                 if (!IsValid)
420                                         throw new ArgumentException ("Invalid ResX input.  Could "
421                                                 + "not find valid \"resheader\" tags for the ResX "
422                                                 + "reader & writer type names.");
423                         }
424
425                         public bool IsValid {
426                                 get {
427                                         if (string.Compare (ResMimeType, ResXResourceWriter.ResMimeType) != 0)
428                                                 return false;
429                                         if (Reader == null || Writer == null)
430                                                 return false;
431                                         string readerType = Reader.Split (',') [0].Trim ();
432                                         if (readerType != typeof (ResXResourceReader).FullName)
433                                                 return false;
434                                         string writerType = Writer.Split (',') [0].Trim ();
435                                         if (writerType != typeof (ResXResourceWriter).FullName)
436                                                 return false;
437                                         return true;
438                                 }
439                         }
440                 }
441                 #endregion
442         }
443 }