457 lines
9.9 KiB
C#
457 lines
9.9 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// Return the DataTable after successful operation.
|
|
/// </summary>
|
|
/// <param name="fileSpec">Valid name of the file.</param>
|
|
/// <param name="firstRowColumnHeader">Used the first line as header.</param>
|
|
/// <returns>DataTable if successful.</returns>
|
|
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<string> cols = [];
|
|
try
|
|
{
|
|
//To be use as a temporary
|
|
List<string> 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<string> 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<string> 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");
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="formatter"></param>
|
|
/// <param name="fileSpec"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="formatter"></param>
|
|
/// <param name="fileSpec"></param>
|
|
/// <param name="firstRowColumnHeader"></param>
|
|
/// <returns></returns>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
_table?.Dispose();
|
|
_table = null;
|
|
|
|
GC.SuppressFinalize(this);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |