using System; using System.Collections.Generic; using System.Data; using System.IO; namespace OnlineSalesAutoCrop.CoreAPI.Models.Global { public sealed class ImportHelper : IDisposable { #region Declaration private DataTable _table; #endregion #region Constructor & Desctructor public ImportHelper() { Delimiter = '\0'; _table = null; } ~ImportHelper() { _table?.Dispose(); } #endregion #region Properties #region Property Delimiter: char public static char Delimiter { get; set; } #endregion #endregion #region Formats Class public sealed class Formats { public static readonly IFormatter Text = new TextFormatter(); public static readonly IFormatter Excel = new ExcelFormatter(); private Formats() { } } #endregion #region IFormater Interface public interface IFormatter { /// /// Return the DataTable after successful operation. /// /// Valid name of the file. /// Used the first line as header. /// DataTable if successful. DataTable Import(string fileSpec, bool firstRowColumnHeader); } #endregion #region Excel Formatter Implementation public class ExcelFormatter : IFormatter { #region Declaration & Constructor public ExcelFormatter() { } #endregion #region Functions public DataTable Import(string fileSpec, bool firstRowColumnHeader) { try { return ExcelReader.GetDataSetFromFile(fileSpec: fileSpec, firstRowColumnHeader: firstRowColumnHeader).Tables[0]; } catch (Exception e) { throw new InvalidOperationException(e.Message, e); } } #endregion } #endregion #region Text Formatter Implementation public class TextFormatter : IFormatter { #region Declaration & Construct private DataTable _dt; private readonly char CharQlfr = '"'; private readonly string StringQlfr = "\""; private readonly string ReplacingQlfr = "\"\""; private readonly string ReplacedChar = ((char)1).ToString(); public TextFormatter() { _dt = null; } #endregion #region Properties private int EndColumnIndex { get; set; } #endregion #region Functions private void MakeColumn(bool hasHeader, string line) { try { string[] columns = ParseData(line); EndColumnIndex = columns.Length - 1; if (hasHeader) { for (int i = 0; i <= EndColumnIndex; i++) { DataColumn dc = new(string.Format("Col{0}", i)) { Caption = columns[i], ReadOnly = true, DataType = typeof(string) }; _dt.Columns.Add(dc); } } else { for (int i = 0; i <= EndColumnIndex; i++) { DataColumn dc = new(string.Format("Col{0}", i)) { Caption = string.Format("Column {0}", i), ReadOnly = true, DataType = typeof(string) }; _dt.Columns.Add(dc); } } } catch (Exception e) { throw new InvalidOperationException(e.Message, e); } } private string[] ParseData(string data) { List cols = []; try { //To be use as a temporary List tmpCols = []; //Parse qualifier data = ParseQualifier(tmpCols, data); //Make actual columns data tmpCols = []; ParseData(tmpCols, data); //Convert back to original value foreach (var item in tmpCols) { string value = item.Replace(ReplacedChar, StringQlfr); cols.Add(value); } tmpCols.Clear(); } catch (Exception e) { throw new InvalidOperationException(e.Message, e); } return [.. cols]; } private void ParseData(List cols, string data) { try { //Find out position of qualifier int pos = data.IndexOf(CharQlfr); //If there is no qualifier in the data do nothing if (pos == -1) { cols.AddRange(data.Split(Delimiter)); return; } //Second part of the data after qualifies string part2 = data[pos..]; //If qualifier is beginning of the data if (pos == 0) { //Remove beginning qualifier string part1 = data[1..]; //Find out next position of the qualifier, next qualifier must exists since qualifier starts pos = part1.IndexOf(CharQlfr); //Read remaining data by removing the delimiter part2 = part1[(pos + 1)..]; //Remove delimiter if still exists at the start of the data if (part2.StartsWith(Delimiter.ToString())) part2 = part2[1..]; //Read data just before the position part1 = part1[..pos]; //Remove delimiter if still exists at the start of the data if (part1.EndsWith(Delimiter.ToString())) part1 = part1[..^1]; //Add to the column collection cols.Add(part1); } else { //Read data just before the position string part1 = data[..pos]; //Remove end delimiter if still exists if (part1.EndsWith(Delimiter.ToString())) part1 = part1[..^1]; //If this is a valid data, Add to column collection splitting by delimiter of the first part if (part1.Length > 0) cols.AddRange(part1.Split(Delimiter)); } //Call recursively by using second part of the data ParseData(cols, part2); } catch (Exception e) { throw new InvalidOperationException(e.Message, e); } } private string ParseQualifier(List cols, string data) { try { //Find out the position of qualifier int pos = data.IndexOf(CharQlfr); //If there is no qualifier in the data do nothing if (pos == -1) { cols.Add(data); } else { //Read left part of the data of current position need to add into collection string remData = data[..pos]; cols.Add(remData); //Scan each character of remaining part of the data for (int i = pos; i < data.Length; i++) { //Read character at current position string value = data.Substring(i, 1); //Add this character into collection if (i == pos) { cols.Add(value); } else { //Read two characters from current position string pairValue = i + 1 < data.Length ? data.Substring(i, 2) : data.Substring(i, 1); //If current character is a qualifier but is not a replaceable qualifier if (value[0] == CharQlfr && !pairValue.Equals(ReplacingQlfr)) { //Add this character into collection cols.Add(value); //If current position is the last position of the data just exit from loop if (i + 1 >= data.Length) break; //Read remaining part of the data data = data[(i + 1)..]; //Call this function recursively and exit from loop ParseQualifier(cols, data); break; } else if (pairValue.Equals(ReplacingQlfr)) //If this is a replaceable qualifier just replace the with the special character { value = ReplacedChar; //Increase the current position by 1 i++; } //Add value into collection cols.Add(value); } } } } catch (Exception e) { throw new InvalidOperationException(e.Message, e); } return string.Join("", [.. cols]); } public DataTable Import(string fileSpec, bool firstRowColumnHeader) { try { int rowIdx = -1; bool firstLine = true; _dt = new DataTable("TextReader"); using StreamReader sr = new(fileSpec); while (sr.Peek() != -1) { string line = sr.ReadLine(); if (line.Trim().Length <= 0) continue; if (firstLine) MakeColumn(firstRowColumnHeader, line); if (firstRowColumnHeader && firstLine) { firstLine = false; continue; } firstLine = false; rowIdx++; DataRow dr = _dt.NewRow(); object[] columns = ParseData(line); for (int i = 0; i < columns.Length; i++) { dr[string.Format("Col{0}", i)] = columns[i]; } _dt.Rows.Add(dr); } sr.Close(); } catch (Exception e) { throw new InvalidOperationException(e.Message, e); } return _dt; } #endregion } #endregion #region Functions public void Reset() { _table = new DataTable("ImportHelper"); } /// /// /// /// /// /// public DataTable Import(IFormatter formatter, string fileSpec) { if (formatter == null) throw new ArgumentException("Need to specify a formatter", nameof(formatter)); try { _table = Import(formatter, fileSpec, false); } catch (Exception e) { throw new InvalidOperationException(e.Message, e); } return _table; } /// /// /// /// /// /// /// public DataTable Import(IFormatter formatter, string fileSpec, bool firstRowColumnHeader) { if (formatter == null) throw new ArgumentException("Need to specify a formatter", nameof(formatter)); if (string.IsNullOrEmpty(fileSpec) || string.IsNullOrWhiteSpace(fileSpec)) throw new InvalidOperationException("Provide a valid file name to read."); if (formatter == Formats.Text && Delimiter == '\0') throw new InvalidOperationException("Provide a field delimiter of the file."); try { _table = formatter.Import(fileSpec, firstRowColumnHeader); } catch (Exception e) { throw new InvalidOperationException(e.Message); } return _table; } /// /// /// public void Dispose() { _table?.Dispose(); _table = null; GC.SuppressFinalize(this); } #endregion } }