2008-03-04 Jonathan Pobst <monkey@jpobst.com>
[mono.git] / mcs / class / Managed.Windows.Forms / System.Resources / ResXResourceWriter.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 //      Gonzalo Paniagua Javier gonzalo@ximian.com
25 //      Peter Bartok            pbartok@novell.com
26 //
27
28 // COMPLETE
29
30 using System.ComponentModel;
31 using System.IO;
32 using System.Runtime.Serialization.Formatters.Binary;
33 using System.Text;
34 using System.Xml;
35 using System.Reflection;
36
37 namespace System.Resources
38 {
39         public class ResXResourceWriter : IResourceWriter, IDisposable
40         {
41                 #region Local Variables
42                 private string          filename;
43                 private Stream          stream;
44                 private TextWriter      textwriter;
45                 private XmlTextWriter   writer;
46                 private bool            written;
47                 private string          base_path;
48                 #endregion      // Local Variables
49
50                 #region Static Fields
51                 public static readonly string BinSerializedObjectMimeType       = "application/x-microsoft.net.object.binary.base64";
52                 public static readonly string ByteArraySerializedObjectMimeType = "application/x-microsoft.net.object.bytearray.base64";
53                 public static readonly string DefaultSerializedObjectMimeType   = BinSerializedObjectMimeType;
54                 public static readonly string ResMimeType                       = "text/microsoft-resx";
55                 public static readonly string ResourceSchema                    = schema;
56                 public static readonly string SoapSerializedObjectMimeType      = "application/x-microsoft.net.object.soap.base64";
57 #if NET_2_0
58                 public static readonly string Version                           = "2.0";
59 #else
60                 public static readonly string Version                           = "1.3";
61 #endif
62                 #endregion      // Static Fields
63
64                 #region Constructors & Destructor
65                 public ResXResourceWriter (Stream stream)
66                 {
67                         if (stream == null)
68                                 throw new ArgumentNullException ("stream");
69
70                         if (!stream.CanWrite)
71                                 throw new ArgumentException ("stream is not writable.", "stream");
72
73                         this.stream = stream;
74                 }
75
76                 public ResXResourceWriter (TextWriter textWriter)
77                 {
78                         if (textWriter == null)
79                                 throw new ArgumentNullException ("textWriter");
80
81                         this.textwriter = textWriter;
82                 }
83                 
84                 public ResXResourceWriter (string fileName)
85                 {
86                         if (fileName == null)
87                                 throw new ArgumentNullException ("fileName");
88
89                         this.filename = fileName;
90                 }
91
92                 ~ResXResourceWriter() {
93                         Dispose(false);
94                 }
95                 #endregion      // Constructors & Destructor
96
97                 void InitWriter ()
98                 {
99                         if (filename != null)
100                                 stream = File.OpenWrite (filename);
101                         if (textwriter == null)
102                                 textwriter = new StreamWriter (stream, Encoding.UTF8);
103
104                         writer = new XmlTextWriter (textwriter);
105                         writer.Formatting = Formatting.Indented;
106                         writer.WriteStartDocument ();
107                         writer.WriteStartElement ("root");
108                         writer.WriteRaw (schema);
109                         WriteHeader ("resmimetype", "text/microsoft-resx");
110                         WriteHeader ("version", "1.3");
111                         WriteHeader ("reader", typeof (ResXResourceReader).AssemblyQualifiedName);
112                         WriteHeader ("writer", typeof (ResXResourceWriter).AssemblyQualifiedName);
113                 }
114
115                 void WriteHeader (string name, string value)
116                 {
117                         writer.WriteStartElement ("resheader");
118                         writer.WriteAttributeString ("name", name);
119                         writer.WriteStartElement ("value");
120                         writer.WriteString (value);
121                         writer.WriteEndElement ();
122                         writer.WriteEndElement ();
123                 }
124
125                 void WriteNiceBase64(byte[] value, int offset, int length) {
126                         string          b64;
127                         StringBuilder   sb;
128                         int             pos;
129                         int             inc;
130                         string          ins;
131
132                         b64 = Convert.ToBase64String(value, offset, length);
133
134                         // Wild guess; two extra newlines, and one newline/tab pair for every 80 chars
135                         sb = new StringBuilder(b64, b64.Length + ((b64.Length + 160) / 80) * 3);
136                         pos = 0;
137                         inc = 80 + Environment.NewLine.Length + 1;
138                         ins = Environment.NewLine + "\t";
139                         while (pos < sb.Length) {
140                                 sb.Insert(pos, ins);
141                                 pos += inc;
142                         }
143                         sb.Insert(sb.Length, Environment.NewLine);
144                         writer.WriteString(sb.ToString());
145                 }
146                 void WriteBytes (string name, Type type, byte[] value, int offset, int length)
147                 {
148                         WriteBytes (name, type, value, offset, length, String.Empty);
149                 }
150
151                 void WriteBytes (string name, Type type, byte[] value, int offset, int length, string comment)
152                 {
153                         writer.WriteStartElement ("data");
154                         writer.WriteAttributeString ("name", name);
155
156                         if (type != null) {
157                                 writer.WriteAttributeString ("type", type.AssemblyQualifiedName);
158                                 // byte[] should never get a mimetype, otherwise MS.NET won't be able
159                                 // to parse the data.
160                                 if (type != typeof (byte[]))
161                                         writer.WriteAttributeString ("mimetype", ByteArraySerializedObjectMimeType);
162                                 writer.WriteStartElement ("value");
163                                 WriteNiceBase64 (value, offset, length);
164                         } else {
165                                 writer.WriteAttributeString ("mimetype", BinSerializedObjectMimeType);
166                                 writer.WriteStartElement ("value");
167                                 writer.WriteBase64 (value, offset, length);
168                         }
169
170                         writer.WriteEndElement ();
171
172                         if (!(comment == null || comment.Equals (String.Empty))) {
173                                 writer.WriteStartElement ("comment");
174                                 writer.WriteString (comment);
175                                 writer.WriteEndElement ();
176                         }
177                         
178                         writer.WriteEndElement ();
179                 }
180
181                 void WriteBytes (string name, Type type, byte [] value)
182                 {
183                         WriteBytes (name, type, value, 0, value.Length);
184                 }
185
186                 void WriteString (string name, string value)
187                 {
188                         WriteString (name, value, null);
189                 }
190                 void WriteString (string name, string value, Type type)
191                 {
192                         WriteString (name, value, type, String.Empty);
193                 }
194                 void WriteString (string name, string value, Type type, string comment)
195                 {
196                         writer.WriteStartElement ("data");
197                         writer.WriteAttributeString ("name", name);
198                         if (type != null)
199                                 writer.WriteAttributeString ("type", type.AssemblyQualifiedName);
200                         writer.WriteStartElement ("value");
201                         writer.WriteString (value);
202                         writer.WriteEndElement ();
203                         if (!(comment == null || comment.Equals (String.Empty))) {
204                                 writer.WriteStartElement ("comment");
205                                 writer.WriteString (comment);
206                                 writer.WriteEndElement ();
207                         }
208                         writer.WriteEndElement ();
209                         writer.WriteWhitespace ("\n  ");
210                 }
211
212                 public void AddResource (string name, byte [] value)
213                 {
214                         if (name == null)
215                                 throw new ArgumentNullException ("name");
216
217                         if (value == null)
218                                 throw new ArgumentNullException ("value");
219
220                         if (written)
221                                 throw new InvalidOperationException ("The resource is already generated.");
222
223                         if (writer == null)
224                                 InitWriter ();
225
226                         WriteBytes (name, value.GetType (), value);
227                 }
228
229                 public void AddResource (string name, object value)
230                 {
231                         AddResource (name, value, String.Empty);
232                 }
233
234                 private void AddResource (string name, object value, string comment)
235                 {
236                         if (value is string) {
237                                 AddResource (name, (string) value);
238                                 return;
239                         }
240
241                         if (value is byte[]) {
242                                 AddResource (name, (byte[]) value);
243                                 return;
244                         }
245
246                         if (name == null)
247                                 throw new ArgumentNullException ("name");
248
249                         if (value == null)
250                                 throw new ArgumentNullException ("value");
251
252                         if (!value.GetType ().IsSerializable)
253                                         throw new InvalidOperationException (String.Format ("The element '{0}' of type '{1}' is not serializable.", name, value.GetType ().Name));
254
255                         if (written)
256                                 throw new InvalidOperationException ("The resource is already generated.");
257
258                         if (writer == null)
259                                 InitWriter ();
260
261                         TypeConverter converter = TypeDescriptor.GetConverter (value);
262                         if (converter != null && converter.CanConvertTo (typeof (string)) && converter.CanConvertFrom (typeof (string))) {
263                                 string str = (string) converter.ConvertToInvariantString (value);
264                                 WriteString (name, str, value.GetType ());
265                                 return;
266                         }
267                         
268                         if (converter != null && converter.CanConvertTo (typeof (byte[])) && converter.CanConvertFrom (typeof (byte[]))) {
269                                 byte[] b = (byte[]) converter.ConvertTo (value, typeof (byte[]));
270                                 WriteBytes (name, value.GetType (), b);
271                                 return;
272                         }
273                         
274                         MemoryStream ms = new MemoryStream ();
275                         BinaryFormatter fmt = new BinaryFormatter ();
276                         try {
277                                 fmt.Serialize (ms, value);
278                         } catch (Exception e) {
279                                 throw new InvalidOperationException ("Cannot add a " + value.GetType () +
280                                                                      "because it cannot be serialized: " +
281                                                                      e.Message);
282                         }
283
284                         WriteBytes (name, null, ms.GetBuffer (), 0, (int) ms.Length, comment);
285                         ms.Close ();
286                 }
287                 
288                 public void AddResource (string name, string value)
289                 {
290                         if (name == null)
291                                 throw new ArgumentNullException ("name");
292
293                         if (value == null)
294                                 throw new ArgumentNullException ("value");
295
296                         if (written)
297                                 throw new InvalidOperationException ("The resource is already generated.");
298
299                         if (writer == null)
300                                 InitWriter ();
301
302                         WriteString (name, value);
303                 }
304
305 #if NET_2_0
306                 [MonoTODO ("Stub, not implemented")]
307                 public virtual void AddAlias (string aliasName, AssemblyName assemblyName)
308                 {
309                 }
310                 
311                 public void AddResource (ResXDataNode node)
312                 {
313                         AddResource (node.Name, node.Value, node.Comment);
314                 }
315                 
316                 public void AddMetadata (string name, string value)
317                 {
318                         if (name == null)
319                                 throw new ArgumentNullException ("name");
320
321                         if (value == null)
322                                 throw new ArgumentNullException ("value");
323
324                         if (written)
325                                 throw new InvalidOperationException ("The resource is already generated.");
326
327                         if (writer == null)
328                                 InitWriter ();
329
330                         writer.WriteStartElement ("metadata");
331                         writer.WriteAttributeString ("name", name);
332                         writer.WriteAttributeString ("xml:space", "preserve");
333                         
334                         writer.WriteElementString ("value", value);
335                         
336                         writer.WriteEndElement ();
337                 }
338
339                 public void AddMetadata (string name, byte[] value)
340                 {
341                         if (name == null)
342                                 throw new ArgumentNullException ("name");
343
344                         if (value == null)
345                                 throw new ArgumentNullException ("value");
346
347                         if (written)
348                                 throw new InvalidOperationException ("The resource is already generated.");
349
350                         if (writer == null)
351                                 InitWriter ();
352
353                         writer.WriteStartElement ("metadata");
354                         writer.WriteAttributeString ("name", name);
355
356                         writer.WriteAttributeString ("type", value.GetType ().AssemblyQualifiedName);
357                         
358                         writer.WriteStartElement ("value");
359                         WriteNiceBase64 (value, 0, value.Length);
360                         writer.WriteEndElement ();
361
362                         writer.WriteEndElement ();
363                 }
364                 
365                 public void AddMetadata (string name, object value)
366                 {
367                         if (value is string) {
368                                 AddMetadata (name, (string)value);
369                                 return;
370                         }
371
372                         if (value is byte[]) {
373                                 AddMetadata (name, (byte[])value);
374                                 return;
375                         }
376
377                         if (name == null)
378                                 throw new ArgumentNullException ("name");
379
380                         if (value == null)
381                                 throw new ArgumentNullException ("value");
382
383                         if (!value.GetType ().IsSerializable)
384                                 throw new InvalidOperationException (String.Format ("The element '{0}' of type '{1}' is not serializable.", name, value.GetType ().Name));
385
386                         if (written)
387                                 throw new InvalidOperationException ("The resource is already generated.");
388
389                         if (writer == null)
390                                 InitWriter ();
391
392                         Type type = value.GetType ();
393                         
394                         TypeConverter converter = TypeDescriptor.GetConverter (value);
395                         if (converter != null && converter.CanConvertTo (typeof (string)) && converter.CanConvertFrom (typeof (string))) {
396                                 string str = (string)converter.ConvertToInvariantString (value);
397                                 writer.WriteStartElement ("metadata");
398                                 writer.WriteAttributeString ("name", name);
399                                 if (type != null)
400                                         writer.WriteAttributeString ("type", type.AssemblyQualifiedName);
401                                 writer.WriteStartElement ("value");
402                                 writer.WriteString (str);
403                                 writer.WriteEndElement ();
404                                 writer.WriteEndElement ();
405                                 writer.WriteWhitespace ("\n  ");
406                                 return;
407                         }
408
409                         if (converter != null && converter.CanConvertTo (typeof (byte[])) && converter.CanConvertFrom (typeof (byte[]))) {
410                                 byte[] b = (byte[])converter.ConvertTo (value, typeof (byte[]));
411                                 writer.WriteStartElement ("metadata");
412                                 writer.WriteAttributeString ("name", name);
413
414                                 if (type != null) {
415                                         writer.WriteAttributeString ("type", type.AssemblyQualifiedName);
416                                         writer.WriteAttributeString ("mimetype", ByteArraySerializedObjectMimeType);
417                                         writer.WriteStartElement ("value");
418                                         WriteNiceBase64 (b, 0, b.Length);
419                                 } else {
420                                         writer.WriteAttributeString ("mimetype", BinSerializedObjectMimeType);
421                                         writer.WriteStartElement ("value");
422                                         writer.WriteBase64 (b, 0, b.Length);
423                                 }
424
425                                 writer.WriteEndElement ();
426                                 writer.WriteEndElement ();
427                                 return;
428                         }
429
430                         MemoryStream ms = new MemoryStream ();
431                         BinaryFormatter fmt = new BinaryFormatter ();
432                         try {
433                                 fmt.Serialize (ms, value);
434                         } catch (Exception e) {
435                                 throw new InvalidOperationException ("Cannot add a " + value.GetType () +
436                                                                      "because it cannot be serialized: " +
437                                                                      e.Message);
438                         }
439
440                         writer.WriteStartElement ("metadata");
441                         writer.WriteAttributeString ("name", name);
442
443                         if (type != null) {
444                                 writer.WriteAttributeString ("type", type.AssemblyQualifiedName);
445                                 writer.WriteAttributeString ("mimetype", ByteArraySerializedObjectMimeType);
446                                 writer.WriteStartElement ("value");
447                                 WriteNiceBase64 (ms.GetBuffer (), 0, ms.GetBuffer ().Length);
448                         } else {
449                                 writer.WriteAttributeString ("mimetype", BinSerializedObjectMimeType);
450                                 writer.WriteStartElement ("value");
451                                 writer.WriteBase64 (ms.GetBuffer (), 0, ms.GetBuffer ().Length);
452                         }
453
454                         writer.WriteEndElement ();
455                         writer.WriteEndElement ();
456                         ms.Close ();
457                 }
458 #endif
459
460                 public void Close ()
461                 {
462                         if (!written) {
463                                 Generate ();
464                         }
465
466                         if (writer != null) {
467                                 writer.Close ();
468                                 stream = null;
469                                 filename = null;
470                                 textwriter = null;
471                         }
472                 }
473                 
474                 public virtual void Dispose ()
475                 {
476                         Dispose(true);
477                         GC.SuppressFinalize(this);
478                 }
479
480                 public void Generate ()
481                 {
482                         if (written)
483                                 throw new InvalidOperationException ("The resource is already generated.");
484
485                         written = true;
486                         writer.WriteEndElement ();
487                         writer.Flush ();
488                 }
489
490                 protected virtual void Dispose (bool disposing)
491                 {
492                         if (disposing)
493                                 Close();
494                 }
495
496                 static string schema = @"
497   <xsd:schema id='root' xmlns='' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:msdata='urn:schemas-microsoft-com:xml-msdata'>
498     <xsd:element name='root' msdata:IsDataSet='true'>
499       <xsd:complexType>
500         <xsd:choice maxOccurs='unbounded'>
501           <xsd:element name='data'>
502             <xsd:complexType>
503               <xsd:sequence>
504                 <xsd:element name='value' type='xsd:string' minOccurs='0' msdata:Ordinal='1' />
505                 <xsd:element name='comment' type='xsd:string' minOccurs='0' msdata:Ordinal='2' />
506               </xsd:sequence>
507               <xsd:attribute name='name' type='xsd:string' msdata:Ordinal='1' />
508               <xsd:attribute name='type' type='xsd:string' msdata:Ordinal='3' />
509               <xsd:attribute name='mimetype' type='xsd:string' msdata:Ordinal='4' />
510             </xsd:complexType>
511           </xsd:element>
512           <xsd:element name='resheader'>
513             <xsd:complexType>
514               <xsd:sequence>
515                 <xsd:element name='value' type='xsd:string' minOccurs='0' msdata:Ordinal='1' />
516               </xsd:sequence>
517               <xsd:attribute name='name' type='xsd:string' use='required' />
518             </xsd:complexType>
519           </xsd:element>
520         </xsd:choice>
521       </xsd:complexType>
522     </xsd:element>
523   </xsd:schema>
524 ".Replace ("'", "\"");
525
526                 #region Public Properties
527 #if NET_2_0
528                 public string BasePath {
529                         get { return base_path; }
530                         set { base_path = value; }
531                 }
532 #endif
533                 #endregion
534         }
535 }