* FolderBrowserDialog.cs: Removed need for separate description field.
[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
36 namespace System.Resources
37 {
38         public class ResXResourceWriter : IResourceWriter, IDisposable
39         {
40                 #region Local Variables
41                 private string          filename;
42                 private Stream          stream;
43                 private TextWriter      textwriter;
44                 private XmlTextWriter   writer;
45                 private bool            written;
46                 #endregion      // Local Variables
47
48                 #region Static Fields
49                 public static readonly string BinSerializedObjectMimeType       = "application/x-microsoft.net.object.binary.base64";
50                 public static readonly string ByteArraySerializedObjectMimeType = "application/x-microsoft.net.object.bytearray.base64";
51                 public static readonly string DefaultSerializedObjectMimeType   = BinSerializedObjectMimeType;
52                 public static readonly string ResMimeType                       = "text/microsoft-resx";
53                 public static readonly string ResourceSchema                    = schema;
54                 public static readonly string SoapSerializedObjectMimeType      = "application/x-microsoft.net.object.soap.base64";
55                 public static readonly string Version                           = "1.3";
56                 #endregion      // Static Fields
57
58                 #region Constructors & Destructor
59                 public ResXResourceWriter (Stream stream)
60                 {
61                         if (stream == null)
62                                 throw new ArgumentNullException ("stream");
63
64                         if (stream.CanWrite == false)
65                                 throw new ArgumentException ("stream is not writable.", "stream");
66
67                         this.stream = stream;
68                 }
69
70                 public ResXResourceWriter (TextWriter textwriter)
71                 {
72                         if (textwriter == null)
73                                 throw new ArgumentNullException ("textwriter");
74
75                         this.textwriter = textwriter;
76                 }
77                 
78                 public ResXResourceWriter (string fileName)
79                 {
80                         if (fileName == null)
81                                 throw new ArgumentNullException ("fileName");
82
83                         this.filename = fileName;
84                 }
85
86                 ~ResXResourceWriter() {
87                         Dispose(false);
88                 }
89                 #endregion      // Constructors & Destructor
90
91                 void InitWriter ()
92                 {
93                         if (filename != null)
94                                 stream = File.OpenWrite (filename);
95                         if (textwriter == null)
96                                 textwriter = new StreamWriter (stream, Encoding.UTF8);
97
98                         writer = new XmlTextWriter (textwriter);
99                         writer.Formatting = Formatting.Indented;
100                         writer.WriteStartDocument ();
101                         writer.WriteStartElement ("root");
102                         writer.WriteRaw (schema);
103                         WriteHeader ("resmimetype", "text/microsoft-resx");
104                         WriteHeader ("version", "1.3");
105                         WriteHeader ("reader", typeof (ResXResourceReader).AssemblyQualifiedName);
106                         WriteHeader ("writer", typeof (ResXResourceWriter).AssemblyQualifiedName);
107                 }
108
109                 void WriteHeader (string name, string value)
110                 {
111                         writer.WriteStartElement ("resheader");
112                         writer.WriteAttributeString ("name", name);
113                         writer.WriteStartElement ("value");
114                         writer.WriteString (value);
115                         writer.WriteEndElement ();
116                         writer.WriteEndElement ();
117                 }
118
119                 void WriteNiceBase64(byte[] value, int offset, int length) {
120                         string          b64;
121                         StringBuilder   sb;
122                         int             pos;
123                         int             inc;
124                         string          ins;
125
126                         b64 = Convert.ToBase64String(value, offset, length);
127
128                         // Wild guess; two extra newlines, and one newline/tab pair for every 80 chars
129                         sb = new StringBuilder(b64, b64.Length + ((b64.Length + 160) / 80) * 3);
130                         pos = 0;
131                         inc = 80 + Environment.NewLine.Length + 1;
132                         ins = Environment.NewLine + "\t";
133                         while (pos < sb.Length) {
134                                 sb.Insert(pos, ins);
135                                 pos += inc;
136                         }
137                         sb.Insert(sb.Length, Environment.NewLine);
138                         writer.WriteString(sb.ToString());
139                 }
140                 void WriteBytes (string name, Type type, byte[] value, int offset, int length)
141                 {
142                         WriteBytes (name, type, value, offset, length, String.Empty);
143                 }
144
145                 void WriteBytes (string name, Type type, byte[] value, int offset, int length, string comment)
146                 {
147                         writer.WriteStartElement ("data");
148                         writer.WriteAttributeString ("name", name);
149
150                         if (type != null) {
151                                 writer.WriteAttributeString ("type", type.AssemblyQualifiedName);
152                                 // byte[] should never get a mimetype, otherwise MS.NET won't be able
153                                 // to parse the data.
154                                 if (type != typeof (byte[]))
155                                         writer.WriteAttributeString ("mimetype", ByteArraySerializedObjectMimeType);
156                                 writer.WriteStartElement ("value");
157                                 WriteNiceBase64 (value, offset, length);
158                         } else {
159                                 writer.WriteAttributeString ("mimetype", BinSerializedObjectMimeType);
160                                 writer.WriteStartElement ("value");
161                                 writer.WriteBase64 (value, offset, length);
162                         }
163
164                         writer.WriteEndElement ();
165
166                         if (!(comment == null || comment.Equals (String.Empty))) {
167                                 writer.WriteStartElement ("comment");
168                                 writer.WriteString (comment);
169                                 writer.WriteEndElement ();
170                         }
171                         
172                         writer.WriteEndElement ();
173                 }
174
175                 void WriteBytes (string name, Type type, byte [] value)
176                 {
177                         WriteBytes (name, type, value, 0, value.Length);
178                 }
179
180                 void WriteString (string name, string value)
181                 {
182                         WriteString (name, value, null);
183                 }
184                 void WriteString (string name, string value, Type type)
185                 {
186                         WriteString (name, value, type, String.Empty);
187                 }
188                 void WriteString (string name, string value, Type type, string comment)
189                 {
190                         writer.WriteStartElement ("data");
191                         writer.WriteAttributeString ("name", name);
192                         if (type != null)
193                                 writer.WriteAttributeString ("type", type.AssemblyQualifiedName);
194                         writer.WriteStartElement ("value");
195                         writer.WriteString (value);
196                         writer.WriteEndElement ();
197                         if (!(comment == null || comment.Equals (String.Empty))) {
198                                 writer.WriteStartElement ("comment");
199                                 writer.WriteString (comment);
200                                 writer.WriteEndElement ();
201                         }
202                         writer.WriteEndElement ();
203                         writer.WriteWhitespace ("\n  ");
204                 }
205
206                 public void AddResource (string name, byte [] value)
207                 {
208                         if (name == null)
209                                 throw new ArgumentNullException ("name");
210
211                         if (value == null)
212                                 throw new ArgumentNullException ("value");
213
214                         if (written)
215                                 throw new InvalidOperationException ("The resource is already generated.");
216
217                         if (writer == null)
218                                 InitWriter ();
219
220                         WriteBytes (name, value.GetType (), value);
221                 }
222
223                 public void AddResource (string name, object value)
224                 {
225                         AddResource (name, value, String.Empty);
226                 }
227
228                 private void AddResource (string name, object value, string comment)
229                 {
230                         if (value is string) {
231                                 AddResource (name, (string) value);
232                                 return;
233                         }
234
235                         if (value is byte[]) {
236                                 AddResource (name, (byte[]) value);
237                                 return;
238                         }
239
240                         if (name == null)
241                                 throw new ArgumentNullException ("name");
242
243                         if (value == null)
244                                 throw new ArgumentNullException ("value");
245
246                         if (!value.GetType ().IsSerializable)
247                                         throw new InvalidOperationException (String.Format ("The element '{0}' of type '{1}' is not serializable.", name, value.GetType ().Name));
248
249                         if (written)
250                                 throw new InvalidOperationException ("The resource is already generated.");
251
252                         if (writer == null)
253                                 InitWriter ();
254
255                         TypeConverter converter = TypeDescriptor.GetConverter (value);
256                         if (converter != null && converter.CanConvertTo (typeof (string)) && converter.CanConvertFrom (typeof (string))) {
257                                 string str = (string) converter.ConvertToInvariantString (value);
258                                 WriteString (name, str, value.GetType ());
259                                 return;
260                         }
261                         
262                         if (converter != null && converter.CanConvertTo (typeof (byte[])) && converter.CanConvertFrom (typeof (byte[]))) {
263                                 byte[] b = (byte[]) converter.ConvertTo (value, typeof (byte[]));
264                                 WriteBytes (name, value.GetType (), b);
265                                 return;
266                         }
267                         
268                         MemoryStream ms = new MemoryStream ();
269                         BinaryFormatter fmt = new BinaryFormatter ();
270                         try {
271                                 fmt.Serialize (ms, value);
272                         } catch (Exception e) {
273                                 throw new InvalidOperationException ("Cannot add a " + value.GetType () +
274                                                                      "because it cannot be serialized: " +
275                                                                      e.Message);
276                         }
277
278                         WriteBytes (name, null, ms.GetBuffer (), 0, (int) ms.Length, comment);
279                         ms.Close ();
280                 }
281                 
282                 public void AddResource (string name, string value)
283                 {
284                         if (name == null)
285                                 throw new ArgumentNullException ("name");
286
287                         if (value == null)
288                                 throw new ArgumentNullException ("value");
289
290                         if (written)
291                                 throw new InvalidOperationException ("The resource is already generated.");
292
293                         if (writer == null)
294                                 InitWriter ();
295
296                         WriteString (name, value);
297                 }
298
299 #if NET_2_0
300                 public void AddResource (ResXDataNode node)
301                 {
302                         AddResource (node.Name, node.Value, node.Comment);
303                 }
304 #endif
305
306                 public void Close ()
307                 {
308                         if (!written) {
309                                 Generate ();
310                         }
311
312                         if (writer != null) {
313                                 writer.Close ();
314                                 stream = null;
315                                 filename = null;
316                                 textwriter = null;
317                         }
318                 }
319                 
320                 public virtual void Dispose ()
321                 {
322                         Dispose(true);
323                         GC.SuppressFinalize(this);
324                 }
325
326                 public void Generate ()
327                 {
328                         if (written)
329                                 throw new InvalidOperationException ("The resource is already generated.");
330
331                         written = true;
332                         writer.WriteEndElement ();
333                         writer.Flush ();
334                 }
335
336                 protected virtual void Dispose(bool disposing) {
337                         if (disposing) {
338                                 Close();
339                         }
340                 }
341
342                 static string schema = @"
343   <xsd:schema id='root' xmlns='' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:msdata='urn:schemas-microsoft-com:xml-msdata'>
344     <xsd:element name='root' msdata:IsDataSet='true'>
345       <xsd:complexType>
346         <xsd:choice maxOccurs='unbounded'>
347           <xsd:element name='data'>
348             <xsd:complexType>
349               <xsd:sequence>
350                 <xsd:element name='value' type='xsd:string' minOccurs='0' msdata:Ordinal='1' />
351                 <xsd:element name='comment' type='xsd:string' minOccurs='0' msdata:Ordinal='2' />
352               </xsd:sequence>
353               <xsd:attribute name='name' type='xsd:string' msdata:Ordinal='1' />
354               <xsd:attribute name='type' type='xsd:string' msdata:Ordinal='3' />
355               <xsd:attribute name='mimetype' type='xsd:string' msdata:Ordinal='4' />
356             </xsd:complexType>
357           </xsd:element>
358           <xsd:element name='resheader'>
359             <xsd:complexType>
360               <xsd:sequence>
361                 <xsd:element name='value' type='xsd:string' minOccurs='0' msdata:Ordinal='1' />
362               </xsd:sequence>
363               <xsd:attribute name='name' type='xsd:string' use='required' />
364             </xsd:complexType>
365           </xsd:element>
366         </xsd:choice>
367       </xsd:complexType>
368     </xsd:element>
369   </xsd:schema>
370 ".Replace ("'", "\"");
371         }
372 }
373