Wed Feb 24 15:47:16 CET 2010 Paolo Molaro <lupus@ximian.com>
[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
30 using System;
31 using System.Collections;
32 using System.Collections.Specialized;
33 using System.ComponentModel;
34 using System.ComponentModel.Design;
35 using System.Globalization;
36 using System.IO;
37 using System.Resources;
38 using System.Runtime.Serialization.Formatters.Binary;
39 using System.Xml;
40 using System.Reflection;
41 using System.Drawing;
42
43 namespace System.Resources
44 {
45 #if INSIDE_SYSTEM_WEB
46         internal
47 #else
48         public
49 #endif
50         class ResXResourceReader : IResourceReader, IDisposable
51         {
52                 #region Local Variables
53                 private string fileName;
54                 private Stream stream;
55                 private TextReader reader;
56                 private Hashtable hasht;
57                 private ITypeResolutionService typeresolver;
58                 private XmlTextReader xmlReader;
59
60 #if NET_2_0
61                 private string basepath;
62                 private bool useResXDataNodes;
63                 private AssemblyName [] assemblyNames;
64                 private Hashtable hashtm;
65 #endif
66                 #endregion      // Local Variables
67
68                 #region Constructors & Destructor
69                 public ResXResourceReader (Stream stream)
70                 {
71                         if (stream == null)
72                                 throw new ArgumentNullException ("stream");
73
74                         if (!stream.CanRead)
75                                 throw new ArgumentException ("Stream was not readable.");
76
77                         this.stream = stream;
78                 }
79
80                 public ResXResourceReader (Stream stream, ITypeResolutionService typeResolver)
81                         : this (stream)
82                 {
83                         this.typeresolver = typeResolver;
84                 }
85
86                 public ResXResourceReader (string fileName)
87                 {
88                         this.fileName = fileName;
89                 }
90
91                 public ResXResourceReader (string fileName, ITypeResolutionService typeResolver)
92                         : this (fileName)
93                 {
94                         this.typeresolver = typeResolver;
95                 }
96
97                 public ResXResourceReader (TextReader reader)
98                 {
99                         this.reader = reader;
100                 }
101
102                 public ResXResourceReader (TextReader reader, ITypeResolutionService typeResolver)
103                         : this (reader)
104                 {
105                         this.typeresolver = typeResolver;
106                 }
107
108 #if NET_2_0
109
110                 public ResXResourceReader (Stream stream, AssemblyName [] assemblyNames)
111                         : this (stream)
112                 {
113                         this.assemblyNames = assemblyNames;
114                 }
115
116                 public ResXResourceReader (string fileName, AssemblyName [] assemblyNames)
117                         : this (fileName)
118                 {
119                         this.assemblyNames = assemblyNames;
120                 }
121
122                 public ResXResourceReader (TextReader reader, AssemblyName [] assemblyNames)
123                         : this (reader)
124                 {
125                         this.assemblyNames = assemblyNames;
126                 }
127
128
129 #endif
130                 ~ResXResourceReader ()
131                 {
132                         Dispose (false);
133                 }
134                 #endregion      // Constructors & Destructor
135
136 #if NET_2_0
137                 public string BasePath {
138                         get { return basepath; }
139                         set { basepath = value; }
140                 }
141
142                 public bool UseResXDataNodes {
143                         get { return useResXDataNodes; }
144                         set {
145                                 if (xmlReader != null)
146                                         throw new InvalidOperationException ();
147                                 useResXDataNodes = value; 
148                         }
149                 }
150 #endif
151
152                 #region Private Methods
153                 private void LoadData ()
154                 {
155                         hasht = new Hashtable ();
156 #if NET_2_0
157                         hashtm = new Hashtable ();
158 #endif
159                         if (fileName != null) {
160                                 stream = File.OpenRead (fileName);
161                         }
162
163                         try {
164                                 xmlReader = null;
165                                 if (stream != null) {
166                                         xmlReader = new XmlTextReader (stream);
167                                 } else if (reader != null) {
168                                         xmlReader = new XmlTextReader (reader);
169                                 }
170
171                                 if (xmlReader == null) {
172                                         throw new InvalidOperationException ("ResourceReader is closed.");
173                                 }
174
175                                 xmlReader.WhitespaceHandling = WhitespaceHandling.None;
176
177                                 ResXHeader header = new ResXHeader ();
178                                 try {
179                                         while (xmlReader.Read ()) {
180                                                 if (xmlReader.NodeType != XmlNodeType.Element)
181                                                         continue;
182
183                                                 switch (xmlReader.LocalName) {
184                                                 case "resheader":
185                                                         ParseHeaderNode (header);
186                                                         break;
187                                                 case "data":
188                                                         ParseDataNode (false);
189                                                         break;
190 #if NET_2_0
191                                                 case "metadata":
192                                                         ParseDataNode (true);
193                                                         break;
194 #endif
195                                                 }
196                                         }
197 #if NET_2_0
198                                 } catch (XmlException ex) {
199                                         throw new ArgumentException ("Invalid ResX input.", ex);
200                                 } catch (Exception ex) {
201                                         XmlException xex = new XmlException (ex.Message, ex, 
202                                                 xmlReader.LineNumber, xmlReader.LinePosition);
203                                         throw new ArgumentException ("Invalid ResX input.", xex);
204                                 }
205 #else
206                                 } catch (Exception ex) {
207                                         throw new ArgumentException ("Invalid ResX input.", ex);
208                                 }
209 #endif
210                                 header.Verify ();
211                         } finally {
212                                 if (fileName != null) {
213                                         stream.Close ();
214                                         stream = null;
215                                 }
216                                 xmlReader = null;
217                         }
218                 }
219
220                 private void ParseHeaderNode (ResXHeader header)
221                 {
222                         string v = GetAttribute ("name");
223                         if (v == null)
224                                 return;
225
226                         if (String.Compare (v, "resmimetype", true) == 0) {
227                                 header.ResMimeType = GetHeaderValue ();
228                         } else if (String.Compare (v, "reader", true) == 0) {
229                                 header.Reader = GetHeaderValue ();
230                         } else if (String.Compare (v, "version", true) == 0) {
231                                 header.Version = GetHeaderValue ();
232                         } else if (String.Compare (v, "writer", true) == 0) {
233                                 header.Writer = GetHeaderValue ();
234                         }
235                 }
236
237                 private string GetHeaderValue ()
238                 {
239                         string value = null;
240                         xmlReader.ReadStartElement ();
241                         if (xmlReader.NodeType == XmlNodeType.Element) {
242                                 value = xmlReader.ReadElementString ();
243                         } else {
244                                 value = xmlReader.Value.Trim ();
245                         }
246                         return value;
247                 }
248
249                 private string GetAttribute (string name)
250                 {
251                         if (!xmlReader.HasAttributes)
252                                 return null;
253                         for (int i = 0; i < xmlReader.AttributeCount; i++) {
254                                 xmlReader.MoveToAttribute (i);
255                                 if (String.Compare (xmlReader.Name, name, true) == 0) {
256                                         string v = xmlReader.Value;
257                                         xmlReader.MoveToElement ();
258                                         return v;
259                                 }
260                         }
261                         xmlReader.MoveToElement ();
262                         return null;
263                 }
264
265                 private string GetDataValue (bool meta, out string comment)
266                 {
267                         string value = null;
268                         comment = null;
269 #if NET_2_0
270                         while (xmlReader.Read ()) {
271                                 if (xmlReader.NodeType == XmlNodeType.EndElement && xmlReader.LocalName == (meta ? "metadata" : "data"))
272                                         break;
273
274                                 if (xmlReader.NodeType == XmlNodeType.Element) {
275                                         if (xmlReader.Name.Equals ("value")) {
276                                                 xmlReader.WhitespaceHandling = WhitespaceHandling.Significant;
277                                                 value = xmlReader.ReadString ();
278                                                 xmlReader.WhitespaceHandling = WhitespaceHandling.None;
279                                         } else if (xmlReader.Name.Equals ("comment")) {
280                                                 xmlReader.WhitespaceHandling = WhitespaceHandling.Significant;
281                                                 comment = xmlReader.ReadString ();
282                                                 xmlReader.WhitespaceHandling = WhitespaceHandling.None;
283                                                 if (xmlReader.NodeType == XmlNodeType.EndElement && xmlReader.LocalName == (meta ? "metadata" : "data"))
284                                                         break;
285                                         }
286                                 }
287                                 else
288                                         value = xmlReader.Value.Trim ();
289                         }
290 #else
291                         xmlReader.Read ();
292                         if (xmlReader.NodeType == XmlNodeType.Element) {
293                                 value = xmlReader.ReadElementString ();
294                         } else {
295                                 value = xmlReader.Value.Trim ();
296                         }
297
298                         if (value == null)
299                                 value = string.Empty;
300 #endif
301                         return value;
302                 }
303
304                 private void ParseDataNode (bool meta)
305                 {
306 #if NET_2_0
307                         Hashtable hashtable = ((meta && ! useResXDataNodes) ? hashtm : hasht);
308                         Point pos = new Point (xmlReader.LineNumber, xmlReader.LinePosition);
309 #else
310                         Hashtable hashtable = hasht;
311 #endif
312                         string name = GetAttribute ("name");
313                         string type_name = GetAttribute ("type");
314                         string mime_type = GetAttribute ("mimetype");
315
316
317                         Type type = type_name == null ? null : ResolveType (type_name);
318
319                         if (type_name != null && type == null)
320                                 throw new ArgumentException (String.Format (
321                                         "The type '{0}' of the element '{1}' could not be resolved.", type_name, name));
322
323                         if (type == typeof (ResXNullRef)) {
324                                 
325 #if NET_2_0
326                                 if (useResXDataNodes)
327                                         hashtable [name] = new ResXDataNode (name, null, pos);
328                                 else
329 #endif
330                                         hashtable [name] = null;
331                                 return;
332                         }
333
334                         string comment = null;
335                         string value = GetDataValue (meta, out comment);
336                         object obj = null;
337
338                         if (mime_type != null && mime_type.Length > 0) {
339                                 if (mime_type == ResXResourceWriter.BinSerializedObjectMimeType) {
340                                         byte [] data = Convert.FromBase64String (value);
341                                         BinaryFormatter f = new BinaryFormatter ();
342                                         using (MemoryStream s = new MemoryStream (data)) {
343                                                 obj = f.Deserialize (s);
344                                         }
345                                 } else if (mime_type == ResXResourceWriter.ByteArraySerializedObjectMimeType) {
346                                         if (type != null) {
347                                                 TypeConverter c = TypeDescriptor.GetConverter (type);
348                                                 if (c.CanConvertFrom (typeof (byte [])))
349                                                         obj = c.ConvertFrom (Convert.FromBase64String (value));
350                                         }
351                                 }
352                         } else if (type != null) {
353                                 if (type == typeof (byte [])) {
354                                         obj = Convert.FromBase64String (value);
355                                 } else {
356                                         TypeConverter c = TypeDescriptor.GetConverter (type);
357                                         if (c.CanConvertFrom (typeof (string))) {
358 #if NET_2_0
359                                                 if (BasePath != null && type == typeof (ResXFileRef)) {
360                                                         string [] parts = ResXFileRef.Parse (value);
361                                                         parts [0] = Path.Combine (BasePath, parts [0]);
362                                                         obj = c.ConvertFromInvariantString (string.Join (";", parts));
363                                                 } else {
364                                                         obj = c.ConvertFromInvariantString (value);
365                                                 }
366 #else
367                                                 obj = c.ConvertFromInvariantString (value);
368 #endif
369                                         }
370                                 }
371                         } else {
372                                 obj = value;
373                         }
374
375 #if ONLY_1_1
376                         if (obj == null)
377                                 obj = value;
378 #endif
379
380                         if (name == null)
381                                 throw new ArgumentException (string.Format (CultureInfo.CurrentCulture,
382                                         "Could not find a name for a resource. The resource value "
383                                         + "was '{0}'.", obj));
384 #if NET_2_0
385                         if (useResXDataNodes)
386                         {
387                                 ResXDataNode dataNode = new ResXDataNode(name, obj, pos);
388                                 dataNode.Comment = comment;
389                                 hashtable [name] = dataNode;
390                                 
391                         }
392                         else
393 #endif
394                         hashtable [name] = obj;
395                 }
396
397                 private Type ResolveType (string type)
398                 {
399                         if (typeresolver != null) {
400                                 return typeresolver.GetType (type);
401                         } 
402 #if NET_2_0
403                         if (assemblyNames != null) {
404                                 Type result;
405                                 foreach (AssemblyName assem in assemblyNames) {
406                                         Assembly myAssembly = Assembly.Load (assem);
407                                         result = myAssembly.GetType (type, false);
408                                         if (result != null)
409                                                 return result;
410                                         //else loop
411                                 }
412                                 //if type not found on assembly list we return null or we get from current assembly?
413                                 //=> unit test needed
414                         }
415 #endif
416                         return Type.GetType (type);
417
418                 }
419                 #endregion      // Private Methods
420
421                 #region Public Methods
422                 public void Close ()
423                 {
424                         if (reader != null) {
425                                 reader.Close ();
426                                 reader = null;
427                         }
428                 }
429
430                 public IDictionaryEnumerator GetEnumerator ()
431                 {
432                         if (hasht == null) {
433                                 LoadData ();
434                         }
435                         return hasht.GetEnumerator ();
436                 }
437
438                 IEnumerator IEnumerable.GetEnumerator ()
439                 {
440                         return ((IResourceReader) this).GetEnumerator ();
441                 }
442
443                 void IDisposable.Dispose ()
444                 {
445                         Dispose (true);
446                         GC.SuppressFinalize (this);
447                 }
448
449                 protected virtual void Dispose (bool disposing)
450                 {
451                         if (disposing) {
452                                 Close ();
453                         }
454                 }
455
456                 public static ResXResourceReader FromFileContents (string fileContents)
457                 {
458                         return new ResXResourceReader (new StringReader (fileContents));
459                 }
460
461                 public static ResXResourceReader FromFileContents (string fileContents, ITypeResolutionService typeResolver)
462                 {
463                         return new ResXResourceReader (new StringReader (fileContents), typeResolver);
464                 }
465 #if NET_2_0
466                 public static ResXResourceReader FromFileContents (string fileContents, AssemblyName [] assemblyNames)
467                 {
468                         return new ResXResourceReader (new StringReader (fileContents), assemblyNames);
469                 }
470
471                 public IDictionaryEnumerator GetMetadataEnumerator ()
472                 {
473                         if (hashtm == null)
474                                 LoadData ();
475                         return hashtm.GetEnumerator ();
476                 }
477 #endif
478                 #endregion      // Public Methods
479
480                 #region Internal Classes
481                 private class ResXHeader
482                 {
483                         private string resMimeType;
484                         private string reader;
485                         private string version;
486                         private string writer;
487
488                         public string ResMimeType
489                         {
490                                 get { return resMimeType; }
491                                 set { resMimeType = value; }
492                         }
493
494                         public string Reader {
495                                 get { return reader; }
496                                 set { reader = value; }
497                         }
498
499                         public string Version {
500                                 get { return version; }
501                                 set { version = value; }
502                         }
503
504                         public string Writer {
505                                 get { return writer; }
506                                 set { writer = value; }
507                         }
508
509                         public void Verify ()
510                         {
511                                 if (!IsValid)
512                                         throw new ArgumentException ("Invalid ResX input.  Could "
513                                                 + "not find valid \"resheader\" tags for the ResX "
514                                                 + "reader & writer type names.");
515                         }
516
517                         public bool IsValid {
518                                 get {
519                                         if (string.Compare (ResMimeType, ResXResourceWriter.ResMimeType) != 0)
520                                                 return false;
521                                         if (Reader == null || Writer == null)
522                                                 return false;
523                                         string readerType = Reader.Split (',') [0].Trim ();
524                                         if (readerType != typeof (ResXResourceReader).FullName)
525                                                 return false;
526                                         string writerType = Writer.Split (',') [0].Trim ();
527                                         if (writerType != typeof (ResXResourceWriter).FullName)
528                                                 return false;
529                                         return true;
530                                 }
531                         }
532                 }
533                 #endregion
534         }
535 }