PageRenderTime 51ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/Web2.0/_code/SplendidImport.cs

#
C# | 873 lines | 764 code | 23 blank | 86 comment | 162 complexity | 791905bdd10807e8ce03b7bbf95f59a2 MD5 | raw file
Possible License(s): LGPL-2.1
  1. /**********************************************************************************************************************
  2. * The contents of this file are subject to the SugarCRM Public License Version 1.1.3 ("License"); You may not use this
  3. * file except in compliance with the License. You may obtain a copy of the License at http://www.sugarcrm.com/SPL
  4. * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either
  5. * express or implied. See the License for the specific language governing rights and limitations under the License.
  6. *
  7. * All copies of the Covered Code must include on each user interface screen:
  8. * (i) the "Powered by SugarCRM" logo and
  9. * (ii) the SugarCRM copyright notice
  10. * (iii) the SplendidCRM copyright notice
  11. * in the same form as they appear in the distribution. See full license for requirements.
  12. *
  13. * The Original Code is: SplendidCRM Open Source
  14. * The Initial Developer of the Original Code is SplendidCRM Software, Inc.
  15. * Portions created by SplendidCRM Software are Copyright (C) 2005 SplendidCRM Software, Inc. All Rights Reserved.
  16. * Contributor(s): ______________________________________.
  17. *********************************************************************************************************************/
  18. using System;
  19. using System.IO;
  20. using System.Data;
  21. using System.Data.Common;
  22. using System.Text;
  23. using System.Xml;
  24. using System.Web;
  25. using System.Collections;
  26. using System.Diagnostics;
  27. //using Microsoft.VisualBasic;
  28. namespace SplendidCRM
  29. {
  30. /// <summary>
  31. /// Summary description for SplendidImport.
  32. /// </summary>
  33. public class SplendidImport
  34. {
  35. private static void LogError(ref StringBuilder sbErrors, string sCommand, string sMessage)
  36. {
  37. sbErrors.Append("<hr width=\"100%\" height=\"2px\" /><table width=\"100%\"><tr><td width=\"50%\">" + sCommand + "</td><td><font color=red>" + sMessage + "</font></td></tr></table>" + ControlChars.CrLf);
  38. }
  39. public static void Import(XmlDocument xml, ArrayList arrTables, bool bTruncate)
  40. {
  41. HttpResponse Response = HttpContext.Current.Response;
  42. // 12/16/2005 Paul. First create a hash table to convert tab name to a uppercase table name.
  43. Hashtable hashTables = new Hashtable();
  44. XmlNodeList nlTables = xml.DocumentElement.ChildNodes;
  45. foreach(XmlNode node in nlTables)
  46. {
  47. if ( !hashTables.ContainsKey(node.Name.ToUpper()) )
  48. hashTables.Add(node.Name.ToUpper(), node.Name);
  49. }
  50. ArrayList lstReservedTables = new ArrayList();
  51. lstReservedTables.Add("CONFIG" );
  52. lstReservedTables.Add("DETAILVIEWS" );
  53. lstReservedTables.Add("DETAILVIEWS_FIELDS" );
  54. lstReservedTables.Add("DETAILVIEWS_RELATIONSHIPS");
  55. lstReservedTables.Add("EDITVIEWS" );
  56. lstReservedTables.Add("EDITVIEWS_FIELDS" );
  57. lstReservedTables.Add("GRIDVIEWS" );
  58. lstReservedTables.Add("GRIDVIEWS_COLUMNS" );
  59. lstReservedTables.Add("LANGUAGES" );
  60. lstReservedTables.Add("MODULES" );
  61. lstReservedTables.Add("SHORTCUTS" );
  62. lstReservedTables.Add("TERMINOLOGY" );
  63. lstReservedTables.Add("TIMEZONES" );
  64. // 09/29/2006 Paul. The following are SugarCRM config tables.
  65. lstReservedTables.Add("ACL_ACTIONS" );
  66. lstReservedTables.Add("CURRENCIES" );
  67. lstReservedTables.Add("RELATIONSHIPS" );
  68. // 10/01/2006 Paul. Can't reserve Users because too many other tables depend on it.
  69. //lstReservedTables.Add("USERS" );
  70. StringBuilder sbErrors = new StringBuilder();
  71. DbProviderFactory dbf = DbProviderFactories.GetFactory();
  72. if ( arrTables == null )
  73. {
  74. arrTables = new ArrayList();
  75. using ( IDbConnection con = dbf.CreateConnection() )
  76. {
  77. con.Open();
  78. using ( IDbCommand cmd = con.CreateCommand() )
  79. {
  80. cmd.CommandText = "select * from vwSqlTableDependencies order by 2, 1";
  81. using ( DbDataAdapter da = dbf.CreateDataAdapter() )
  82. {
  83. ((IDbDataAdapter)da).SelectCommand = cmd;
  84. using ( DataTable dt = new DataTable() )
  85. {
  86. da.Fill(dt);
  87. for ( int i = 0 ; i < dt.Rows.Count ; i++ )
  88. {
  89. DataRow row = dt.Rows[i];
  90. arrTables.Add(row["name"].ToString());
  91. }
  92. }
  93. }
  94. // 10/02/2006 Paul. We need to delete tables that reference the tables we are importing.
  95. Hashtable hashReferenced = new Hashtable();
  96. StringBuilder sbReferenced = new StringBuilder();
  97. foreach ( string sKey in hashTables.Keys )
  98. {
  99. if ( sbReferenced.Length > 0 )
  100. sbReferenced.Append(", ");
  101. sbReferenced.Append("'" + sKey + "'");
  102. }
  103. if ( sbReferenced.Length > 0 )
  104. {
  105. cmd.CommandText = "select distinct TABLE_NAME from vwSqlForeignKeys where REFERENCED_TABLE_NAME in (" + sbReferenced.ToString() + ")";
  106. using ( DbDataAdapter da = dbf.CreateDataAdapter() )
  107. {
  108. ((IDbDataAdapter)da).SelectCommand = cmd;
  109. using ( DataTable dt = new DataTable() )
  110. {
  111. da.Fill(dt);
  112. for ( int i = 0 ; i < dt.Rows.Count ; i++ )
  113. {
  114. DataRow row = dt.Rows[i];
  115. hashReferenced.Add(row["TABLE_NAME"].ToString(), null);
  116. }
  117. }
  118. }
  119. }
  120. if ( bTruncate )
  121. {
  122. cmd.CommandText = "select * from vwSqlTableDependencies order by 2 desc, 1 desc";
  123. using ( DbDataAdapter da = dbf.CreateDataAdapter() )
  124. {
  125. ((IDbDataAdapter)da).SelectCommand = cmd;
  126. using ( DataTable dt = new DataTable() )
  127. {
  128. da.Fill(dt);
  129. for ( int i = 0 ; i < dt.Rows.Count && Response.IsClientConnected ; i++ )
  130. {
  131. DataRow row = dt.Rows[i];
  132. string sTABLE_NAME = row["name"].ToString().ToUpper();
  133. // 12/18/2005 Paul. Some tables are reserved and should not be truncated or imported.
  134. if ( lstReservedTables.Contains(sTABLE_NAME) )
  135. continue;
  136. // 12/18/2005 Paul. Only truncate tables that are being imported or a table it references.
  137. // 10/02/2006 Paul. We need to truncated referenced tables because they may reference a table being imported.
  138. if ( hashTables.ContainsKey(sTABLE_NAME) || hashReferenced.ContainsKey(sTABLE_NAME) )
  139. {
  140. try
  141. {
  142. if ( sTABLE_NAME == "USERS" )
  143. {
  144. // 12/17/2005 Paul. Don't delete the existing user, otherwise it will cause a login problem in the future.
  145. cmd.CommandText = "delete from USERS where ID != @ID";
  146. Sql.AddParameter(cmd, "@ID", Security.USER_ID);
  147. }
  148. else
  149. {
  150. cmd.CommandText = "delete from " + sTABLE_NAME;
  151. }
  152. SplendidError.SystemWarning(new StackTrace(true).GetFrame(0), "Delete Database Table: " + sTABLE_NAME);
  153. cmd.ExecuteNonQuery();
  154. Response.Write(" "); // Write a singe byte to keep the connection open.
  155. #if DEBUG
  156. LogError(ref sbErrors, Sql.ExpandParameters(cmd), "");
  157. #endif
  158. }
  159. catch(Exception ex)
  160. {
  161. LogError(ref sbErrors, Sql.ExpandParameters(cmd), ex.Message);
  162. }
  163. }
  164. }
  165. }
  166. }
  167. }
  168. }
  169. }
  170. }
  171. for ( int i = 0 ; i < arrTables.Count && Response.IsClientConnected ; i++ )
  172. {
  173. string sTABLE_NAME = arrTables[i].ToString().ToUpper();
  174. // 12/18/2005 Paul. Some tables are reserved and should not be truncated or imported.
  175. if ( lstReservedTables.Contains(sTABLE_NAME) )
  176. continue;
  177. if ( hashTables.ContainsKey(sTABLE_NAME) )
  178. {
  179. string sXML_TABLE_NAME = hashTables[sTABLE_NAME].ToString();
  180. XmlNodeList nlRows = xml.DocumentElement.SelectNodes(sXML_TABLE_NAME);
  181. if ( nlRows.Count > 0 )
  182. {
  183. LogError(ref sbErrors, sTABLE_NAME, "Importing " + nlRows.Count.ToString() + " records.");
  184. SplendidError.SystemWarning(new StackTrace(true).GetFrame(0), "Import Database Table: " + sTABLE_NAME);
  185. // 12/17/2005 Paul. Use a new connection for each table import so that connection state will be reset.
  186. // My main concern is that the identity_insert gets reset.
  187. using ( IDbConnection con = dbf.CreateConnection() )
  188. {
  189. con.Open();
  190. try
  191. {
  192. if ( Sql.IsSQLServer(con) )
  193. {
  194. // 12/17/2005 Paul. In SQL Server, turn on identity_insert.
  195. string sIDENTITY_NAME = String.Empty;
  196. // 09/30/2006 Paul. Switch needs to use sTABLE_NAME and not sIDENTITY_NAME.
  197. switch ( sTABLE_NAME )
  198. {
  199. case "BUGS" : sIDENTITY_NAME = "BUGS" ; break;
  200. case "CASES" : sIDENTITY_NAME = "CASES" ; break;
  201. case "CAMPAIGNS" : sIDENTITY_NAME = "CAMPAIGNS" ; break;
  202. case "PROSPECTS" : sIDENTITY_NAME = "PROSPECTS" ; break;
  203. case "QUOTES" : sIDENTITY_NAME = "QUOTES" ; break;
  204. case "EMAILMAN" : sIDENTITY_NAME = "EMAILMAN" ; break;
  205. case "CAMPAIGN_TRKRS": sIDENTITY_NAME = "CAMPAIGN_TRKRS"; break;
  206. }
  207. if ( !Sql.IsEmptyString(sIDENTITY_NAME) )
  208. {
  209. IDbCommand cmdIdentity = con.CreateCommand();
  210. cmdIdentity.CommandText = "set identity_insert " + sIDENTITY_NAME + " on";
  211. cmdIdentity.ExecuteNonQuery();
  212. }
  213. }
  214. else if ( Sql.IsOracle(con) )
  215. {
  216. // 12/17/2005 Paul. In Oracle, disable sequence triggers.
  217. string sTRIGGER_NAME = String.Empty;
  218. switch ( sTABLE_NAME )
  219. {
  220. case "BUGS" : sTRIGGER_NAME = "TR_S_BUGS_BUG_NUMBER" ; break;
  221. case "CASES" : sTRIGGER_NAME = "TR_S_CASES_CASE_NUMBER" ; break;
  222. case "CAMPAIGNS" : sTRIGGER_NAME = "TR_S_CAMPAIGNS_TRACKER_KEY" ; break;
  223. case "PROSPECTS" : sTRIGGER_NAME = "TR_S_PROSPECTS_TRACKER_KEY" ; break;
  224. case "QUOTES" : sTRIGGER_NAME = "TR_S_QUOTES_TRACKER_KEY" ; break;
  225. case "EMAILMAN" : sTRIGGER_NAME = "TR_S_EMAILMAN_TRACKER_KEY" ; break;
  226. case "CAMPAIGN_TRKRS": sTRIGGER_NAME = "TR_S_CAMPAIGN_TRKRS_TRACKER"; break;
  227. }
  228. if ( !Sql.IsEmptyString(sTRIGGER_NAME) )
  229. {
  230. IDbCommand cmdTrigger = con.CreateCommand();
  231. cmdTrigger.CommandText = "alter trigger " + sTRIGGER_NAME + " disable";
  232. cmdTrigger.ExecuteNonQuery();
  233. }
  234. }
  235. int nTableErrors = 0;
  236. using ( DataTable dtColumns = new DataTable() )
  237. {
  238. string sSQL;
  239. sSQL = "select * " + ControlChars.CrLf
  240. + " from vwSqlColumns " + ControlChars.CrLf
  241. + " where ObjectName = @ObjectName" + ControlChars.CrLf
  242. + " and ObjectType = 'U' " + ControlChars.CrLf
  243. + " order by colid " + ControlChars.CrLf;
  244. using ( IDbCommand cmd = con.CreateCommand() )
  245. {
  246. cmd.CommandText = sSQL;
  247. Sql.AddParameter(cmd, "@ObjectName", sTABLE_NAME);
  248. using ( DbDataAdapter da = dbf.CreateDataAdapter() )
  249. {
  250. ((IDbDataAdapter)da).SelectCommand = cmd;
  251. da.Fill(dtColumns);
  252. }
  253. }
  254. DataView vwColumns = new DataView(dtColumns);
  255. foreach(XmlNode node in nlRows)
  256. {
  257. if ( !Response.IsClientConnected )
  258. {
  259. break;
  260. }
  261. // 09/28/2006 Paul. Always start with a blank command and only insert fields that are used.
  262. using ( IDbCommand cmdImport = con.CreateCommand() )
  263. {
  264. cmdImport.CommandText = "";
  265. cmdImport.CommandType = CommandType.Text;
  266. // 09/30/2006 Paul. Count row errors so that we can skip bad rows, but continue with the rest of the import.
  267. int nRowErrors = 0;
  268. // 09/28/2006 Paul. Build the insert statement using only the fields provided in the data.
  269. // This is so that we can allow default table values to do their job.
  270. StringBuilder sbFields = new StringBuilder();
  271. for ( int j = 0; j < node.ChildNodes.Count; j++ )
  272. {
  273. string sName = node.ChildNodes[j].Name.ToUpper();
  274. vwColumns.RowFilter = "ColumnName = '" + sName + "'";
  275. if ( vwColumns.Count == 1 )
  276. {
  277. string sCsType = Sql.ToString (vwColumns[0]["CsType"]);
  278. int nLength = Sql.ToInteger(vwColumns[0]["length"]);
  279. // 09/28/2006 Paul. If the field is specified twice, then the second instance will prevail.
  280. if ( Sql.FindParameter(cmdImport, "@" + sName) == null )
  281. {
  282. if ( sbFields.Length > 0 )
  283. sbFields.Append(", ");
  284. sbFields.Append(sName);
  285. // 09/28/2006 Paul. We cannot use a StringBuilder for the values because we need to allow
  286. // the Sql.CreateParameter() function to correct the parameter token, and it does this directly to CommandText.
  287. if ( cmdImport.CommandText.Length > 0 )
  288. cmdImport.CommandText += ", ";
  289. cmdImport.CommandText += Sql.CreateDbName(cmdImport, "@" + sName);
  290. IDbDataParameter par = Sql.CreateParameter(cmdImport, "@" + sName, sCsType, nLength);
  291. }
  292. // 12/18/2005 Paul. A short-sighted programmer at SugarCRM created GUIDs with invalid characters.
  293. // We need to convert them to valid GUIDs.
  294. string sText = node.ChildNodes[j].InnerText;
  295. // 08/20/2006 Paul. Dynamically attempt to fix invalid GUIDs. It really only works for the ones defined below.
  296. // 09/30/2006 Paul. CREATED_BY counts as an ID.
  297. if ( sName == "ID" || sName.EndsWith("_ID") || sName == "CREATED_BY" )
  298. {
  299. // 09/30/2006 Paul. IDs must be in upper case. This is primarily for platforms that are case-significant.
  300. // 10/05/2006 Paul. We need to use upper case for SQL Server as well so that the SugarCRM user names are correctly replaced.
  301. sText = sText.ToUpper();
  302. if ( sText.Length < 36 && sText.Length > 0 )
  303. {
  304. sText = "00000000-0000-0000-0000-000000000000".Substring(0, 36 - sText.Length) + sText;
  305. switch ( sText )
  306. {
  307. case "00000000-0000-0000-0000-000000JIM_ID": sText = "00000000-0000-0000-0001-000000000000"; break;
  308. case "00000000-0000-0000-0000-000000MAX_ID": sText = "00000000-0000-0000-0002-000000000000"; break;
  309. case "00000000-0000-0000-0000-00000WILL_ID": sText = "00000000-0000-0000-0003-000000000000"; break;
  310. case "00000000-0000-0000-0000-0000CHRIS_ID": sText = "00000000-0000-0000-0004-000000000000"; break;
  311. case "00000000-0000-0000-0000-0000SALLY_ID": sText = "00000000-0000-0000-0005-000000000000"; break;
  312. case "00000000-0000-0000-0000-0000SARAH_ID": sText = "00000000-0000-0000-0006-000000000000"; break;
  313. }
  314. }
  315. }
  316. try
  317. {
  318. Sql.SetParameter(cmdImport, sName, sText);
  319. }
  320. catch(Exception ex)
  321. {
  322. LogError(ref sbErrors, Sql.ExpandParameters(cmdImport), ex.Message + ControlChars.CrLf + sName + "=" + sText);
  323. nRowErrors++;
  324. nTableErrors++ ;
  325. }
  326. }
  327. }
  328. // 10/01/2006 Paul. Some tables in SugarCRM do not have an ID. If SplendidCRM has an ID, then it is likely required.
  329. // 10/01/2006 Paul. We only need to specify the ID if the platform is not SQL Server.
  330. vwColumns.RowFilter = "ColumnName = 'ID'";
  331. if ( vwColumns.Count == 1 && !Sql.IsSQLServer(con) )
  332. {
  333. string sName = "ID";
  334. if ( Sql.FindParameter(cmdImport, "@ID") == null )
  335. {
  336. if ( sbFields.Length > 0 )
  337. sbFields.Append(", ");
  338. sbFields.Append(sName);
  339. if ( cmdImport.CommandText.Length > 0 )
  340. cmdImport.CommandText += ", ";
  341. cmdImport.CommandText += Sql.CreateDbName(cmdImport, "@" + sName);
  342. string sCsType = Sql.ToString (vwColumns[0]["CsType"]);
  343. int nLength = Sql.ToInteger(vwColumns[0]["length"]);
  344. IDbDataParameter parID = Sql.CreateParameter(cmdImport, "@" + sName, sCsType, nLength);
  345. parID.Value = Guid.NewGuid();
  346. }
  347. }
  348. if ( nRowErrors == 0 )
  349. {
  350. try
  351. {
  352. // 09/28/2006 Paul. We cannot use a StringBuilder for the values because we need to allow
  353. // the Sql.CreateParameter() function to correct the parameter token, and it does this directly to CommandText.
  354. cmdImport.CommandText = "insert into " + sTABLE_NAME + "(" + sbFields.ToString() + ")" + ControlChars.CrLf
  355. + "values(" + cmdImport.CommandText + ")" + ControlChars.CrLf;
  356. if ( cmdImport.Parameters.Count > 0 )
  357. cmdImport.ExecuteNonQuery();
  358. Response.Write(" ");
  359. }
  360. catch(Exception ex)
  361. {
  362. LogError(ref sbErrors, Sql.ExpandParameters(cmdImport), ex.Message);
  363. // 12/17/2005 Paul. If there is an error, stop importing from this table.
  364. // 12/18/2005 Paul. I'd like to see the first 100 errors.
  365. nTableErrors++ ;
  366. if ( nTableErrors > 100 )
  367. break;
  368. }
  369. }
  370. }
  371. }
  372. }
  373. }
  374. catch(Exception ex)
  375. {
  376. LogError(ref sbErrors, sTABLE_NAME, ex.Message);
  377. }
  378. finally
  379. {
  380. try
  381. {
  382. if ( Sql.IsSQLServer(con) )
  383. {
  384. // 12/17/2005 Paul. In SQL Server, turn off identity_insert.
  385. string sIDENTITY_NAME = String.Empty;
  386. // 09/30/2006 Paul. Switch needs to use sTABLE_NAME and not sIDENTITY_NAME.
  387. switch ( sTABLE_NAME )
  388. {
  389. case "BUGS" : sIDENTITY_NAME = "BUGS" ; break;
  390. case "CASES" : sIDENTITY_NAME = "CASES" ; break;
  391. case "CAMPAIGNS" : sIDENTITY_NAME = "CAMPAIGNS" ; break;
  392. case "PROSPECTS" : sIDENTITY_NAME = "PROSPECTS" ; break;
  393. case "QUOTES" : sIDENTITY_NAME = "QUOTES" ; break;
  394. case "EMAILMAN" : sIDENTITY_NAME = "EMAILMAN" ; break;
  395. case "CAMPAIGN_TRKRS": sIDENTITY_NAME = "CAMPAIGN_TRKRS"; break;
  396. }
  397. if ( !Sql.IsEmptyString(sIDENTITY_NAME) )
  398. {
  399. IDbCommand cmdIdentity = con.CreateCommand();
  400. cmdIdentity.CommandText = "set identity_insert " + sIDENTITY_NAME + " off";
  401. cmdIdentity.ExecuteNonQuery();
  402. }
  403. }
  404. else if ( Sql.IsOracle(con) )
  405. {
  406. // 12/17/2005 Paul. In Oracle, enable sequence triggers.
  407. string sTRIGGER_NAME = String.Empty;
  408. switch ( sTABLE_NAME )
  409. {
  410. case "BUGS" : sTRIGGER_NAME = "TR_S_BUGS_BUG_NUMBER" ; break;
  411. case "CASES" : sTRIGGER_NAME = "TR_S_CASES_CASE_NUMBER" ; break;
  412. case "CAMPAIGNS" : sTRIGGER_NAME = "TR_S_CAMPAIGNS_TRACKER_KEY" ; break;
  413. case "PROSPECTS" : sTRIGGER_NAME = "TR_S_PROSPECTS_TRACKER_KEY" ; break;
  414. case "QUOTES" : sTRIGGER_NAME = "TR_S_QUOTES_TRACKER_KEY" ; break;
  415. case "EMAILMAN" : sTRIGGER_NAME = "TR_S_EMAILMAN_TRACKER_KEY" ; break;
  416. case "CAMPAIGN_TRKRS": sTRIGGER_NAME = "TR_S_CAMPAIGN_TRKRS_TRACKER"; break;
  417. }
  418. if ( !Sql.IsEmptyString(sTRIGGER_NAME) )
  419. {
  420. IDbCommand cmdTrigger = con.CreateCommand();
  421. cmdTrigger.CommandText = "alter trigger " + sTRIGGER_NAME + " enable";
  422. cmdTrigger.ExecuteNonQuery();
  423. }
  424. }
  425. }
  426. catch(Exception ex)
  427. {
  428. LogError(ref sbErrors, sTABLE_NAME, ex.Message);
  429. }
  430. }
  431. }
  432. Response.Write(" "); // Write a singe byte to keep the connection open.
  433. }
  434. }
  435. }
  436. // 12/18/2005 Paul. Reserved tables will still be imported, but we use the associated spXXX_Update procedure.
  437. for ( int i = 0 ; i < arrTables.Count && Response.IsClientConnected ; i++ )
  438. {
  439. string sTABLE_NAME = arrTables[i].ToString().ToUpper();
  440. if ( hashTables.ContainsKey(sTABLE_NAME) && lstReservedTables.Contains(sTABLE_NAME) )
  441. {
  442. string sXML_TABLE_NAME = hashTables[sTABLE_NAME].ToString();
  443. XmlNodeList nlRows = xml.DocumentElement.SelectNodes(sXML_TABLE_NAME);
  444. if ( nlRows.Count > 0 )
  445. {
  446. SplendidError.SystemWarning(new StackTrace(true).GetFrame(0), "Import Database Table: " + sTABLE_NAME);
  447. // 12/17/2005 Paul. Use a new connection for each table import so that connection state will be reset.
  448. // My main concern is that the identity_insert gets reset.
  449. using ( IDbConnection con = dbf.CreateConnection() )
  450. {
  451. con.Open();
  452. try
  453. {
  454. int nTableErrors = 0;
  455. IDbCommand cmdImport = SqlProcs.Factory(con, "sp" + sTABLE_NAME + "_Update");
  456. foreach(XmlNode node in nlRows)
  457. {
  458. if ( !Response.IsClientConnected )
  459. {
  460. break;
  461. }
  462. // 10/10/2006 Paul. Use IDbDataParameter to be consistent.
  463. foreach(IDbDataParameter par in cmdImport.Parameters)
  464. {
  465. par.Value = DBNull.Value;
  466. }
  467. // 09/30/2006 Paul. Count row errors so that we can skip bad rows, but continue with the rest of the import.
  468. int nRowErrors = 0;
  469. for ( int j = 0; j < node.ChildNodes.Count; j++ )
  470. {
  471. string sName = node.ChildNodes[j].Name.ToUpper();
  472. string sText = node.ChildNodes[j].InnerText;
  473. // 08/20/2006 Paul. Dynamically attempt to fix invalid GUIDs. It really only works for the ones defined below.
  474. // 09/30/2006 Paul. CREATED_BY counts as an ID.
  475. if ( (sName == "ID" || sName.EndsWith("_ID") || sName == "CREATED_BY") )
  476. {
  477. // 10/05/2006 Paul. IDs must be in upper case. This is primarily for platforms that are case-significant.
  478. sText = sText.ToUpper();
  479. if ( sText.Length < 36 && sText.Length > 0 )
  480. {
  481. sText = "00000000-0000-0000-0000-000000000000".Substring(0, 36 - sText.Length) + sText;
  482. switch ( sText )
  483. {
  484. case "00000000-0000-0000-0000-000000JIM_ID": sText = "00000000-0000-0000-0001-000000000000"; break;
  485. case "00000000-0000-0000-0000-000000MAX_ID": sText = "00000000-0000-0000-0002-000000000000"; break;
  486. case "00000000-0000-0000-0000-00000WILL_ID": sText = "00000000-0000-0000-0003-000000000000"; break;
  487. case "00000000-0000-0000-0000-0000CHRIS_ID": sText = "00000000-0000-0000-0004-000000000000"; break;
  488. case "00000000-0000-0000-0000-0000SALLY_ID": sText = "00000000-0000-0000-0005-000000000000"; break;
  489. case "00000000-0000-0000-0000-0000SARAH_ID": sText = "00000000-0000-0000-0006-000000000000"; break;
  490. }
  491. }
  492. }
  493. try
  494. {
  495. Sql.SetParameter(cmdImport, node.ChildNodes[j].Name, sText);
  496. }
  497. catch(Exception ex)
  498. {
  499. LogError(ref sbErrors, Sql.ExpandParameters(cmdImport), ex.Message + ControlChars.CrLf + sName + "=" + sText);
  500. nRowErrors++;
  501. nTableErrors++ ;
  502. }
  503. }
  504. if ( nRowErrors == 0 )
  505. {
  506. try
  507. {
  508. cmdImport.ExecuteNonQuery();
  509. Response.Write(" ");
  510. }
  511. catch(Exception ex)
  512. {
  513. LogError(ref sbErrors, Sql.ExpandParameters(cmdImport), ex.Message);
  514. // 12/17/2005 Paul. If there is an error, stop importing from this table.
  515. // 12/18/2005 Paul. I'd like to see the first 100 errors.
  516. nTableErrors++ ;
  517. if ( nTableErrors > 100 )
  518. break;
  519. }
  520. }
  521. }
  522. }
  523. catch(Exception ex)
  524. {
  525. LogError(ref sbErrors, sTABLE_NAME, ex.Message);
  526. }
  527. }
  528. Response.Write(" "); // Write a singe byte to keep the connection open.
  529. }
  530. }
  531. }
  532. if ( sbErrors.Length > 0 )
  533. {
  534. throw(new Exception(sbErrors.ToString()));
  535. }
  536. }
  537. public static XmlDocument ConvertTableToXml(DataTable dt, string sRecordName)
  538. {
  539. XmlDocument xml = new XmlDocument();
  540. xml.AppendChild(xml.CreateProcessingInstruction("xml" , "version=\"1.0\" encoding=\"UTF-8\""));
  541. xml.AppendChild(xml.CreateElement("xml"));
  542. foreach ( DataRow row in dt.Rows )
  543. {
  544. XmlNode xRecord = xml.CreateElement(sRecordName);
  545. xml.DocumentElement.AppendChild(xRecord);
  546. for ( int nField = 0; nField < dt.Columns.Count; nField++ )
  547. {
  548. XmlNode xField = xml.CreateElement("ImportField" + nField.ToString("000"));
  549. xRecord.AppendChild(xField);
  550. if ( row[nField] != DBNull.Value )
  551. {
  552. xField.InnerText = row[nField].ToString();
  553. }
  554. }
  555. }
  556. return xml;
  557. }
  558. public static XmlDocument ConvertTextToXml(string sRecordName, Stream stm, char chFieldSeparator)
  559. {
  560. int nMaxField = 0;
  561. XmlDocument xml = new XmlDocument();
  562. xml.AppendChild(xml.CreateProcessingInstruction("xml" , "version=\"1.0\" encoding=\"UTF-8\""));
  563. xml.AppendChild(xml.CreateElement("xml"));
  564. using ( TextReader reader = new StreamReader(stm) )
  565. {
  566. string sLine = null;
  567. while ( (sLine = reader.ReadLine()) != null )
  568. {
  569. if ( sLine.Length == 0 )
  570. continue;
  571. XmlNode xRecord = xml.CreateElement(sRecordName);
  572. xml.DocumentElement.AppendChild(xRecord);
  573. int i = 0;
  574. int nMode = 0;
  575. int nField = 0;
  576. bool bContinueParsing = true;
  577. while ( bContinueParsing )
  578. {
  579. switch ( nMode )
  580. {
  581. case 0: // Search for next entry.
  582. {
  583. if ( chFieldSeparator == ControlChars.Tab )
  584. {
  585. // Don't skip the tab when it is used as a separator.
  586. while ( Char.IsWhiteSpace(sLine[i]) && sLine[i] != ControlChars.Tab )
  587. i++;
  588. }
  589. else
  590. {
  591. while ( Char.IsWhiteSpace(sLine[i]) )
  592. i++;
  593. }
  594. nMode = 1;
  595. break;
  596. }
  597. case 1: // Determine if field is quoted or unquoted.
  598. {
  599. // first check if field is empty.
  600. char chPunctuation = sLine[i];
  601. if ( chPunctuation == chFieldSeparator )
  602. {
  603. i++;
  604. XmlNode xField = xml.CreateElement("ImportField" + nField.ToString("000"));
  605. xRecord.AppendChild(xField);
  606. nField++;
  607. nMode = 0;
  608. }
  609. if ( chPunctuation == '\"' )
  610. {
  611. i++;
  612. // Field is quoted, so start reading until next quote.
  613. nMode = 3;
  614. }
  615. else
  616. {
  617. // Field is unquoted, so start reading until next separator or end-of-line.
  618. nMode = 2;
  619. }
  620. break;
  621. }
  622. case 2: // Extract unquoted field.
  623. {
  624. XmlNode xField = xml.CreateElement("ImportField" + nField.ToString("000"));
  625. xRecord.AppendChild(xField);
  626. nField++;
  627. int nFieldStart = i;
  628. // Field is unquoted, so start reading until next separator or end-of-line.
  629. while ( i < sLine.Length && sLine[i] != chFieldSeparator )
  630. i++;
  631. int nFieldEnd = i;
  632. string sField = sLine.Substring(nFieldStart, nFieldEnd-nFieldStart);
  633. xField.InnerText = sField;
  634. nMode = 0;
  635. i++;
  636. break;
  637. }
  638. case 3: // Extract quoted field.
  639. {
  640. XmlNode xField = xml.CreateElement("ImportField" + nField.ToString("000"));
  641. xRecord.AppendChild(xField);
  642. nField++;
  643. int nFieldStart = i;
  644. // Field is quoted, so start reading until next quote. Watch out for an escaped quote (two double quotes).
  645. while ( ( i < sLine.Length && sLine[i] != '\"' ) || ( i + 1 < sLine.Length && sLine[i] == '\"' && sLine[i+1] == '\"' ) )
  646. {
  647. if ( i + 1 < sLine.Length && sLine[i] == '\"' && sLine[i+1] == '\"' )
  648. i++;
  649. i++;
  650. }
  651. int nFieldEnd = i;
  652. // Skip all characters until we reach the separator or end-of-line.
  653. while ( i < sLine.Length && sLine[i] != chFieldSeparator )
  654. i++;
  655. string sField = sLine.Substring(nFieldStart, nFieldEnd-nFieldStart);
  656. sField = sField.Replace("\"\"", "\"");
  657. xField.InnerText = sField;
  658. nMode = 0;
  659. i++;
  660. break;
  661. }
  662. default:
  663. bContinueParsing = false;
  664. break;
  665. }
  666. if ( i >= sLine.Length )
  667. break;
  668. }
  669. nMaxField = Math.Max(nField, nMaxField);
  670. }
  671. }
  672. XmlNodeList nlRows = xml.DocumentElement.SelectNodes(sRecordName);
  673. if ( nlRows.Count > 0 )
  674. {
  675. // If the first record does not have all the fields, then add the missing fields.
  676. XmlNode xNode = nlRows[0];
  677. while ( xNode.ChildNodes.Count < nMaxField )
  678. {
  679. XmlNode xField = xml.CreateElement("ImportField" + xNode.ChildNodes.Count.ToString("000"));
  680. xNode.AppendChild(xField);
  681. }
  682. }
  683. return xml;
  684. }
  685. public static XmlDocument ConvertXmlSpreadsheetToXml(XmlDocument xml, string sRecordName)
  686. {
  687. XmlDocument xmlImport = new XmlDocument();
  688. xmlImport.AppendChild(xmlImport.CreateProcessingInstruction("xml" , "version=\"1.0\" encoding=\"UTF-8\""));
  689. xmlImport.AppendChild(xmlImport.CreateElement("xml"));
  690. XmlNamespaceManager nsmgr = new XmlNamespaceManager(xml.NameTable);
  691. string sSpreadsheetNamespace = "urn:schemas-microsoft-com:office:spreadsheet";
  692. nsmgr.AddNamespace("ss", sSpreadsheetNamespace);
  693. // 08/22/2006 Paul. The Spreadsheet namespace is also the default namespace, so make sure to prefix nodes with ss.
  694. XmlNode xWorksheet = xml.DocumentElement.SelectSingleNode("ss:Worksheet", nsmgr);
  695. if ( xWorksheet != null )
  696. {
  697. XmlNode xTable = xWorksheet.SelectSingleNode("ss:Table", nsmgr);
  698. if ( xTable != null )
  699. {
  700. int nColumnCount = 0;
  701. XmlNode xColumnCount = xTable.Attributes.GetNamedItem("ss:ExpandedColumnCount");
  702. if ( xColumnCount != null )
  703. nColumnCount = Sql.ToInteger(xColumnCount.Value);
  704. XmlNodeList nlRows = xTable.SelectNodes("ss:Row", nsmgr);
  705. if ( nlRows.Count > 0 )
  706. {
  707. // 08/22/2006 Paul. The first row is special in that we must make sure that all nodes exist.
  708. XmlNode xRow = nlRows[0];
  709. if ( nColumnCount == 0 )
  710. nColumnCount = xRow.ChildNodes.Count;
  711. for ( int i = 0; i < nlRows.Count; i++ )
  712. {
  713. XmlNode xRecord = xmlImport.CreateElement(sRecordName);
  714. xmlImport.DocumentElement.AppendChild(xRecord);
  715. xRow = nlRows[i];
  716. for ( int j = 0, nField = 0; j < xRow.ChildNodes.Count; j++, nField++ )
  717. {
  718. XmlNode xField = xmlImport.CreateElement("ImportField" + nField.ToString("000"));
  719. xRecord.AppendChild(xField);
  720. XmlNode xCell = xRow.ChildNodes[j];
  721. int nCellIndex = 0;
  722. XmlNode xCellIndex = xCell.Attributes.GetNamedItem("ss:Index");
  723. if ( xCellIndex != null )
  724. nCellIndex = Sql.ToInteger(xCellIndex.Value);
  725. // 08/22/2006 Paul. If there are any missing cells, then add them.
  726. while ( (nField + 1) < nCellIndex )
  727. {
  728. nField++;
  729. xField = xmlImport.CreateElement("ImportField" + nField.ToString("000"));
  730. xRecord.AppendChild(xField);
  731. }
  732. if ( xCell.ChildNodes.Count > 0 )
  733. {
  734. if ( xCell.ChildNodes[0].Name == "Data" )
  735. {
  736. xField.InnerText = xCell.ChildNodes[0].InnerText;
  737. }
  738. }
  739. }
  740. }
  741. }
  742. }
  743. }
  744. return xmlImport;
  745. }
  746. public static XmlDocument ConvertStreamToXml(string sImportModule, string sSourceType, string sCustomDelimiterValue, Stream stm)
  747. {
  748. XmlDocument xml = new XmlDocument();
  749. switch ( sSourceType )
  750. {
  751. case "xmlspreadsheet":
  752. {
  753. xml.Load(stm);
  754. xml = ConvertXmlSpreadsheetToXml(xml, sImportModule.ToLower());
  755. break;
  756. }
  757. case "xml":
  758. {
  759. // 10/10/2006 Paul. Don't require that the file end in XML in order to be imported as a XML document.
  760. // sFILE_MIME_TYPE == "text/xml"
  761. // 10/10/2006 Paul. The reason we use a memory stream to load an XML file is to give us the opportunity to fix the MySQL data file.
  762. // MySQL stores binary 0s and 1s for bit values, and we need them to be text 0s and 1s.
  763. using ( MemoryStream mstm = new MemoryStream() )
  764. {
  765. using ( BinaryWriter mwtr = new BinaryWriter(mstm) )
  766. {
  767. using ( BinaryReader reader = new BinaryReader(stm) )
  768. {
  769. byte[] binBYTES = reader.ReadBytes(8 * 1024);
  770. while ( binBYTES.Length > 0 )
  771. {
  772. for ( int i = 0; i < binBYTES.Length; i++ )
  773. {
  774. // MySQL dump seems to dump binary 0 & 1 for byte values.
  775. if ( binBYTES[i] == 0 )
  776. mstm.WriteByte(Convert.ToByte('0'));
  777. else if ( binBYTES[i] == 1 )
  778. mstm.WriteByte(Convert.ToByte('1'));
  779. else
  780. mstm.WriteByte(binBYTES[i]);
  781. }
  782. binBYTES = reader.ReadBytes(8 * 1024);
  783. }
  784. }
  785. mwtr.Flush();
  786. mstm.Seek(0, SeekOrigin.Begin);
  787. xml.Load(mstm);
  788. bool bExcelSheet = false;
  789. foreach ( XmlNode xNode in xml )
  790. {
  791. if ( xNode.NodeType == XmlNodeType.ProcessingInstruction )
  792. {
  793. if ( xNode.Name == "mso-application" && xNode.InnerText == "progid=\"Excel.Sheet\"" )
  794. {
  795. bExcelSheet = true;
  796. break;
  797. }
  798. }
  799. }
  800. if ( bExcelSheet )
  801. xml = ConvertXmlSpreadsheetToXml(xml, sImportModule.ToLower());
  802. }
  803. }
  804. break;
  805. }
  806. case "excel":
  807. {
  808. ExcelDataReader.ExcelDataReader spreadsheet = new ExcelDataReader.ExcelDataReader(stm);
  809. if ( spreadsheet.WorkbookData.Tables.Count > 0 )
  810. {
  811. xml = ConvertTableToXml(spreadsheet.WorkbookData.Tables[0], sImportModule.ToLower());
  812. }
  813. break;
  814. }
  815. case "other_tab":
  816. {
  817. CsvDataReader spreadsheet = new CsvDataReader(stm, ControlChars.Tab);
  818. if ( spreadsheet.Table != null )
  819. {
  820. xml = ConvertTableToXml(spreadsheet.Table, sImportModule.ToLower());
  821. }
  822. break;
  823. }
  824. case "custom_delimited":
  825. {
  826. // 10/10/2006 Paul. We are only going to allow a single character separator for now.
  827. if ( sCustomDelimiterValue.Length == 0 )
  828. sCustomDelimiterValue = ",";
  829. CsvDataReader spreadsheet = new CsvDataReader(stm, sCustomDelimiterValue[0]);
  830. if ( spreadsheet.Table != null )
  831. {
  832. xml = ConvertTableToXml(spreadsheet.Table, sImportModule.ToLower());
  833. }
  834. break;
  835. }
  836. default:
  837. {
  838. // 08/21/2006 Paul. Everything else is comma separated. Convert to XML.
  839. CsvDataReader spreadsheet = new CsvDataReader(stm, ',');
  840. if ( spreadsheet.Table != null )
  841. {
  842. xml = ConvertTableToXml(spreadsheet.Table, sImportModule.ToLower());
  843. }
  844. break;
  845. }
  846. }
  847. return xml;
  848. }
  849. }
  850. }