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