Code Refacturing

This commit is contained in:
wei 2021-04-08 09:41:16 +08:00
parent f04243c029
commit 346b1f6e12
13 changed files with 208 additions and 142 deletions

View File

@ -1,33 +0,0 @@
using System.Collections.Generic;
using System.IO;
namespace MiniExcelLibs.Csv
{
internal class CsvProvider : ExcelProviderBase
{
private IExcelReader _csvlReader;
private IExcelWriter _csvWriter;
public CsvProvider()
{
_csvWriter = new CsvWriter();
_csvlReader = new CsvReader();
}
public override IEnumerable<IDictionary<string, object>> Query(Stream stream, bool UseHeaderRow = false)
{
return _csvlReader.Query(stream, UseHeaderRow);
}
public override IEnumerable<T> Query<T>(Stream stream)
{
return _csvlReader.Query<T>(stream);
}
public override void SaveAs(Stream stream, object input)
{
_csvWriter.SaveAs(stream, input);
}
}
}

View File

@ -9,11 +9,16 @@ namespace MiniExcelLibs.Csv
{
internal class CsvReader : IExcelReader
{
public IEnumerable<IDictionary<string, object>> Query(Stream stream, bool useHeaderRow)
private Stream _stream;
public CsvReader(Stream stream)
{
this._stream = stream;
}
public IEnumerable<IDictionary<string, object>> Query(bool useHeaderRow, string sheetName)
{
var configuration = new CsvConfiguration();
using (var reader = configuration.GetStreamReaderFunc(stream))
using (var reader = configuration.GetStreamReaderFunc(_stream))
{
char[] seperators = { configuration.Seperator };
@ -56,13 +61,13 @@ namespace MiniExcelLibs.Csv
}
}
public IEnumerable<T> Query<T>(Stream stream) where T : class, new()
public IEnumerable<T> Query<T>(string sheetName) where T : class, new()
{
var type = typeof(T);
var props = Helpers.GetSaveAsProperties(type);
Dictionary<int, PropertyInfo> idxProps = new Dictionary<int, PropertyInfo>();
var configuration = new CsvConfiguration();
using (var reader = configuration.GetStreamReaderFunc(stream))
using (var reader = configuration.GetStreamReaderFunc(_stream))
{
char[] seperators = { configuration.Seperator };

View File

@ -8,9 +8,16 @@ namespace MiniExcelLibs.Csv
{
internal class CsvWriter : IExcelWriter
{
public void SaveAs(Stream stream, object input)
private Stream _stream;
public CsvWriter(Stream stream)
{
using (StreamWriter writer = new StreamWriter(stream))
this._stream = stream;
}
public void SaveAs(object input, bool printHeader)
{
using (StreamWriter writer = new StreamWriter(_stream))
{
// notice : if first one is null then it can't get Type infomation
var first = true;

View File

@ -3,24 +3,34 @@
using MiniExcelLibs.OpenXml;
using System;
using MiniExcelLibs.Csv;
using System.IO;
/// <summary>
/// use statics factory,If want to do OCP we can use a Interface factory to instead of statics factory
/// </summary>
internal class ExcelFacorty
internal class ExcelWriterFactory
{
internal static ExcelProviderBase GetExcelProvider(ExcelType excelType, string sheetName)
{
return GetExcelProvider(excelType, true, sheetName);
}
internal static ExcelProviderBase GetExcelProvider(ExcelType excelType, bool useHeaderRow, string sheetName)
internal static IExcelWriter GetProvider(Stream stream,ExcelType excelType)
{
switch (excelType)
{
case ExcelType.CSV:
return new CsvProvider();
return new CsvWriter(stream);
case ExcelType.XLSX:
return new ExcelOpenXmlProvider(useHeaderRow, sheetName);
return new ExcelOpenXmlSheetWriter(stream);
default:
throw new NotSupportedException($"Please Issue for me");
}
}
}
internal class ExcelReaderFactory
{
internal static IExcelReader GetProvider(Stream stream, ExcelType excelType)
{
switch (excelType)
{
case ExcelType.CSV:
return new CsvReader(stream);
case ExcelType.XLSX:
return new ExcelOpenXmlSheetReader(stream);
default:
throw new NotSupportedException($"Please Issue for me");
}

View File

@ -1,11 +0,0 @@
using System.Collections.Generic;
using System.IO;
namespace MiniExcelLibs
{
internal abstract class ExcelProviderBase : IExcelReader, IExcelWriter
{
public abstract IEnumerable<IDictionary<string, object>> Query(Stream stream, bool UseHeaderRow = false);
public abstract IEnumerable<T> Query<T>(Stream stream) where T : class, new();
public abstract void SaveAs(Stream stream, object input);
}
}

View File

@ -5,7 +5,7 @@ namespace MiniExcelLibs
{
internal interface IExcelReader
{
IEnumerable<IDictionary<string, object>> Query(Stream stream, bool UseHeaderRow = false);
IEnumerable<T> Query<T>(Stream stream) where T : class, new();
IEnumerable<IDictionary<string, object>> Query(bool UseHeaderRow, string sheetName);
IEnumerable<T> Query<T>(string sheetName) where T : class, new();
}
}

View File

@ -4,6 +4,6 @@ namespace MiniExcelLibs
{
internal interface IExcelWriter
{
void SaveAs(Stream stream, object value);
void SaveAs(object value, bool printHeader);
}
}

View File

@ -1,5 +1,8 @@
namespace MiniExcelLibs
{
using MiniExcelLibs.OpenXml;
using MiniExcelLibs.Zip;
using System;
using System.Collections.Generic;
using System.IO;
@ -18,7 +21,7 @@
{
if (excelType == ExcelType.UNKNOWN)
throw new InvalidDataException("Please specify excelType");
ExcelFacorty.GetExcelProvider(excelType, printHeader, sheetName).SaveAs(stream, value);
ExcelWriterFactory.GetProvider(stream,excelType).SaveAs(value, printHeader);
}
public static IEnumerable<T> Query<T>(string path, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null) where T : class, new()
@ -30,7 +33,7 @@
public static IEnumerable<T> Query<T>(this Stream stream, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null) where T : class, new()
{
return ExcelFacorty.GetExcelProvider(GetExcelType(stream, excelType), sheetName).Query<T>(stream);
return ExcelReaderFactory.GetProvider(stream,GetExcelType(stream, excelType)).Query<T>(sheetName);
}
public static IEnumerable<dynamic> Query(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null)
@ -42,7 +45,56 @@
public static IEnumerable<dynamic> Query(this Stream stream, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null)
{
return ExcelFacorty.GetExcelProvider(GetExcelType(stream, excelType), useHeaderRow, sheetName).Query(stream, useHeaderRow);
return ExcelReaderFactory.GetProvider(stream,GetExcelType(stream, excelType)).Query(useHeaderRow, sheetName);
}
public static GridReader QueryMultiple(string path, bool useHeaderRow = false)
{
// only xlsx support this
return new GridReader(path);
}
}
public class GridReader : IDisposable
{
private string _path;
private List<SheetRecord> _sheetRecords;
private int _sheetIndex = 0;
public GridReader(string path)
{
_path = path;
using (var stream = File.OpenRead(path))
using (var archive = new ExcelOpenXmlZip(stream))
{
_sheetRecords = ExcelOpenXmlSheetReader.GetWorkbookRels(archive.Entries);
}
}
public IEnumerable<string> GetSheetNames()
{
foreach (var item in _sheetRecords)
{
yield return item.Name;
}
}
public void Dispose()
{
//if (archive != null)
//{
// archive.Dispose();
// archive = null;
//}
GC.SuppressFinalize(this);
}
public IEnumerable<dynamic> Read(bool useHeaderRow = false)
{
var sheetRecord = _sheetRecords[_sheetIndex];//TODO
_sheetIndex = _sheetIndex + 1;
return MiniExcel.Query(_path,useHeaderRow, sheetRecord.Name);
}
}
}

View File

@ -1,33 +0,0 @@
using System.Collections.Generic;
using System.IO;
namespace MiniExcelLibs.OpenXml
{
internal class ExcelOpenXmlProvider : ExcelProviderBase
{
private IExcelReader _excelReader;
private IExcelWriter _excelWriter;
public ExcelOpenXmlProvider(bool printHeader, string sheetName)
{
_excelWriter = new ExcelOpenXmlSheetWriter(printHeader);
_excelReader = new ExcelOpenXmlSheetReader(sheetName);
}
public override IEnumerable<IDictionary<string, object>> Query(Stream stream, bool UseHeaderRow = false)
{
return _excelReader.Query(stream, UseHeaderRow);
}
public override IEnumerable<T> Query<T>(Stream stream)
{
return _excelReader.Query<T>(stream);
}
public override void SaveAs(Stream stream, object input)
{
_excelWriter.SaveAs(stream, input);
}
}
}

View File

@ -15,51 +15,53 @@ namespace MiniExcelLibs.OpenXml
internal class ExcelOpenXmlSheetReader : IExcelReader
{
private const string _ns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
private List<SheetRecord> _sheetRecords = null;
private List<SheetRecord> _sheetRecords;
private List<string> _sharedStrings;
private ExcelOpenXmlStyles _style;
private string _sheetName;
private ExcelOpenXmlZip _archive;
private static readonly XmlReaderSettings _xmlSettings = new XmlReaderSettings
{
IgnoreComments = true,
IgnoreWhitespace = true,
XmlResolver = null,
};
public ExcelOpenXmlSheetReader(string sheetName)
public ExcelOpenXmlSheetReader(Stream stream)
{
this._sheetName = sheetName;
_archive = new ExcelOpenXmlZip(stream);
}
public IEnumerable<IDictionary<string, object>> Query(Stream stream, bool UseHeaderRow = false)
public IEnumerable<IDictionary<string, object>> Query(bool UseHeaderRow, string sheetName)
{
using (var archive = new ExcelOpenXmlZip(stream))
//using (var archive = new ExcelOpenXmlZip(stream))
//var archive = new ExcelOpenXmlZip(stream);
{
//TODO:need to optimize
_sharedStrings = GetSharedStrings(archive);
SetSharedStrings(_archive);
// if sheets count > 1 need to read xl/_rels/workbook.xml.rels and
var sheets = archive.Entries.Where(w => w.FullName.StartsWith("xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase)
var sheets = _archive.Entries.Where(w => w.FullName.StartsWith("xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase)
|| w.FullName.StartsWith("/xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase)
);
ZipArchiveEntry sheetEntry = null;
if (_sheetName != null)
if (sheetName != null)
{
ReadWorkbookRels(archive.Entries);
var s = _sheetRecords.SingleOrDefault(_ => _.Name == _sheetName);
SetWorkbookRels(_archive.Entries);
var s = _sheetRecords.SingleOrDefault(_ => _.Name == sheetName);
if (s == null)
throw new InvalidOperationException("Please check sheetName/Index is correct");
sheetEntry = sheets.Single(w => w.FullName == $"xl/{s.Path}" || w.FullName == $"/xl/{s.Path}");
}
else if (sheets.Count() > 1)
{
ReadWorkbookRels(archive.Entries);
SetWorkbookRels(_archive.Entries);
var s = _sheetRecords[0];
sheetEntry = sheets.Single(w => w.FullName == $"xl/{s.Path}" || w.FullName == $"/xl/{s.Path}");
}
else
sheetEntry = sheets.Single();
// TODO: need to optimize performance
var withoutCR = false;
@ -234,7 +236,7 @@ namespace MiniExcelLibs.OpenXml
}
// only when have s attribute then load styles xml data
if (_style == null)
_style = new ExcelOpenXmlStyles(archive);
_style = new ExcelOpenXmlStyles(_archive);
//if not using First Head then using 1,2,3 as index
if (UseHeaderRow)
{
@ -308,11 +310,13 @@ namespace MiniExcelLibs.OpenXml
}
}
}
public IEnumerable<T> Query<T>(Stream stream) where T : class, new()
public IEnumerable<T> Query<T>( string sheetName) where T : class, new()
{
var type = typeof(T);
var props = Helpers.GetExcelCustomPropertyInfos(type);
foreach (var item in Query(stream, true))
foreach (var item in Query(true, sheetName))
{
var v = new T();
foreach (var pInfo in props)
@ -365,16 +369,20 @@ namespace MiniExcelLibs.OpenXml
yield return v;
}
}
private List<string> GetSharedStrings(ExcelOpenXmlZip archive)
private void SetSharedStrings(ExcelOpenXmlZip archive)
{
if (_sharedStrings != null)
return;
var sharedStringsEntry = archive.GetEntry("xl/sharedStrings.xml");
if (sharedStringsEntry == null)
return null;
return;
using (var stream = sharedStringsEntry.Open())
{
return GetSharedStrings(stream).ToList();
_sharedStrings = GetSharedStrings(stream).ToList();
}
}
private IEnumerable<string> GetSharedStrings(Stream stream)
{
using (var reader = XmlReader.Create(stream))
@ -399,7 +407,15 @@ namespace MiniExcelLibs.OpenXml
}
}
}
private IEnumerable<SheetRecord> ReadWorkbook(ReadOnlyCollection<ZipArchiveEntry> entries)
private void SetWorkbookRels(ReadOnlyCollection<ZipArchiveEntry> entries)
{
if (_sheetRecords != null)
return;
_sheetRecords = GetWorkbookRels(entries);
}
internal static IEnumerable<SheetRecord> ReadWorkbook(ReadOnlyCollection<ZipArchiveEntry> entries)
{
using (var stream = entries.Single(w => w.FullName == "xl/workbook.xml").Open())
using (XmlReader reader = XmlReader.Create(stream, _xmlSettings))
@ -441,26 +457,26 @@ namespace MiniExcelLibs.OpenXml
}
}
}
private void ReadWorkbookRels(ReadOnlyCollection<ZipArchiveEntry> entries)
internal static List<SheetRecord> GetWorkbookRels(ReadOnlyCollection<ZipArchiveEntry> entries)
{
_sheetRecords = ReadWorkbook(entries).ToList();
//_styles = ReadStyle(entries).ToList();
var sheetRecords = ReadWorkbook(entries).ToList();
using (var stream = entries.Single(w => w.FullName == "xl/_rels/workbook.xml.rels").Open())
using (XmlReader reader = XmlReader.Create(stream, _xmlSettings))
{
if (!reader.IsStartElement("Relationships", "http://schemas.openxmlformats.org/package/2006/relationships"))
return;
return null;
if (!XmlReaderHelper.ReadFirstContent(reader))
return;
return null;
while (!reader.EOF)
{
if (reader.IsStartElement("Relationship", "http://schemas.openxmlformats.org/package/2006/relationships"))
{
string rid = reader.GetAttribute("Id");
foreach (var sheet in _sheetRecords)
foreach (var sheet in sheetRecords)
{
if (sheet.Rid == rid)
{
@ -477,7 +493,10 @@ namespace MiniExcelLibs.OpenXml
}
}
}
return sheetRecords;
}
private object ReadCell(XmlReader reader, int nextColumnIndex, bool withoutCR, out int columnIndex)
{
int xfIndex = -1;
@ -518,6 +537,7 @@ namespace MiniExcelLibs.OpenXml
return value;
}
private void ConvertCellValue(string rawValue, string aT, int xfIndex, out object value)
{
const NumberStyles style = NumberStyles.Any;

View File

@ -15,22 +15,23 @@ namespace MiniExcelLibs.OpenXml
{
internal class ExcelOpenXmlSheetWriter : IExcelWriter
{
private readonly bool _printHeader;
public ExcelOpenXmlSheetWriter(bool printHeader)
private readonly UTF8Encoding _utf8WithBom = new System.Text.UTF8Encoding(true);
private Stream _stream;
public ExcelOpenXmlSheetWriter(Stream stream)
{
this._printHeader = printHeader;
this._stream = stream;
}
public void SaveAs(Stream stream, object value)
public void SaveAs(object value,bool printHeader)
{
using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, true, Utf8WithBom))
using (var archive = new ZipArchive(_stream, ZipArchiveMode.Create, true, _utf8WithBom))
{
var packages = DefualtOpenXml.GenerateDefaultOpenXml(archive);
var sheetPath = "xl/worksheets/sheet1.xml";
{
ZipArchiveEntry entry = archive.CreateEntry(sheetPath);
using (var zipStream = entry.Open())
using (StreamWriter writer = new StreamWriter(zipStream, Utf8WithBom))
using (StreamWriter writer = new StreamWriter(zipStream, _utf8WithBom))
{
if (value == null)
{
@ -105,13 +106,13 @@ namespace MiniExcelLibs.OpenXml
writer.Write($@"<?xml version=""1.0"" encoding=""utf-8""?><x:worksheet xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"">");
// dimension
var maxRowIndex = rowCount + (_printHeader && rowCount > 0 ? 1 : 0); //TODO:it can optimize
var maxRowIndex = rowCount + (printHeader && rowCount > 0 ? 1 : 0); //TODO:it can optimize
writer.Write($@"<dimension ref=""{GetDimension(maxRowIndex, maxColumnIndex)}""/><x:sheetData>");
//header
var yIndex = 1;
var xIndex = 1;
if (_printHeader)
if (printHeader)
{
var cellIndex = xIndex;
writer.Write($"<x:row r=\"{yIndex.ToString()}\">");
@ -149,7 +150,7 @@ namespace MiniExcelLibs.OpenXml
}
else if (value is DataTable)
{
GenerateSheetByDataTable(writer, archive, value as DataTable);
GenerateSheetByDataTable(writer, archive, value as DataTable, printHeader);
}
else
{
@ -284,7 +285,7 @@ namespace MiniExcelLibs.OpenXml
}
}
internal void GenerateSheetByDataTable(StreamWriter writer, ZipArchive archive, DataTable value)
internal void GenerateSheetByDataTable(StreamWriter writer, ZipArchive archive, DataTable value, bool printHeader)
{
var xy = ExcelOpenXmlUtils.ConvertCellToXY("A1");
@ -294,11 +295,11 @@ namespace MiniExcelLibs.OpenXml
var yIndex = xy.Item2;
// dimension
var maxRowIndex = value.Rows.Count + (_printHeader && value.Rows.Count > 0 ? 1 : 0);
var maxRowIndex = value.Rows.Count + (printHeader && value.Rows.Count > 0 ? 1 : 0);
var maxColumnIndex = value.Columns.Count;
writer.Write($@"<dimension ref=""{GetDimension(maxRowIndex, maxColumnIndex)}""/><x:sheetData>");
if (_printHeader)
if (printHeader)
{
writer.Write($"<x:row r=\"{yIndex.ToString()}\">");
var xIndex = xy.Item1;
@ -364,7 +365,7 @@ namespace MiniExcelLibs.OpenXml
ZipArchiveEntry entry = archive.CreateEntry("[Content_Types].xml");
using (var zipStream = entry.Open())
using (StreamWriter writer = new StreamWriter(zipStream, Utf8WithBom))
using (StreamWriter writer = new StreamWriter(zipStream, _utf8WithBom))
writer.Write(sb.ToString());
}
@ -381,7 +382,5 @@ namespace MiniExcelLibs.OpenXml
dimensionRef = $"A1:{Helpers.GetAlphabetColumnName(maxColumnIndex - 1)}{maxRowIndex}";
return dimensionRef;
}
private readonly UTF8Encoding Utf8WithBom = new System.Text.UTF8Encoding(true);
}
}

View File

@ -1,6 +1,7 @@
using Xunit;
using System.Linq;
using System;
using System.IO;
namespace MiniExcelLibs.Tests
{
@ -31,6 +32,55 @@ namespace MiniExcelLibs.Tests
{
Assert.Throws<InvalidOperationException>(() => MiniExcel.Query(path, sheetName: "xxxx").ToList());
}
using (var stream = File.OpenRead(path))
{
{
var rows = stream.Query(sheetName: "Sheet3").ToList();
Assert.Equal(5, rows.Count);
Assert.Equal(3, rows[0].A);
Assert.Equal(3, rows[0].B);
}
{
var rows = stream.Query(sheetName: "Sheet2").ToList();
Assert.Equal(12, rows.Count);
Assert.Equal(1, rows[0].A);
Assert.Equal(1, rows[0].B);
}
{
var rows = stream.Query(sheetName: "Sheet1").ToList();
Assert.Equal(12, rows.Count);
Assert.Equal(2, rows[0].A);
Assert.Equal(2, rows[0].B);
}
}
}
[Fact]
public void MultiSheetsQueryBasicTest()
{
var path = @"..\..\..\..\..\samples\xlsx\TestMultiSheet.xlsx";
using (var stream = File.OpenRead(path))
{
var sheet1 = stream.Query(sheetName: "Sheet1");
var sheet2 = stream.Query(sheetName: "Sheet2");
var sheet3 = stream.Query(sheetName: "Sheet3");
}
}
[Fact]
public void MultiSheetsQueryTest()
{
var path = @"..\..\..\..\..\samples\xlsx\TestMultiSheet.xlsx";
using (var multi = MiniExcel.QueryMultiple(path))
{
var names = multi.GetSheetNames().ToList();
Assert.Equal(new[] { "Sheet2", "Sheet1", "Sheet3" }, names);
var sheet2Rows = multi.Read().ToList();
var sheet1Rows = multi.Read().ToList();
var sheet3Rows = multi.Read().ToList();
}
}
}
}

View File

@ -354,7 +354,7 @@ namespace MiniExcelLibs.Tests
DataSet exceldatareaderResult;
using (var stream = File.OpenRead(path))
using (var reader = ExcelReaderFactory.CreateReader(stream))
using (var reader = ExcelDataReader.ExcelReaderFactory.CreateReader(stream))
{
exceldatareaderResult = reader.AsDataSet();
}