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