ResXResourceWriter.cs 20 KB

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