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:
9 // The above copyright notice and this permission notice shall be
10 // included in all copies or substantial portions of the Software.
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.
20 // Copyright (c) 2004-2005 Novell, Inc.
23 // Duncan Mak duncan@ximian.com
24 // Gonzalo Paniagua Javier gonzalo@ximian.com
25 // Peter Bartok pbartok@novell.com
27 // includes code by Mike Krüger and Lluis Sanchez
29 using System.ComponentModel;
31 using System.Runtime.Serialization.Formatters.Binary;
34 using System.Reflection;
36 namespace System.Resources
43 class ResXResourceWriter : IResourceWriter, IDisposable
45 #region Local Variables
46 private string filename;
47 private Stream stream;
48 private TextWriter textwriter;
49 private XmlTextWriter writer;
51 private string base_path;
52 #endregion // Local Variables
55 public static readonly string BinSerializedObjectMimeType = "application/x-microsoft.net.object.binary.base64";
56 public static readonly string ByteArraySerializedObjectMimeType = "application/x-microsoft.net.object.bytearray.base64";
57 public static readonly string DefaultSerializedObjectMimeType = BinSerializedObjectMimeType;
58 public static readonly string ResMimeType = "text/microsoft-resx";
59 public static readonly string ResourceSchema = schema;
60 public static readonly string SoapSerializedObjectMimeType = "application/x-microsoft.net.object.soap.base64";
61 public static readonly string Version = "2.0";
62 #endregion // Static Fields
64 #region Constructors & Destructor
65 public ResXResourceWriter (Stream stream)
68 throw new ArgumentNullException ("stream");
71 throw new ArgumentException ("stream is not writable.", "stream");
76 public ResXResourceWriter (TextWriter textWriter)
78 if (textWriter == null)
79 throw new ArgumentNullException ("textWriter");
81 this.textwriter = textWriter;
84 public ResXResourceWriter (string fileName)
87 throw new ArgumentNullException ("fileName");
89 this.filename = fileName;
92 ~ResXResourceWriter() {
95 #endregion // Constructors & Destructor
100 stream = File.Open (filename, FileMode.Create);
101 if (textwriter == null)
102 textwriter = new StreamWriter (stream, Encoding.UTF8);
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);
115 void WriteHeader (string name, string value)
117 writer.WriteStartElement ("resheader");
118 writer.WriteAttributeString ("name", name);
119 writer.WriteStartElement ("value");
120 writer.WriteString (value);
121 writer.WriteEndElement ();
122 writer.WriteEndElement ();
125 void WriteNiceBase64(byte[] value, int offset, int length) {
132 b64 = Convert.ToBase64String(value, offset, length);
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);
137 inc = 80 + Environment.NewLine.Length + 1;
138 ins = Environment.NewLine + "\t";
139 while (pos < sb.Length) {
143 sb.Insert(sb.Length, Environment.NewLine);
144 writer.WriteString(sb.ToString());
146 void WriteBytes (string name, Type type, byte[] value, int offset, int length)
148 WriteBytes (name, type, value, offset, length, String.Empty);
151 void WriteBytes (string name, Type type, byte[] value, int offset, int length, string comment)
153 writer.WriteStartElement ("data");
154 writer.WriteAttributeString ("name", name);
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);
165 writer.WriteAttributeString ("mimetype", BinSerializedObjectMimeType);
166 writer.WriteStartElement ("value");
167 writer.WriteBase64 (value, offset, length);
170 writer.WriteEndElement ();
172 if (!(comment == null || comment.Equals (String.Empty))) {
173 writer.WriteStartElement ("comment");
174 writer.WriteString (comment);
175 writer.WriteEndElement ();
178 writer.WriteEndElement ();
181 void WriteBytes (string name, Type type, byte [] value, string comment)
183 WriteBytes (name, type, value, 0, value.Length, comment);
186 void WriteString (string name, string value)
188 WriteString (name, value, null);
190 void WriteString (string name, string value, Type type)
192 WriteString (name, value, type, String.Empty);
194 void WriteString (string name, string value, Type type, string comment)
196 writer.WriteStartElement ("data");
197 writer.WriteAttributeString ("name", name);
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 ();
208 writer.WriteEndElement ();
209 writer.WriteWhitespace ("\n ");
212 public void AddResource (string name, byte [] value)
215 throw new ArgumentNullException ("name");
218 throw new ArgumentNullException ("value");
221 throw new InvalidOperationException ("The resource is already generated.");
226 WriteBytes (name, value.GetType (), value, null);
229 public void AddResource (string name, object value)
231 AddResource (name, value, String.Empty);
234 private void AddResource (string name, object value, string comment)
236 if (value is string) {
237 AddResource (name, (string) value, comment);
242 throw new ArgumentNullException ("name");
244 if (value != null && !value.GetType ().IsSerializable)
245 throw new InvalidOperationException (String.Format ("The element '{0}' of type '{1}' is not serializable.", name, value.GetType ().Name));
248 throw new InvalidOperationException ("The resource is already generated.");
253 if (value is byte[]) {
254 WriteBytes (name, value.GetType (), (byte []) value, comment);
259 // nulls written as ResXNullRef
260 WriteString (name, "", typeof (ResXNullRef), comment);
264 TypeConverter converter = TypeDescriptor.GetConverter (value);
265 if (value is ResXFileRef) {
266 ResXFileRef fileRef = ProcessFileRefBasePath ((ResXFileRef) value);
267 string str = (string) converter.ConvertToInvariantString (fileRef);
268 WriteString (name, str, value.GetType (), comment);
272 if (converter != null && converter.CanConvertTo (typeof (string)) && converter.CanConvertFrom (typeof (string))) {
273 string str = (string) converter.ConvertToInvariantString (value);
274 WriteString (name, str, value.GetType (), comment);
278 if (converter != null && converter.CanConvertTo (typeof (byte[])) && converter.CanConvertFrom (typeof (byte[]))) {
279 byte[] b = (byte[]) converter.ConvertTo (value, typeof (byte[]));
280 WriteBytes (name, value.GetType (), b, comment);
284 MemoryStream ms = new MemoryStream ();
285 BinaryFormatter fmt = new BinaryFormatter ();
287 fmt.Serialize (ms, value);
288 } catch (Exception e) {
289 throw new InvalidOperationException ("Cannot add a " + value.GetType () +
290 "because it cannot be serialized: " +
294 WriteBytes (name, null, ms.GetBuffer (), 0, (int) ms.Length, comment);
298 public void AddResource (string name, string value)
300 AddResource (name, value, string.Empty);
303 private void AddResource (string name, string value, string comment)
306 throw new ArgumentNullException ("name");
309 throw new ArgumentNullException ("value");
312 throw new InvalidOperationException ("The resource is already generated.");
317 WriteString (name, value, null, comment);
320 [MonoTODO ("Stub, not implemented")]
321 public virtual void AddAlias (string aliasName, AssemblyName assemblyName)
325 public void AddResource (ResXDataNode node)
328 throw new ArgumentNullException ("node");
334 WriteWritableNode (node);
335 else if (node.FileRef != null)
336 AddResource (node.Name, node.FileRef, node.Comment);
338 AddResource (node.Name, node.GetValue ((AssemblyName []) null), node.Comment);
341 ResXFileRef ProcessFileRefBasePath (ResXFileRef fileRef)
343 if (String.IsNullOrEmpty (BasePath))
346 string newPath = AbsoluteToRelativePath (BasePath, fileRef.FileName);
347 return new ResXFileRef (newPath, fileRef.TypeName, fileRef.TextFileEncoding);
350 static bool IsSeparator (char ch)
352 return ch == Path.DirectorySeparatorChar || ch == Path.AltDirectorySeparatorChar || ch == Path.VolumeSeparatorChar;
354 //adapted from MonoDevelop.Core
355 unsafe static string AbsoluteToRelativePath (string baseDirectoryPath, string absPath)
357 if (string.IsNullOrEmpty (baseDirectoryPath))
360 baseDirectoryPath = baseDirectoryPath.TrimEnd (Path.DirectorySeparatorChar);
362 fixed (char* bPtr = baseDirectoryPath, aPtr = absPath) {
363 var bEnd = bPtr + baseDirectoryPath.Length;
364 var aEnd = aPtr + absPath.Length;
365 char* lastStartA = aEnd;
366 char* lastStartB = bEnd;
369 // search common base path
375 if (IsSeparator (*a)) {
383 if (a >= aEnd || IsSeparator (*a)) {
394 if (lastStartA >= aEnd)
397 // handle case a: some/path b: some/path/deeper...
399 if (IsSeparator (*b)) {
405 // look how many levels to go up into the base path
407 while (lastStartB < bEnd) {
408 if (IsSeparator (*lastStartB))
412 var size = goUpCount * 2 + goUpCount + aEnd - lastStartA;
413 var result = new char [size];
414 fixed (char* rPtr = result) {
417 for (int i = 0; i < goUpCount; i++) {
420 *(r++) = Path.DirectorySeparatorChar;
422 // copy the remaining absulute path
423 while (lastStartA < aEnd)
424 *(r++) = *(lastStartA++);
426 return new string (result);
430 // avoids instantiating objects
431 void WriteWritableNode (ResXDataNode node)
433 writer.WriteStartElement ("data");
434 writer.WriteAttributeString ("name", node.Name);
435 if (!(node.type == null || node.type.Equals (String.Empty)))
436 writer.WriteAttributeString ("type", node.type);
437 if (!(node.mime_type == null || node.mime_type.Equals (String.Empty)))
438 writer.WriteAttributeString ("mimetype", node.mime_type);
439 writer.WriteStartElement ("value");
440 writer.WriteString (node.dataString);
441 writer.WriteEndElement ();
442 if (!(node.Comment == null || node.Comment.Equals (String.Empty))) {
443 writer.WriteStartElement ("comment");
444 writer.WriteString (node.Comment);
445 writer.WriteEndElement ();
447 writer.WriteEndElement ();
448 writer.WriteWhitespace ("\n ");
451 public void AddMetadata (string name, string value)
454 throw new ArgumentNullException ("name");
457 throw new ArgumentNullException ("value");
460 throw new InvalidOperationException ("The resource is already generated.");
465 writer.WriteStartElement ("metadata");
466 writer.WriteAttributeString ("name", name);
467 writer.WriteAttributeString ("xml:space", "preserve");
469 writer.WriteElementString ("value", value);
471 writer.WriteEndElement ();
474 public void AddMetadata (string name, byte[] value)
477 throw new ArgumentNullException ("name");
480 throw new ArgumentNullException ("value");
483 throw new InvalidOperationException ("The resource is already generated.");
488 writer.WriteStartElement ("metadata");
489 writer.WriteAttributeString ("name", name);
491 writer.WriteAttributeString ("type", value.GetType ().AssemblyQualifiedName);
493 writer.WriteStartElement ("value");
494 WriteNiceBase64 (value, 0, value.Length);
495 writer.WriteEndElement ();
497 writer.WriteEndElement ();
500 public void AddMetadata (string name, object value)
502 if (value is string) {
503 AddMetadata (name, (string)value);
507 if (value is byte[]) {
508 AddMetadata (name, (byte[])value);
513 throw new ArgumentNullException ("name");
516 throw new ArgumentNullException ("value");
518 if (!value.GetType ().IsSerializable)
519 throw new InvalidOperationException (String.Format ("The element '{0}' of type '{1}' is not serializable.", name, value.GetType ().Name));
522 throw new InvalidOperationException ("The resource is already generated.");
527 Type type = value.GetType ();
529 TypeConverter converter = TypeDescriptor.GetConverter (value);
530 if (converter != null && converter.CanConvertTo (typeof (string)) && converter.CanConvertFrom (typeof (string))) {
531 string str = (string)converter.ConvertToInvariantString (value);
532 writer.WriteStartElement ("metadata");
533 writer.WriteAttributeString ("name", name);
535 writer.WriteAttributeString ("type", type.AssemblyQualifiedName);
536 writer.WriteStartElement ("value");
537 writer.WriteString (str);
538 writer.WriteEndElement ();
539 writer.WriteEndElement ();
540 writer.WriteWhitespace ("\n ");
544 if (converter != null && converter.CanConvertTo (typeof (byte[])) && converter.CanConvertFrom (typeof (byte[]))) {
545 byte[] b = (byte[])converter.ConvertTo (value, typeof (byte[]));
546 writer.WriteStartElement ("metadata");
547 writer.WriteAttributeString ("name", name);
550 writer.WriteAttributeString ("type", type.AssemblyQualifiedName);
551 writer.WriteAttributeString ("mimetype", ByteArraySerializedObjectMimeType);
552 writer.WriteStartElement ("value");
553 WriteNiceBase64 (b, 0, b.Length);
555 writer.WriteAttributeString ("mimetype", BinSerializedObjectMimeType);
556 writer.WriteStartElement ("value");
557 writer.WriteBase64 (b, 0, b.Length);
560 writer.WriteEndElement ();
561 writer.WriteEndElement ();
565 MemoryStream ms = new MemoryStream ();
566 BinaryFormatter fmt = new BinaryFormatter ();
568 fmt.Serialize (ms, value);
569 } catch (Exception e) {
570 throw new InvalidOperationException ("Cannot add a " + value.GetType () +
571 "because it cannot be serialized: " +
575 writer.WriteStartElement ("metadata");
576 writer.WriteAttributeString ("name", name);
579 writer.WriteAttributeString ("type", type.AssemblyQualifiedName);
580 writer.WriteAttributeString ("mimetype", ByteArraySerializedObjectMimeType);
581 writer.WriteStartElement ("value");
582 WriteNiceBase64 (ms.GetBuffer (), 0, ms.GetBuffer ().Length);
584 writer.WriteAttributeString ("mimetype", BinSerializedObjectMimeType);
585 writer.WriteStartElement ("value");
586 writer.WriteBase64 (ms.GetBuffer (), 0, ms.GetBuffer ().Length);
589 writer.WriteEndElement ();
590 writer.WriteEndElement ();
600 if (writer != null) {
608 public virtual void Dispose ()
611 GC.SuppressFinalize(this);
614 public void Generate ()
617 throw new InvalidOperationException ("The resource is already generated.");
620 writer.WriteEndElement ();
624 protected virtual void Dispose (bool disposing)
630 static string schema = @"
631 <xsd:schema id='root' xmlns='' xmlns:xsd='http://www.w3.org/2001/XMLSchema' xmlns:msdata='urn:schemas-microsoft-com:xml-msdata'>
632 <xsd:element name='root' msdata:IsDataSet='true'>
634 <xsd:choice maxOccurs='unbounded'>
635 <xsd:element name='data'>
638 <xsd:element name='value' type='xsd:string' minOccurs='0' msdata:Ordinal='1' />
639 <xsd:element name='comment' type='xsd:string' minOccurs='0' msdata:Ordinal='2' />
641 <xsd:attribute name='name' type='xsd:string' msdata:Ordinal='1' />
642 <xsd:attribute name='type' type='xsd:string' msdata:Ordinal='3' />
643 <xsd:attribute name='mimetype' type='xsd:string' msdata:Ordinal='4' />
646 <xsd:element name='resheader'>
649 <xsd:element name='value' type='xsd:string' minOccurs='0' msdata:Ordinal='1' />
651 <xsd:attribute name='name' type='xsd:string' use='required' />
658 ".Replace ("'", "\"");
660 #region Public Properties
661 public string BasePath {
662 get { return base_path; }
663 set { base_path = value; }