PageRenderTime 63ms CodeModel.GetById 34ms RepoModel.GetById 0ms app.codeStats 0ms

/Raptorious.SharpMT940Lib/Mt940Parser.cs

https://bitbucket.org/raptux/sharpmt940lib
C# | 458 lines | 290 code | 55 blank | 113 comment | 51 complexity | 449e80fcd45899dc3a22cfa07acb519a MD5 | raw file
  1. /*
  2. * Copyright (c) 2012 Jaco Adriaansen
  3. * This code is distributed under the MIT (for details please see license.txt)
  4. */
  5. using Raptorious.SharpMt940Lib.Mt940Format;
  6. using System;
  7. using System.Collections.Generic;
  8. using System.Globalization;
  9. using System.IO;
  10. using System.Linq;
  11. using System.Text;
  12. namespace Raptorious.SharpMt940Lib
  13. {
  14. /// <summary>
  15. ///
  16. /// </summary>
  17. public static class Mt940Parser
  18. {
  19. /// <summary>
  20. /// Opens the given file, reads it and returns a collection of CustomerStatementMessages using the current culture.
  21. /// </summary>
  22. /// <param name="file">File to read.</param>
  23. /// <param name="format">Bank specific format of this file.</param>
  24. /// <param name="mt940Params">Additional mt940 parameters</param>
  25. /// <returns>Returns a collection of customer statement messages, populated by the data of the given file.</returns>
  26. [Obsolete("Please use the Parse method with CultureInfo")]
  27. public static ICollection<CustomerStatementMessage> Parse(IMt940Format format, string file, Parameters mt940Params = null)
  28. {
  29. if (format == null)
  30. {
  31. throw new ArgumentNullException(nameof(format));
  32. }
  33. if (String.IsNullOrWhiteSpace(file))
  34. {
  35. throw new ArgumentException("file can not be empty", file);
  36. }
  37. return Parse(format, file, CultureInfo.CurrentCulture, mt940Params);
  38. }
  39. /// <summary>
  40. /// Opens the given file, reads it and returns a collection of CustomerStatementMessages.
  41. /// </summary>
  42. /// <param name="file">File to read.</param>
  43. /// <param name="format">Bank specific format of this file.</param>
  44. /// <param name="cultureInfo">Specifies the culture to use</param>
  45. /// <param name="mt940Parameters">Additional mt940 parameters</param>
  46. /// <returns>Returns a collection of customer statement messages, populated by the data of the given file.</returns>
  47. public static ICollection<CustomerStatementMessage> Parse(IMt940Format format, string file, CultureInfo cultureInfo, Parameters mt940Parameters = null)
  48. {
  49. if (format == null)
  50. {
  51. throw new ArgumentNullException(nameof(format));
  52. }
  53. if (String.IsNullOrWhiteSpace(file))
  54. {
  55. throw new ArgumentException("file can not be empty");
  56. }
  57. if (cultureInfo == null)
  58. {
  59. throw new ArgumentNullException(nameof(cultureInfo));
  60. }
  61. if (!File.Exists(file))
  62. {
  63. throw new FileNotFoundException("Can not find file.", file);
  64. }
  65. using (StreamReader reader = new StreamReader(File.OpenRead(file)))
  66. {
  67. return Parse(format, reader, cultureInfo, mt940Parameters);
  68. }
  69. }
  70. /// <summary>
  71. /// Reads the given string to the end and parses the data to Customer Statement Messages.
  72. /// </summary>
  73. /// <param name="fileStream">Filestream to read.</param>
  74. /// <param name="format">Bank specific format of this file.</param>
  75. /// <param name="mt940Params">Additional mt940 parameters</param>
  76. /// <returns></returns>
  77. [Obsolete("Please use the Parse method with CultureInfo")]
  78. public static ICollection<CustomerStatementMessage> Parse(IMt940Format format, TextReader fileStream, Parameters mt940Params = null)
  79. {
  80. if (format == null)
  81. {
  82. throw new ArgumentNullException(nameof(format));
  83. }
  84. if (fileStream == null)
  85. {
  86. throw new ArgumentNullException(nameof(fileStream));
  87. }
  88. return Parse(format, fileStream, CultureInfo.CurrentCulture, mt940Params);
  89. }
  90. /// <summary>
  91. /// Reads the given string to the end and parses the data to Customer Statement Messages.
  92. /// </summary>
  93. /// <param name="fileStream">Filestream to read.</param>
  94. /// <param name="format">Bank specific format of this file.</param>
  95. /// <param name="cultureInfo">Specifies the culture information to use</param>
  96. /// <param name="mt940Parameters">Additional mt940 parameters</param>
  97. /// <returns></returns>
  98. public static ICollection<CustomerStatementMessage> Parse(IMt940Format format, TextReader fileStream, CultureInfo cultureInfo, Parameters mt940Parameters = null)
  99. {
  100. if (format == null)
  101. {
  102. throw new ArgumentNullException(nameof(format));
  103. }
  104. if (fileStream == null)
  105. {
  106. throw new ArgumentNullException(nameof(fileStream));
  107. }
  108. if (cultureInfo == null)
  109. {
  110. throw new ArgumentNullException(nameof(cultureInfo));
  111. }
  112. var completeFile = fileStream.ReadToEnd();
  113. return ParseData(format, completeFile, cultureInfo, mt940Parameters);
  114. }
  115. /// <summary>
  116. /// Reads the given string to the end and parses the data to Customer Statement Messages using the current culture
  117. /// </summary>
  118. /// <param name="format">Bank specific format of this file.</param>
  119. /// <param name="data">String containing the MT940 file.</param>
  120. /// <param name="mt940Params">Additional mt940 parameters</param>
  121. /// <returns></returns>
  122. [Obsolete("Please use the Parse method with CultureInfo")]
  123. public static ICollection<CustomerStatementMessage> ParseData(IMt940Format format, string data, Parameters mt940Params = null)
  124. {
  125. if (format == null)
  126. {
  127. throw new ArgumentNullException(nameof(format));
  128. }
  129. if (String.IsNullOrWhiteSpace(data))
  130. {
  131. throw new ArgumentException("data can not be empty", nameof(data));
  132. }
  133. if (mt940Params == null)
  134. {
  135. // default values
  136. mt940Params = new Parameters();
  137. }
  138. return ParseData(format, data, CultureInfo.CurrentCulture, mt940Params);
  139. }
  140. /// <summary>
  141. /// Reads the given string to the end and parses the data to Customer Statement Messages
  142. /// </summary>
  143. /// <param name="format"></param>
  144. /// <param name="data"></param>
  145. /// <param name="cultureInfo">Specifies the culture information to use</param>
  146. /// <param name="mt940Parameters">Additional mt940 parameters</param>
  147. /// <returns></returns>
  148. public static ICollection<CustomerStatementMessage> ParseData(IMt940Format format, string data, CultureInfo cultureInfo, Parameters mt940Parameters = null)
  149. {
  150. if (format == null)
  151. {
  152. throw new ArgumentNullException(nameof(format));
  153. }
  154. if (String.IsNullOrWhiteSpace(data))
  155. {
  156. throw new ArgumentException("data can not be empty", nameof(data));
  157. }
  158. if (mt940Parameters == null)
  159. {
  160. // default values
  161. mt940Parameters = new Parameters();
  162. }
  163. var listData = CreateStringTransactions(format, data);
  164. return CreateObjectTransactions(format, listData, cultureInfo, mt940Parameters);
  165. }
  166. /// <summary>
  167. ///
  168. /// </summary>
  169. /// <param name="format">IMt940Format implementation</param>
  170. /// <param name="data">A collection of string arrays formatted by CreateStringTransactions()</param>
  171. /// <param name="cultureInfo">The culture to use</param>
  172. /// <param name="mt940Params">Additional mt940 parameters</param>
  173. /// <see cref="CreateStringTransactions"></see>
  174. /// <returns></returns>
  175. /// TODO: This method is way to complex. It should be simplified.
  176. private static ICollection<CustomerStatementMessage> CreateObjectTransactions(
  177. IMt940Format format,
  178. ICollection<String[]> data,
  179. CultureInfo cultureInfo,
  180. Parameters mt940Params)
  181. {
  182. // Create a new list.
  183. var customerStatementList = new List<CustomerStatementMessage>();
  184. // For each string collection of commands.
  185. foreach (String[] line in data)
  186. {
  187. int transactionPointer = 0; // Start of the transaction.
  188. // Skip the header (for some reason)
  189. transactionPointer += format.Header.LineCount; // SWIFT HEADER.
  190. var transaction = default(Transaction); // Set transaction to its default (null).
  191. var customerStatementMessage = new CustomerStatementMessage();
  192. // ReSharper disable AccessToModifiedClosure
  193. Action addAndNullTransactionIfPresent = () =>
  194. {
  195. if (customerStatementMessage != null && transaction != null)
  196. customerStatementMessage.Transactions.Add(transaction);
  197. transaction = null;
  198. };
  199. // ReSharper restore AccessToModifiedClosure
  200. // Loop through the array.
  201. for (; transactionPointer < line.Length; transactionPointer++)
  202. {
  203. // Set transactionLine to the current line.
  204. string transactionLine = line[transactionPointer];
  205. // Skip if null, CreateObjectTransactions kinda leaves a mess.
  206. if (transactionLine != null)
  207. {
  208. // Get the command number.
  209. var tag = transactionLine.Substring(transactionLine.IndexOf(':'), transactionLine.IndexOf(':', 1) + 1);
  210. // Get the command data.
  211. var transactionData = transactionLine.Substring(tag.Length);
  212. // Fill the object the right data.
  213. switch (tag)
  214. {
  215. case ":20:":
  216. customerStatementMessage = customerStatementMessage.SetTransactionReference(transactionData);
  217. break;
  218. case ":21:":
  219. customerStatementMessage = customerStatementMessage.SetRelatedMessage(transactionData);
  220. break;
  221. case ":25:":
  222. customerStatementMessage = customerStatementMessage.SetAccount(transactionData, mt940Params.ClearAccount);
  223. break;
  224. case ":28:":
  225. case ":28C:":
  226. customerStatementMessage.SetSequenceNumber(transactionData, cultureInfo);
  227. break;
  228. case ":60m:":
  229. case ":60F:":
  230. case ":60M:":
  231. customerStatementMessage = customerStatementMessage.SetOpeningBalance(new TransactionBalance(transactionData, cultureInfo));
  232. break;
  233. case ":61:":
  234. addAndNullTransactionIfPresent();
  235. transaction = new Transaction(transactionData, customerStatementMessage.OpeningBalance.Currency, cultureInfo);
  236. break;
  237. case ":86:":
  238. /*
  239. * If the previous line was a 61 (ie, we have a transaction), the 'Information to Account Owner'
  240. * applies to the transaction, otherwise it applies to the whole message.
  241. */
  242. if (transaction == null)
  243. customerStatementMessage = customerStatementMessage.SetDescription(transactionData);
  244. else
  245. {
  246. transaction.Description = transactionData;
  247. }
  248. addAndNullTransactionIfPresent();
  249. break;
  250. case ":62F:":
  251. addAndNullTransactionIfPresent();
  252. customerStatementMessage = customerStatementMessage.SetClosingBalance(new TransactionBalance(transactionData, cultureInfo));
  253. break;
  254. case ":62m:":
  255. case ":62M:":
  256. customerStatementMessage = customerStatementMessage.SetClosingBalance(new TransactionBalance(transactionData, cultureInfo));
  257. break;
  258. case ":64:":
  259. addAndNullTransactionIfPresent();
  260. customerStatementMessage = customerStatementMessage.SetClosingAvailableBalance(new TransactionBalance(transactionData, cultureInfo));
  261. break;
  262. case ":65:":
  263. customerStatementMessage = customerStatementMessage.SetForwardAvailableBalance(new TransactionBalance(transactionData, cultureInfo));
  264. break;
  265. }
  266. }
  267. }
  268. customerStatementList.Add(customerStatementMessage);
  269. }
  270. // geting :86: details
  271. if (mt940Params.Codes.Count > 0)
  272. {
  273. var constantCode = mt940Params.ConstantCode();
  274. foreach (var item in customerStatementList)
  275. {
  276. foreach (var tr in item.Transactions)
  277. {
  278. Mt940Parser.Parse86Details(tr, mt940Params, constantCode);
  279. }
  280. }
  281. }
  282. return customerStatementList;
  283. }
  284. private static void Parse86Details(Transaction tr, Parameters mt940Params, bool constantCode)
  285. {
  286. if (constantCode)
  287. {
  288. throw new NotImplementedException("to do...");
  289. }
  290. else
  291. {
  292. string data = tr.Description;
  293. char separator = mt940Params.Codes.First().Value[0][0];
  294. foreach (var item in mt940Params.Codes)
  295. {
  296. string[] _codes = item.Value;
  297. string value = "";
  298. foreach (var cs in _codes)
  299. {
  300. var startIndex = data.IndexOf(cs, StringComparison.CurrentCulture);
  301. if (startIndex > -1)
  302. {
  303. startIndex += cs.Length;
  304. var endIndex = data.IndexOf(separator, startIndex);
  305. var line = data.Substring(startIndex, endIndex - startIndex).Trim();
  306. if (mt940Params.MaxLineLength > 0 && line.Length >= mt940Params.MaxLineLength)
  307. value += line;
  308. else
  309. value += line + " ";
  310. }
  311. }
  312. value = value.Trim();
  313. if (!string.IsNullOrEmpty(value))
  314. {
  315. switch (item.Key)
  316. {
  317. case TransactionDetail.Account:
  318. tr.Details.Account = value;
  319. break;
  320. case TransactionDetail.Name:
  321. tr.Details.Name = value;
  322. break;
  323. case TransactionDetail.Description:
  324. tr.Details.Description = value;
  325. break;
  326. case TransactionDetail.Empty:
  327. default:
  328. break;
  329. }
  330. }
  331. }
  332. }
  333. }
  334. /// <summary>
  335. /// This method accepts mt940 data file given as a string. The string
  336. /// is split by Environment.NewLine as each line contains a command.
  337. ///
  338. /// Every line that starts with a ':' is a mt940 'command'. Lines that
  339. /// does not start with a ':' belongs to the previous command.
  340. ///
  341. /// The method returns a collection of string arrays. Every item in
  342. /// the collection is a mt940 message.
  343. /// </summary>
  344. /// <param name="data">A string of MT940 data to parse.</param>
  345. /// <param name="format">Specifies the bank specific format</param>
  346. /// <returns>A collection :)</returns>
  347. private static ICollection<String[]> CreateStringTransactions(IMt940Format format, string data)
  348. {
  349. // Split on the new line seperator. In a MT940 messsage, every command is on a seperate line.
  350. // Assumption is made it is in the same format as the enviroments new line.
  351. var tokenized = data.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries);
  352. // Create an empty list of string arrays.
  353. var transactions = new List<String[]>();
  354. // Offset pointer, starts a the first line (zero based index).
  355. int pointer = 0;
  356. // Loop trough the entire file?
  357. while (pointer < tokenized.Length)
  358. {
  359. // Seperator, this is the Trailer! We split messages based on trailer! - Right, check.
  360. var trailerIndex = Array.IndexOf(tokenized, format.Trailer.Data, pointer);
  361. // When we found a trailer.. then..
  362. if (trailerIndex >= 0)
  363. {
  364. // Create a new array the holds the correct number of elements.
  365. String[] currentTransaction = new String[trailerIndex - pointer];
  366. // Copy the data from the source array to our current transaction.
  367. Array.Copy(tokenized, pointer, currentTransaction, 0, currentTransaction.Length);
  368. // Walk trough the current message. Start at the current
  369. // index and stop at the separator.
  370. for (int index = currentTransaction.Length - 1;
  371. index > format.Header.LineCount;
  372. index--)
  373. {
  374. //
  375. String transactionItem = currentTransaction[index];
  376. System.Diagnostics.Debug.Assert(transactionItem != null);
  377. // If the transactionItem doesn't start with : then the
  378. // current line belongs to the previous one.
  379. if (!transactionItem.StartsWith(":", StringComparison.Ordinal))
  380. {
  381. // Append ths current line to the previous line seperated by
  382. // and NewLine.
  383. currentTransaction[index - 1] += Environment.NewLine;
  384. currentTransaction[index - 1] += transactionItem;
  385. // Set the current item to null, it doesn't exist anymore.
  386. currentTransaction[index] = null;
  387. }
  388. }
  389. // Add the current transaction.
  390. transactions.Add(currentTransaction);
  391. // Next up!
  392. pointer = (trailerIndex + 1);
  393. }
  394. else
  395. {
  396. // Message doesn't contain a trailer. So it is invalid!
  397. throw new InvalidDataException("Can not find trailer!");
  398. }
  399. }
  400. return transactions;
  401. }
  402. }
  403. }