DbaseReader.cs
上传用户:sex100000
上传日期:2013-11-09
资源大小:1377k
文件大小:20k
源码类别:

GIS编程

开发平台:

C#

  1. // Copyright 2005, 2006 - Morten Nielsen (www.iter.dk)
  2. //
  3. // This file is part of SharpMap.
  4. // SharpMap is free software; you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation; either version 2 of the License, or
  7. // (at your option) any later version.
  8. // 
  9. // SharpMap is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12. // GNU Lesser General Public License for more details.
  13. // You should have received a copy of the GNU Lesser General Public License
  14. // along with SharpMap; if not, write to the Free Software
  15. // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
  16. // Note:
  17. // Good stuff on DBase format: http://www.clicketyclick.dk/databases/xbase/format/
  18. using System;
  19. using System.Collections.Generic;
  20. using System.Text;
  21. using System.IO;
  22. using System.Data;
  23. namespace SharpMap.Data.Providers
  24. {
  25. internal class DbaseReader : IDisposable
  26. {
  27. private struct DbaseField
  28. {
  29. public string ColumnName;
  30. public Type DataType;
  31. public int Address;
  32. public int Length;
  33. public int Decimals;
  34. }
  35. private DateTime _lastUpdate;
  36. private int _NumberOfRecords;
  37. private Int16 _HeaderLength;
  38. private Int16 _RecordLength;
  39. private string _filename;
  40. private DbaseField[] DbaseColumns;
  41. private FileStream fs;
  42. private BinaryReader br;
  43. private bool HeaderIsParsed;
  44. public DbaseReader(string filename)
  45. {
  46. if (!File.Exists(filename))
  47. throw new FileNotFoundException(String.Format("Could not find file "{0}"", filename));
  48. _filename = filename;
  49. HeaderIsParsed = false;
  50. }
  51. private bool _isOpen;
  52. public bool IsOpen
  53. {
  54. get { return _isOpen; }
  55. set { _isOpen = value; }
  56. }
  57. public void Open()
  58. {
  59. fs = new FileStream(_filename, FileMode.Open, FileAccess.Read);
  60. br = new BinaryReader(fs);
  61. _isOpen = true;
  62. if (!HeaderIsParsed) //Don't read the header if it's already parsed
  63. ParseDbfHeader(_filename);
  64. }
  65. public void Close()
  66. {
  67. br.Close();
  68. fs.Close();
  69. _isOpen = false;
  70. }
  71. public void Dispose()
  72. {
  73. if(_isOpen)
  74. this.Close();
  75. br = null;
  76. fs = null;
  77. }
  78. // Binary Tree not working yet on Mono 
  79. // see bug: http://bugzilla.ximian.com/show_bug.cgi?id=78502
  80. #if !MONO
  81. /// <summary>
  82. /// Indexes a DBF column in a binary tree [NOT COMPLETE]
  83. /// </summary>
  84. /// <typeparam name="T">datatype to be indexed</typeparam>
  85. /// <param name="ColumnId">Column to index</param>
  86. /// <returns></returns>
  87. public SharpMap.Utilities.Indexing.BinaryTree<T, UInt32> CreateDbfIndex<T>(int ColumnId) where T:IComparable<T>
  88. {
  89. SharpMap.Utilities.Indexing.BinaryTree<T, UInt32> tree = new SharpMap.Utilities.Indexing.BinaryTree<T, uint>();
  90. for (uint i = 0; i < ((this._NumberOfRecords>10000)?10000:this._NumberOfRecords); i++)
  91. tree.Add(new SharpMap.Utilities.Indexing.BinaryTree<T, uint>.ItemValue((T)GetValue(i, ColumnId), i));
  92. return tree;
  93. }
  94. #endif
  95. /*
  96. /// <summary>
  97. /// Creates an index on the columns for faster searching [EXPERIMENTAL - Requires Lucene dependencies]
  98. /// </summary>
  99. /// <returns></returns>
  100. public string CreateLuceneIndex()
  101. {
  102. string dir = this._filename + ".idx";
  103. if (!System.IO.Directory.Exists(dir))
  104. System.IO.Directory.CreateDirectory(dir);
  105. Lucene.Net.Index.IndexWriter iw = new Lucene.Net.Index.IndexWriter(dir,new Lucene.Net.Analysis.Standard.StandardAnalyzer(),true);
  106. for (uint i = 0; i < this._NumberOfRecords; i++)
  107. {
  108. FeatureDataRow dr = GetFeature(i,this.NewTable);
  109. Lucene.Net.Documents.Document doc = new Lucene.Net.Documents.Document();
  110. // Add the object-id as a field, so that index can be maintained.
  111. // This field is not stored with document, it is indexed, but it is not
  112.             // tokenized prior to indexing.
  113. //doc.Add(Lucene.Net.Documents.Field.UnIndexed("SharpMap_oid", i.ToString())); //Add OID index
  114. foreach(System.Data.DataColumn col in dr.Table.Columns) //Add and index values from DBF
  115. {
  116. if(col.DataType.Equals(typeof(string)))
  117. // Add the contents as a valued Text field so it will get tokenized and indexed.
  118. doc.Add(Lucene.Net.Documents.Field.UnStored(col.ColumnName,(string)dr[col]));
  119. else
  120. doc.Add(Lucene.Net.Documents.Field.UnStored(col.ColumnName, dr[col].ToString()));
  121. }
  122. iw.AddDocument(doc);
  123. }
  124. iw.Optimize();
  125. iw.Close();
  126. return this._filename + ".idx";
  127. }
  128. */
  129. /// <summary>
  130. /// Gets the date this file was last updated.
  131. /// </summary>
  132. public DateTime LastUpdate
  133. {
  134. get { return _lastUpdate; }
  135. }
  136. private void ParseDbfHeader(string filename)
  137. {
  138. if (br.ReadByte() != 0x03)
  139. throw new NotSupportedException("Unsupported DBF Type");
  140. _lastUpdate = new DateTime((int)br.ReadByte() + 1900, (int)br.ReadByte(), (int)br.ReadByte()); //Read the last update date
  141. _NumberOfRecords = br.ReadInt32(); // read number of records.
  142. _HeaderLength = br.ReadInt16(); // read length of header structure.
  143. _RecordLength = br.ReadInt16(); // read length of a record
  144. fs.Seek(29, SeekOrigin.Begin); //Seek to encoding flag
  145. _FileEncoding = GetDbaseLanguageDriver(br.ReadByte()); //Read and parse Language driver
  146. fs.Seek(32, SeekOrigin.Begin); //Move past the reserved bytes
  147. int NumberOfColumns = (_HeaderLength - 31) / 32;  // calculate the number of DataColumns in the header
  148. DbaseColumns = new DbaseField[NumberOfColumns];
  149. for (int i = 0; i < DbaseColumns.Length;i++)
  150. {
  151. DbaseColumns[i] = new DbaseField();
  152. DbaseColumns[i].ColumnName = System.Text.Encoding.UTF7.GetString((br.ReadBytes(11))).Replace("", "").Trim();
  153. char fieldtype = br.ReadChar();
  154. switch (fieldtype)
  155. {
  156. case 'L':
  157. DbaseColumns[i].DataType = typeof(bool);
  158. break;
  159. case 'C':
  160. DbaseColumns[i].DataType = typeof(string);
  161. break;
  162. case 'D':
  163. DbaseColumns[i].DataType = typeof(DateTime);
  164. break;
  165. case 'N':
  166. DbaseColumns[i].DataType = typeof(double);
  167. break;
  168. case 'F':
  169. DbaseColumns[i].DataType = typeof(float);
  170. break;
  171. case 'B':
  172. DbaseColumns[i].DataType = typeof(byte[]);
  173. break;
  174. default:
  175. throw (new NotSupportedException("Invalid or unknown DBase field type '" + fieldtype +
  176. "' in column '" + DbaseColumns[i].ColumnName + "'"));
  177. }
  178. DbaseColumns[i].Address = br.ReadInt32();
  179. int Length = (int)br.ReadByte();
  180. if (Length < 0) Length = Length + 256;
  181. DbaseColumns[i].Length = Length;
  182. DbaseColumns[i].Decimals = (int)br.ReadByte();
  183. //If the double-type doesn't have any decimals, make the type an integer
  184. if (DbaseColumns[i].Decimals == 0 && DbaseColumns[i].DataType == typeof(double))
  185. if (DbaseColumns[i].Length <= 2)
  186. DbaseColumns[i].DataType = typeof(Int16);
  187. else if(DbaseColumns[i].Length<=4)
  188. DbaseColumns[i].DataType = typeof(Int32);
  189. else
  190. DbaseColumns[i].DataType = typeof(Int64);
  191. fs.Seek(fs.Position + 14, 0);
  192. }
  193. HeaderIsParsed = true;
  194. CreateBaseTable();
  195. }
  196. private System.Text.Encoding GetDbaseLanguageDriver(byte dbasecode)
  197. {
  198. switch (dbasecode)
  199. {
  200. case 0x01: return System.Text.Encoding.GetEncoding(437); //DOS USA code page 437 
  201. case 0x02: return System.Text.Encoding.GetEncoding(850); // DOS Multilingual code page 850 
  202. case 0x03: return System.Text.Encoding.GetEncoding(1252); // Windows ANSI code page 1252 
  203. case 0x04: return System.Text.Encoding.GetEncoding(10000); // Standard Macintosh 
  204. case 0x08: return System.Text.Encoding.GetEncoding(865); // Danish OEM
  205. case 0x09: return System.Text.Encoding.GetEncoding(437); // Dutch OEM
  206. case 0x0A: return System.Text.Encoding.GetEncoding(850); // Dutch OEM Secondary codepage
  207. case 0x0B: return System.Text.Encoding.GetEncoding(437); // Finnish OEM
  208. case 0x0D: return System.Text.Encoding.GetEncoding(437); // French OEM
  209. case 0x0E: return System.Text.Encoding.GetEncoding(850); // French OEM Secondary codepage
  210. case 0x0F: return System.Text.Encoding.GetEncoding(437); // German OEM
  211. case 0x10: return System.Text.Encoding.GetEncoding(850); // German OEM Secondary codepage
  212. case 0x11: return System.Text.Encoding.GetEncoding(437); // Italian OEM
  213. case 0x12: return System.Text.Encoding.GetEncoding(850); // Italian OEM Secondary codepage
  214. case 0x13: return System.Text.Encoding.GetEncoding(932); // Japanese Shift-JIS
  215. case 0x14: return System.Text.Encoding.GetEncoding(850); // Spanish OEM secondary codepage
  216. case 0x15: return System.Text.Encoding.GetEncoding(437); // Swedish OEM
  217. case 0x16: return System.Text.Encoding.GetEncoding(850); // Swedish OEM secondary codepage
  218. case 0x17: return System.Text.Encoding.GetEncoding(865); // Norwegian OEM
  219. case 0x18: return System.Text.Encoding.GetEncoding(437); // Spanish OEM
  220. case 0x19: return System.Text.Encoding.GetEncoding(437); // English OEM (Britain)
  221. case 0x1A: return System.Text.Encoding.GetEncoding(850); // English OEM (Britain) secondary codepage
  222. case 0x1B: return System.Text.Encoding.GetEncoding(437); // English OEM (U.S.)
  223. case 0x1C: return System.Text.Encoding.GetEncoding(863); // French OEM (Canada)
  224. case 0x1D: return System.Text.Encoding.GetEncoding(850); // French OEM secondary codepage
  225. case 0x1F: return System.Text.Encoding.GetEncoding(852); // Czech OEM
  226. case 0x22: return System.Text.Encoding.GetEncoding(852); // Hungarian OEM
  227. case 0x23: return System.Text.Encoding.GetEncoding(852); // Polish OEM
  228. case 0x24: return System.Text.Encoding.GetEncoding(860); // Portuguese OEM
  229. case 0x25: return System.Text.Encoding.GetEncoding(850); // Portuguese OEM secondary codepage
  230. case 0x26: return System.Text.Encoding.GetEncoding(866); // Russian OEM
  231. case 0x37: return System.Text.Encoding.GetEncoding(850); // English OEM (U.S.) secondary codepage
  232. case 0x40: return System.Text.Encoding.GetEncoding(852); // Romanian OEM
  233. case 0x4D: return System.Text.Encoding.GetEncoding(936); // Chinese GBK (PRC)
  234. case 0x4E: return System.Text.Encoding.GetEncoding(949); // Korean (ANSI/OEM)
  235. case 0x4F: return System.Text.Encoding.GetEncoding(950); // Chinese Big5 (Taiwan)
  236. case 0x50: return System.Text.Encoding.GetEncoding(874); // Thai (ANSI/OEM)
  237. case 0x57: return System.Text.Encoding.GetEncoding(1252); // ANSI
  238. case 0x58: return System.Text.Encoding.GetEncoding(1252); // Western European ANSI
  239. case 0x59: return System.Text.Encoding.GetEncoding(1252); // Spanish ANSI
  240. case 0x64: return System.Text.Encoding.GetEncoding(852); // Eastern European MS朌OS
  241. case 0x65: return System.Text.Encoding.GetEncoding(866); // Russian MS朌OS
  242. case 0x66: return System.Text.Encoding.GetEncoding(865); // Nordic MS朌OS
  243. case 0x67: return System.Text.Encoding.GetEncoding(861); // Icelandic MS朌OS
  244. case 0x68: return System.Text.Encoding.GetEncoding(895); // Kamenicky (Czech) MS-DOS 
  245. case 0x69: return System.Text.Encoding.GetEncoding(620); // Mazovia (Polish) MS-DOS 
  246. case 0x6A: return System.Text.Encoding.GetEncoding(737); // Greek MS朌OS (437G)
  247. case 0x6B: return System.Text.Encoding.GetEncoding(857); // Turkish MS朌OS
  248. case 0x6C: return System.Text.Encoding.GetEncoding(863); // French朇anadian MS朌OS
  249. case 0x78: return System.Text.Encoding.GetEncoding(950); // Taiwan Big 5
  250. case 0x79: return System.Text.Encoding.GetEncoding(949); // Hangul (Wansung)
  251. case 0x7A: return System.Text.Encoding.GetEncoding(936); // PRC GBK
  252. case 0x7B: return System.Text.Encoding.GetEncoding(932); // Japanese Shift-JIS
  253. case 0x7C: return System.Text.Encoding.GetEncoding(874); // Thai Windows/MS朌OS
  254. case 0x7D: return System.Text.Encoding.GetEncoding(1255); // Hebrew Windows 
  255. case 0x7E: return System.Text.Encoding.GetEncoding(1256); // Arabic Windows 
  256. case 0x86: return System.Text.Encoding.GetEncoding(737); // Greek OEM
  257. case 0x87: return System.Text.Encoding.GetEncoding(852); // Slovenian OEM
  258. case 0x88: return System.Text.Encoding.GetEncoding(857); // Turkish OEM
  259. case 0x96: return System.Text.Encoding.GetEncoding(10007); // Russian Macintosh 
  260. case 0x97: return System.Text.Encoding.GetEncoding(10029); // Eastern European Macintosh 
  261. case 0x98: return System.Text.Encoding.GetEncoding(10006); // Greek Macintosh 
  262. case 0xC8: return System.Text.Encoding.GetEncoding(1250); // Eastern European Windows
  263. case 0xC9: return System.Text.Encoding.GetEncoding(1251); // Russian Windows
  264. case 0xCA: return System.Text.Encoding.GetEncoding(1254); // Turkish Windows
  265. case 0xCB: return System.Text.Encoding.GetEncoding(1253); // Greek Windows
  266. case 0xCC: return System.Text.Encoding.GetEncoding(1257); // Baltic Windows
  267. default:
  268. return System.Text.Encoding.UTF7;
  269. }
  270. }
  271. /// <summary>
  272. /// Returns a DataTable that describes the column metadata of the DBase file.
  273. /// </summary>
  274. /// <returns>A DataTable that describes the column metadata.</returns>
  275. public DataTable GetSchemaTable()
  276. {
  277. DataTable tab = new DataTable();
  278. // all of common, non "base-table" fields implemented
  279. tab.Columns.Add("ColumnName", typeof(System.String));
  280. tab.Columns.Add("ColumnSize", typeof(Int32));
  281. tab.Columns.Add("ColumnOrdinal", typeof(Int32));
  282. tab.Columns.Add("NumericPrecision", typeof(Int16));
  283. tab.Columns.Add("NumericScale", typeof(Int16));
  284. tab.Columns.Add("DataType", typeof(System.Type));
  285. tab.Columns.Add("AllowDBNull", typeof(bool));
  286. tab.Columns.Add("IsReadOnly", typeof(bool));
  287. tab.Columns.Add("IsUnique", typeof(bool));
  288. tab.Columns.Add("IsRowVersion", typeof(bool));
  289. tab.Columns.Add("IsKey", typeof(bool));
  290. tab.Columns.Add("IsAutoIncrement", typeof(bool));
  291. tab.Columns.Add("IsLong", typeof(bool));
  292. foreach (DbaseField dbf in DbaseColumns)
  293. tab.Columns.Add(dbf.ColumnName, dbf.DataType);
  294. for (int i = 0; i < DbaseColumns.Length; i++)
  295. {
  296. DataRow r = tab.NewRow();
  297. r["ColumnName"] = DbaseColumns[i].ColumnName;
  298. r["ColumnSize"] = DbaseColumns[i].Length;
  299. r["ColumnOrdinal"] = i;
  300. r["NumericPrecision"] = DbaseColumns[i].Decimals;
  301. r["NumericScale"] = 0;
  302. r["DataType"] = DbaseColumns[i].DataType;
  303. r["AllowDBNull"] = true;
  304. r["IsReadOnly"] = true;
  305. r["IsUnique"] = false;
  306. r["IsRowVersion"] = false;
  307. r["IsKey"] = false;
  308. r["IsAutoIncrement"] = false;
  309. r["IsLong"] = false;
  310. // specializations, if ID is unique
  311. //if (_ColumnNames[i] == "ID")
  312. // r["IsUnique"] = true;
  313. tab.Rows.Add(r);
  314. }
  315. return tab;
  316. }
  317. private SharpMap.Data.FeatureDataTable baseTable;
  318. private void CreateBaseTable()
  319. {
  320. baseTable = new SharpMap.Data.FeatureDataTable();
  321. foreach (DbaseField dbf in DbaseColumns)
  322. baseTable.Columns.Add(dbf.ColumnName, dbf.DataType);
  323. }
  324. internal SharpMap.Data.FeatureDataTable NewTable
  325. {
  326. get { return baseTable.Clone(); }
  327. }
  328. internal object GetValue(uint oid, int colid)
  329. {
  330. if (!_isOpen)
  331. throw (new ApplicationException("An attempt was made to read from a closed DBF file"));
  332. if (oid >= _NumberOfRecords)
  333. throw (new ArgumentException("Invalid DataRow requested at index " + oid.ToString()));
  334. if (colid >= DbaseColumns.Length || colid < 0)
  335. throw ((new ArgumentException("Column index out of range")));
  336. fs.Seek(_HeaderLength + oid * _RecordLength, 0);
  337. for (int i = 0; i < colid; i++)
  338. br.BaseStream.Seek(DbaseColumns[i].Length,SeekOrigin.Current);
  339. return ReadDbfValue(DbaseColumns[colid]);
  340. }
  341. private System.Text.Encoding _Encoding;
  342. private System.Text.Encoding _FileEncoding;
  343. /// <summary>
  344. /// Gets or sets the <see cref="System.Text.Encoding"/> used for parsing strings from the DBase DBF file.
  345. /// </summary>
  346. /// <remarks>
  347. /// If the encoding type isn't set, the dbase driver will try to determine the correct <see cref="System.Text.Encoding"/>.
  348. /// </remarks>
  349. public System.Text.Encoding Encoding
  350. {
  351. get { return _Encoding; }
  352. set { _Encoding = value; }
  353. }
  354. /// <summary>
  355. /// Gets the feature at the specified Object ID
  356. /// </summary>
  357. /// <param name="oid"></param>
  358. /// <param name="table"></param>
  359. /// <returns></returns>
  360. internal SharpMap.Data.FeatureDataRow GetFeature(uint oid, SharpMap.Data.FeatureDataTable table)
  361. {
  362. if (!_isOpen)
  363. throw (new ApplicationException("An attempt was made to read from a closed DBF file"));
  364. if (oid >= _NumberOfRecords)
  365. throw (new ArgumentException("Invalid DataRow requested at index " + oid.ToString()));
  366. fs.Seek(_HeaderLength + oid * _RecordLength,0);
  367. SharpMap.Data.FeatureDataRow dr = table.NewRow();
  368. if (br.ReadChar() == '*') //is record marked deleted?
  369. return null;
  370. for (int i = 0; i < DbaseColumns.Length;i++ )
  371. {
  372. DbaseField dbf = DbaseColumns[i];
  373. dr[dbf.ColumnName] = ReadDbfValue(dbf);
  374. }
  375. return dr;
  376. }
  377. private object ReadDbfValue(DbaseField dbf)
  378. {
  379. switch (dbf.DataType.ToString())
  380. {
  381. case "System.String":
  382. if(_Encoding==null)
  383. return _FileEncoding.GetString(br.ReadBytes(dbf.Length)).Replace("", "").Trim();
  384. else
  385. return _Encoding.GetString(br.ReadBytes(dbf.Length)).Replace("", "").Trim();
  386. case "System.Double":
  387. string temp = System.Text.Encoding.UTF7.GetString(br.ReadBytes(dbf.Length)).Replace("", "").Trim();
  388. double dbl = 0;
  389. if(double.TryParse(temp, System.Globalization.NumberStyles.Float, SharpMap.Map.numberFormat_EnUS, out dbl))
  390. return dbl;
  391. else
  392. return DBNull.Value;
  393. case "System.Int16":
  394. string temp16 = System.Text.Encoding.UTF7.GetString((br.ReadBytes(dbf.Length))).Replace("", "").Trim();
  395. Int16 i16 = 0;
  396. if (Int16.TryParse(temp16, System.Globalization.NumberStyles.Float, SharpMap.Map.numberFormat_EnUS, out i16))
  397. return i16;
  398. else
  399. return DBNull.Value;
  400. case "System.Int32":
  401. string temp32 = System.Text.Encoding.UTF7.GetString((br.ReadBytes(dbf.Length))).Replace("", "").Trim();
  402. Int32 i32 = 0;
  403. if (Int32.TryParse(temp32, System.Globalization.NumberStyles.Float, SharpMap.Map.numberFormat_EnUS, out i32))
  404. return i32;
  405. else
  406. return DBNull.Value;
  407. case "System.Int64":
  408. string temp64 = System.Text.Encoding.UTF7.GetString((br.ReadBytes(dbf.Length))).Replace("", "").Trim();
  409. Int64 i64 = 0;
  410. if (Int64.TryParse(temp64, System.Globalization.NumberStyles.Float, SharpMap.Map.numberFormat_EnUS, out i64))
  411. return i64;
  412. else
  413. return DBNull.Value;
  414. case "System.Single":
  415. string temp4 = System.Text.Encoding.UTF8.GetString((br.ReadBytes(dbf.Length)));
  416. float f = 0;
  417. if (float.TryParse(temp4, System.Globalization.NumberStyles.Float, SharpMap.Map.numberFormat_EnUS, out f))
  418. return f;
  419. else
  420. return DBNull.Value;
  421. case "System.Boolean":
  422. char tempChar = br.ReadChar();
  423. return ((tempChar == 'T') || (tempChar == 't') || (tempChar == 'Y') || (tempChar == 'y'));
  424. case "System.DateTime":
  425. DateTime date;
  426. // Mono has not yet implemented DateTime.TryParseExact
  427. #if !MONO
  428. if (DateTime.TryParseExact(System.Text.Encoding.UTF7.GetString((br.ReadBytes(8))),
  429. "yyyyMMdd", SharpMap.Map.numberFormat_EnUS, System.Globalization.DateTimeStyles.None, out date))
  430. return date;
  431. else
  432. return DBNull.Value;
  433. #else
  434. try 
  435. {
  436. return date = DateTime.ParseExact ( System.Text.Encoding.UTF7.GetString((br.ReadBytes(8))), 
  437. "yyyyMMdd", SharpMap.Map.numberFormat_EnUS, System.Globalization.DateTimeStyles.None );
  438. }
  439. catch ( Exception e )
  440. {
  441. return DBNull.Value;
  442. }
  443. #endif
  444. default:
  445. throw (new NotSupportedException("Cannot parse DBase field '" + dbf.ColumnName + "' of type '" + dbf.DataType.ToString() + "'"));
  446. }
  447. }
  448. }
  449. }