Add:MiniExcelHepler.Read and Tests

This commit is contained in:
wei 2021-03-04 17:07:36 +08:00
parent 459024b401
commit 449d2549a4
13 changed files with 248 additions and 192 deletions

BIN
samples/xlsx/Test10x10.xlsx Normal file

Binary file not shown.

Binary file not shown.

View File

@ -2,23 +2,23 @@
{
internal static class DefualtXml
{
internal const string defaultRels = @"<?xml version=""1.0"" encoding=""utf-8""?>
internal const string DefaultRels = @"<?xml version=""1.0"" encoding=""utf-8""?>
<Relationships xmlns=""http://schemas.openxmlformats.org/package/2006/relationships"">
<Relationship Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument"" Target=""/xl/workbook.xml"" Id=""Rfc2254092b6248a9"" />
</Relationships>";
internal const string defaultSheetXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
internal const string DefaultSheetXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
<x:worksheet xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"">
<x:sheetData>
</x:sheetData>
</x:worksheet>";
internal const string defaultWorkbookXmlRels = @"<?xml version=""1.0"" encoding=""utf-8""?>
internal const string DefaultWorkbookXmlRels = @"<?xml version=""1.0"" encoding=""utf-8""?>
<Relationships xmlns=""http://schemas.openxmlformats.org/package/2006/relationships"">
<Relationship Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet"" Target=""/xl/worksheets/sheet1.xml"" Id=""R1274d0d920f34a32"" />
<Relationship Type=""http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles"" Target=""/xl/styles.xml"" Id=""R3db9602ace774fdb"" />
</Relationships>";
internal const string defaultStylesXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
internal const string DefaultStylesXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
<x:styleSheet xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"">
<x:fonts>
<x:font />
@ -38,20 +38,12 @@
</x:cellXfs>
</x:styleSheet>";
internal const string defaultWorkbookXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
internal const string DefaultWorkbookXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
<x:workbook xmlns:r=""http://schemas.openxmlformats.org/officeDocument/2006/relationships""
xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"">
<x:sheets>
<x:sheet xmlns:r=""http://schemas.openxmlformats.org/officeDocument/2006/relationships"" name=""Sheet1"" sheetId=""1"" r:id=""R1274d0d920f34a32"" />
</x:sheets>
</x:workbook>";
internal const string defaultContent_TypesXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
<Types xmlns=""http://schemas.openxmlformats.org/package/2006/content-types"">
<Default Extension=""xml"" ContentType=""application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"" />
<Default Extension=""rels"" ContentType=""application/vnd.openxmlformats-package.relationships+xml"" />
<Override PartName=""/xl/worksheets/sheet1.xml"" ContentType=""application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"" />
<Override PartName=""/xl/styles.xml"" ContentType=""application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"" />
</Types>";
}
}

View File

@ -1,10 +1,12 @@
namespace MiniExcel
{
using System.Xml.Linq;
internal static class ExcelNamespaces
public static partial class MiniExcelHelper
{
internal static XNamespace excelNamespace = XNamespace.Get("http://schemas.openxmlformats.org/spreadsheetml/2006/main");
internal static XNamespace excelRelationshipsNamepace = XNamespace.Get("http://schemas.openxmlformats.org/officeDocument/2006/relationships");
internal static class ExcelNamespaces
{
internal static XNamespace excelNamespace = XNamespace.Get("http://schemas.openxmlformats.org/spreadsheetml/2006/main");
internal static XNamespace excelRelationshipsNamepace = XNamespace.Get("http://schemas.openxmlformats.org/officeDocument/2006/relationships");
}
}
}

View File

@ -6,51 +6,23 @@
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
public static class MiniExcelHelper
public static partial class MiniExcelHelper
{
internal static Dictionary<string, ZipPackageInfo> DefaultFilesTree => new Dictionary<string, ZipPackageInfo>()
private static Dictionary<string, ZipPackageInfo> GetDefaultFiles() => new Dictionary<string, ZipPackageInfo>()
{
{ @"_rels/.rels",new ZipPackageInfo(DefualtXml.defaultRels, "application/vnd.openxmlformats-package.relationships+xml")},
{ @"xl/_rels/workbook.xml.rels",new ZipPackageInfo(DefualtXml.defaultWorkbookXmlRels, "application/vnd.openxmlformats-package.relationships+xml")},
{ @"xl/styles.xml",new ZipPackageInfo(DefualtXml.defaultStylesXml, "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml")},
{ @"xl/workbook.xml",new ZipPackageInfo(DefualtXml.defaultWorkbookXml, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml")},
{ @"xl/worksheets/sheet1.xml",new ZipPackageInfo(DefualtXml.defaultSheetXml, "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml")},
{ @"_rels/.rels",new ZipPackageInfo(DefualtXml.DefaultRels, "application/vnd.openxmlformats-package.relationships+xml")},
{ @"xl/_rels/workbook.xml.rels",new ZipPackageInfo(DefualtXml.DefaultWorkbookXmlRels, "application/vnd.openxmlformats-package.relationships+xml")},
{ @"xl/styles.xml",new ZipPackageInfo(DefualtXml.DefaultStylesXml, "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml")},
{ @"xl/workbook.xml",new ZipPackageInfo(DefualtXml.DefaultWorkbookXml, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml")},
{ @"xl/worksheets/sheet1.xml",new ZipPackageInfo(DefualtXml.DefaultSheetXml, "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml")},
};
private static FileStream CreateZipFileStream(string path, Dictionary<string, object> filesTree)
{
var utf8WithBom = new System.Text.UTF8Encoding(true); // 用true来指定包含bom
using (FileStream stream = new FileStream(path, FileMode.CreateNew))
{
using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Create, false, UTF8Encoding.UTF8))
{
foreach (var fileTree in filesTree)
{
ZipArchiveEntry entry = archive.CreateEntry(fileTree.Key);
using (var zipStream = entry.Open())
{
//var bytes = utf8WithBom.GetBytes(fileTree.Value.ToString());
//zipStream.Write(bytes, 0, bytes.Length);
using (StreamWriter writer = new StreamWriter(zipStream, utf8WithBom))
{
writer.Write(fileTree.Value.ToString()); //entry contents "baz123"
}
}
}
}
return stream;
}
}
public static void Create(string path, object value, string startCell = "A1", bool printHeader = true)
public static void Create(string filePath, object value, string startCell = "A1", bool printHeader = true)
{
var xy = XlsxUtils.ConvertCellToXY(startCell);
var filesTree = DefaultFilesTree;
var defaultFiles = GetDefaultFiles();
{
var sb = new StringBuilder();
@ -121,86 +93,88 @@
}
}
filesTree[@"xl/worksheets/sheet1.xml"].Xml = $@"<?xml version=""1.0"" encoding=""utf-8""?>
defaultFiles[@"xl/worksheets/sheet1.xml"].Xml = $@"<?xml version=""1.0"" encoding=""utf-8""?>
<x:worksheet xmlns:x=""http://schemas.openxmlformats.org/spreadsheetml/2006/main"">
<x:sheetData>{sb.ToString()}</x:sheetData>
</x:worksheet>";
}
CreateXlsxFile(path, filesTree);
CreateXlsxFile(filePath, defaultFiles);
}
//public static Dictionary<string, object> Read(string fileName)
//{
// var parsedCells = new Dictionary<string, object>();
// using (Package xlsxPackage = Package.Open(fileName, FileMode.Open, FileAccess.Read))
// {
// var allParts = xlsxPackage.GetParts();
public static XlsxWorkbook Read(string path)
{
using (FileStream stream = new FileStream(path, FileMode.Open))
using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Read, false, UTF8Encoding.UTF8))
{
return GetXlsxWorkbook(archive);
}
}
private static string ConvertToString(ZipArchiveEntry entry)
{
using (var eStream = entry.Open())
using (var reader = new StreamReader(eStream))
return reader.ReadToEnd();
}
private static XlsxWorkbook GetXlsxWorkbook(ZipArchive archive)
{
XlsxWorkbook w = new XlsxWorkbook();
{
var xml = ConvertToString(archive.Entries.SingleOrDefault(s => s.FullName == ("xl/workbook.xml")));
var xl = XElement.Parse(xml);
var xSheets = xl.Descendants(ExcelNamespaces.excelNamespace + "sheet").Select(s => new
{
name = s.Attribute("name")?.Value?.ToString(),
sheetId = s.Attribute("sheetId")?.Value?.ToString(),
id = s.Attribute("id")?.Value?.ToString()
});
var wss = new List<XlsxWorksheet>();
w.Worksheets = wss;
foreach (var xs in xSheets)
{
var e = archive.Entries.SingleOrDefault(s => s.FullName == ($"xl/worksheets/{xs.name.ToLowerInvariant()}.xml"));
var ws = GetXlsxWorksheet(ConvertToString(e));
ws.Name = xs.name;
ws.SheetID = xs.sheetId;
ws.ID = xs.id;
wss.Add(ws);
}
}
return w;
}
private static XlsxWorksheet GetXlsxWorksheet(string xml)
{
var xl = XElement.Parse(xml);
var rows = xl.Descendants(ExcelNamespaces.excelNamespace + "row")
.Select(
x => new XlsxRow
{
RowNumber = x.Attribute("r")?.Value?.ToString(),
Cells = x.Descendants(ExcelNamespaces.excelNamespace + "c")?.Select(cell =>
new XlsxCell
{
Address = cell.Attribute("r")?.Value?.ToString(),
Value = cell.Descendants(ExcelNamespaces.excelNamespace + "v").SingleOrDefault()?.Value?.ToString(),
FormulaA1 = cell.Descendants(ExcelNamespaces.excelNamespace + "f").SingleOrDefault()?.Value?.ToString(),
DataType = cell.Attribute("t")?.Value?.ToString(),
}
).ToList()
}
);
var ws = new XlsxWorksheet();
ws.Rows = rows;
return ws;
}
// var worksheetElement = GetFirstWorksheet(allParts);
// var cells = from c in worksheetElement.Descendants(ExcelNamespaces.excelNamespace + "c")
// select c;
// var sharedStrings = GetSharedStrings(allParts);
// foreach (XElement cell in cells)
// {
// var r = cell.Attribute("r");
// {
// var cellPosition = r.Value;
// var v = cell.Descendants(ExcelNamespaces.excelNamespace + "v").SingleOrDefault()?.Value;
// var t = cell.Attribute("t")?.Value;
// if (t == "s")
// {
// parsedCells.Add(cellPosition, sharedStrings[Convert.ToInt32(v)]);
// }
// else
// {
// parsedCells.Add(cellPosition, v);
// }
// }
// }
// }
// return parsedCells;
//}
//private static Dictionary<int, string> GetSharedStrings(PackagePartCollection allParts)
//{
// var sharedStringsPart = (from part in allParts
// where part.ContentType.Equals("application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml")
// select part).SingleOrDefault();
// if (sharedStringsPart == null)
// return null;
// Dictionary<int, string> sharedStrings = new Dictionary<int, string>();
// var sharedStringsElement = XElement.Load(XmlReader.Create(sharedStringsPart.GetStream()));
// IEnumerable<XElement> sharedStringsElements = from s in sharedStringsElement.Descendants(ExcelNamespaces.excelNamespace + "t")
// select s;
// int Counter = 0;
// foreach (XElement sharedString in sharedStringsElements)
// {
// sharedStrings.Add(Counter, sharedString.Value);
// Counter++;
// }
// return sharedStrings;
//}
//private static XElement GetFirstWorksheet(PackagePartCollection allParts)
//{
// PackagePart worksheetPart = (from part in allParts
// where part.ContentType.Equals("application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml")
// select part).FirstOrDefault();
// return XElement.Load(XmlReader.Create(worksheetPart.GetStream()));
//}
private readonly static UTF8Encoding _utf8WithBom = new System.Text.UTF8Encoding(true);
private readonly static UTF8Encoding Utf8WithBom = new System.Text.UTF8Encoding(true);
private static void CreateXlsxFile(string path, Dictionary<string, ZipPackageInfo> zipPackageInfos)
{
using (FileStream stream = new FileStream(path, FileMode.CreateNew))
using (FileStream stream = new FileStream(path, FileMode.CreateNew))
using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Create, false, UTF8Encoding.UTF8))
{
//[Content_Types].xml
@ -217,7 +191,7 @@
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());
}
@ -225,75 +199,10 @@
{
ZipArchiveEntry entry = archive.CreateEntry(p.Key);
using (var zipStream = entry.Open())
using (StreamWriter writer = new StreamWriter(zipStream, _utf8WithBom))
using (StreamWriter writer = new StreamWriter(zipStream, Utf8WithBom))
writer.Write(p.Value.Xml.ToString());
}
}
}
}
internal static class XlsxUtils
{
internal static string GetValue(object value) => value == null ? "" : value.ToString().Replace("<", "&lt;").Replace(">", "&gt;");
/// <summary>X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2)</summary>
internal static string ConvertXyToCell(Tuple<int, int> xy)
{
return ConvertXyToCell(xy.Item1, xy.Item2);
}
/// <summary>X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2)</summary>
internal static string ConvertXyToCell(int x, int y)
{
int dividend = x;
string columnName = String.Empty;
int modulo;
while (dividend > 0)
{
modulo = (dividend - 1) % 26;
columnName = Convert.ToChar(65 + modulo).ToString() + columnName;
dividend = (int)((dividend - modulo) / 26);
}
return $"{columnName}{y}";
}
/// <summary>X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2)</summary>
internal static Tuple<int, int> ConvertCellToXY(string cell)
{
const string keys = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const int mode = 26;
var x = 0;
var cellLetter = GetCellLetter(cell);
//AA=27,ZZ=702
for (int i = 0; i < cellLetter.Length; i++)
x = x * mode + keys.IndexOf(cellLetter[i]);
var cellNumber = GetCellNumber(cell);
return Tuple.Create(x, int.Parse(cellNumber));
}
internal static string GetCellNumber(string cell)
{
string cellNumber = string.Empty;
for (int i = 0; i < cell.Length; i++)
{
if (Char.IsDigit(cell[i]))
cellNumber += cell[i];
}
return cellNumber;
}
internal static string GetCellLetter(string cell)
{
string GetCellLetter = string.Empty;
for (int i = 0; i < cell.Length; i++)
{
if (Char.IsLetter(cell[i]))
GetCellLetter += cell[i];
}
return GetCellLetter;
}
}
}

10
src/MiniExcel/XlsxCell.cs Normal file
View File

@ -0,0 +1,10 @@
namespace MiniExcel
{
public class XlsxCell
{
public string Address { get; set; }
public object Value { get; set; }
public string DataType { get; set; }
public string FormulaA1 { get; set; }
}
}

9
src/MiniExcel/XlsxRow.cs Normal file
View File

@ -0,0 +1,9 @@
namespace MiniExcel
{
using System.Collections.Generic;
public class XlsxRow
{
public string RowNumber { get; set; }
public IEnumerable<XlsxCell> Cells { get; set; }
}
}

View File

@ -0,0 +1,69 @@
namespace MiniExcel
{
using System;
internal static class XlsxUtils
{
internal static string GetValue(object value) => value == null ? "" : value.ToString().Replace("<", "&lt;").Replace(">", "&gt;");
/// <summary>X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2)</summary>
internal static string ConvertXyToCell(Tuple<int, int> xy)
{
return ConvertXyToCell(xy.Item1, xy.Item2);
}
/// <summary>X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2)</summary>
internal static string ConvertXyToCell(int x, int y)
{
int dividend = x;
string columnName = String.Empty;
int modulo;
while (dividend > 0)
{
modulo = (dividend - 1) % 26;
columnName = Convert.ToChar(65 + modulo).ToString() + columnName;
dividend = (int)((dividend - modulo) / 26);
}
return $"{columnName}{y}";
}
/// <summary>X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2)</summary>
internal static Tuple<int, int> ConvertCellToXY(string cell)
{
const string keys = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const int mode = 26;
var x = 0;
var cellLetter = GetCellLetter(cell);
//AA=27,ZZ=702
for (int i = 0; i < cellLetter.Length; i++)
x = x * mode + keys.IndexOf(cellLetter[i]);
var cellNumber = GetCellNumber(cell);
return Tuple.Create(x, int.Parse(cellNumber));
}
internal static string GetCellNumber(string cell)
{
string cellNumber = string.Empty;
for (int i = 0; i < cell.Length; i++)
{
if (Char.IsDigit(cell[i]))
cellNumber += cell[i];
}
return cellNumber;
}
internal static string GetCellLetter(string cell)
{
string GetCellLetter = string.Empty;
for (int i = 0; i < cell.Length; i++)
{
if (Char.IsLetter(cell[i]))
GetCellLetter += cell[i];
}
return GetCellLetter;
}
}
}

View File

@ -0,0 +1,18 @@
namespace MiniExcel
{
using System.Collections.Generic;
using System.Linq;
public class XlsxWorkbook
{
public IEnumerable<XlsxWorksheet> Worksheets { get; set; }
public XlsxWorksheet GetWorksheet(int index)
{
return (Worksheets as IList<XlsxWorksheet>)[index];
}
public XlsxWorksheet GetWorksheet(string sheetName)
{
return Worksheets.Single(w=>w.Name== sheetName);
}
}
}

View File

@ -0,0 +1,11 @@
namespace MiniExcel
{
using System.Collections.Generic;
public class XlsxWorksheet
{
public string ID { get; set; }
public string SheetID { get; set; }
public string Name { get; set; }
public IEnumerable<XlsxRow> Rows { get; set; }
}
}

View File

@ -4,7 +4,6 @@
{
public string Xml { get; set; }
public string ContentType { get; set; }
//public CompressionOption CompressionOption { get; set; } = CompressionOption.Normal;
public ZipPackageInfo(string xml, string contentType)
{
Xml = xml;

View File

@ -14,6 +14,43 @@ namespace MiniExcel.Tests
{
public class MiniExcelHelperTests
{
[Fact()]
public void ReadMultipleSheetsTest()
{
var path = @"..\..\..\..\..\samples\xlsx\TestMultiSheet.xlsx";
var w = MiniExcelHelper.Read(path);
var wss = w.Worksheets;
Assert.True(wss.Count() == 3);
Assert.True(w.GetWorksheet(0).Name == "Sheet2");
Assert.True(w.GetWorksheet(1).Name == "Sheet1");
Assert.True(w.GetWorksheet(2).Name == "Sheet3");
Assert.True(w.GetWorksheet("Sheet2").Name == "Sheet2");
Assert.True(w.GetWorksheet("Sheet1").Name == "Sheet1");
Assert.True(w.GetWorksheet("Sheet3").Name == "Sheet3");
}
[Fact()]
public void ReadForeachSheetsRowsCellsTest()
{
var path = @"..\..\..\..\..\samples\xlsx\TestMultiSheet.xlsx";
var w = MiniExcelHelper.Read(path);
var wss = w.Worksheets;
foreach (var ws in wss)
{
Console.WriteLine($"==== {ws.SheetID}.Sheet Name : {ws.Name} ====");
foreach (var row in ws.Rows)
{
Console.Write($"RowNumber:{row.RowNumber} | ");
foreach (var cell in row.Cells)
Console.Write($"Address:{cell.Address}&value:{cell.Value}");
Console.WriteLine();
}
}
}
[Fact()]
public void CreateTest()
{

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netcoreapp3.1;net461;net5.0</TargetFrameworks>
<TargetFrameworks>netcoreapp2.0;net461;</TargetFrameworks>
<IsPackable>false</IsPackable>
</PropertyGroup>