In corlib/System.Runtime.InteropServices:
[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 //
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
41 namespace System.Resources
42 {
43         public class ResXResourceReader : IResourceReader, IDisposable
44         {
45                 #region Local Variables
46                 private string fileName;
47                 private Stream stream;
48                 private TextReader reader;
49                 private Hashtable hasht;
50                 private ITypeResolutionService typeresolver;
51 #if NET_2_0
52                 private string basepath;
53 #endif
54                 #endregion      // Local Variables
55
56                 #region Constructors & Destructor
57                 public ResXResourceReader (Stream stream)
58                 {
59                         if (stream == null)
60                                 throw new ArgumentNullException ("Value cannot be null.");
61
62                         if (!stream.CanRead)
63                                 throw new ArgumentException ("Stream was not readable.");
64
65                         this.stream = stream;
66                 }
67
68                 public ResXResourceReader (Stream stream, ITypeResolutionService typeresolver)
69                         : this (stream)
70                 {
71                         this.typeresolver = typeresolver;
72                 }
73
74                 public ResXResourceReader (string fileName)
75                 {
76                         this.fileName = fileName;
77                 }
78
79                 public ResXResourceReader (string fileName, ITypeResolutionService typeresolver)
80                         : this (fileName)
81                 {
82                         this.typeresolver = typeresolver;
83                 }
84
85                 public ResXResourceReader (TextReader reader)
86                 {
87                         this.reader = reader;
88                 }
89
90                 public ResXResourceReader (TextReader reader, ITypeResolutionService typeresolver)
91                         : this (reader)
92                 {
93                         this.typeresolver = typeresolver;
94                 }
95
96                 ~ResXResourceReader ()
97                 {
98                         Dispose (false);
99                 }
100                 #endregion      // Constructors & Destructor
101
102 #if NET_2_0
103                 public string BasePath {
104                         get { return basepath; }
105                         set { basepath = value; }
106                 }
107 #endif
108
109                 #region Private Methods
110
111                 static string get_attr (XmlTextReader xtr, string name)
112                 {
113                         if (!xtr.HasAttributes)
114                                 return null;
115                         for (int i = 0; i < xtr.AttributeCount; i++) {
116                                 xtr.MoveToAttribute (i);
117                                 if (String.Compare (xtr.Name, name, true) == 0) {
118                                         string v = xtr.Value;
119                                         xtr.MoveToElement ();
120                                         return v;
121                                 }
122                         }
123                         xtr.MoveToElement ();
124                         return null;
125                 }
126
127                 private void load_data ()
128                 {
129                         if (fileName != null) {
130                                 stream = File.OpenRead (fileName);
131                         }
132
133                         try {
134                                 XmlTextReader xtr = null;
135                                 if (stream != null) {
136                                         xtr = new XmlTextReader (stream);
137                                 } else if (reader != null) {
138                                         xtr = new XmlTextReader (reader);
139                                 }
140
141                                 if (xtr == null) {
142                                         throw new InvalidOperationException ("ResourceReader is closed.");
143                                 }
144
145                                 xtr.WhitespaceHandling = WhitespaceHandling.None;
146
147                                 ResXHeader header = new ResXHeader ();
148                                 try {
149                                         while (xtr.Read ()) {
150                                                 if (xtr.NodeType != XmlNodeType.Element)
151                                                         continue;
152
153                                                 switch (xtr.LocalName) {
154                                                 case "resheader":
155                                                         parse_header_node (xtr, header);
156                                                         break;
157                                                 case "data":
158                                                         parse_data_node (xtr);
159                                                         break;
160                                                 }
161                                         }
162 #if NET_2_0
163                                 } catch (XmlException ex) {
164                                         throw new ArgumentException ("Invalid ResX input.", ex);
165                                 } catch (Exception ex) {
166                                         XmlException xex = new XmlException (ex.Message, ex, 
167                                                 xtr.LineNumber, xtr.LinePosition);
168                                         throw new ArgumentException ("Invalid ResX input.", xex);
169                                 }
170 #else
171                                 } catch (Exception ex) {
172                                         throw new ArgumentException ("Invalid ResX input.", ex);
173                                 }
174 #endif
175
176                                 header.Verify ();
177                         } finally {
178                                 if (fileName != null) {
179                                         stream.Close ();
180                                         stream = null;
181                                 }
182                         }
183                 }
184
185                 void parse_header_node (XmlTextReader xtr, ResXHeader header)
186                 {
187                         string v = get_attr (xtr, "name");
188                         if (v == null)
189                                 return;
190
191                         if (String.Compare (v, "resmimetype", true) == 0) {
192                                 header.ResMimeType = get_header_value (xtr);
193                         } else if (String.Compare (v, "reader", true) == 0) {
194                                 header.Reader = get_header_value (xtr);
195                         } else if (String.Compare (v, "version", true) == 0) {
196                                 header.Version = get_header_value (xtr);
197                         } else if (String.Compare (v, "writer", true) == 0) {
198                                 header.Writer = get_header_value (xtr);
199                         }
200                 }
201
202                 string get_header_value (XmlTextReader xtr)
203                 {
204                         string value = null;
205                         xtr.ReadStartElement ();
206                         if (xtr.NodeType == XmlNodeType.Element) {
207                                 value = xtr.ReadElementString ();
208                         } else {
209                                 value = xtr.Value.Trim ();
210                         }
211                         return value;
212                 }
213
214                 void parse_data_node (XmlTextReader xtr)
215                 {
216                         string name = get_attr (xtr, "name");
217                         string type_name = get_attr (xtr, "type");
218                         string mime_type = get_attr (xtr, "mimetype");
219
220                         Type type = type_name == null ? null : ResolveType (type_name);
221
222                         if (type_name != null && type == null)
223                                 throw new ArgumentException (String.Format (
224                                         "The type '{0}' of the element '{1}' could not be resolved.", type_name, name));
225
226                         if (type == typeof (ResXNullRef)) {
227                                 hasht [name] = null;
228                                 return;
229                         }
230
231                         string value = get_data_value (xtr);
232                         object obj = null;
233
234                         if (type != null) {
235                                 TypeConverter c = TypeDescriptor.GetConverter (type);
236
237                                 if (c == null) {
238                                         obj = null;
239                                 } else if (c.CanConvertFrom (typeof (string))) {
240 #if NET_2_0
241                                         if (BasePath != null && type == typeof (ResXFileRef)) {
242                                                 string [] parts = ResXFileRef.Parse (value);
243                                                 parts [0] = Path.Combine (BasePath, parts [0]);
244                                                 obj = c.ConvertFromInvariantString (string.Join (";", parts));
245                                         } else {
246                                                 obj = c.ConvertFromInvariantString (value);
247                                         }
248 #else
249                                         obj = c.ConvertFromInvariantString (value);
250 #endif
251                                 } else if (c.CanConvertFrom (typeof (byte []))) {
252                                         obj = c.ConvertFrom (Convert.FromBase64String (value));
253                                 } else {
254                                         // the type must be a byte[]
255                                         obj = Convert.FromBase64String (value);
256                                 }
257                         } else if (mime_type != null && mime_type != String.Empty) {
258                                 if (mime_type == ResXResourceWriter.BinSerializedObjectMimeType) {
259                                         byte [] data = Convert.FromBase64String (value);
260                                         BinaryFormatter f = new BinaryFormatter ();
261                                         using (MemoryStream s = new MemoryStream (data)) {
262                                                 obj = f.Deserialize (s);
263                                         }
264                                 } else {
265                                         // invalid mime type
266 #if NET_2_0
267                                         obj = null;
268 #else
269                                         obj = value;
270 #endif
271                                 }
272                         } else {
273                                 obj = value;
274                         }
275
276                         if (name == null)
277                                 throw new ArgumentException (string.Format (CultureInfo.CurrentCulture,
278                                         "Could not find a name for a resource. The resource value "
279                                         + "was '{0}'.", obj));
280
281                         hasht [name] = obj;
282                 }
283
284
285                 // Returns the value of the next XML element with the specified
286                 // name from the reader. canBeCdata == true specifies that
287                 // the element may be a CDATA node as well.
288                 static string get_data_value (XmlTextReader xtr)
289                 {
290                         string value = null;
291 #if NET_2_0
292                         while (xtr.Read ()) {
293                                 if (xtr.NodeType == XmlNodeType.EndElement && xtr.LocalName == "data")
294                                         break;
295                                 if (xtr.NodeType == XmlNodeType.Element) {
296                                         if (xtr.Name == "value") {
297                                                 value = xtr.ReadString ();
298                                         }
299                                         continue;
300                                 }
301                                 value = xtr.Value.Trim ();
302                         }
303 #else
304                         xtr.Read ();
305                         if (xtr.NodeType == XmlNodeType.Element) {
306                                 value = xtr.ReadElementString ();
307                         } else {
308                                 value = xtr.Value.Trim ();
309                         }
310
311                         if (value == null)
312                                 value = string.Empty;
313 #endif
314                         return value;
315                 }
316
317                 private Type ResolveType (string type)
318                 {
319                         if (typeresolver == null) {
320                                 return Type.GetType (type);
321                         } else {
322                                 return typeresolver.GetType (type);
323                         }
324                 }
325                 #endregion      // Private Methods
326
327                 #region Public Methods
328                 public void Close ()
329                 {
330                         if (reader != null) {
331                                 reader.Close ();
332                                 reader = null;
333                         }
334                 }
335
336                 public IDictionaryEnumerator GetEnumerator ()
337                 {
338                         if (hasht == null) {
339                                 hasht = new Hashtable ();
340                                 load_data ();
341                         }
342                         return hasht.GetEnumerator ();
343                 }
344
345                 IEnumerator IEnumerable.GetEnumerator ()
346                 {
347                         return ((IResourceReader) this).GetEnumerator ();
348                 }
349
350                 void IDisposable.Dispose ()
351                 {
352                         Dispose (true);
353                         GC.SuppressFinalize (this);
354                 }
355
356                 protected virtual void Dispose (bool disposing)
357                 {
358                         if (disposing) {
359                                 Close ();
360                         }
361                 }
362
363                 public static ResXResourceReader FromFileContents (string fileContents)
364                 {
365                         return new ResXResourceReader (new StringReader (fileContents));
366                 }
367
368                 public static ResXResourceReader FromFileContents (string fileContents, ITypeResolutionService typeResolver)
369                 {
370                         return new ResXResourceReader (new StringReader (fileContents), typeResolver);
371                 }
372
373                 #endregion      // Public Methods
374
375                 private class ResXHeader
376                 {
377                         public string ResMimeType {
378                                 get { return _resMimeType; }
379                                 set { _resMimeType = value; }
380                         }
381
382                         public string Reader {
383                                 get { return _reader; }
384                                 set { _reader = value; }
385                         }
386
387                         public string Version {
388                                 get { return _version; }
389                                 set { _version = value; }
390                         }
391
392                         public string Writer {
393                                 get { return _writer; }
394                                 set { _writer = value; }
395                         }
396
397                         public void Verify ()
398                         {
399                                 if (!IsValid)
400                                         throw new ArgumentException ("Invalid ResX input.  Could "
401                                                 + "not find valid \"resheader\" tags for the ResX "
402                                                 + "reader & writer type names.");
403                         }
404
405                         public bool IsValid {
406                                 get {
407                                         if (string.Compare (ResMimeType, ResXResourceWriter.ResMimeType) != 0)
408                                                 return false;
409                                         if (Reader == null || Writer == null)
410                                                 return false;
411                                         string readerType = Reader.Split (',') [0].Trim ();
412                                         if (readerType != typeof (ResXResourceReader).FullName)
413                                                 return false;
414                                         string writerType = Writer.Split (',') [0].Trim ();
415                                         if (writerType != typeof (ResXResourceWriter).FullName)
416                                                 return false;
417                                         return true;
418                                 }
419                         }
420
421                         private string _resMimeType;
422                         private string _reader;
423                         private string _version;
424                         private string _writer;
425                 }
426         }
427 }