diff --git a/.editorconfig b/.editorconfig index 68c52882..527d6172 100644 --- a/.editorconfig +++ b/.editorconfig @@ -28,3 +28,5 @@ dotnet_diagnostic.CA2007.severity = error # CA2016: Forward the CancellationToken parameter to methods that take one dotnet_diagnostic.CA2016.severity = error + +csharp_style_namespace_declarations = file_scoped:silent \ No newline at end of file diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 86ec56e9..361346e3 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -13,10 +13,10 @@ name: "CodeQL" on: push: - branches: [ master ] + branches: [ master, v1.x-maintenance ] pull_request: # The branches below must be a subset of the branches above - branches: [ master ] + branches: [ master, v1.x-maintenance ] schedule: - cron: '18 0 * * 0' diff --git a/MiniExcel.sln b/MiniExcel.sln index 8d3d52f2..bebab6af 100644 --- a/MiniExcel.sln +++ b/MiniExcel.sln @@ -18,6 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs and setting", "Docs an README.zh-Hant.md = README.zh-Hant.md .github\workflows\benchmark.yml = .github\workflows\benchmark.yml .github\workflows\codeql-analysis.yml = .github\workflows\codeql-analysis.yml + docs\core_logic_diagram.drawio = docs\core_logic_diagram.drawio EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{CC1E0601-AEC9-42D7-8F6A-3FB3939EED16}" diff --git a/README.md b/README.md index b02bd0a6..3328916f 100644 --- a/README.md +++ b/README.md @@ -1457,6 +1457,8 @@ MiniExcel.SaveAs(path, reader,configuration:config); #### 6. Batch Add Image (MiniExcel.AddPicture) +Please add pictures before batch generate rows data, or system will load large memory usage when calling AddPicture. + ```csharp var images = new[] { diff --git a/README.zh-CN.md b/README.zh-CN.md index 1a938305..e55b6a1a 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1634,6 +1634,8 @@ public class Order #### 6. 批量添加/插入图片 (MiniExcel.AddPicture) +请在批量生成行数据之前添加图片,否则在调用 AddPicture 时系统会占用大量内存。 + ```csharp var images = new[] { diff --git a/README.zh-Hant.md b/README.zh-Hant.md index ff1562b3..13b3c64d 100644 --- a/README.zh-Hant.md +++ b/README.zh-Hant.md @@ -1368,6 +1368,8 @@ MiniExcel.SaveAs(path, reader,configuration:config); #### 6. 批量添加/插入圖片 (MiniExcel.AddPicture) +請在批量生成行資料之前新增圖片,否則在呼叫 AddPicture 時系統將會佔用大量記憶體。 + ```csharp var images = new[] { diff --git a/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/QueryXlsxBenchmark.cs b/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/QueryXlsxBenchmark.cs index 1e429e3b..f2f9a1a5 100644 --- a/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/QueryXlsxBenchmark.cs +++ b/benchmarks/MiniExcel.Benchmarks/BenchmarkSections/QueryXlsxBenchmark.cs @@ -113,12 +113,12 @@ public void NPOI_Query_Test() for (var i = start; i <= end; i++) { - var row = worksheet.GetRow(i); - if (row == null) - continue; - for (var j = row.FirstCellNum; j <= row.LastCellNum; j++) + if (worksheet.GetRow(i) is { } row) { - var cellValue = row.GetCell(j)?.StringCellValue; + for (var j = row.FirstCellNum; j <= row.LastCellNum; j++) + { + var cellValue = row.GetCell(j)?.StringCellValue; + } } } } diff --git a/docs/README.md b/docs/README.md index cf28a705..b5fe2077 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,6 +23,14 @@ --- +### 1.41.3 +- [New] Adding QuoteWhitespaces option to CsvConfiguration for adding double quotes to string containing whitespaces #790 (via @michelebastione ) +- [Bug] Fixed bug that made the DynamicExcelColumn property "Ignore" not work when generating using an IDataReader as source #584 (via @michelebastione ) +- [Bug] v1.41.2 AddPicture not working #814 (via @shps951023) +- [Bug] v1.41.1 AddPicture image max column and row are 2 cells #815 (via @shps951023) +- [Bug] AddPicture get error same export file and second time. #817 (via @shps951023) + + ### 1.41.2 - [New] Fixes enum behaviour and adds support for DescriptionAttribute when saving by template (via @michelebastione ) - [Bug] SaveAsByTemplate - Excel Dimension Xml is null #459 (via @michelebastione ) diff --git a/docs/README.zh-CN.md b/docs/README.zh-CN.md index b1df9803..7b9d4fe6 100644 --- a/docs/README.zh-CN.md +++ b/docs/README.zh-CN.md @@ -27,6 +27,17 @@ +### 1.41.3 + +* 【新增】在 `CsvConfiguration` 中新增 `QuoteWhitespaces` 选项,用于对空白单元的字符串加上双引号 #790(贡献者:@michelebastione) +* 【修复】修正使用 `IDataReader` 作为数据源时,`DynamicExcelColumn` 的 `Ignore` 属性无效的问题 #584(贡献者:@michelebastione) +* 【修复】v1.41.2 中 `AddPicture` 功能无法使用的问题 #814(贡献者:@shps951023) +* 【修复】v1.41.1 中 `AddPicture` 插入的图片最大列和行仅限于2个单元格的问题 #815(贡献者:@shps951023) +* 【修复】`AddPicture` 在导出文件后再次执行时出现错误的问题 #817(贡献者:@shps951023) + + + + ### 1.41.2 - [New] 增加 enum behaviour and adds support for DescriptionAttribute when saving by template (via @michelebastione ) - [Bug] SaveAsByTemplate - Excel Dimension Xml is null #459 (via @michelebastione ) diff --git a/docs/README.zh-Hant.md b/docs/README.zh-Hant.md index 2dfbbd81..b019e584 100644 --- a/docs/README.zh-Hant.md +++ b/docs/README.zh-Hant.md @@ -25,6 +25,16 @@ --- + +### 1.41.3 + +* 【新增】在 CsvConfiguration 中新增 `QuoteWhitespaces` 選項,讓包含空白字元的字串自動加上雙引號 #790(由 @michelebastione 提供) +* 【修正】修正當使用 IDataReader 作為資料來源時,DynamicExcelColumn 的 `Ignore` 屬性無效的問題 #584(由 @michelebastione 提供) +* 【修正】v1.41.2 中 `AddPicture` 功能無法使用的問題 #814(由 @shps951023 提供) +* 【修正】v1.41.1 中 `AddPicture` 插入的圖片最大列與欄僅為兩格儲存格的問題 #815(由 @shps951023 提供) +* 【修正】當再次使用相同匯出檔案時,`AddPicture` 發生錯誤的問題 #817(由 @shps951023 提供) + + ### 1.41.2 - [New] 增加 enum behaviour and adds support for DescriptionAttribute when saving by template (via @michelebastione ) - [Bug] SaveAsByTemplate - Excel Dimension Xml is null #459 (via @michelebastione ) diff --git a/docs/core logic 1.drawio b/docs/core logic 1.drawio deleted file mode 100644 index 6b229705..00000000 --- a/docs/core logic 1.drawio +++ /dev/null @@ -1 +0,0 @@ -7Vzdj6o4FP9r+rgJ35ZHUdxNdszcZB52sm8IVckANQWvzv3rty1FscW9zG68ZWBetByKyPn1fPcA7EV+/p1Eh/0aJygDlpGcgb0ElmVahkW/GOW9pswMrybsSJqISVfCS/oDCaIhqMc0QeXNxArjrEoPt8QYFwWKqxtaRAg+3U7b4uz2rodohxTCSxxlKvWvNKn2NRW6xpX+B0p3++bOpiHO5FEzWRDKfZTgU4tkh8BeEIyrepSfFyhjzGv4Ul+3unP28scIKqo+F6yTp/wt+vu49s6nYvdalXP0529O/Svfo+woHvj16eUVhB7wPQBnIHRAEICAYuZl9C7BhtDRjo1ACMF8BuY2CF326dPBDASUGIrnrd4bJtJHP7BhWaEDvfSASJqjChFB+nY9Dk77tEIvhyhm8090TVHavsozemTS4TY9o2aV1MdZtsAZJvxGdhIhuI0pvawIfkOtM14M0WbL/nf9wIhU6HyXk+YFH7qwEaZ/jrzTKeKCy+IUa9q0xfHpukLMBvZ9a3VAQYvEotxdfvqKGx0I6D4Ao6vAGJ5jLoktmCimIfANNghcAE2G1xwCGCh4EXwsEsRuaPSBRAMEjoSA0xMB71EIeAoCHZxXhOj5gIrX9dPYBcaGQxMY2AGXw7QX5BIDKXQLhtvcBNATAAaegtPHRGMLYxR3MnsDXcc1HiIalu1rFo1GW8qyAYEPGWspgylrQx/MA+A7+mTh18AjGw/L6onPw2TBNL/wua+rBoCPpeCzTotUWPj/pZESF8HE6WIptDa25z1EI9najbVpKxx9qQiK8qtvpM8i/xJIZC1ka7fIphqKsMgiMLinCnmIseRayGTGmJ5iRtoduzpyZXOuXx2pwQb3lCBDhsJCwYE8OgyWAPrcgKyArwYZI/NxZaD0+7hmd0wSrJgVZ0AFXJBmTOPVYSGLUhZjB8rzBwfUbOrmSHa6BmCO1Ajx2/PiuSsP5jFL5FNhM5L3IsrTuGPSyPGTld8A8PO73YlAxDJUpgJut/wV8yKYg0FdC6sL31ZegKUDFl0URa2OHHBZieoHvLmZBDh0mWfCALf5gAJl88Q2z3D7/tj9Rxko/f6j1ZFuEFKXpN8bseNlCMugPEPNWXq31oSOa47Fj/Rwb/a4nRo5nnOgdnlUsxZNTSIqouy9TEv2hATHqCwVeD5dBcLxezL8YUkNS01qdMjIt4iUabHrLVOXCsUUpUp2SwcgVWqWpJGqFYlydMLkTUHlE1QpXKi7SmGpaY01yrH45RF7CLLpcGfaPQQ1cTENKGR9MwAo1NTEp649ePrNtJpYEMmekYeMsp7x9BtTNUfA7OcGY2pD2W6NKGeMrD/VzACbW+4RqsauluTsjn611GQb2mpJbD1UcYpxQR+/THGhUcS01CD0+6u2GuzjY3U4apQZPVWGAUChBuTTMDyyV6Xf8NhqqN5dXKDsaFUVxg2TrLwGAJMabC/RFhGCkk5DE55RXB0rbmjkcwSf6DUb9rx8OG4sZe03ACzV8D7iV/EWgZE7cDIcA3Dg1LjyXhHgbi/CT3OXNy0KsN6WMgPBgpX6JpnPlEOwWd9o+HHrQI2Gp9W5AI2eEDwsIWGrUfC/y5LUzfBVQviI86lf5JoFOIHeB+j3tHMPEy6na2+9y/gKXd44t2Sb7EfufshmB/Yt9jxOBtRYeHqwyKppALCocfGnrjaYhnbr7qgx7MT2lsrax9dvgdVY9Kvu0CMjNAAF1bWl/k7vQ+1EwSXfVTpuJ1cGagBObkd+obv3IeBvLpho78MAgLpbDp+KfZLdsAHYp14ZgXbFokcq7m6XxGDyAVqqHPrRbl5l87P8z6V5olajq9vmiVWreaLHavhvPRVTXDCyyh7AgrmT05A6Lejq4O27wgnS2BKjp8jySG+VHl5fHMXPtV6/ZYf/AA== \ No newline at end of file diff --git a/docs/core_logic_diagram.drawio b/docs/core_logic_diagram.drawio new file mode 100644 index 00000000..2080c187 --- /dev/null +++ b/docs/core_logic_diagram.drawio @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/samples/xlsx/TestIssue815_1.xlsx b/samples/xlsx/TestIssue815_1.xlsx new file mode 100644 index 00000000..a2e266e8 Binary files /dev/null and b/samples/xlsx/TestIssue815_1.xlsx differ diff --git a/samples/xlsx/TestIssue815_1/[Content_Types].xml b/samples/xlsx/TestIssue815_1/[Content_Types].xml new file mode 100644 index 00000000..431bc4e5 --- /dev/null +++ b/samples/xlsx/TestIssue815_1/[Content_Types].xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_1/_rels/.rels b/samples/xlsx/TestIssue815_1/_rels/.rels new file mode 100644 index 00000000..74bfd8d9 --- /dev/null +++ b/samples/xlsx/TestIssue815_1/_rels/.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_1/docProps/app.xml b/samples/xlsx/TestIssue815_1/docProps/app.xml new file mode 100644 index 00000000..2e1cee69 --- /dev/null +++ b/samples/xlsx/TestIssue815_1/docProps/app.xml @@ -0,0 +1,2 @@ + +Microsoft Excel0falseWorksheets1Sheet1falsefalsefalse16.0300 \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_1/docProps/core.xml b/samples/xlsx/TestIssue815_1/docProps/core.xml new file mode 100644 index 00000000..5ff2c2da --- /dev/null +++ b/samples/xlsx/TestIssue815_1/docProps/core.xml @@ -0,0 +1,2 @@ + +WeiWei Lin2025-06-20T17:24:16Z2025-06-20T17:24:16Z \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_1/xl/_rels/workbook.xml.rels b/samples/xlsx/TestIssue815_1/xl/_rels/workbook.xml.rels new file mode 100644 index 00000000..84cb2b36 --- /dev/null +++ b/samples/xlsx/TestIssue815_1/xl/_rels/workbook.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_1/xl/drawings/_rels/drawing1.xml.rels b/samples/xlsx/TestIssue815_1/xl/drawings/_rels/drawing1.xml.rels new file mode 100644 index 00000000..0d3c6177 --- /dev/null +++ b/samples/xlsx/TestIssue815_1/xl/drawings/_rels/drawing1.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_1/xl/drawings/drawing1.xml b/samples/xlsx/TestIssue815_1/xl/drawings/drawing1.xml new file mode 100644 index 00000000..621e88ba --- /dev/null +++ b/samples/xlsx/TestIssue815_1/xl/drawings/drawing1.xml @@ -0,0 +1,86 @@ + + + + + 2 + 0 + 4 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 12 + 0 + 6 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_1/xl/media/image1.Png b/samples/xlsx/TestIssue815_1/xl/media/image1.Png new file mode 100644 index 00000000..fe3479dd Binary files /dev/null and b/samples/xlsx/TestIssue815_1/xl/media/image1.Png differ diff --git a/samples/xlsx/TestIssue815_1/xl/styles.xml b/samples/xlsx/TestIssue815_1/xl/styles.xml new file mode 100644 index 00000000..606c7134 --- /dev/null +++ b/samples/xlsx/TestIssue815_1/xl/styles.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_1/xl/theme/theme1.xml b/samples/xlsx/TestIssue815_1/xl/theme/theme1.xml new file mode 100644 index 00000000..4a95cff0 --- /dev/null +++ b/samples/xlsx/TestIssue815_1/xl/theme/theme1.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_1/xl/workbook.xml b/samples/xlsx/TestIssue815_1/xl/workbook.xml new file mode 100644 index 00000000..75d5a5ad --- /dev/null +++ b/samples/xlsx/TestIssue815_1/xl/workbook.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_1/xl/worksheets/_rels/sheet1.xml.rels b/samples/xlsx/TestIssue815_1/xl/worksheets/_rels/sheet1.xml.rels new file mode 100644 index 00000000..205832e9 --- /dev/null +++ b/samples/xlsx/TestIssue815_1/xl/worksheets/_rels/sheet1.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_1/xl/worksheets/sheet1.xml b/samples/xlsx/TestIssue815_1/xl/worksheets/sheet1.xml new file mode 100644 index 00000000..cf3c093e --- /dev/null +++ b/samples/xlsx/TestIssue815_1/xl/worksheets/sheet1.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_2.xlsx b/samples/xlsx/TestIssue815_2.xlsx new file mode 100644 index 00000000..d64d0771 Binary files /dev/null and b/samples/xlsx/TestIssue815_2.xlsx differ diff --git a/samples/xlsx/TestIssue815_2/[Content_Types].xml b/samples/xlsx/TestIssue815_2/[Content_Types].xml new file mode 100644 index 00000000..431bc4e5 --- /dev/null +++ b/samples/xlsx/TestIssue815_2/[Content_Types].xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_2/_rels/.rels b/samples/xlsx/TestIssue815_2/_rels/.rels new file mode 100644 index 00000000..74bfd8d9 --- /dev/null +++ b/samples/xlsx/TestIssue815_2/_rels/.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_2/docProps/app.xml b/samples/xlsx/TestIssue815_2/docProps/app.xml new file mode 100644 index 00000000..2e1cee69 --- /dev/null +++ b/samples/xlsx/TestIssue815_2/docProps/app.xml @@ -0,0 +1,2 @@ + +Microsoft Excel0falseWorksheets1Sheet1falsefalsefalse16.0300 \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_2/docProps/core.xml b/samples/xlsx/TestIssue815_2/docProps/core.xml new file mode 100644 index 00000000..879b1b76 --- /dev/null +++ b/samples/xlsx/TestIssue815_2/docProps/core.xml @@ -0,0 +1,2 @@ + +WeiWei Lin2025-06-20T17:25:32Z2025-06-20T17:25:32Z \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_2/xl/_rels/workbook.xml.rels b/samples/xlsx/TestIssue815_2/xl/_rels/workbook.xml.rels new file mode 100644 index 00000000..84cb2b36 --- /dev/null +++ b/samples/xlsx/TestIssue815_2/xl/_rels/workbook.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_2/xl/drawings/_rels/drawing1.xml.rels b/samples/xlsx/TestIssue815_2/xl/drawings/_rels/drawing1.xml.rels new file mode 100644 index 00000000..0d3c6177 --- /dev/null +++ b/samples/xlsx/TestIssue815_2/xl/drawings/_rels/drawing1.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_2/xl/drawings/drawing1.xml b/samples/xlsx/TestIssue815_2/xl/drawings/drawing1.xml new file mode 100644 index 00000000..a7a0b29c --- /dev/null +++ b/samples/xlsx/TestIssue815_2/xl/drawings/drawing1.xml @@ -0,0 +1,86 @@ + + + + + 2 + 0 + 4 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 12 + 0 + 6 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_2/xl/media/image1.Png b/samples/xlsx/TestIssue815_2/xl/media/image1.Png new file mode 100644 index 00000000..fe3479dd Binary files /dev/null and b/samples/xlsx/TestIssue815_2/xl/media/image1.Png differ diff --git a/samples/xlsx/TestIssue815_2/xl/styles.xml b/samples/xlsx/TestIssue815_2/xl/styles.xml new file mode 100644 index 00000000..606c7134 --- /dev/null +++ b/samples/xlsx/TestIssue815_2/xl/styles.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_2/xl/theme/theme1.xml b/samples/xlsx/TestIssue815_2/xl/theme/theme1.xml new file mode 100644 index 00000000..4a95cff0 --- /dev/null +++ b/samples/xlsx/TestIssue815_2/xl/theme/theme1.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_2/xl/workbook.xml b/samples/xlsx/TestIssue815_2/xl/workbook.xml new file mode 100644 index 00000000..5f963b6a --- /dev/null +++ b/samples/xlsx/TestIssue815_2/xl/workbook.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_2/xl/worksheets/_rels/sheet1.xml.rels b/samples/xlsx/TestIssue815_2/xl/worksheets/_rels/sheet1.xml.rels new file mode 100644 index 00000000..205832e9 --- /dev/null +++ b/samples/xlsx/TestIssue815_2/xl/worksheets/_rels/sheet1.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue815_2/xl/worksheets/sheet1.xml b/samples/xlsx/TestIssue815_2/xl/worksheets/sheet1.xml new file mode 100644 index 00000000..3893564e --- /dev/null +++ b/samples/xlsx/TestIssue815_2/xl/worksheets/sheet1.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_1.xlsx b/samples/xlsx/TestIssue816_1.xlsx new file mode 100644 index 00000000..9cbb6ef6 Binary files /dev/null and b/samples/xlsx/TestIssue816_1.xlsx differ diff --git a/samples/xlsx/TestIssue816_1/[Content_Types].xml b/samples/xlsx/TestIssue816_1/[Content_Types].xml new file mode 100644 index 00000000..51b87737 --- /dev/null +++ b/samples/xlsx/TestIssue816_1/[Content_Types].xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_1/_rels/.rels b/samples/xlsx/TestIssue816_1/_rels/.rels new file mode 100644 index 00000000..74bfd8d9 --- /dev/null +++ b/samples/xlsx/TestIssue816_1/_rels/.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_1/docProps/app.xml b/samples/xlsx/TestIssue816_1/docProps/app.xml new file mode 100644 index 00000000..3db85d1a --- /dev/null +++ b/samples/xlsx/TestIssue816_1/docProps/app.xml @@ -0,0 +1,2 @@ + +Microsoft Excel0falseWorksheets1Demofalsefalsefalse16.0300 \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_1/docProps/core.xml b/samples/xlsx/TestIssue816_1/docProps/core.xml new file mode 100644 index 00000000..84719d57 --- /dev/null +++ b/samples/xlsx/TestIssue816_1/docProps/core.xml @@ -0,0 +1,2 @@ + +WeiWei Lin2015-06-05T18:17:20Z2025-04-20T14:24:59Z \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_1/xl/_rels/workbook.xml.rels b/samples/xlsx/TestIssue816_1/xl/_rels/workbook.xml.rels new file mode 100644 index 00000000..65485f71 --- /dev/null +++ b/samples/xlsx/TestIssue816_1/xl/_rels/workbook.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_1/xl/drawings/_rels/drawing8155941d47d148b990d1736b0f18eb14.xml.rels b/samples/xlsx/TestIssue816_1/xl/drawings/_rels/drawing8155941d47d148b990d1736b0f18eb14.xml.rels new file mode 100644 index 00000000..f54325b6 --- /dev/null +++ b/samples/xlsx/TestIssue816_1/xl/drawings/_rels/drawing8155941d47d148b990d1736b0f18eb14.xml.rels @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_1/xl/drawings/drawing8155941d47d148b990d1736b0f18eb14.xml b/samples/xlsx/TestIssue816_1/xl/drawings/drawing8155941d47d148b990d1736b0f18eb14.xml new file mode 100644 index 00000000..4b79ac58 --- /dev/null +++ b/samples/xlsx/TestIssue816_1/xl/drawings/drawing8155941d47d148b990d1736b0f18eb14.xml @@ -0,0 +1,119 @@ + + + + 2 + 0 + 2 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 0 + 8 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4 + 0 + 8 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_1/xl/media/image6ef1fcd090e14c71a7feddd858da8770.png b/samples/xlsx/TestIssue816_1/xl/media/image6ef1fcd090e14c71a7feddd858da8770.png new file mode 100644 index 00000000..8efaf819 Binary files /dev/null and b/samples/xlsx/TestIssue816_1/xl/media/image6ef1fcd090e14c71a7feddd858da8770.png differ diff --git a/samples/xlsx/TestIssue816_1/xl/media/imagef63d9dc50eff42f7964d4d5830991165.png b/samples/xlsx/TestIssue816_1/xl/media/imagef63d9dc50eff42f7964d4d5830991165.png new file mode 100644 index 00000000..fe3479dd Binary files /dev/null and b/samples/xlsx/TestIssue816_1/xl/media/imagef63d9dc50eff42f7964d4d5830991165.png differ diff --git a/samples/xlsx/TestIssue816_1/xl/media/imagefb0519c58a7b412ba445a8775ba5a5cf.png b/samples/xlsx/TestIssue816_1/xl/media/imagefb0519c58a7b412ba445a8775ba5a5cf.png new file mode 100644 index 00000000..8efaf819 Binary files /dev/null and b/samples/xlsx/TestIssue816_1/xl/media/imagefb0519c58a7b412ba445a8775ba5a5cf.png differ diff --git a/samples/xlsx/TestIssue816_1/xl/sharedStrings.xml b/samples/xlsx/TestIssue816_1/xl/sharedStrings.xml new file mode 100644 index 00000000..a74a5953 --- /dev/null +++ b/samples/xlsx/TestIssue816_1/xl/sharedStrings.xml @@ -0,0 +1,2 @@ + +Image1:Image2: \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_1/xl/styles.xml b/samples/xlsx/TestIssue816_1/xl/styles.xml new file mode 100644 index 00000000..04695051 --- /dev/null +++ b/samples/xlsx/TestIssue816_1/xl/styles.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_1/xl/theme/theme1.xml b/samples/xlsx/TestIssue816_1/xl/theme/theme1.xml new file mode 100644 index 00000000..f82d9ebb --- /dev/null +++ b/samples/xlsx/TestIssue816_1/xl/theme/theme1.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_1/xl/workbook.xml b/samples/xlsx/TestIssue816_1/xl/workbook.xml new file mode 100644 index 00000000..2a833c3d --- /dev/null +++ b/samples/xlsx/TestIssue816_1/xl/workbook.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_1/xl/worksheets/_rels/sheet1.xml.rels b/samples/xlsx/TestIssue816_1/xl/worksheets/_rels/sheet1.xml.rels new file mode 100644 index 00000000..45203544 --- /dev/null +++ b/samples/xlsx/TestIssue816_1/xl/worksheets/_rels/sheet1.xml.rels @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_1/xl/worksheets/sheet1.xml b/samples/xlsx/TestIssue816_1/xl/worksheets/sheet1.xml new file mode 100644 index 00000000..12cc2053 --- /dev/null +++ b/samples/xlsx/TestIssue816_1/xl/worksheets/sheet1.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + 0 + + + + + 1 + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_2.xlsx b/samples/xlsx/TestIssue816_2.xlsx new file mode 100644 index 00000000..bf718ad2 Binary files /dev/null and b/samples/xlsx/TestIssue816_2.xlsx differ diff --git a/samples/xlsx/TestIssue816_2/[Content_Types].xml b/samples/xlsx/TestIssue816_2/[Content_Types].xml new file mode 100644 index 00000000..3077b7d2 --- /dev/null +++ b/samples/xlsx/TestIssue816_2/[Content_Types].xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_2/_rels/.rels b/samples/xlsx/TestIssue816_2/_rels/.rels new file mode 100644 index 00000000..c50b69e8 --- /dev/null +++ b/samples/xlsx/TestIssue816_2/_rels/.rels @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_2/docProps/app.xml b/samples/xlsx/TestIssue816_2/docProps/app.xml new file mode 100644 index 00000000..ebd9f193 --- /dev/null +++ b/samples/xlsx/TestIssue816_2/docProps/app.xml @@ -0,0 +1,27 @@ + + + Microsoft Excel + 0 + false + + + + Worksheets + + + 1 + + + + + + Demo + + + + false + false + false + 16.0300 + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_2/docProps/core.xml b/samples/xlsx/TestIssue816_2/docProps/core.xml new file mode 100644 index 00000000..09f6e3aa --- /dev/null +++ b/samples/xlsx/TestIssue816_2/docProps/core.xml @@ -0,0 +1,11 @@ + + + Wei + Wei Lin + 2015-06-05T18:17:20Z + 2025-04-20T14:24:59Z + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_2/xl/_rels/workbook.xml.rels b/samples/xlsx/TestIssue816_2/xl/_rels/workbook.xml.rels new file mode 100644 index 00000000..aafbae06 --- /dev/null +++ b/samples/xlsx/TestIssue816_2/xl/_rels/workbook.xml.rels @@ -0,0 +1,15 @@ + + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_2/xl/drawings/_rels/drawinga13dc3bfd6be4e238ad8f91fcc6c646d.xml.rels b/samples/xlsx/TestIssue816_2/xl/drawings/_rels/drawinga13dc3bfd6be4e238ad8f91fcc6c646d.xml.rels new file mode 100644 index 00000000..f9d9ae72 --- /dev/null +++ b/samples/xlsx/TestIssue816_2/xl/drawings/_rels/drawinga13dc3bfd6be4e238ad8f91fcc6c646d.xml.rels @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_2/xl/drawings/_rels/drawingcea5e87afab34a1b886841980881e6a6.xml.rels b/samples/xlsx/TestIssue816_2/xl/drawings/_rels/drawingcea5e87afab34a1b886841980881e6a6.xml.rels new file mode 100644 index 00000000..ea055178 --- /dev/null +++ b/samples/xlsx/TestIssue816_2/xl/drawings/_rels/drawingcea5e87afab34a1b886841980881e6a6.xml.rels @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_2/xl/drawings/drawinga13dc3bfd6be4e238ad8f91fcc6c646d.xml b/samples/xlsx/TestIssue816_2/xl/drawings/drawinga13dc3bfd6be4e238ad8f91fcc6c646d.xml new file mode 100644 index 00000000..9b1e2419 --- /dev/null +++ b/samples/xlsx/TestIssue816_2/xl/drawings/drawinga13dc3bfd6be4e238ad8f91fcc6c646d.xml @@ -0,0 +1,119 @@ + + + + 3 + 0 + 2 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3 + 0 + 8 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3 + 0 + 8 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_2/xl/drawings/drawingcea5e87afab34a1b886841980881e6a6.xml b/samples/xlsx/TestIssue816_2/xl/drawings/drawingcea5e87afab34a1b886841980881e6a6.xml new file mode 100644 index 00000000..0bebe2c5 --- /dev/null +++ b/samples/xlsx/TestIssue816_2/xl/drawings/drawingcea5e87afab34a1b886841980881e6a6.xml @@ -0,0 +1,119 @@ + + + + 2 + 0 + 2 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 0 + 8 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 4 + 0 + 8 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_2/xl/media/image037f9747190c43eca595e57604de932c.png b/samples/xlsx/TestIssue816_2/xl/media/image037f9747190c43eca595e57604de932c.png new file mode 100644 index 00000000..fe3479dd Binary files /dev/null and b/samples/xlsx/TestIssue816_2/xl/media/image037f9747190c43eca595e57604de932c.png differ diff --git a/samples/xlsx/TestIssue816_2/xl/media/image3f83eec97c6c49569a75f076c2d4045a.png b/samples/xlsx/TestIssue816_2/xl/media/image3f83eec97c6c49569a75f076c2d4045a.png new file mode 100644 index 00000000..8efaf819 Binary files /dev/null and b/samples/xlsx/TestIssue816_2/xl/media/image3f83eec97c6c49569a75f076c2d4045a.png differ diff --git a/samples/xlsx/TestIssue816_2/xl/media/image804ac538d3674fa2abc309543b617971.png b/samples/xlsx/TestIssue816_2/xl/media/image804ac538d3674fa2abc309543b617971.png new file mode 100644 index 00000000..8efaf819 Binary files /dev/null and b/samples/xlsx/TestIssue816_2/xl/media/image804ac538d3674fa2abc309543b617971.png differ diff --git a/samples/xlsx/TestIssue816_2/xl/media/image8d00375faa1f4b41aa5109c3e419c964.png b/samples/xlsx/TestIssue816_2/xl/media/image8d00375faa1f4b41aa5109c3e419c964.png new file mode 100644 index 00000000..8efaf819 Binary files /dev/null and b/samples/xlsx/TestIssue816_2/xl/media/image8d00375faa1f4b41aa5109c3e419c964.png differ diff --git a/samples/xlsx/TestIssue816_2/xl/media/imagec4303bb6725242df941fea79b5e1296f.png b/samples/xlsx/TestIssue816_2/xl/media/imagec4303bb6725242df941fea79b5e1296f.png new file mode 100644 index 00000000..8efaf819 Binary files /dev/null and b/samples/xlsx/TestIssue816_2/xl/media/imagec4303bb6725242df941fea79b5e1296f.png differ diff --git a/samples/xlsx/TestIssue816_2/xl/media/imagee87f98b0db1a48898a63678d92b3660a.png b/samples/xlsx/TestIssue816_2/xl/media/imagee87f98b0db1a48898a63678d92b3660a.png new file mode 100644 index 00000000..fe3479dd Binary files /dev/null and b/samples/xlsx/TestIssue816_2/xl/media/imagee87f98b0db1a48898a63678d92b3660a.png differ diff --git a/samples/xlsx/TestIssue816_2/xl/sharedStrings.xml b/samples/xlsx/TestIssue816_2/xl/sharedStrings.xml new file mode 100644 index 00000000..a74a5953 --- /dev/null +++ b/samples/xlsx/TestIssue816_2/xl/sharedStrings.xml @@ -0,0 +1,2 @@ + +Image1:Image2: \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_2/xl/styles.xml b/samples/xlsx/TestIssue816_2/xl/styles.xml new file mode 100644 index 00000000..04695051 --- /dev/null +++ b/samples/xlsx/TestIssue816_2/xl/styles.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_2/xl/theme/theme1.xml b/samples/xlsx/TestIssue816_2/xl/theme/theme1.xml new file mode 100644 index 00000000..2c75da85 --- /dev/null +++ b/samples/xlsx/TestIssue816_2/xl/theme/theme1.xml @@ -0,0 +1,262 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_2/xl/workbook.xml b/samples/xlsx/TestIssue816_2/xl/workbook.xml new file mode 100644 index 00000000..2462c3ec --- /dev/null +++ b/samples/xlsx/TestIssue816_2/xl/workbook.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_2/xl/worksheets/_rels/sheet1.xml.rels b/samples/xlsx/TestIssue816_2/xl/worksheets/_rels/sheet1.xml.rels new file mode 100644 index 00000000..9fc078e3 --- /dev/null +++ b/samples/xlsx/TestIssue816_2/xl/worksheets/_rels/sheet1.xml.rels @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_2/xl/worksheets/sheet1.xml b/samples/xlsx/TestIssue816_2/xl/worksheets/sheet1.xml new file mode 100644 index 00000000..f986fca9 --- /dev/null +++ b/samples/xlsx/TestIssue816_2/xl/worksheets/sheet1.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + 0 + + + + + 1 + + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_3.xlsx b/samples/xlsx/TestIssue816_3.xlsx new file mode 100644 index 00000000..52e750d1 Binary files /dev/null and b/samples/xlsx/TestIssue816_3.xlsx differ diff --git a/samples/xlsx/TestIssue816_3/[Content_Types].xml b/samples/xlsx/TestIssue816_3/[Content_Types].xml new file mode 100644 index 00000000..8ee982ab --- /dev/null +++ b/samples/xlsx/TestIssue816_3/[Content_Types].xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_3/_rels/.rels b/samples/xlsx/TestIssue816_3/_rels/.rels new file mode 100644 index 00000000..74bfd8d9 --- /dev/null +++ b/samples/xlsx/TestIssue816_3/_rels/.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_3/docProps/app.xml b/samples/xlsx/TestIssue816_3/docProps/app.xml new file mode 100644 index 00000000..d5e92215 --- /dev/null +++ b/samples/xlsx/TestIssue816_3/docProps/app.xml @@ -0,0 +1,2 @@ + +Microsoft Excel0falseWorksheets1Sheet1falsefalsefalse16.0300 \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_3/docProps/core.xml b/samples/xlsx/TestIssue816_3/docProps/core.xml new file mode 100644 index 00000000..50439f08 --- /dev/null +++ b/samples/xlsx/TestIssue816_3/docProps/core.xml @@ -0,0 +1,2 @@ + +Wei LinWei Lin2025-06-21T14:00:28Z2025-06-21T14:01:05Z \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_3/xl/_rels/workbook.xml.rels b/samples/xlsx/TestIssue816_3/xl/_rels/workbook.xml.rels new file mode 100644 index 00000000..65485f71 --- /dev/null +++ b/samples/xlsx/TestIssue816_3/xl/_rels/workbook.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_3/xl/drawings/_rels/drawing1.xml.rels b/samples/xlsx/TestIssue816_3/xl/drawings/_rels/drawing1.xml.rels new file mode 100644 index 00000000..9db7c50e --- /dev/null +++ b/samples/xlsx/TestIssue816_3/xl/drawings/_rels/drawing1.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_3/xl/drawings/drawing1.xml b/samples/xlsx/TestIssue816_3/xl/drawings/drawing1.xml new file mode 100644 index 00000000..e68275f7 --- /dev/null +++ b/samples/xlsx/TestIssue816_3/xl/drawings/drawing1.xml @@ -0,0 +1,2 @@ + +4013016370514382800615040281132952528096 \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_3/xl/media/image1.png b/samples/xlsx/TestIssue816_3/xl/media/image1.png new file mode 100644 index 00000000..e52f6c8a Binary files /dev/null and b/samples/xlsx/TestIssue816_3/xl/media/image1.png differ diff --git a/samples/xlsx/TestIssue816_3/xl/media/image2.png b/samples/xlsx/TestIssue816_3/xl/media/image2.png new file mode 100644 index 00000000..66283404 Binary files /dev/null and b/samples/xlsx/TestIssue816_3/xl/media/image2.png differ diff --git a/samples/xlsx/TestIssue816_3/xl/sharedStrings.xml b/samples/xlsx/TestIssue816_3/xl/sharedStrings.xml new file mode 100644 index 00000000..d36ba8f0 --- /dev/null +++ b/samples/xlsx/TestIssue816_3/xl/sharedStrings.xml @@ -0,0 +1,2 @@ + +f \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_3/xl/styles.xml b/samples/xlsx/TestIssue816_3/xl/styles.xml new file mode 100644 index 00000000..659107a0 --- /dev/null +++ b/samples/xlsx/TestIssue816_3/xl/styles.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_3/xl/theme/theme1.xml b/samples/xlsx/TestIssue816_3/xl/theme/theme1.xml new file mode 100644 index 00000000..4a95cff0 --- /dev/null +++ b/samples/xlsx/TestIssue816_3/xl/theme/theme1.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_3/xl/workbook.xml b/samples/xlsx/TestIssue816_3/xl/workbook.xml new file mode 100644 index 00000000..6182b7c8 --- /dev/null +++ b/samples/xlsx/TestIssue816_3/xl/workbook.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_3/xl/worksheets/_rels/sheet1.xml.rels b/samples/xlsx/TestIssue816_3/xl/worksheets/_rels/sheet1.xml.rels new file mode 100644 index 00000000..205832e9 --- /dev/null +++ b/samples/xlsx/TestIssue816_3/xl/worksheets/_rels/sheet1.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_3/xl/worksheets/sheet1.xml b/samples/xlsx/TestIssue816_3/xl/worksheets/sheet1.xml new file mode 100644 index 00000000..cb9d4e7c --- /dev/null +++ b/samples/xlsx/TestIssue816_3/xl/worksheets/sheet1.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + 0 + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_4.xlsx b/samples/xlsx/TestIssue816_4.xlsx new file mode 100644 index 00000000..1d9aedfe Binary files /dev/null and b/samples/xlsx/TestIssue816_4.xlsx differ diff --git a/samples/xlsx/TestIssue816_4/[Content_Types].xml b/samples/xlsx/TestIssue816_4/[Content_Types].xml new file mode 100644 index 00000000..ce9f84a0 --- /dev/null +++ b/samples/xlsx/TestIssue816_4/[Content_Types].xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_4/_rels/.rels b/samples/xlsx/TestIssue816_4/_rels/.rels new file mode 100644 index 00000000..22c9382f --- /dev/null +++ b/samples/xlsx/TestIssue816_4/_rels/.rels @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_4/docProps/app.xml b/samples/xlsx/TestIssue816_4/docProps/app.xml new file mode 100644 index 00000000..d5e92215 --- /dev/null +++ b/samples/xlsx/TestIssue816_4/docProps/app.xml @@ -0,0 +1,2 @@ + +Microsoft Excel0falseWorksheets1Sheet1falsefalsefalse16.0300 \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_4/docProps/core.xml b/samples/xlsx/TestIssue816_4/docProps/core.xml new file mode 100644 index 00000000..50439f08 --- /dev/null +++ b/samples/xlsx/TestIssue816_4/docProps/core.xml @@ -0,0 +1,2 @@ + +Wei LinWei Lin2025-06-21T14:00:28Z2025-06-21T14:01:05Z \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_4/xl/_rels/workbook.xml.rels b/samples/xlsx/TestIssue816_4/xl/_rels/workbook.xml.rels new file mode 100644 index 00000000..e7fe8ebe --- /dev/null +++ b/samples/xlsx/TestIssue816_4/xl/_rels/workbook.xml.rels @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_4/xl/drawings/_rels/drawing1.xml.rels b/samples/xlsx/TestIssue816_4/xl/drawings/_rels/drawing1.xml.rels new file mode 100644 index 00000000..fad44dfe --- /dev/null +++ b/samples/xlsx/TestIssue816_4/xl/drawings/_rels/drawing1.xml.rels @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_4/xl/drawings/drawing1.xml b/samples/xlsx/TestIssue816_4/xl/drawings/drawing1.xml new file mode 100644 index 00000000..bf5f98f9 --- /dev/null +++ b/samples/xlsx/TestIssue816_4/xl/drawings/drawing1.xml @@ -0,0 +1,2 @@ + +401301636195038280051504028104775252809520140 120160 \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_4/xl/media/image1.png b/samples/xlsx/TestIssue816_4/xl/media/image1.png new file mode 100644 index 00000000..e52f6c8a Binary files /dev/null and b/samples/xlsx/TestIssue816_4/xl/media/image1.png differ diff --git a/samples/xlsx/TestIssue816_4/xl/media/image2.png b/samples/xlsx/TestIssue816_4/xl/media/image2.png new file mode 100644 index 00000000..66283404 Binary files /dev/null and b/samples/xlsx/TestIssue816_4/xl/media/image2.png differ diff --git a/samples/xlsx/TestIssue816_4/xl/media/image3.Png b/samples/xlsx/TestIssue816_4/xl/media/image3.Png new file mode 100644 index 00000000..fe3479dd Binary files /dev/null and b/samples/xlsx/TestIssue816_4/xl/media/image3.Png differ diff --git a/samples/xlsx/TestIssue816_4/xl/sharedStrings.xml b/samples/xlsx/TestIssue816_4/xl/sharedStrings.xml new file mode 100644 index 00000000..33295552 --- /dev/null +++ b/samples/xlsx/TestIssue816_4/xl/sharedStrings.xml @@ -0,0 +1 @@ +f \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_4/xl/styles.xml b/samples/xlsx/TestIssue816_4/xl/styles.xml new file mode 100644 index 00000000..e431b21a --- /dev/null +++ b/samples/xlsx/TestIssue816_4/xl/styles.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_4/xl/theme/theme1.xml b/samples/xlsx/TestIssue816_4/xl/theme/theme1.xml new file mode 100644 index 00000000..e57b14ae --- /dev/null +++ b/samples/xlsx/TestIssue816_4/xl/theme/theme1.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_4/xl/workbook.xml b/samples/xlsx/TestIssue816_4/xl/workbook.xml new file mode 100644 index 00000000..61bb5488 --- /dev/null +++ b/samples/xlsx/TestIssue816_4/xl/workbook.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_4/xl/worksheets/_rels/sheet1.xml.rels b/samples/xlsx/TestIssue816_4/xl/worksheets/_rels/sheet1.xml.rels new file mode 100644 index 00000000..5f1e29b8 --- /dev/null +++ b/samples/xlsx/TestIssue816_4/xl/worksheets/_rels/sheet1.xml.rels @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/samples/xlsx/TestIssue816_4/xl/worksheets/sheet1.xml b/samples/xlsx/TestIssue816_4/xl/worksheets/sheet1.xml new file mode 100644 index 00000000..95d494b1 --- /dev/null +++ b/samples/xlsx/TestIssue816_4/xl/worksheets/sheet1.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + 0 + + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_1.xlsx b/samples/xlsx/TestIssueICFV1I_1_41_1.xlsx new file mode 100644 index 00000000..9c86969c Binary files /dev/null and b/samples/xlsx/TestIssueICFV1I_1_41_1.xlsx differ diff --git a/samples/xlsx/TestIssueICFV1I_1_41_1/[Content_Types].xml b/samples/xlsx/TestIssueICFV1I_1_41_1/[Content_Types].xml new file mode 100644 index 00000000..7ba678e5 --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_1/[Content_Types].xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_1/_rels/.rels b/samples/xlsx/TestIssueICFV1I_1_41_1/_rels/.rels new file mode 100644 index 00000000..74bfd8d9 --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_1/_rels/.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_1/docProps/app.xml b/samples/xlsx/TestIssueICFV1I_1_41_1/docProps/app.xml new file mode 100644 index 00000000..3db85d1a --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_1/docProps/app.xml @@ -0,0 +1,2 @@ + +Microsoft Excel0falseWorksheets1Demofalsefalsefalse16.0300 \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_1/docProps/core.xml b/samples/xlsx/TestIssueICFV1I_1_41_1/docProps/core.xml new file mode 100644 index 00000000..84719d57 --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_1/docProps/core.xml @@ -0,0 +1,2 @@ + +WeiWei Lin2015-06-05T18:17:20Z2025-04-20T14:24:59Z \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_1/xl/_rels/workbook.xml.rels b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/_rels/workbook.xml.rels new file mode 100644 index 00000000..65485f71 --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/_rels/workbook.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_1/xl/drawings/_rels/drawing98482106b774449083d3adfa462f1707.xml.rels b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/drawings/_rels/drawing98482106b774449083d3adfa462f1707.xml.rels new file mode 100644 index 00000000..dec5e486 --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/drawings/_rels/drawing98482106b774449083d3adfa462f1707.xml.rels @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_1/xl/drawings/drawing98482106b774449083d3adfa462f1707.xml b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/drawings/drawing98482106b774449083d3adfa462f1707.xml new file mode 100644 index 00000000..209cc8b1 --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/drawings/drawing98482106b774449083d3adfa462f1707.xml @@ -0,0 +1,114 @@ + + + + + 2 + 0 + 2 + 0 + + + 3 + 0 + 3 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 0 + 8 + 0 + + + 3 + 4762500 + 9 + 4762500 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 7 + 0 + 8 + 0 + + 8 + 476250 + 9 + 476250 + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_1/xl/media/image8d8065829ab5449aa3b52723e51a7b20.png b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/media/image8d8065829ab5449aa3b52723e51a7b20.png new file mode 100644 index 00000000..fe3479dd Binary files /dev/null and b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/media/image8d8065829ab5449aa3b52723e51a7b20.png differ diff --git a/samples/xlsx/TestIssueICFV1I_1_41_1/xl/media/image9f62c0e5edfb4a4fb4df39966f9e930f.png b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/media/image9f62c0e5edfb4a4fb4df39966f9e930f.png new file mode 100644 index 00000000..fe3479dd Binary files /dev/null and b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/media/image9f62c0e5edfb4a4fb4df39966f9e930f.png differ diff --git a/samples/xlsx/TestIssueICFV1I_1_41_1/xl/media/imagea8f3b74b2f8041e9ae2cad406188704c.png b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/media/imagea8f3b74b2f8041e9ae2cad406188704c.png new file mode 100644 index 00000000..fe3479dd Binary files /dev/null and b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/media/imagea8f3b74b2f8041e9ae2cad406188704c.png differ diff --git a/samples/xlsx/TestIssueICFV1I_1_41_1/xl/sharedStrings.xml b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/sharedStrings.xml new file mode 100644 index 00000000..a74a5953 --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/sharedStrings.xml @@ -0,0 +1,2 @@ + +Image1:Image2: \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_1/xl/styles.xml b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/styles.xml new file mode 100644 index 00000000..04695051 --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/styles.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_1/xl/theme/theme1.xml b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/theme/theme1.xml new file mode 100644 index 00000000..f82d9ebb --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/theme/theme1.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_1/xl/workbook.xml b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/workbook.xml new file mode 100644 index 00000000..2a833c3d --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/workbook.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_1/xl/worksheets/_rels/sheet1.xml.rels b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/worksheets/_rels/sheet1.xml.rels new file mode 100644 index 00000000..194fc9f1 --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/worksheets/_rels/sheet1.xml.rels @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_1/xl/worksheets/sheet1.xml b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/worksheets/sheet1.xml new file mode 100644 index 00000000..0d58db40 --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_1/xl/worksheets/sheet1.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + 0 + + + + + 1 + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_2.xlsx b/samples/xlsx/TestIssueICFV1I_1_41_2.xlsx new file mode 100644 index 00000000..41c44144 Binary files /dev/null and b/samples/xlsx/TestIssueICFV1I_1_41_2.xlsx differ diff --git a/samples/xlsx/TestIssueICFV1I_1_41_2/[Content_Types].xml b/samples/xlsx/TestIssueICFV1I_1_41_2/[Content_Types].xml new file mode 100644 index 00000000..5cf7020b --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_2/[Content_Types].xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_2/_rels/.rels b/samples/xlsx/TestIssueICFV1I_1_41_2/_rels/.rels new file mode 100644 index 00000000..74bfd8d9 --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_2/_rels/.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_2/docProps/app.xml b/samples/xlsx/TestIssueICFV1I_1_41_2/docProps/app.xml new file mode 100644 index 00000000..3db85d1a --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_2/docProps/app.xml @@ -0,0 +1,2 @@ + +Microsoft Excel0falseWorksheets1Demofalsefalsefalse16.0300 \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_2/docProps/core.xml b/samples/xlsx/TestIssueICFV1I_1_41_2/docProps/core.xml new file mode 100644 index 00000000..84719d57 --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_2/docProps/core.xml @@ -0,0 +1,2 @@ + +WeiWei Lin2015-06-05T18:17:20Z2025-04-20T14:24:59Z \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_2/xl/_rels/workbook.xml.rels b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/_rels/workbook.xml.rels new file mode 100644 index 00000000..65485f71 --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/_rels/workbook.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_2/xl/drawings/_rels/drawing2605be385d194157a1ed844bf85a6825.xml.rels b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/drawings/_rels/drawing2605be385d194157a1ed844bf85a6825.xml.rels new file mode 100644 index 00000000..9b97e377 --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/drawings/_rels/drawing2605be385d194157a1ed844bf85a6825.xml.rels @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_2/xl/drawings/drawing2605be385d194157a1ed844bf85a6825.xml b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/drawings/drawing2605be385d194157a1ed844bf85a6825.xml new file mode 100644 index 00000000..005072ef --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/drawings/drawing2605be385d194157a1ed844bf85a6825.xml @@ -0,0 +1,114 @@ + + + + + 2 + 0 + 2 + 0 + + + 3 + 0 + 3 + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 2 + 0 + 8 + 0 + + + 3 + 4762500 + 9 + 4762500 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 7 + 0 + 8 + 0 + + 8 + 476250 + 9 + 476250 + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_2/xl/media/image6c041a7f44094767b6c8f3d48a6f6a1f.png b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/media/image6c041a7f44094767b6c8f3d48a6f6a1f.png new file mode 100644 index 00000000..fe3479dd Binary files /dev/null and b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/media/image6c041a7f44094767b6c8f3d48a6f6a1f.png differ diff --git a/samples/xlsx/TestIssueICFV1I_1_41_2/xl/media/imagec8cb923a01634a9489b256bae76a61b2.png b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/media/imagec8cb923a01634a9489b256bae76a61b2.png new file mode 100644 index 00000000..fe3479dd Binary files /dev/null and b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/media/imagec8cb923a01634a9489b256bae76a61b2.png differ diff --git a/samples/xlsx/TestIssueICFV1I_1_41_2/xl/media/imagef69f64bf01fc457985842f20542262c5.png b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/media/imagef69f64bf01fc457985842f20542262c5.png new file mode 100644 index 00000000..fe3479dd Binary files /dev/null and b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/media/imagef69f64bf01fc457985842f20542262c5.png differ diff --git a/samples/xlsx/TestIssueICFV1I_1_41_2/xl/sharedStrings.xml b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/sharedStrings.xml new file mode 100644 index 00000000..a74a5953 --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/sharedStrings.xml @@ -0,0 +1,2 @@ + +Image1:Image2: \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_2/xl/styles.xml b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/styles.xml new file mode 100644 index 00000000..04695051 --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/styles.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_2/xl/theme/theme1.xml b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/theme/theme1.xml new file mode 100644 index 00000000..f82d9ebb --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/theme/theme1.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_2/xl/workbook.xml b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/workbook.xml new file mode 100644 index 00000000..2a833c3d --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/workbook.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_2/xl/worksheets/_rels/sheet1.xml.rels b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/worksheets/_rels/sheet1.xml.rels new file mode 100644 index 00000000..2891bcc5 --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/worksheets/_rels/sheet1.xml.rels @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/samples/xlsx/TestIssueICFV1I_1_41_2/xl/worksheets/sheet1.xml b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/worksheets/sheet1.xml new file mode 100644 index 00000000..a9ca2b84 --- /dev/null +++ b/samples/xlsx/TestIssueICFV1I_1_41_2/xl/worksheets/sheet1.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + 0 + + + + + 1 + + + + + \ No newline at end of file diff --git a/src/MiniExcel/Attributes/ExcelColumnAttribute.cs b/src/MiniExcel/Attributes/ExcelColumnAttribute.cs index 36211b8f..3ce2977e 100644 --- a/src/MiniExcel/Attributes/ExcelColumnAttribute.cs +++ b/src/MiniExcel/Attributes/ExcelColumnAttribute.cs @@ -1,57 +1,54 @@ -using MiniExcelLibs.Utils; -using System; +using System; +using MiniExcelLibs.Utils; -namespace MiniExcelLibs.Attributes +namespace MiniExcelLibs.Attributes; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +public class ExcelColumnAttribute : Attribute { - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] - public class ExcelColumnAttribute : Attribute + private int _index = -1; + private string? _xName; + + internal int FormatId { get; set; } = -1; + + public string? Name { get; set; } + public string[]? Aliases { get; set; } = []; + public double Width { get; set; } = 9.28515625; + public string? Format { get; set; } + public bool Ignore { get; set; } + public ColumnType Type { get; set; } = ColumnType.Value; + + public int Index { - private int _index = -1; - private string _xName; - - internal int FormatId { get; set; } = -1; - - public string Name { get; set; } - public string[] Aliases { get; set; } - public double Width { get; set; } = 9.28515625; - public string Format { get; set; } - public bool Ignore { get; set; } - public ColumnType Type { get; set; } = ColumnType.Value; - - public int Index - { - get => _index; - set => Init(value); - } - - public string IndexName - { - get => _xName; - set => Init(ColumnHelper.GetColumnIndex(value), value); - } - - private void Init(int index, string columnName = null) - { - if (index < 0) - throw new ArgumentOutOfRangeException(nameof(index), index, $"Column index {index} must be greater or equal to zero."); - - if (_xName == null) - _xName = columnName ?? ColumnHelper.GetAlphabetColumnName(index); - - _index = index; - } + get => _index; + set => Init(value); } - public enum ColumnType { Value, Formula } + public string? IndexName + { + get => _xName; + set => Init(ColumnHelper.GetColumnIndex(value), value); + } - public class DynamicExcelColumn : ExcelColumnAttribute + private void Init(int index, string? columnName = null) { - public string Key { get; set; } - public Func CustomFormatter { get; set; } + if (index < 0) + throw new ArgumentOutOfRangeException(nameof(index), index, $"Column index {index} must be greater or equal to zero."); - public DynamicExcelColumn(string key) - { - Key = key; - } + _index = index; + _xName ??= columnName ?? ColumnHelper.GetAlphabetColumnName(index); + } +} + +public enum ColumnType { Value, Formula } + +public class DynamicExcelColumn : ExcelColumnAttribute +{ + public string Key { get; set; } + public Func CustomFormatter { get; set; } + + public DynamicExcelColumn(string key) + { + Key = key; } } \ No newline at end of file diff --git a/src/MiniExcel/Attributes/ExcelColumnIndexAttribute.cs b/src/MiniExcel/Attributes/ExcelColumnIndexAttribute.cs index d576baa0..0bdad2fd 100644 --- a/src/MiniExcel/Attributes/ExcelColumnIndexAttribute.cs +++ b/src/MiniExcel/Attributes/ExcelColumnIndexAttribute.cs @@ -1,29 +1,22 @@ -namespace MiniExcelLibs.Attributes +using System; +using MiniExcelLibs.Utils; + +namespace MiniExcelLibs.Attributes; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +public class ExcelColumnIndexAttribute : Attribute { - using MiniExcelLibs.Utils; - using System; + public int ExcelColumnIndex { get; set; } + internal string? ExcelXName { get; set; } + public ExcelColumnIndexAttribute(string columnName) => Init(ColumnHelper.GetColumnIndex(columnName), columnName); + public ExcelColumnIndexAttribute(int columnIndex) => Init(columnIndex); - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] - public class ExcelColumnIndexAttribute : Attribute + private void Init(int columnIndex, string? columnName = null) { - public int ExcelColumnIndex { get; set; } - internal string ExcelXName { get; set; } - public ExcelColumnIndexAttribute(string columnName) => Init(ColumnHelper - .GetColumnIndex(columnName), columnName); - public ExcelColumnIndexAttribute(int columnIndex) => Init(columnIndex); - - private void Init(int columnIndex, string columnName = null) - { - if (columnIndex < 0) - { - throw new ArgumentOutOfRangeException(nameof(columnIndex), columnIndex, $"Column index {columnIndex} must be greater or equal to zero."); - } - if (ExcelXName == null) - if (columnName != null) - ExcelXName = columnName; - else - ExcelXName = ColumnHelper.GetAlphabetColumnName(columnIndex); - ExcelColumnIndex = columnIndex; - } + if (columnIndex < 0) + throw new ArgumentOutOfRangeException(nameof(columnIndex), columnIndex, $"Column index {columnIndex} must be greater or equal to zero."); + + ExcelXName ??= columnName ?? ColumnHelper.GetAlphabetColumnName(columnIndex); + ExcelColumnIndex = columnIndex; } -} +} \ No newline at end of file diff --git a/src/MiniExcel/Attributes/ExcelColumnNameAttribute.cs b/src/MiniExcel/Attributes/ExcelColumnNameAttribute.cs index eb16530b..ff271b5b 100644 --- a/src/MiniExcel/Attributes/ExcelColumnNameAttribute.cs +++ b/src/MiniExcel/Attributes/ExcelColumnNameAttribute.cs @@ -1,15 +1,10 @@ -namespace MiniExcelLibs.Attributes +using System; + +namespace MiniExcelLibs.Attributes; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +public class ExcelColumnNameAttribute(string excelColumnName, string[]? aliases = null) : Attribute { - using System; - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] - public class ExcelColumnNameAttribute : Attribute - { - public string ExcelColumnName { get; set; } - public string[] Aliases { get; set; } - public ExcelColumnNameAttribute(string excelColumnName, string[] aliases = null) - { - ExcelColumnName = excelColumnName; - Aliases = aliases; - } - } -} + public string ExcelColumnName { get; set; } = excelColumnName; + public string[] Aliases { get; set; } = aliases ?? []; +} \ No newline at end of file diff --git a/src/MiniExcel/Attributes/ExcelColumnWidthAttribute.cs b/src/MiniExcel/Attributes/ExcelColumnWidthAttribute.cs index fd7dfcb5..699b6f6f 100644 --- a/src/MiniExcel/Attributes/ExcelColumnWidthAttribute.cs +++ b/src/MiniExcel/Attributes/ExcelColumnWidthAttribute.cs @@ -1,11 +1,9 @@ -namespace MiniExcelLibs.Attributes -{ - using System; +using System; + +namespace MiniExcelLibs.Attributes; - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] - public class ExcelColumnWidthAttribute : Attribute - { - public double ExcelColumnWidth { get; set; } - public ExcelColumnWidthAttribute(double excelColumnWidth) => ExcelColumnWidth = excelColumnWidth; - } -} +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +public class ExcelColumnWidthAttribute(double excelColumnWidth) : Attribute +{ + public double ExcelColumnWidth { get; set; } = excelColumnWidth; +} \ No newline at end of file diff --git a/src/MiniExcel/Attributes/ExcelFormatAttribute.cs b/src/MiniExcel/Attributes/ExcelFormatAttribute.cs index da503e5c..a2aaea4b 100644 --- a/src/MiniExcel/Attributes/ExcelFormatAttribute.cs +++ b/src/MiniExcel/Attributes/ExcelFormatAttribute.cs @@ -1,11 +1,9 @@ using System; -namespace MiniExcelLibs.Attributes +namespace MiniExcelLibs.Attributes; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +public class ExcelFormatAttribute(string format) : Attribute { - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] - public class ExcelFormatAttribute : Attribute - { - public string Format { get; set; } - public ExcelFormatAttribute(string format) => Format = format; - } -} + public string Format { get; set; } = format; +} \ No newline at end of file diff --git a/src/MiniExcel/Attributes/ExcelIgnoreAttribute.cs b/src/MiniExcel/Attributes/ExcelIgnoreAttribute.cs index 0331edc2..0f06f105 100644 --- a/src/MiniExcel/Attributes/ExcelIgnoreAttribute.cs +++ b/src/MiniExcel/Attributes/ExcelIgnoreAttribute.cs @@ -1,11 +1,9 @@ using System; -namespace MiniExcelLibs.Attributes +namespace MiniExcelLibs.Attributes; + +[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] +public class ExcelIgnoreAttribute(bool excelIgnore = true) : Attribute { - [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)] - public class ExcelIgnoreAttribute : Attribute - { - public bool ExcelIgnore { get; set; } - public ExcelIgnoreAttribute(bool excelIgnore = true) => ExcelIgnore = excelIgnore; - } -} + public bool ExcelIgnore { get; set; } = excelIgnore; +} \ No newline at end of file diff --git a/src/MiniExcel/Attributes/ExcelSheetAttribute.cs b/src/MiniExcel/Attributes/ExcelSheetAttribute.cs index b3f1b7d6..3a4285a2 100644 --- a/src/MiniExcel/Attributes/ExcelSheetAttribute.cs +++ b/src/MiniExcel/Attributes/ExcelSheetAttribute.cs @@ -1,21 +1,16 @@ -using MiniExcelLibs.OpenXml; -using System; +using System; +using MiniExcelLibs.OpenXml; -namespace MiniExcelLibs.Attributes +namespace MiniExcelLibs.Attributes; + +[AttributeUsage(AttributeTargets.Class)] +public class ExcelSheetAttribute : Attribute { - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)] - public class ExcelSheetAttribute : Attribute - { - public string Name { get; set; } - public SheetState State { get; set; } = SheetState.Visible; - } + public string? Name { get; set; } + public SheetState State { get; set; } = SheetState.Visible; +} - public class DynamicExcelSheet : ExcelSheetAttribute - { - public string Key { get; set; } - public DynamicExcelSheet(string key) - { - Key = key; - } - } +public class DynamicExcelSheet(string key) : ExcelSheetAttribute +{ + public string Key { get; set; } = key; } \ No newline at end of file diff --git a/src/MiniExcel/Csv/CsvConfiguration.cs b/src/MiniExcel/Csv/CsvConfiguration.cs index 1d795c40..729e0059 100644 --- a/src/MiniExcel/Csv/CsvConfiguration.cs +++ b/src/MiniExcel/Csv/CsvConfiguration.cs @@ -2,23 +2,21 @@ using System.IO; using System.Text; -namespace MiniExcelLibs.Csv -{ - public class CsvConfiguration : Configuration - { - private static readonly Encoding DefaultEncoding = new UTF8Encoding(true); +namespace MiniExcelLibs.Csv; - public char Seperator { get; set; } = ','; - public string NewLine { get; set; } = "\r\n"; - public bool ReadLineBreaksWithinQuotes { get; set; } = true; - public bool ReadEmptyStringAsNull { get; set; } = false; - public bool AlwaysQuote { get; set; } = false; - public bool QuoteWhitespaces { get; set; } = true; - public Func SplitFn { get; set; } - public Func StreamReaderFunc { get; set; } = (stream) => new StreamReader(stream, DefaultEncoding); - public Func StreamWriterFunc { get; set; } = (stream) => new StreamWriter(stream, DefaultEncoding); +public class CsvConfiguration : MiniExcelConfiguration +{ + private static readonly Encoding DefaultEncoding = new UTF8Encoding(true); - internal static readonly CsvConfiguration DefaultConfiguration = new CsvConfiguration(); - } -} + public char Seperator { get; set; } = ','; + public string NewLine { get; set; } = "\r\n"; + public bool ReadLineBreaksWithinQuotes { get; set; } = true; + public bool ReadEmptyStringAsNull { get; set; } = false; + public bool AlwaysQuote { get; set; } = false; + public bool QuoteWhitespaces { get; set; } = true; + public Func? SplitFn { get; set; } + public Func StreamReaderFunc { get; set; } = (stream) => new StreamReader(stream, DefaultEncoding); + public Func StreamWriterFunc { get; set; } = (stream) => new StreamWriter(stream, DefaultEncoding); + internal static readonly CsvConfiguration DefaultConfiguration = new CsvConfiguration(); +} \ No newline at end of file diff --git a/src/MiniExcel/Csv/CsvHelpers.cs b/src/MiniExcel/Csv/CsvHelpers.cs index b6fd5a10..54ca3b46 100644 --- a/src/MiniExcel/Csv/CsvHelpers.cs +++ b/src/MiniExcel/Csv/CsvHelpers.cs @@ -1,26 +1,25 @@ -namespace MiniExcelLibs.Csv +namespace MiniExcelLibs.Csv; + +internal static class CsvHelpers { - internal static class CsvHelpers + /// If content contains special characters then use "{value}" format + public static string ConvertToCsvValue(string? value, CsvConfiguration configuration) { - /// If content contains special characters then use "{value}" format - public static string ConvertToCsvValue(string value, CsvConfiguration configuration) - { - if (value == null) - return string.Empty; + if (value is null) + return string.Empty; - if (value.Contains("\"")) - { - value = value.Replace("\"", "\"\""); - return $"\"{value}\""; - } + if (value.Contains("\"")) + { + value = value.Replace("\"", "\"\""); + return $"\"{value}\""; + } - var shouldQuote = configuration.AlwaysQuote || - (configuration.QuoteWhitespaces && value.Contains(" ")) || - value.Contains(configuration.Seperator.ToString()) || - value.Contains("\r") || - value.Contains("\n"); + var shouldQuote = configuration.AlwaysQuote || + (configuration.QuoteWhitespaces && value.Contains(" ")) || + value.Contains(configuration.Seperator.ToString()) || + value.Contains("\r") || + value.Contains("\n"); - return shouldQuote ? $"\"{value}\"" : value; - } + return shouldQuote ? $"\"{value}\"" : value; } -} +} \ No newline at end of file diff --git a/src/MiniExcel/Csv/CsvReader.cs b/src/MiniExcel/Csv/CsvReader.cs index dddec16d..93496c05 100644 --- a/src/MiniExcel/Csv/CsvReader.cs +++ b/src/MiniExcel/Csv/CsvReader.cs @@ -1,174 +1,163 @@ -using MiniExcelLibs.Exceptions; -using MiniExcelLibs.OpenXml; -using MiniExcelLibs.Utils; using System; using System.Collections.Generic; using System.IO; using System.Linq; +using System.Runtime.CompilerServices; using System.Text.RegularExpressions; using System.Threading; -using System.Threading.Tasks; -using System.Runtime.CompilerServices; +using MiniExcelLibs.Exceptions; +using MiniExcelLibs.OpenXml; +using MiniExcelLibs.Utils; +using Zomp.SyncMethodGenerator; -namespace MiniExcelLibs.Csv -{ - internal partial class CsvReader : IExcelReader - { - private Stream _stream; - private CsvConfiguration _config; +namespace MiniExcelLibs.Csv; - public CsvReader(Stream stream, IConfiguration configuration) - { - _stream = stream; - _config = configuration == null ? CsvConfiguration.DefaultConfiguration : (CsvConfiguration)configuration; - } +internal partial class CsvReader(Stream stream, IMiniExcelConfiguration? configuration) : IExcelReader +{ + private readonly Stream _stream = stream; + private readonly CsvConfiguration _config = configuration is null ? CsvConfiguration.DefaultConfiguration : (CsvConfiguration)configuration; - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public async IAsyncEnumerable> QueryAsync(bool useHeaderRow, string sheetName, string startCell, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); + [CreateSyncVersion] + public async IAsyncEnumerable> QueryAsync(bool useHeaderRow, string? sheetName, string startCell, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); - if (startCell != "A1") - throw new NotImplementedException("CSV does not implement parameter startCell"); + if (startCell != "A1") + throw new NotImplementedException("CSV does not implement parameter startCell"); - if (_stream.CanSeek) - _stream.Position = 0; + if (_stream.CanSeek) + _stream.Position = 0; - var reader = _config.StreamReaderFunc(_stream); - var firstRow = true; - var headRows = new Dictionary(); + var reader = _config.StreamReaderFunc(_stream); + var firstRow = true; + var headRows = new Dictionary(); - string row; - for (var rowIndex = 1; (row = await reader.ReadLineAsync( + string row; + for (var rowIndex = 1; (row = await reader.ReadLineAsync( #if NET7_0_OR_GREATER -cancellationToken + cancellationToken #endif - ).ConfigureAwait(false)) != null; rowIndex++) + ).ConfigureAwait(false)) is not null; rowIndex++) + { + string finalRow = row; + if (_config.ReadLineBreaksWithinQuotes) { - string finalRow = row; - if (_config.ReadLineBreaksWithinQuotes) + while (finalRow.Count(c => c == '"') % 2 != 0) { - while (finalRow.Count(c => c == '"') % 2 != 0) - { - var nextPart = await reader.ReadLineAsync( + var nextPart = await reader.ReadLineAsync( #if NET7_0_OR_GREATER -cancellationToken + cancellationToken #endif - ).ConfigureAwait(false); - if (nextPart == null) - { - break; - } - finalRow = string.Concat(finalRow, _config.NewLine, nextPart); - } - } - var read = Split(finalRow); - - // invalid row check - if (read.Length < headRows.Count) - { - var colIndex = read.Length; - var headers = headRows.ToDictionary(x => x.Value, x => x.Key); - var rowValues = read - .Select((x, i) => new KeyValuePair(headRows[i], x)) - .ToDictionary(x => x.Key, x => x.Value); - - throw new ExcelColumnNotFoundException(columnIndex: null, headRows[colIndex], null, rowIndex, headers, rowValues, $"Csv read error: Column {colIndex} not found in Row {rowIndex}"); - } - - //header - if (useHeaderRow) - { - if (firstRow) + ).ConfigureAwait(false); + if (nextPart is null) { - firstRow = false; - for (int i = 0; i <= read.Length - 1; i++) - headRows.Add(i, read[i]); - continue; + break; } + finalRow = string.Concat(finalRow, _config.NewLine, nextPart); + } + } + var read = Split(finalRow); - var headCell = CustomPropertyHelper.GetEmptyExpandoObject(headRows); - for (int i = 0; i <= read.Length - 1; i++) - headCell[headRows[i]] = read[i]; + // invalid row check + if (read.Length < headRows.Count) + { + var colIndex = read.Length; + var headers = headRows.ToDictionary(x => x.Value, x => x.Key); + var rowValues = read + .Select((x, i) => new KeyValuePair(headRows[i], x)) + .ToDictionary(x => x.Key, x => x.Value); - yield return headCell; - continue; - } + throw new ExcelColumnNotFoundException(columnIndex: null, headRows[colIndex], [], rowIndex, headers, rowValues, $"Csv read error: Column {colIndex} not found in Row {rowIndex}"); + } - //body - if (firstRow) // record first row as reference + //header + if (useHeaderRow) + { + if (firstRow) { firstRow = false; for (int i = 0; i <= read.Length - 1; i++) - headRows.Add(i, $"c{i + 1}"); + headRows.Add(i, read[i]); + continue; } - var cell = CustomPropertyHelper.GetEmptyExpandoObject(read.Length - 1, 0); - if (_config.ReadEmptyStringAsNull) - { - for (int i = 0; i <= read.Length - 1; i++) - cell[ColumnHelper.GetAlphabetColumnName(i)] = read[i]?.Length == 0 ? null : read[i]; - } - else - { - for (int i = 0; i <= read.Length - 1; i++) - cell[ColumnHelper.GetAlphabetColumnName(i)] = read[i]; - } + var headCell = CustomPropertyHelper.GetEmptyExpandoObject(headRows); + for (int i = 0; i <= read.Length - 1; i++) + headCell[headRows[i]] = read[i]; - yield return cell; + yield return headCell; + continue; } - } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public IAsyncEnumerable QueryAsync(string sheetName, string startCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() - { - var dynamicRecords = QueryAsync(false, sheetName, startCell, cancellationToken); - return ExcelOpenXmlSheetReader.QueryImplAsync(dynamicRecords, startCell, hasHeader, _config, cancellationToken); - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public IAsyncEnumerable> QueryRangeAsync(bool useHeaderRow, string sheetName, string startCell, string endCell, CancellationToken cancellationToken = default) - { - throw new NotImplementedException("CSV does not implement QueryRange"); - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public IAsyncEnumerable QueryRangeAsync(string sheetName, string startCell, string endCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() - { - var dynamicRecords = QueryRangeAsync(false, sheetName, startCell, endCell, cancellationToken); - return ExcelOpenXmlSheetReader.QueryImplAsync(dynamicRecords, startCell, hasHeader, this._config, cancellationToken); - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public IAsyncEnumerable> QueryRangeAsync(bool useHeaderRow, string sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, CancellationToken cancellationToken = default) - { - throw new NotImplementedException("CSV does not implement QueryRange"); - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public IAsyncEnumerable QueryRangeAsync(string sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() - { - var dynamicRecords = QueryRangeAsync(false, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, cancellationToken); - return ExcelOpenXmlSheetReader.QueryImplAsync(dynamicRecords, ReferenceHelper.ConvertXyToCell(startRowIndex, startColumnIndex), hasHeader, this._config, cancellationToken); - } + //body + if (firstRow) // record first row as reference + { + firstRow = false; + for (int i = 0; i <= read.Length - 1; i++) + headRows.Add(i, $"c{i + 1}"); + } - private string[] Split(string row) - { - if (_config.SplitFn != null) + var cell = CustomPropertyHelper.GetEmptyExpandoObject(read.Length - 1, 0); + if (_config.ReadEmptyStringAsNull) { - return _config.SplitFn(row); + for (int i = 0; i <= read.Length - 1; i++) + cell[ColumnHelper.GetAlphabetColumnName(i)] = read[i]?.Length == 0 ? null : read[i]; } else { - //this code from S.O : https://stackoverflow.com/a/11365961/9131476 - return Regex.Split(row, $"[\t{_config.Seperator}](?=(?:[^\"]|\"[^\"]*\")*$)") - .Select(s => Regex.Replace(s.Replace("\"\"", "\""), "^\"|\"$", "")) - .ToArray(); + for (int i = 0; i <= read.Length - 1; i++) + cell[ColumnHelper.GetAlphabetColumnName(i)] = read[i]; } - } - public void Dispose() - { + yield return cell; } } -} + + [CreateSyncVersion] + public IAsyncEnumerable QueryAsync(string? sheetName, string startCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() + { + var dynamicRecords = QueryAsync(false, sheetName, startCell, cancellationToken); + return ExcelOpenXmlSheetReader.QueryImplAsync(dynamicRecords, startCell, hasHeader, _config, cancellationToken); + } + + [CreateSyncVersion] + public IAsyncEnumerable> QueryRangeAsync(bool useHeaderRow, string? sheetName, string startCell, string endCell, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("CSV does not implement QueryRange"); + } + + [CreateSyncVersion] + public IAsyncEnumerable QueryRangeAsync(string? sheetName, string startCell, string endCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() + { + var dynamicRecords = QueryRangeAsync(false, sheetName, startCell, endCell, cancellationToken); + return ExcelOpenXmlSheetReader.QueryImplAsync(dynamicRecords, startCell, hasHeader, this._config, cancellationToken); + } + + [CreateSyncVersion] + public IAsyncEnumerable> QueryRangeAsync(bool useHeaderRow, string? sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, CancellationToken cancellationToken = default) + { + throw new NotImplementedException("CSV does not implement QueryRange"); + } + + [CreateSyncVersion] + public IAsyncEnumerable QueryRangeAsync(string? sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() + { + var dynamicRecords = QueryRangeAsync(false, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, cancellationToken); + return ExcelOpenXmlSheetReader.QueryImplAsync(dynamicRecords, ReferenceHelper.ConvertXyToCell(startRowIndex, startColumnIndex), hasHeader, this._config, cancellationToken); + } + + private string[] Split(string row) + { + if (_config.SplitFn is not null) + return _config.SplitFn(row); + + //this code from S.O : https://stackoverflow.com/a/11365961/9131476 + return Regex.Split(row, $"[\t{_config.Seperator}](?=(?:[^\"]|\"[^\"]*\")*$)") + .Select(s => Regex.Replace(s.Replace("\"\"", "\""), "^\"|\"$", "")) + .ToArray(); + } + + public void Dispose() + { + } +} \ No newline at end of file diff --git a/src/MiniExcel/Csv/CsvWriter.cs b/src/MiniExcel/Csv/CsvWriter.cs index f4318639..8e5e308e 100644 --- a/src/MiniExcel/Csv/CsvWriter.cs +++ b/src/MiniExcel/Csv/CsvWriter.cs @@ -9,240 +9,238 @@ using System.Threading; using System.Threading.Tasks; -namespace MiniExcelLibs.Csv +namespace MiniExcelLibs.Csv; + +internal partial class CsvWriter : IExcelWriter, IDisposable { - internal partial class CsvWriter : IExcelWriter, IDisposable + private readonly StreamWriter _writer; + private readonly CsvConfiguration _configuration; + private readonly bool _printHeader; + private readonly object? _value; + + private bool _disposedValue; + + public CsvWriter(Stream stream, object? value, IMiniExcelConfiguration? configuration, bool printHeader) { - private readonly StreamWriter _writer; - private readonly CsvConfiguration _configuration; - private readonly bool _printHeader; - private readonly object _value; - private bool _disposedValue; + _configuration = configuration is null ? CsvConfiguration.DefaultConfiguration : (CsvConfiguration)configuration; + _writer = _configuration.StreamWriterFunc(stream); + _printHeader = printHeader; + _value = value; + } - public CsvWriter(Stream stream, object value, IConfiguration configuration, bool printHeader) - { - _configuration = configuration == null ? CsvConfiguration.DefaultConfiguration : (CsvConfiguration)configuration; - _printHeader = printHeader; - _value = value; - _writer = _configuration.StreamWriterFunc(stream); - } + private void AppendColumn(StringBuilder rowBuilder, CellWriteInfo column) + { + rowBuilder.Append(CsvHelpers.ConvertToCsvValue(ToCsvString(column.Value, column.Prop), _configuration)); + rowBuilder.Append(_configuration.Seperator); + } - private void AppendColumn(StringBuilder rowBuilder, CellWriteInfo column) - { - rowBuilder.Append(CsvHelpers.ConvertToCsvValue(ToCsvString(column.Value, column.Prop), _configuration)); - rowBuilder.Append(_configuration.Seperator); - } + private static void RemoveTrailingSeparator(StringBuilder rowBuilder) + { + if (rowBuilder.Length == 0) + return; - private static void RemoveTrailingSeparator(StringBuilder rowBuilder) - { - if (rowBuilder.Length == 0) - return; + rowBuilder.Remove(rowBuilder.Length - 1, 1); + } - rowBuilder.Remove(rowBuilder.Length - 1, 1); - } + private string GetHeader(List props) => string.Join( + _configuration.Seperator.ToString(), + props.Select(s => CsvHelpers.ConvertToCsvValue(s?.ExcelColumnName, _configuration))); - private string GetHeader(List props) => string.Join( - _configuration.Seperator.ToString(), - props.Select(s => CsvHelpers.ConvertToCsvValue(s?.ExcelColumnName, _configuration))); + [Zomp.SyncMethodGenerator.CreateSyncVersion] + private async Task WriteValuesAsync(StreamWriter writer, object values, string seperator, string newLine, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task WriteValuesAsync(StreamWriter writer, object values, string seperator, string newLine, CancellationToken cancellationToken = default) + IMiniExcelWriteAdapter? writeAdapter = null; + if (!MiniExcelWriteAdapterFactory.TryGetAsyncWriteAdapter(values, _configuration, out var asyncWriteAdapter)) { - cancellationToken.ThrowIfCancellationRequested(); - - IMiniExcelWriteAdapter writeAdapter = null; - if (!MiniExcelWriteAdapterFactory.TryGetAsyncWriteAdapter(values, _configuration, out var asyncWriteAdapter)) - { - writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); - } - List props; + writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); + } + List? props; #if SYNC_ONLY - props = writeAdapter?.GetColumns(); + props = writeAdapter?.GetColumns(); #else - props = writeAdapter != null ? writeAdapter.GetColumns() : await asyncWriteAdapter.GetColumnsAsync().ConfigureAwait(false); + props = writeAdapter is not null ? writeAdapter.GetColumns() : await asyncWriteAdapter.GetColumnsAsync().ConfigureAwait(false); #endif - if (props == null) - { - await _writer.WriteAsync(_configuration.NewLine + if (props is null) + { + await _writer.WriteAsync(_configuration.NewLine #if NET5_0_OR_GREATER - .AsMemory(), cancellationToken + .AsMemory(), cancellationToken #endif - ).ConfigureAwait(false); - await _writer.FlushAsync( + ).ConfigureAwait(false); + await _writer.FlushAsync( #if NET8_0_OR_GREATER - cancellationToken + cancellationToken #endif - ).ConfigureAwait(false); - return 0; - } + ).ConfigureAwait(false); + return 0; + } - if (_printHeader) - { - await _writer.WriteAsync(GetHeader(props) + if (_printHeader) + { + await _writer.WriteAsync(GetHeader(props) #if NET5_0_OR_GREATER - .AsMemory(), cancellationToken + .AsMemory(), cancellationToken #endif - ).ConfigureAwait(false); - await _writer.WriteAsync(newLine + ).ConfigureAwait(false); + await _writer.WriteAsync(newLine #if NET5_0_OR_GREATER - .AsMemory(), cancellationToken + .AsMemory(), cancellationToken #endif - ).ConfigureAwait(false); - } + ).ConfigureAwait(false); + } - var rowBuilder = new StringBuilder(); - var rowsWritten = 0; + var rowBuilder = new StringBuilder(); + var rowsWritten = 0; - if (writeAdapter != null) + if (writeAdapter is not null) + { + foreach (var row in writeAdapter.GetRows(props, cancellationToken)) { - foreach (var row in writeAdapter.GetRows(props, cancellationToken)) + rowBuilder.Clear(); + foreach (var column in row) { - rowBuilder.Clear(); - foreach (var column in row) - { - cancellationToken.ThrowIfCancellationRequested(); - AppendColumn(rowBuilder, column); - } - - RemoveTrailingSeparator(rowBuilder); - await _writer.WriteAsync(rowBuilder.ToString() + cancellationToken.ThrowIfCancellationRequested(); + AppendColumn(rowBuilder, column); + } + + RemoveTrailingSeparator(rowBuilder); + await _writer.WriteAsync(rowBuilder.ToString() #if NET5_0_OR_GREATER - .AsMemory(), cancellationToken + .AsMemory(), cancellationToken #endif - ).ConfigureAwait(false); - await _writer.WriteAsync(newLine + ).ConfigureAwait(false); + await _writer.WriteAsync(newLine #if NET5_0_OR_GREATER - .AsMemory(), cancellationToken + .AsMemory(), cancellationToken #endif - ).ConfigureAwait(false); + ).ConfigureAwait(false); - rowsWritten++; - } + rowsWritten++; } - else - { + } + else + { #if !SYNC_ONLY - await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken).ConfigureAwait(false)) - { - cancellationToken.ThrowIfCancellationRequested(); - rowBuilder.Clear(); + await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken).ConfigureAwait(false)) + { + cancellationToken.ThrowIfCancellationRequested(); + rowBuilder.Clear(); - await foreach (var column in row.ConfigureAwait(false)) - { - cancellationToken.ThrowIfCancellationRequested(); - AppendColumn(rowBuilder, column); - } + await foreach (var column in row.WithCancellation(cancellationToken).ConfigureAwait(false)) + { + AppendColumn(rowBuilder, column); + } - RemoveTrailingSeparator(rowBuilder); - await _writer.WriteAsync(rowBuilder.ToString() + RemoveTrailingSeparator(rowBuilder); + await _writer.WriteAsync(rowBuilder.ToString() #if NET5_0_OR_GREATER .AsMemory(), cancellationToken #endif - ).ConfigureAwait(false); - await _writer.WriteAsync(newLine + ).ConfigureAwait(false); + await _writer.WriteAsync(newLine #if NET5_0_OR_GREATER .AsMemory(), cancellationToken #endif - ).ConfigureAwait(false); + ).ConfigureAwait(false); - rowsWritten++; - } -#endif + rowsWritten++; } - return rowsWritten; +#endif } + return rowsWritten; + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public async Task SaveAsAsync(CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); + [Zomp.SyncMethodGenerator.CreateSyncVersion] + public async Task SaveAsAsync(CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); - var seperator = _configuration.Seperator.ToString(); - var newLine = _configuration.NewLine; + var seperator = _configuration.Seperator.ToString(); + var newLine = _configuration.NewLine; - if (_value == null) - { - await _writer.WriteAsync("" + if (_value is null) + { + await _writer.WriteAsync("" #if NET5_0_OR_GREATER - .AsMemory(), cancellationToken + .AsMemory(), cancellationToken #endif - ).ConfigureAwait(false); - await _writer.FlushAsync( + ).ConfigureAwait(false); + await _writer.FlushAsync( #if NET8_0_OR_GREATER - cancellationToken + cancellationToken #endif - ).ConfigureAwait(false); - return new int[0]; - } + ).ConfigureAwait(false); + return []; + } - var rowsWritten = await WriteValuesAsync(_writer, _value, seperator, newLine, cancellationToken).ConfigureAwait(false); - await _writer.FlushAsync( + var rowsWritten = await WriteValuesAsync(_writer, _value, seperator, newLine, cancellationToken).ConfigureAwait(false); + await _writer.FlushAsync( #if NET8_0_OR_GREATER - cancellationToken + cancellationToken #endif - ).ConfigureAwait(false); + ).ConfigureAwait(false); - return new[] { rowsWritten }; - } + return [rowsWritten]; + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default) - { - var rowsWritten = await SaveAsAsync(cancellationToken).ConfigureAwait(false); - return rowsWritten.FirstOrDefault(); - } + [Zomp.SyncMethodGenerator.CreateSyncVersion] + public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default) + { + var rowsWritten = await SaveAsAsync(cancellationToken).ConfigureAwait(false); + return rowsWritten.FirstOrDefault(); + } - public string ToCsvString(object value, ExcelColumnInfo p) - { - if (value == null) - return ""; + public string ToCsvString(object? value, ExcelColumnInfo? p) + { + if (value is null) + return ""; - if (value is DateTime dateTime) - { - if (p?.ExcelFormat != null) - { - return dateTime.ToString(p.ExcelFormat, _configuration.Culture); - } - return _configuration.Culture.Equals(CultureInfo.InvariantCulture) - ? dateTime.ToString("yyyy-MM-dd HH:mm:ss", _configuration.Culture) - : dateTime.ToString(_configuration.Culture); - } + if (value is DateTime dateTime) + { + if (p?.ExcelFormat is not null) + return dateTime.ToString(p.ExcelFormat, _configuration.Culture); + + return _configuration.Culture.Equals(CultureInfo.InvariantCulture) + ? dateTime.ToString("yyyy-MM-dd HH:mm:ss", _configuration.Culture) + : dateTime.ToString(_configuration.Culture); + } - if (p?.ExcelFormat != null && value is IFormattable formattableValue) - return formattableValue.ToString(p.ExcelFormat, _configuration.Culture); + if (p?.ExcelFormat is not null && value is IFormattable formattableValue) + return formattableValue.ToString(p.ExcelFormat, _configuration.Culture); - return Convert.ToString(value, _configuration.Culture); - } + return Convert.ToString(value, _configuration.Culture) ?? ""; + } - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) { - if (!_disposedValue) + if (disposing) { - if (disposing) - { - _writer.Dispose(); - // TODO: dispose managed state (managed objects) - } - - // TODO: free unmanaged resources (unmanaged objects) and override finalizer - // TODO: set large fields to null - _disposedValue = true; + _writer.Dispose(); + // TODO: dispose managed state (managed objects) } - } - // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources - ~CsvWriter() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: false); + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + _disposedValue = true; } + } - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } + // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources + ~CsvWriter() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: false); + } + + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); } } \ No newline at end of file diff --git a/src/MiniExcel/ExcelFactory.cs b/src/MiniExcel/ExcelFactory.cs index a07b4d3d..714d7474 100644 --- a/src/MiniExcel/ExcelFactory.cs +++ b/src/MiniExcel/ExcelFactory.cs @@ -1,65 +1,57 @@ -namespace MiniExcelLibs -{ - using MiniExcelLibs.Csv; - using MiniExcelLibs.OpenXml; - using MiniExcelLibs.OpenXml.SaveByTemplate; - using System; - using System.IO; - using System.Threading; - using System.Threading.Tasks; +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using MiniExcelLibs.Csv; +using MiniExcelLibs.OpenXml; +using MiniExcelLibs.SaveByTemplate; +using Zomp.SyncMethodGenerator; +using ExcelOpenXmlTemplate = MiniExcelLibs.SaveByTemplate.ExcelOpenXmlTemplate; + +namespace MiniExcelLibs; - internal static partial class ExcelReaderFactory +internal static partial class ExcelReaderFactory +{ + [CreateSyncVersion] + internal static async Task GetProviderAsync(Stream stream, ExcelType excelType, IMiniExcelConfiguration? configuration, CancellationToken cancellationToken = default) { - [Zomp.SyncMethodGenerator.CreateSyncVersion] - internal static async Task GetProviderAsync(Stream stream, ExcelType excelType, IConfiguration configuration, CancellationToken cancellationToken = default) + return excelType switch { - switch (excelType) - { - case ExcelType.CSV: - return new CsvReader(stream, configuration); - case ExcelType.XLSX: - return await ExcelOpenXmlSheetReader.CreateAsync(stream, configuration, cancellationToken: cancellationToken).ConfigureAwait(false); - default: - throw new NotSupportedException("Something went wrong. Please report this issue you are experiencing with MiniExcel."); - } - } + ExcelType.CSV => new CsvReader(stream, configuration), + ExcelType.XLSX => await ExcelOpenXmlSheetReader.CreateAsync(stream, configuration, cancellationToken: cancellationToken).ConfigureAwait(false), + _ => throw new NotSupportedException("Something went wrong. Please report this issue you are experiencing with MiniExcel.") + }; } +} - internal static class ExcelWriterFactory +internal static class ExcelWriterFactory +{ + internal static IExcelWriter GetProvider(Stream stream, object value, string? sheetName, ExcelType excelType, IMiniExcelConfiguration? configuration, bool printHeader) { - internal static IExcelWriter GetProvider(Stream stream, object value, string sheetName, ExcelType excelType, IConfiguration configuration, bool printHeader) - { - if (string.IsNullOrEmpty(sheetName)) - throw new ArgumentException("Sheet names can not be empty or null", nameof(sheetName)); - if (sheetName.Length > 31 && excelType == ExcelType.XLSX) - throw new ArgumentException("Sheet names must be less than 31 characters", nameof(sheetName)); - if (excelType == ExcelType.UNKNOWN) - throw new ArgumentException("Excel type cannot be ExcelType.UNKNOWN", nameof(excelType)); + if (string.IsNullOrEmpty(sheetName)) + throw new ArgumentException("Sheet names cannot be empty or null", nameof(sheetName)); + if (sheetName?.Length > 31 && excelType == ExcelType.XLSX) + throw new ArgumentException("Sheet names must be less than 31 characters", nameof(sheetName)); + if (excelType == ExcelType.UNKNOWN) + throw new ArgumentException("Excel type cannot be ExcelType.UNKNOWN", nameof(excelType)); - switch (excelType) - { - case ExcelType.CSV: - return new CsvWriter(stream, value, configuration, printHeader); - case ExcelType.XLSX: - return new ExcelOpenXmlSheetWriter(stream, value, sheetName, configuration, printHeader); - default: - throw new NotSupportedException($"The {excelType} Excel format is not supported"); - } - } + return excelType switch + { + ExcelType.CSV => new CsvWriter(stream, value, configuration, printHeader), + ExcelType.XLSX => new ExcelOpenXmlSheetWriter(stream, value, sheetName, configuration, printHeader), + _ => throw new NotSupportedException($"The {excelType} Excel format is not supported") + }; } +} - internal static class ExcelTemplateFactory +internal static class ExcelTemplateFactory +{ + internal static IExcelTemplate GetProvider(Stream stream, IMiniExcelConfiguration? configuration, ExcelType excelType = ExcelType.XLSX) { - internal static IExcelTemplateAsync GetProvider(Stream stream, IConfiguration configuration, ExcelType excelType = ExcelType.XLSX) - { - switch (excelType) - { - case ExcelType.XLSX: - var valueExtractor = new InputValueExtractor(); - return new ExcelOpenXmlTemplate(stream, configuration, valueExtractor); - default: - throw new NotSupportedException("Something went wrong. Please report this issue you are experiencing with MiniExcel."); - } - } + if (excelType != ExcelType.XLSX) + throw new NotSupportedException("Something went wrong. Please report this issue you are experiencing with MiniExcel."); + + var valueExtractor = new InputValueExtractor(); + return new ExcelOpenXmlTemplate(stream, configuration, valueExtractor); } -} +} \ No newline at end of file diff --git a/src/MiniExcel/ExcelType.cs b/src/MiniExcel/ExcelType.cs index 90709b2e..4a610571 100644 --- a/src/MiniExcel/ExcelType.cs +++ b/src/MiniExcel/ExcelType.cs @@ -1,13 +1,12 @@ -namespace MiniExcelLibs +namespace MiniExcelLibs; + +public enum ExcelType { - public enum ExcelType - { - XLSX, - //XLS, - CSV, - /// - /// Will auto check excel type by stream or file path - /// - UNKNOWN - } -} + XLSX, + //XLS, + CSV, + /// + /// Will auto check excel type by stream or file path + /// + UNKNOWN +} \ No newline at end of file diff --git a/src/MiniExcel/Exceptions/ExcelColumnNotFoundException.cs b/src/MiniExcel/Exceptions/ExcelColumnNotFoundException.cs index 5bd4492a..23d3cb86 100644 --- a/src/MiniExcel/Exceptions/ExcelColumnNotFoundException.cs +++ b/src/MiniExcel/Exceptions/ExcelColumnNotFoundException.cs @@ -1,24 +1,20 @@ using System.Collections.Generic; -namespace MiniExcelLibs.Exceptions -{ - public class ExcelColumnNotFoundException : KeyNotFoundException - { - public string ColumnName { get; set; } - public string[] ColumnAliases { get; } - public string ColumnIndex { get; set; } - public int RowIndex { get; set; } - public IDictionary Headers { get; } - public object RowValues { get; set; } +namespace MiniExcelLibs.Exceptions; - public ExcelColumnNotFoundException(string columnIndex, string columnName, string[] columnAliases, int rowIndex, IDictionary headers, object value, string message) : base(message) - { - ColumnIndex = columnIndex; - ColumnName = columnName; - ColumnAliases = columnAliases; - RowIndex = rowIndex; - Headers = headers; - RowValues = value; - } - } -} +public class ExcelColumnNotFoundException( + string? columnIndex, + string? columnName, + string[] columnAliases, + int rowIndex, + IDictionary headers, + object value, + string message) : KeyNotFoundException(message) +{ + public string? ColumnName { get; set; } = columnName; + public string? ColumnIndex { get; set; } = columnIndex; + public string[] ColumnAliases { get; } = columnAliases; + public int RowIndex { get; set; } = rowIndex; + public IDictionary Headers { get; } = headers; + public object RowValues { get; set; } = value; +} \ No newline at end of file diff --git a/src/MiniExcel/Exceptions/ExcelInvalidCastException.cs b/src/MiniExcel/Exceptions/ExcelInvalidCastException.cs index 35155e8b..2bc0a27d 100644 --- a/src/MiniExcel/Exceptions/ExcelInvalidCastException.cs +++ b/src/MiniExcel/Exceptions/ExcelInvalidCastException.cs @@ -1,19 +1,12 @@ using System; -namespace MiniExcelLibs.Exceptions +namespace MiniExcelLibs.Exceptions; + +public class ExcelInvalidCastException(string columnName, int row, object value, Type invalidCastType, string message) + : InvalidCastException(message) { - public class ExcelInvalidCastException : InvalidCastException - { - public string ColumnName { get; set; } - public int Row { get; set; } - public object Value { get; set; } - public Type InvalidCastType { get; set; } - public ExcelInvalidCastException(string columnName, int row, object value, Type invalidCastType, string message) : base(message) - { - ColumnName = columnName; - Row = row; - Value = value; - InvalidCastType = invalidCastType; - } - } -} + public string ColumnName { get; set; } = columnName; + public int Row { get; set; } = row; + public object Value { get; set; } = value; + public Type InvalidCastType { get; set; } = invalidCastType; +} \ No newline at end of file diff --git a/src/MiniExcel/IConfiguration.cs b/src/MiniExcel/IConfiguration.cs deleted file mode 100644 index 4faedbc4..00000000 --- a/src/MiniExcel/IConfiguration.cs +++ /dev/null @@ -1,19 +0,0 @@ -using MiniExcelLibs.Attributes; -using System.Globalization; - -namespace MiniExcelLibs -{ - public interface IConfiguration { } - public abstract class Configuration : IConfiguration - { - public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; - public DynamicExcelColumn[] DynamicColumns { get; set; } - public int BufferSize { get; set; } = 1024 * 512; - public bool FastMode { get; set; } = false; - - /// - /// When exporting using DataReader, the data not in DynamicColumn will be filtered. - /// - public bool DynamicColumnFirst { get; set; } = false; - } -} diff --git a/src/MiniExcel/IExcelReader.cs b/src/MiniExcel/IExcelReader.cs index eb8e2010..7f106895 100644 --- a/src/MiniExcel/IExcelReader.cs +++ b/src/MiniExcel/IExcelReader.cs @@ -1,22 +1,27 @@ using System; using System.Collections.Generic; using System.Threading; +using Zomp.SyncMethodGenerator; -namespace MiniExcelLibs +namespace MiniExcelLibs; + +internal partial interface IExcelReader : IDisposable { - internal partial interface IExcelReader : IDisposable - { - [Zomp.SyncMethodGenerator.CreateSyncVersion] - IAsyncEnumerable> QueryAsync(bool useHeaderRow, string sheetName, string startCell, CancellationToken cancellationToken = default); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - IAsyncEnumerable QueryAsync(string sheetName, string startCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new(); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - IAsyncEnumerable> QueryRangeAsync(bool useHeaderRow, string sheetName, string startCell, string endCell, CancellationToken cancellationToken = default); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - IAsyncEnumerable QueryRangeAsync(string sheetName, string startCell, string endCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new(); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - IAsyncEnumerable> QueryRangeAsync(bool useHeaderRow, string sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, CancellationToken cancellationToken = default); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - IAsyncEnumerable QueryRangeAsync(string sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new(); - } -} + [CreateSyncVersion] + IAsyncEnumerable> QueryAsync(bool useHeaderRow, string? sheetName, string startCell, CancellationToken cancellationToken = default); + + [CreateSyncVersion] + IAsyncEnumerable QueryAsync(string? sheetName, string startCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new(); + + [CreateSyncVersion] + IAsyncEnumerable> QueryRangeAsync(bool useHeaderRow, string? sheetName, string startCell, string endCell, CancellationToken cancellationToken = default); + + [CreateSyncVersion] + IAsyncEnumerable QueryRangeAsync(string? sheetName, string startCell, string endCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new(); + + [CreateSyncVersion] + IAsyncEnumerable> QueryRangeAsync(bool useHeaderRow, string? sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, CancellationToken cancellationToken = default); + + [CreateSyncVersion] + IAsyncEnumerable QueryRangeAsync(string? sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new(); +} \ No newline at end of file diff --git a/src/MiniExcel/IExcelTemplate.cs b/src/MiniExcel/IExcelTemplate.cs index 6436dc46..c5ec65a6 100644 --- a/src/MiniExcel/IExcelTemplate.cs +++ b/src/MiniExcel/IExcelTemplate.cs @@ -1,17 +1,20 @@ using System.Threading; using System.Threading.Tasks; +using Zomp.SyncMethodGenerator; -namespace MiniExcelLibs +namespace MiniExcelLibs; + +internal partial interface IExcelTemplate { - internal partial interface IExcelTemplateAsync - { - [Zomp.SyncMethodGenerator.CreateSyncVersion] - Task SaveAsByTemplateAsync(string templatePath, object value, CancellationToken cancellationToken = default(CancellationToken)); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - Task SaveAsByTemplateAsync(byte[] templateBytes, object value, CancellationToken cancellationToken = default(CancellationToken)); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - Task MergeSameCellsAsync(string path, CancellationToken cancellationToken = default(CancellationToken)); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - Task MergeSameCellsAsync(byte[] fileInBytes, CancellationToken cancellationToken = default(CancellationToken)); - } -} + [CreateSyncVersion] + Task SaveAsByTemplateAsync(string templatePath, object value, CancellationToken cancellationToken = default); + + [CreateSyncVersion] + Task SaveAsByTemplateAsync(byte[] templateBytes, object value, CancellationToken cancellationToken = default); + + [CreateSyncVersion] + Task MergeSameCellsAsync(string path, CancellationToken cancellationToken = default); + + [CreateSyncVersion] + Task MergeSameCellsAsync(byte[] fileInBytes, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/MiniExcel/IExcelWriter.cs b/src/MiniExcel/IExcelWriter.cs index 74928dd2..2b7e59c1 100644 --- a/src/MiniExcel/IExcelWriter.cs +++ b/src/MiniExcel/IExcelWriter.cs @@ -1,13 +1,14 @@ using System.Threading; using System.Threading.Tasks; +using Zomp.SyncMethodGenerator; -namespace MiniExcelLibs +namespace MiniExcelLibs; + +internal partial interface IExcelWriter { - internal partial interface IExcelWriter - { - [Zomp.SyncMethodGenerator.CreateSyncVersion] - Task SaveAsAsync(CancellationToken cancellationToken = default); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default); - } -} + [CreateSyncVersion] + Task SaveAsAsync(CancellationToken cancellationToken = default); + + [CreateSyncVersion] + Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/MiniExcel/IMiniExcelDataReader.cs b/src/MiniExcel/IMiniExcelDataReader.cs index 3f304451..253374c7 100644 --- a/src/MiniExcel/IMiniExcelDataReader.cs +++ b/src/MiniExcel/IMiniExcelDataReader.cs @@ -3,22 +3,16 @@ using System.Threading; using System.Threading.Tasks; -namespace MiniExcelLibs -{ -#if !NET8_0_OR_GREATER - public interface IMiniExcelDataReader : IDataReader -#else - public interface IMiniExcelDataReader : IDataReader, IAsyncDisposable -#endif - { - Task CloseAsync(); - - Task GetNameAsync(int i, CancellationToken cancellationToken = default); - - Task GetValueAsync(int i, CancellationToken cancellationToken = default); +namespace MiniExcelLibs; - Task NextResultAsync(CancellationToken cancellationToken = default); - - Task ReadAsync(CancellationToken cancellationToken = default); - } -} +public interface IMiniExcelDataReader : IDataReader +#if NET8_0_OR_GREATER + ,IAsyncDisposable +#endif +{ + Task CloseAsync(); + Task GetNameAsync(int i, CancellationToken cancellationToken = default); + Task GetValueAsync(int i, CancellationToken cancellationToken = default); + Task NextResultAsync(CancellationToken cancellationToken = default); + Task ReadAsync(CancellationToken cancellationToken = default); +} \ No newline at end of file diff --git a/src/MiniExcel/MiniExcel.Code.cs b/src/MiniExcel/MiniExcel.Code.cs deleted file mode 100644 index 0df1e936..00000000 --- a/src/MiniExcel/MiniExcel.Code.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace MiniExcelLibs -{ - public static partial class MiniExcel - { - public static string LISENCE_CODE = null; - } -} diff --git a/src/MiniExcel/MiniExcel.cs b/src/MiniExcel/MiniExcel.cs index a9ae3215..461343d4 100644 --- a/src/MiniExcel/MiniExcel.cs +++ b/src/MiniExcel/MiniExcel.cs @@ -1,407 +1,425 @@ -using MiniExcelLibs.OpenXml; -using MiniExcelLibs.OpenXml.Models; -using MiniExcelLibs.Picture; -using MiniExcelLibs.Utils; -using MiniExcelLibs.Zip; -using System; +using System; using System.Collections; using System.Collections.Generic; using System.Data; +using System.Diagnostics.CodeAnalysis; using System.Dynamic; using System.IO; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using MiniExcelLibs.OpenXml; +using MiniExcelLibs.OpenXml.Models; +using MiniExcelLibs.Picture; +using MiniExcelLibs.Utils; +using MiniExcelLibs.Zip; +using Zomp.SyncMethodGenerator; + +namespace MiniExcelLibs; -namespace MiniExcelLibs +public static partial class MiniExcel { - public static partial class MiniExcel + [CreateSyncVersion] + public static async Task AddPictureAsync(string path, CancellationToken cancellationToken = default, params MiniExcelPicture[] images) { - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task AddPictureAsync(string path, CancellationToken cancellationToken = default, params MiniExcelPicture[] images) - { - using (var stream = File.Open(path, FileMode.OpenOrCreate)) - await MiniExcelPictureImplement.AddPictureAsync(stream, cancellationToken, images).ConfigureAwait(false); - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task AddPictureAsync(Stream excelStream, CancellationToken cancellationToken = default, params MiniExcelPicture[] images) - { - await MiniExcelPictureImplement.AddPictureAsync(excelStream, cancellationToken, images).ConfigureAwait(false); - } - - public static MiniExcelDataReader GetReader(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) - { - var stream = FileHelper.OpenSharedRead(path); - return new MiniExcelDataReader(stream, useHeaderRow, sheetName, excelType, startCell, configuration); - } + using var stream = File.Open(path, FileMode.OpenOrCreate); + await MiniExcelPictureImplement.AddPictureAsync(stream, cancellationToken, images).ConfigureAwait(false); + } - public static MiniExcelDataReader GetReader(this Stream stream, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) - { - return new MiniExcelDataReader(stream, useHeaderRow, sheetName, excelType, startCell, configuration); - } + [CreateSyncVersion] + public static async Task AddPictureAsync(Stream excelStream, CancellationToken cancellationToken = default, params MiniExcelPicture[] images) + { + await MiniExcelPictureImplement.AddPictureAsync(excelStream, cancellationToken, images).ConfigureAwait(false); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task InsertAsync(string path, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, bool printHeader = true, bool overwriteSheet = false, CancellationToken cancellationToken = default) - { - if (Path.GetExtension(path).ToLowerInvariant() == ".xlsm") - throw new NotSupportedException("MiniExcel's Insert does not support the .xlsm format"); + public static MiniExcelDataReader GetReader(string path, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null) + { + var stream = FileHelper.OpenSharedRead(path); + return new MiniExcelDataReader(stream, useHeaderRow, sheetName, excelType, startCell, configuration); + } - if (!File.Exists(path)) - { - var rowsWritten = await SaveAsAsync(path, value, printHeader, sheetName, excelType, configuration, cancellationToken: cancellationToken).ConfigureAwait(false); - return rowsWritten.FirstOrDefault(); - } + public static MiniExcelDataReader GetReader(this Stream stream, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null) + { + return new MiniExcelDataReader(stream, useHeaderRow, sheetName, excelType, startCell, configuration); + } - if (excelType == ExcelType.CSV) - { - using (var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read, 4096, FileOptions.SequentialScan)) - return await InsertAsync(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet, cancellationToken).ConfigureAwait(false); - } - else - { - using (var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.SequentialScan)) - return await InsertAsync(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet, cancellationToken).ConfigureAwait(false); - } - } + [CreateSyncVersion] + public static async Task InsertAsync(string path, object value, string? sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IMiniExcelConfiguration? configuration = null, bool printHeader = true, bool overwriteSheet = false, CancellationToken cancellationToken = default) + { + if (Path.GetExtension(path).Equals(".xlsm", StringComparison.InvariantCultureIgnoreCase)) + throw new NotSupportedException("MiniExcel's Insert does not support the .xlsm format"); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "TODO: CsvWriter needs to be disposed")] - public static async Task InsertAsync(this Stream stream, object value, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, bool printHeader = true, bool overwriteSheet = false, CancellationToken cancellationToken = default) + if (!File.Exists(path)) { - stream.Seek(0, SeekOrigin.End); - if (excelType == ExcelType.CSV) - { - var newValue = value is IEnumerable || value is IDataReader ? value : new[] { value }; - var provider = ExcelWriterFactory.GetProvider(stream, newValue, sheetName, excelType, configuration, false); - return await provider.InsertAsync(overwriteSheet, cancellationToken).ConfigureAwait(false); - } - else - { - var configOrDefault = configuration ?? new OpenXmlConfiguration { FastMode = true }; - return await ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configOrDefault, printHeader).InsertAsync(overwriteSheet, cancellationToken).ConfigureAwait(false); - } + var rowsWritten = await SaveAsAsync(path, value, printHeader, sheetName, excelType, configuration, cancellationToken: cancellationToken).ConfigureAwait(false); + return rowsWritten.FirstOrDefault(); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task SaveAsAsync(string path, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IConfiguration configuration = null, bool overwriteFile = false, CancellationToken cancellationToken = default) + if (excelType == ExcelType.CSV) { - if (Path.GetExtension(path).ToLowerInvariant() == ".xlsm") - throw new NotSupportedException("MiniExcel's SaveAs does not support the .xlsm format"); - - using (var stream = overwriteFile ? File.Create(path) : new FileStream(path, FileMode.CreateNew)) - return await SaveAsAsync(stream, value, printHeader, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, cancellationToken).ConfigureAwait(false); + using var stream = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read, 4096, FileOptions.SequentialScan); + return await InsertAsync(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet, cancellationToken).ConfigureAwait(false); } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - [System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "TODO: CsvWriter needs to be disposed")] - public static async Task SaveAsAsync(this Stream stream, object value, bool printHeader = true, string sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, CancellationToken cancellationToken = default) + else { - return await ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader).SaveAsAsync(cancellationToken).ConfigureAwait(false); + using var stream = new FileStream(path, FileMode.Open, FileAccess.ReadWrite, FileShare.Read, 4096, FileOptions.SequentialScan); + return await InsertAsync(stream, value, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, printHeader, overwriteSheet, cancellationToken).ConfigureAwait(false); } + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async IAsyncEnumerable QueryAsync(string path, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, bool hasHeader = true, [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, new() + [CreateSyncVersion] + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "TODO: CsvWriter needs to be disposed")] + public static async Task InsertAsync(this Stream stream, object value, string? sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IMiniExcelConfiguration? configuration = null, bool printHeader = true, bool overwriteSheet = false, CancellationToken cancellationToken = default) + { + stream.Seek(0, SeekOrigin.End); + if (excelType == ExcelType.CSV) { - using (var stream = FileHelper.OpenSharedRead(path)) - await foreach (var item in QueryAsync(stream, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration, hasHeader, cancellationToken).ConfigureAwait(false)) - yield return item; //Foreach yield return twice reason : https://stackoverflow.com/questions/66791982/ienumerable-extract-code-lazy-loading-show-stream-was-not-readable + var newValue = value is IEnumerable or IDataReader ? value : new[] { value }; + var provider = ExcelWriterFactory.GetProvider(stream, newValue, sheetName, excelType, configuration, false); + return await provider.InsertAsync(overwriteSheet, cancellationToken).ConfigureAwait(false); } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async IAsyncEnumerable QueryAsync(this Stream stream, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, bool hasHeader = true, [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, new() + else { - using (var excelReader = await ExcelReaderFactory.GetProviderAsync(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration, cancellationToken).ConfigureAwait(false)) - await foreach (var item in excelReader.QueryAsync(sheetName, startCell, hasHeader, cancellationToken).ConfigureAwait(false)) - yield return item; + configuration ??= new OpenXmlConfiguration { FastMode = true }; + return await ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader).InsertAsync(overwriteSheet, cancellationToken).ConfigureAwait(false); } + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async IAsyncEnumerable QueryAsync(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - using (var stream = FileHelper.OpenSharedRead(path)) - await foreach (var item in QueryAsync(stream, useHeaderRow, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration, cancellationToken).ConfigureAwait(false)) - yield return item; - } + [CreateSyncVersion] + public static async Task SaveAsAsync(string path, object value, bool printHeader = true, string? sheetName = "Sheet1", ExcelType excelType = ExcelType.UNKNOWN, IMiniExcelConfiguration? configuration = null, bool overwriteFile = false, CancellationToken cancellationToken = default) + { + if (Path.GetExtension(path).ToLowerInvariant() == ".xlsm") + throw new NotSupportedException("MiniExcel's SaveAs does not support the .xlsm format"); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async IAsyncEnumerable QueryAsync(this Stream stream, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - using (var excelReader = await ExcelReaderFactory.GetProviderAsync(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration, cancellationToken).ConfigureAwait(false)) - await foreach (var item in excelReader.QueryAsync(useHeaderRow, sheetName, startCell, cancellationToken).ConfigureAwait(false)) - yield return item.Aggregate(new ExpandoObject() as IDictionary, - (dict, p) => { dict.Add(p); return dict; }); - } + using var stream = overwriteFile ? File.Create(path) : new FileStream(path, FileMode.CreateNew); + return await SaveAsAsync(stream, value, printHeader, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), configuration, cancellationToken).ConfigureAwait(false); + } - #region QueryRange - - /// - /// Extract the given range。 Only uppercase letters are effective。 - /// e.g. - /// MiniExcel.QueryRange(path, startCell: "A2", endCell: "C3") - /// A2 represents the second row of column A, C3 represents the third row of column C - /// If you don't want to restrict rows, just don't include numbers - /// - /// - /// - /// - /// - /// top left corner - /// lower right corner - /// - /// - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async IAsyncEnumerable QueryRangeAsync(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", string endCell = "", IConfiguration configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - using (var stream = FileHelper.OpenSharedRead(path)) - await foreach (var item in QueryRangeAsync(stream, useHeaderRow, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startCell, endCell, configuration, cancellationToken).ConfigureAwait(false)) - yield return item; - } + [CreateSyncVersion] + [SuppressMessage("Reliability", "CA2000:Dispose objects before losing scope", Justification = "TODO: CsvWriter needs to be disposed")] + public static async Task SaveAsAsync(this Stream stream, object value, bool printHeader = true, string? sheetName = "Sheet1", ExcelType excelType = ExcelType.XLSX, IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + return await ExcelWriterFactory.GetProvider(stream, value, sheetName, excelType, configuration, printHeader) + .SaveAsAsync(cancellationToken) + .ConfigureAwait(false); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async IAsyncEnumerable QueryRangeAsync(this Stream stream, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", string endCell = "", IConfiguration configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - using (var excelReader = await ExcelReaderFactory.GetProviderAsync(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration, cancellationToken).ConfigureAwait(false)) - await foreach (var item in excelReader.QueryRangeAsync(useHeaderRow, sheetName, startCell, endCell, cancellationToken).ConfigureAwait(false)) - yield return item.Aggregate(new ExpandoObject() as IDictionary, - (dict, p) => { dict.Add(p); return dict; }); - } + [CreateSyncVersion] + public static async IAsyncEnumerable QueryAsync(string path, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null, bool hasHeader = true, [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, new() + { + using var stream = FileHelper.OpenSharedRead(path); + + var query = QueryAsync(stream, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration, hasHeader, cancellationToken); + await foreach (var item in query.ConfigureAwait(false)) + yield return item; //Foreach yield return twice reason : https://stackoverflow.com/questions/66791982/ienumerable-extract-code-lazy-loading-show-stream-was-not-readable + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async IAsyncEnumerable QueryRangeAsync(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, int startRowIndex = 1, int startColumnIndex = 1, int? endRowIndex = null, int? endColumnIndex = null, IConfiguration configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - using (var stream = FileHelper.OpenSharedRead(path)) - await foreach (var item in QueryRangeAsync(stream, useHeaderRow, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, configuration, cancellationToken).ConfigureAwait(false)) - yield return item; - } + [CreateSyncVersion] + public static async IAsyncEnumerable QueryAsync(this Stream stream, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null, bool hasHeader = true, [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, new() + { + using var excelReader = await ExcelReaderFactory.GetProviderAsync(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration, cancellationToken).ConfigureAwait(false); + await foreach (var item in excelReader.QueryAsync(sheetName, startCell, hasHeader, cancellationToken).ConfigureAwait(false)) + yield return item; + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async IAsyncEnumerable QueryRangeAsync(this Stream stream, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, int startRowIndex = 1, int startColumnIndex = 1, int? endRowIndex = null, int? endColumnIndex = null, IConfiguration configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - using (var excelReader = await ExcelReaderFactory.GetProviderAsync(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration, cancellationToken).ConfigureAwait(false)) - await foreach (var item in excelReader.QueryRangeAsync(useHeaderRow, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, cancellationToken).ConfigureAwait(false)) - yield return item.Aggregate(new ExpandoObject() as IDictionary, - (dict, p) => { dict.Add(p); return dict; }); - } + [CreateSyncVersion] + public static async IAsyncEnumerable QueryAsync(string path, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + using var stream = FileHelper.OpenSharedRead(path); + await foreach (var item in QueryAsync(stream, useHeaderRow, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration, cancellationToken).ConfigureAwait(false)) + yield return item; + } - #endregion QueryRange + [CreateSyncVersion] + public static async IAsyncEnumerable QueryAsync(this Stream stream, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + using var excelReader = await ExcelReaderFactory.GetProviderAsync(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration, cancellationToken).ConfigureAwait(false); + await foreach (var item in excelReader.QueryAsync(useHeaderRow, sheetName, startCell, cancellationToken).ConfigureAwait(false)) + yield return item.Aggregate( + new ExpandoObject() as IDictionary, + (dict, p) => { dict.Add(p); return dict; }); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task SaveAsByTemplateAsync(string path, string templatePath, object value, IConfiguration configuration = null, CancellationToken cancellationToken = default) - { - using (var stream = File.Create(path)) - await SaveAsByTemplateAsync(stream, templatePath, value, configuration, cancellationToken).ConfigureAwait(false); - } + #region QueryRange + + /// + /// Extract the given range。 Only uppercase letters are effective。 + /// e.g. + /// MiniExcel.QueryRange(path, startCell: "A2", endCell: "C3") + /// A2 represents the second row of column A, C3 represents the third row of column C + /// If you don't want to restrict rows, just don't include numbers + /// + /// + /// + /// + /// + /// top left corner + /// lower right corner + /// + /// + /// + [CreateSyncVersion] + public static async IAsyncEnumerable QueryRangeAsync(string path, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", string endCell = "", IMiniExcelConfiguration? configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + using var stream = FileHelper.OpenSharedRead(path); + await foreach (var item in QueryRangeAsync(stream, useHeaderRow, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startCell, endCell, configuration, cancellationToken).ConfigureAwait(false)) + yield return item; + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task SaveAsByTemplateAsync(string path, byte[] templateBytes, object value, IConfiguration configuration = null, CancellationToken cancellationToken = default) - { - using (var stream = File.Create(path)) - await SaveAsByTemplateAsync(stream, templateBytes, value, configuration, cancellationToken).ConfigureAwait(false); - } + [CreateSyncVersion] + public static async IAsyncEnumerable QueryRangeAsync(this Stream stream, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", string endCell = "", IMiniExcelConfiguration? configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + using var excelReader = await ExcelReaderFactory.GetProviderAsync(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration, cancellationToken).ConfigureAwait(false); + await foreach (var item in excelReader.QueryRangeAsync(useHeaderRow, sheetName, startCell, endCell, cancellationToken).ConfigureAwait(false)) + yield return item.Aggregate( + new ExpandoObject() as IDictionary, + (dict, p) => { dict.Add(p); return dict; }); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task SaveAsByTemplateAsync(this Stream stream, string templatePath, object value, IConfiguration configuration = null, CancellationToken cancellationToken = default) - { - await ExcelTemplateFactory.GetProvider(stream, configuration).SaveAsByTemplateAsync(templatePath, value, cancellationToken).ConfigureAwait(false); - } + [CreateSyncVersion] + public static async IAsyncEnumerable QueryRangeAsync(string path, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, int startRowIndex = 1, int startColumnIndex = 1, int? endRowIndex = null, int? endColumnIndex = null, IMiniExcelConfiguration? configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + using var stream = FileHelper.OpenSharedRead(path); + await foreach (var item in QueryRangeAsync(stream, useHeaderRow, sheetName, ExcelTypeHelper.GetExcelType(path, excelType), startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, configuration, cancellationToken).ConfigureAwait(false)) + yield return item; + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task SaveAsByTemplateAsync(this Stream stream, byte[] templateBytes, object value, IConfiguration configuration = null, CancellationToken cancellationToken = default) - { - await ExcelTemplateFactory.GetProvider(stream, configuration).SaveAsByTemplateAsync(templateBytes, value, cancellationToken).ConfigureAwait(false); - } + [CreateSyncVersion] + public static async IAsyncEnumerable QueryRangeAsync(this Stream stream, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, int startRowIndex = 1, int startColumnIndex = 1, int? endRowIndex = null, int? endColumnIndex = null, IMiniExcelConfiguration? configuration = null, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + using var excelReader = await ExcelReaderFactory.GetProviderAsync(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration, cancellationToken).ConfigureAwait(false); + await foreach (var item in excelReader.QueryRangeAsync(useHeaderRow, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, cancellationToken).ConfigureAwait(false)) + yield return item.Aggregate( + new ExpandoObject() as IDictionary, + (dict, p) => { dict.Add(p); return dict; }); + } - #region MergeCells + #endregion QueryRange - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task MergeSameCellsAsync(string mergedFilePath, string path, ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, CancellationToken cancellationToken = default) - { - using (var stream = File.Create(mergedFilePath)) - await MergeSameCellsAsync(stream, path, excelType, configuration, cancellationToken).ConfigureAwait(false); - } + [CreateSyncVersion] + public static async Task SaveAsByTemplateAsync(string path, string templatePath, object value, IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + using var stream = File.Create(path); + await SaveAsByTemplateAsync(stream, templatePath, value, configuration, cancellationToken).ConfigureAwait(false); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task MergeSameCellsAsync(this Stream stream, string path, ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, CancellationToken cancellationToken = default) - { - await ExcelTemplateFactory.GetProvider(stream, configuration, excelType).MergeSameCellsAsync(path, cancellationToken).ConfigureAwait(false); - } + [CreateSyncVersion] + public static async Task SaveAsByTemplateAsync(string path, byte[] templateBytes, object value, IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + using var stream = File.Create(path); + await SaveAsByTemplateAsync(stream, templateBytes, value, configuration, cancellationToken).ConfigureAwait(false); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task MergeSameCellsAsync(this Stream stream, byte[] filePath, ExcelType excelType = ExcelType.XLSX, IConfiguration configuration = null, CancellationToken cancellationToken = default) - { - await ExcelTemplateFactory.GetProvider(stream, configuration, excelType).MergeSameCellsAsync(filePath, cancellationToken).ConfigureAwait(false); - } + [CreateSyncVersion] + public static async Task SaveAsByTemplateAsync(this Stream stream, string templatePath, object value, IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + await ExcelTemplateFactory.GetProvider(stream, configuration) + .SaveAsByTemplateAsync(templatePath, value, cancellationToken) + .ConfigureAwait(false); + } - #endregion + [CreateSyncVersion] + public static async Task SaveAsByTemplateAsync(this Stream stream, byte[] templateBytes, object value, IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + await ExcelTemplateFactory.GetProvider(stream, configuration) + .SaveAsByTemplateAsync(templateBytes, value, cancellationToken) + .ConfigureAwait(false); + } - /// - /// QueryAsDataTable is not recommended, because it'll load all data into memory. - /// - [Obsolete("QueryAsDataTable is not recommended, because it'll load all data into memory.")] - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public async static Task QueryAsDataTableAsync(string path, bool useHeaderRow = true, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, CancellationToken cancellationToken = default) - { - using (var stream = FileHelper.OpenSharedRead(path)) - { - return await QueryAsDataTableAsync(stream, useHeaderRow, sheetName, excelType: ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration, cancellationToken).ConfigureAwait(false); - } - } + #region MergeCells - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public async static Task QueryAsDataTableAsync(this Stream stream, bool useHeaderRow = true, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); + [CreateSyncVersion] + public static async Task MergeSameCellsAsync(string mergedFilePath, string path, ExcelType excelType = ExcelType.XLSX, IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + using var stream = File.Create(mergedFilePath); + await MergeSameCellsAsync(stream, path, excelType, configuration, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + public static async Task MergeSameCellsAsync(this Stream stream, string path, ExcelType excelType = ExcelType.XLSX, IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + await ExcelTemplateFactory.GetProvider(stream, configuration, excelType) + .MergeSameCellsAsync(path, cancellationToken) + .ConfigureAwait(false); + } + + [CreateSyncVersion] + public static async Task MergeSameCellsAsync(this Stream stream, byte[] filePath, ExcelType excelType = ExcelType.XLSX, IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + await ExcelTemplateFactory.GetProvider(stream, configuration, excelType) + .MergeSameCellsAsync(filePath, cancellationToken) + .ConfigureAwait(false); + } + + #endregion + + /// + /// QueryAsDataTable is not recommended, because it'll load all data into memory. + /// + [Obsolete("QueryAsDataTable is not recommended, because it'll load all data into memory.")] + [CreateSyncVersion] + public static async Task QueryAsDataTableAsync(string path, bool useHeaderRow = true, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + using var stream = FileHelper.OpenSharedRead(path); + return await QueryAsDataTableAsync(stream, useHeaderRow, sheetName, excelType: ExcelTypeHelper.GetExcelType(path, excelType), startCell, configuration, cancellationToken).ConfigureAwait(false); + } - if (sheetName == null && excelType != ExcelType.CSV) /*Issue #279*/ - sheetName = (await stream.GetSheetNamesAsync(configuration as OpenXmlConfiguration, cancellationToken).ConfigureAwait(false)).First(); + [CreateSyncVersion] + public static async Task QueryAsDataTableAsync(this Stream stream, bool useHeaderRow = true, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + /*Issue #279*/ + if (sheetName is null && excelType != ExcelType.CSV) + sheetName = (await stream.GetSheetNamesAsync(configuration as OpenXmlConfiguration, cancellationToken).ConfigureAwait(false)).First(); - var dt = new DataTable(sheetName); - var first = true; - var provider = await ExcelReaderFactory.GetProviderAsync(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration, cancellationToken).ConfigureAwait(false); - var rows = provider.QueryAsync(false, sheetName, startCell, cancellationToken).ConfigureAwait(false); + var dt = new DataTable(sheetName); + var first = true; + using var provider = await ExcelReaderFactory.GetProviderAsync(stream, ExcelTypeHelper.GetExcelType(stream, excelType), configuration, cancellationToken).ConfigureAwait(false); + var rows = provider.QueryAsync(false, sheetName, startCell, cancellationToken); - var columnDict = new Dictionary(); - await foreach (IDictionary row in rows) + var columnDict = new Dictionary(); + await foreach (var row in rows.ConfigureAwait(false)) + { + if (first) { - if (first) + foreach (var entry in row) { - foreach (var entry in row) - { - var columnName = useHeaderRow ? entry.Value?.ToString() : entry.Key; - if (!string.IsNullOrWhiteSpace(columnName)) // avoid #298 : Column '' does not belong to table - { - var column = new DataColumn(columnName, typeof(object)) { Caption = columnName }; - dt.Columns.Add(column); - columnDict.Add(entry.Key, columnName);//same column name throw exception??? - } - } - dt.BeginLoadData(); - first = false; - if (useHeaderRow) + cancellationToken.ThrowIfCancellationRequested(); + + var columnName = useHeaderRow ? entry.Value?.ToString() : entry.Key; + if (!string.IsNullOrWhiteSpace(columnName)) // avoid #298 : Column '' does not belong to table { - continue; + var column = new DataColumn(columnName, typeof(object)) { Caption = columnName }; + dt.Columns.Add(column); + columnDict.Add(entry.Key, columnName!);//same column name throw exception??? } } - - var newRow = dt.NewRow(); - foreach (var entry in columnDict) + + dt.BeginLoadData(); + first = false; + if (useHeaderRow) { - newRow[entry.Value] = row[entry.Key]; //TODO: optimize not using string key + continue; } + } - dt.Rows.Add(newRow); + var newRow = dt.NewRow(); + foreach (var entry in columnDict) + { + newRow[entry.Value] = row[entry.Key]; //TODO: optimize not using string key } - dt.EndLoadData(); - return dt; + dt.Rows.Add(newRow); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task> GetSheetNamesAsync(string path, OpenXmlConfiguration config = null, CancellationToken cancellationToken = default) - { - using (var stream = FileHelper.OpenSharedRead(path)) - return await GetSheetNamesAsync(stream, config, cancellationToken).ConfigureAwait(false); - } + dt.EndLoadData(); + return dt; + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task> GetSheetNamesAsync(this Stream stream, OpenXmlConfiguration config = null, CancellationToken cancellationToken = default) - { - config = config ?? OpenXmlConfiguration.DefaultConfig; + [CreateSyncVersion] + public static async Task> GetSheetNamesAsync(string path, OpenXmlConfiguration? config = null, CancellationToken cancellationToken = default) + { + using var stream = FileHelper.OpenSharedRead(path); + return await GetSheetNamesAsync(stream, config, cancellationToken).ConfigureAwait(false); + } - // For some reason this breaks the tests + [CreateSyncVersion] + public static async Task> GetSheetNamesAsync(this Stream stream, OpenXmlConfiguration? config = null, CancellationToken cancellationToken = default) + { + config ??= OpenXmlConfiguration.DefaultConfig; + + // todo: figure out why adding using statement breaks the tests #pragma warning disable CA2000 // Dispose objects before losing scope - var archive = new ExcelOpenXmlZip(stream); + var archive = new ExcelOpenXmlZip(stream); #pragma warning restore CA2000 // Dispose objects before losing scope - using var reader = await ExcelOpenXmlSheetReader.CreateAsync(stream, config, cancellationToken: cancellationToken).ConfigureAwait(false); - var rels = await reader.GetWorkbookRelsAsync(archive.entries, cancellationToken).ConfigureAwait(false); - return rels.Select(s => s.Name).ToList(); - } + + using var reader = await ExcelOpenXmlSheetReader.CreateAsync(stream, config, cancellationToken: cancellationToken).ConfigureAwait(false); + var rels = await reader.GetWorkbookRelsAsync(archive.EntryCollection, cancellationToken).ConfigureAwait(false); + + return rels?.Select(s => s.Name).ToList() ?? []; + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task> GetSheetInformationsAsync(string path, OpenXmlConfiguration config = null, CancellationToken cancellationToken = default) - { - using (var stream = FileHelper.OpenSharedRead(path)) - return await GetSheetInformationsAsync(stream, config, cancellationToken).ConfigureAwait(false); - } + [CreateSyncVersion] + public static async Task> GetSheetInformationsAsync(string path, OpenXmlConfiguration? config = null, CancellationToken cancellationToken = default) + { + using var stream = FileHelper.OpenSharedRead(path); + return await GetSheetInformationsAsync(stream, config, cancellationToken).ConfigureAwait(false); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task> GetSheetInformationsAsync(this Stream stream, OpenXmlConfiguration config = null, CancellationToken cancellationToken = default) - { - config = config ?? OpenXmlConfiguration.DefaultConfig; + [CreateSyncVersion] + public static async Task> GetSheetInformationsAsync(this Stream stream, OpenXmlConfiguration? config = null, CancellationToken cancellationToken = default) + { + config ??= OpenXmlConfiguration.DefaultConfig; - using var archive = new ExcelOpenXmlZip(stream); - using var reader = await ExcelOpenXmlSheetReader.CreateAsync(stream, config, cancellationToken: cancellationToken).ConfigureAwait(false); - var rels = await reader.GetWorkbookRelsAsync(archive.entries, cancellationToken).ConfigureAwait(false); - return rels.Select((s, i) => s.ToSheetInfo((uint)i)).ToList(); - } + using var archive = new ExcelOpenXmlZip(stream); + using var reader = await ExcelOpenXmlSheetReader.CreateAsync(stream, config, cancellationToken: cancellationToken).ConfigureAwait(false); + var rels = await reader.GetWorkbookRelsAsync(archive.EntryCollection, cancellationToken).ConfigureAwait(false); + + return rels?.Select((s, i) => s.ToSheetInfo((uint)i)).ToList() ?? []; + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task> GetColumnsAsync(string path, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, CancellationToken cancellationToken = default) - { - using (var stream = FileHelper.OpenSharedRead(path)) - return await GetColumnsAsync(stream, useHeaderRow, sheetName, excelType, startCell, configuration, cancellationToken).ConfigureAwait(false); - } + [CreateSyncVersion] + public static async Task> GetColumnsAsync(string path, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) + { + using var stream = FileHelper.OpenSharedRead(path); + return await GetColumnsAsync(stream, useHeaderRow, sheetName, excelType, startCell, configuration, cancellationToken).ConfigureAwait(false); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task> GetColumnsAsync(this Stream stream, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null, CancellationToken cancellationToken = default) - { + [CreateSyncVersion] + public static async Task> GetColumnsAsync(this Stream stream, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null, CancellationToken cancellationToken = default) + { #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - await using var enumerator = QueryAsync(stream, useHeaderRow, sheetName, excelType, startCell, configuration, cancellationToken).GetAsyncEnumerator(cancellationToken); + await using var enumerator = QueryAsync(stream, useHeaderRow, sheetName, excelType, startCell, configuration, cancellationToken).GetAsyncEnumerator(cancellationToken); #pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task - _ = enumerator.ConfigureAwait(false); - if (!await enumerator.MoveNextAsync().ConfigureAwait(false)) - { - return null; - } - return (enumerator.Current as IDictionary)?.Keys; - } + + _ = enumerator.ConfigureAwait(false); + if (!await enumerator.MoveNextAsync().ConfigureAwait(false)) + return []; + + return (enumerator.Current as IDictionary)?.Keys ?? []; + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task> GetSheetDimensionsAsync(string path, CancellationToken cancellationToken = default) - { - using (var stream = FileHelper.OpenSharedRead(path)) - return await GetSheetDimensionsAsync(stream, cancellationToken).ConfigureAwait(false); - } + [CreateSyncVersion] + public static async Task> GetSheetDimensionsAsync(string path, CancellationToken cancellationToken = default) + { + using var stream = FileHelper.OpenSharedRead(path); + return await GetSheetDimensionsAsync(stream, cancellationToken).ConfigureAwait(false); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task> GetSheetDimensionsAsync(this Stream stream, CancellationToken cancellationToken = default) - { - using var reader = await ExcelOpenXmlSheetReader.CreateAsync(stream, null, cancellationToken: cancellationToken).ConfigureAwait(false); - return await reader.GetDimensionsAsync(cancellationToken).ConfigureAwait(false); - } + [CreateSyncVersion] + public static async Task> GetSheetDimensionsAsync(this Stream stream, CancellationToken cancellationToken = default) + { + using var reader = await ExcelOpenXmlSheetReader.CreateAsync(stream, null, cancellationToken: cancellationToken).ConfigureAwait(false); + return await reader.GetDimensionsAsync(cancellationToken).ConfigureAwait(false); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task ConvertCsvToXlsxAsync(string csv, string xlsx, CancellationToken cancellationToken = default) - { - using (var csvStream = FileHelper.OpenSharedRead(csv)) - using (var xlsxStream = new FileStream(xlsx, FileMode.CreateNew)) - { - await ConvertCsvToXlsxAsync(csvStream, xlsxStream, cancellationToken: cancellationToken).ConfigureAwait(false); - } - } + [CreateSyncVersion] + public static async Task ConvertCsvToXlsxAsync(string csv, string xlsx, CancellationToken cancellationToken = default) + { + using var csvStream = FileHelper.OpenSharedRead(csv); + using var xlsxStream = new FileStream(xlsx, FileMode.CreateNew); + await ConvertCsvToXlsxAsync(csvStream, xlsxStream, cancellationToken: cancellationToken).ConfigureAwait(false); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task ConvertCsvToXlsxAsync(Stream csv, Stream xlsx, CancellationToken cancellationToken = default) - { - var value = QueryAsync(csv, useHeaderRow: false, excelType: ExcelType.CSV, cancellationToken: cancellationToken); - await SaveAsAsync(xlsx, value, printHeader: false, excelType: ExcelType.XLSX, cancellationToken: cancellationToken).ConfigureAwait(false); - } + [CreateSyncVersion] + public static async Task ConvertCsvToXlsxAsync(Stream csv, Stream xlsx, CancellationToken cancellationToken = default) + { + var value = QueryAsync(csv, useHeaderRow: false, excelType: ExcelType.CSV, cancellationToken: cancellationToken).ConfigureAwait(false); + await SaveAsAsync(xlsx, value, printHeader: false, excelType: ExcelType.XLSX, cancellationToken: cancellationToken).ConfigureAwait(false); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task ConvertXlsxToCsvAsync(string xlsx, string csv, CancellationToken cancellationToken = default) - { - using (var xlsxStream = FileHelper.OpenSharedRead(xlsx)) - using (var csvStream = new FileStream(csv, FileMode.CreateNew)) - await ConvertXlsxToCsvAsync(xlsxStream, csvStream, cancellationToken).ConfigureAwait(false); - } + [CreateSyncVersion] + public static async Task ConvertXlsxToCsvAsync(string xlsx, string csv, CancellationToken cancellationToken = default) + { + using var xlsxStream = FileHelper.OpenSharedRead(xlsx); + using var csvStream = new FileStream(csv, FileMode.CreateNew); + await ConvertXlsxToCsvAsync(xlsxStream, csvStream, cancellationToken).ConfigureAwait(false); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task ConvertXlsxToCsvAsync(Stream xlsx, Stream csv, CancellationToken cancellationToken = default) - { - var value = QueryAsync(xlsx, useHeaderRow: false, excelType: ExcelType.XLSX, cancellationToken: cancellationToken); - await SaveAsAsync(csv, value, printHeader: false, excelType: ExcelType.CSV, cancellationToken: cancellationToken).ConfigureAwait(false); - } + [CreateSyncVersion] + public static async Task ConvertXlsxToCsvAsync(Stream xlsx, Stream csv, CancellationToken cancellationToken = default) + { + var value = QueryAsync(xlsx, useHeaderRow: false, excelType: ExcelType.XLSX, cancellationToken: cancellationToken).ConfigureAwait(false); + await SaveAsAsync(csv, value, printHeader: false, excelType: ExcelType.CSV, cancellationToken: cancellationToken).ConfigureAwait(false); } } \ No newline at end of file diff --git a/src/MiniExcel/MiniExcelConfiguration.cs b/src/MiniExcel/MiniExcelConfiguration.cs new file mode 100644 index 00000000..9518aef7 --- /dev/null +++ b/src/MiniExcel/MiniExcelConfiguration.cs @@ -0,0 +1,19 @@ +using System.Globalization; +using MiniExcelLibs.Attributes; + +namespace MiniExcelLibs; + +public interface IMiniExcelConfiguration; + +public abstract class MiniExcelConfiguration : IMiniExcelConfiguration +{ + public CultureInfo Culture { get; set; } = CultureInfo.InvariantCulture; + public DynamicExcelColumn[]? DynamicColumns { get; set; } = []; + public int BufferSize { get; set; } = 1024 * 512; + public bool FastMode { get; set; } + + /// + /// When exporting using DataReader, the data not in DynamicColumn will be filtered. + /// + public bool DynamicColumnFirst { get; set; } = false; +} \ No newline at end of file diff --git a/src/MiniExcel/MiniExcelDataReader.cs b/src/MiniExcel/MiniExcelDataReader.cs index 6a130444..8dbb1756 100644 --- a/src/MiniExcel/MiniExcelDataReader.cs +++ b/src/MiniExcel/MiniExcelDataReader.cs @@ -1,99 +1,99 @@ -namespace MiniExcelLibs +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace MiniExcelLibs; + +public class MiniExcelDataReader : MiniExcelDataReaderBase { - using System; - using System.Collections.Generic; - using System.Data; - using System.IO; - using System.Linq; + private readonly IEnumerator> _source; + private readonly Stream _stream; + private readonly List _keys; + private readonly int _fieldCount; - public class MiniExcelDataReader : MiniExcelDataReaderBase - { - private readonly IEnumerator> _source; - private readonly int _fieldCount; - private readonly List _keys; - private readonly Stream _stream; - private bool _isFirst = true; - private bool _disposed = false; + private bool _isFirst = true; + private bool _disposed = false; - /// - /// Initializes a new instance of the class. - /// - /// The stream to read from. - /// Whether to use the header row. - /// The name of the sheet. - /// The type of the Excel file. - /// The start cell. - /// The configuration. - internal MiniExcelDataReader(Stream stream, bool useHeaderRow = false, string sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IConfiguration configuration = null) + /// + /// Initializes a new instance of the class. + /// + /// The stream to read from. + /// Whether to use the header row. + /// The name of the sheet. + /// The type of the Excel file. + /// The start cell. + /// The configuration. + internal MiniExcelDataReader(Stream stream, bool useHeaderRow = false, string? sheetName = null, ExcelType excelType = ExcelType.UNKNOWN, string startCell = "A1", IMiniExcelConfiguration? configuration = null) + { + _stream = stream ?? throw new ArgumentNullException(nameof(stream)); + _source = _stream.Query(useHeaderRow, sheetName, excelType, startCell, configuration).Cast>().GetEnumerator(); + if (_source.MoveNext()) { - _stream = stream ?? throw new ArgumentNullException(nameof(stream)); - _source = MiniExcel.Query(_stream, useHeaderRow, sheetName, excelType, startCell, configuration).Cast>().GetEnumerator(); - if (_source.MoveNext()) - { - _keys = _source.Current?.Keys.ToList() ?? new List(); - _fieldCount = _keys.Count; - } + _keys = _source.Current?.Keys.ToList() ?? []; + _fieldCount = _keys.Count; } + } - /// - public override object GetValue(int i) - { - if (_source.Current == null) - throw new InvalidOperationException("No current row available."); - return _source.Current[_keys[i]]; - } + /// + public override object GetValue(int i) + { + if (_source.Current is null) + throw new InvalidOperationException("No current row available."); + + return _source.Current[_keys[i]]; + } - /// - public override int FieldCount => _fieldCount; + /// + public override int FieldCount => _fieldCount; - /// - /// - /// - /// - public override bool Read() + /// + /// + /// + /// + public override bool Read() + { + if (_isFirst) { - if (_isFirst) - { - _isFirst = false; - return true; - } - return _source.MoveNext(); + _isFirst = false; + return true; } + return _source.MoveNext(); + } - /// - public override string GetName(int i) - { - return _keys[i]; - } + /// + public override string GetName(int i) + { + return _keys[i]; + } - /// - public override int GetOrdinal(string name) - { - return _keys.IndexOf(name); - } + /// + public override int GetOrdinal(string name) + { + return _keys.IndexOf(name); + } - /// - protected override void Dispose(bool disposing) + /// + protected override void Dispose(bool disposing) + { + if (!_disposed) { - if (!_disposed) + if (disposing) { - if (disposing) - { - _stream?.Dispose(); - _source?.Dispose(); - } - _disposed = true; + _stream?.Dispose(); + _source?.Dispose(); } - base.Dispose(disposing); + _disposed = true; } + base.Dispose(disposing); + } - /// - /// Disposes the object. - /// - public new void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + /// + /// Disposes the object. + /// + public new void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); } -} +} \ No newline at end of file diff --git a/src/MiniExcel/MiniExcelDataReaderBase.cs b/src/MiniExcel/MiniExcelDataReaderBase.cs index 402e96c0..e39df9fc 100644 --- a/src/MiniExcel/MiniExcelDataReaderBase.cs +++ b/src/MiniExcel/MiniExcelDataReaderBase.cs @@ -1,384 +1,366 @@ -namespace MiniExcelLibs +using System; +using System.Data; +using System.Threading; +using System.Threading.Tasks; + +namespace MiniExcelLibs; + +/// +/// IMiniExcelDataReader Base Class +/// +public abstract class MiniExcelDataReaderBase : IMiniExcelDataReader { - using System; - using System.Data; - using System.Threading; - using System.Threading.Tasks; + /// + /// + /// + /// + /// + public virtual object? this[int i] => null; + + /// + /// + /// + /// + /// + public virtual object? this[string name] => null; + + /// + /// + /// + public virtual int Depth { get; } = 0; + + /// + /// + /// + public virtual bool IsClosed { get; } = false; + + /// + /// + /// + public virtual int RecordsAffected { get; } = 0; + + /// + /// + /// + public virtual int FieldCount { get; } + + /// + /// + /// + /// + /// + public virtual bool GetBoolean(int i) => false; + + /// + /// + /// + /// + /// + public virtual byte GetByte(int i) => byte.MinValue; + + /// + /// + /// + /// + /// + /// + /// + /// + /// + public virtual long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferOffset, int length) => 0; + + /// + /// + /// + /// + /// + public virtual char GetChar(int i) => char.MinValue; + + /// + /// + /// + /// + /// + /// + /// + /// + /// + public virtual long GetChars(int i, long fieldOffset, char[]? buffer, int bufferOffset, int length) => 0; + + /// + /// + /// + /// + /// + public virtual IDataReader? GetData(int i) => null; + + /// + /// + /// + /// + /// + public virtual string GetDataTypeName(int i) => string.Empty; + + /// + /// + /// + /// + /// + public virtual DateTime GetDateTime(int i) => DateTime.MinValue; + + /// + /// + /// + /// + /// + public virtual decimal GetDecimal(int i) => 0; + + /// + /// + /// + /// + /// + public virtual double GetDouble(int i) => 0; + + /// + /// + /// + /// + /// + public virtual Type? GetFieldType(int i) => null; + + /// + /// + /// + /// + /// + public virtual float GetFloat(int i) => 0f; + + /// + /// + /// + /// + /// + public virtual Guid GetGuid(int i) => Guid.Empty; + + /// + /// + /// + /// + /// + public virtual short GetInt16(int i) => 0; + + /// + /// + /// + /// + /// + public virtual int GetInt32(int i) => 0; + + /// + /// + /// + /// + /// + public virtual long GetInt64(int i) => 0; + + /// + /// + /// + /// + /// + public virtual int GetOrdinal(string name) => 0; + + /// + /// + /// + /// + public virtual DataTable? GetSchemaTable() => null; + + /// + /// + /// + /// + /// + public virtual string GetString(int i) => string.Empty; + + /// + /// + /// + /// + /// + public virtual int GetValues(object[] values) => 0; + + /// + /// + /// + /// + /// + public virtual bool IsDBNull(int i) => false; + + /// + /// + /// + /// + public virtual bool NextResult() => false; /// - /// IMiniExcelDataReader Base Class + /// /// - public abstract class MiniExcelDataReaderBase : IMiniExcelDataReader + /// + /// + public virtual Task NextResultAsync(CancellationToken cancellationToken = default) { - /// - /// - /// - /// - /// - public virtual object this[int i] => null; - - /// - /// - /// - /// - /// - public virtual object this[string name] => null; - - /// - /// - /// - public virtual int Depth { get; } = 0; - - /// - /// - /// - public virtual bool IsClosed { get; } = false; - - /// - /// - /// - public virtual int RecordsAffected { get; } = 0; - - /// - /// - /// - public virtual int FieldCount { get; } - - /// - /// - /// - /// - /// - public virtual bool GetBoolean(int i) => false; - - /// - /// - /// - /// - /// - public virtual byte GetByte(int i) => byte.MinValue; - - /// - /// - /// - /// - /// - /// - /// - /// - /// - public virtual long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferOffset, int length) => 0; - - /// - /// - /// - /// - /// - public virtual char GetChar(int i) => char.MinValue; - - /// - /// - /// - /// - /// - /// - /// - /// - /// - public virtual long GetChars(int i, long fieldOffset, char[] buffer, int bufferOffset, int length) => 0; - - /// - /// - /// - /// - /// - public virtual IDataReader GetData(int i) => null; - - /// - /// - /// - /// - /// - public virtual string GetDataTypeName(int i) => string.Empty; - - /// - /// - /// - /// - /// - public virtual DateTime GetDateTime(int i) => DateTime.MinValue; - - /// - /// - /// - /// - /// - public virtual decimal GetDecimal(int i) => 0; - - /// - /// - /// - /// - /// - public virtual double GetDouble(int i) => 0; - - /// - /// - /// - /// - /// - public virtual Type GetFieldType(int i) => null; - - /// - /// - /// - /// - /// - public virtual float GetFloat(int i) => 0f; - - /// - /// - /// - /// - /// - public virtual Guid GetGuid(int i) => Guid.Empty; - - /// - /// - /// - /// - /// - public virtual short GetInt16(int i) => 0; - - /// - /// - /// - /// - /// - public virtual int GetInt32(int i) => 0; - - /// - /// - /// - /// - /// - public virtual long GetInt64(int i) => 0; - - /// - /// - /// - /// - /// - public virtual int GetOrdinal(string name) => 0; - - /// - /// - /// - /// - public virtual DataTable GetSchemaTable() => null; - - /// - /// - /// - /// - /// - public virtual string GetString(int i) => string.Empty; - - /// - /// - /// - /// - /// - public virtual int GetValues(object[] values) => 0; - - /// - /// - /// - /// - /// - public virtual bool IsDBNull(int i) => false; - - /// - /// - /// - /// - public virtual bool NextResult() => false; - - /// - /// - /// - /// - /// - public virtual Task NextResultAsync(CancellationToken cancellationToken = default) + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + try { - if (cancellationToken.IsCancellationRequested) - { - return MiniExcelTask.FromCanceled(cancellationToken); - } - else - { - try - { - return NextResult() ? Task.FromResult(true) : Task.FromResult(false); - } - catch (Exception e) - { - return MiniExcelTask.FromException(e); - } - } + return NextResult() ? Task.FromResult(true) : Task.FromResult(false); } - - /// - /// - /// - /// - /// - public abstract string GetName(int i); - - /// - /// - /// - /// - /// - /// - public virtual Task GetNameAsync(int i, CancellationToken cancellationToken = default) + catch (Exception e) { - if (cancellationToken.IsCancellationRequested) - { - return MiniExcelTask.FromCanceled(cancellationToken); - } - else - { - try - { - return Task.FromResult(GetName(i)); - } - catch (Exception e) - { - return MiniExcelTask.FromException(e); - } - } + return Task.FromException(e); } + } + + /// + /// + /// + /// + /// + public abstract string GetName(int i); - /// - /// - /// - /// - /// - public abstract object GetValue(int i); - - /// - /// - /// - /// - /// - /// - public virtual Task GetValueAsync(int i, CancellationToken cancellationToken = default) + /// + /// + /// + /// + /// + /// + public virtual Task GetNameAsync(int i, CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + try { - if (cancellationToken.IsCancellationRequested) - { - return MiniExcelTask.FromCanceled(cancellationToken); - } - else - { - try - { - return Task.FromResult(GetValue(i)); - } - catch (Exception e) - { - return MiniExcelTask.FromException(e); - } - } + return Task.FromResult(GetName(i)); } - - /// - /// - /// - /// - public abstract bool Read(); - - /// - /// - /// - /// - /// - public virtual Task ReadAsync(CancellationToken cancellationToken = default) + catch (Exception e) { - if (cancellationToken.IsCancellationRequested) - { - return MiniExcelTask.FromCanceled(cancellationToken); - } - else - { - try - { - return Read() ? Task.FromResult(true) : Task.FromResult(false); - } - catch (Exception e) - { - return MiniExcelTask.FromException(e); - } - } + return Task.FromException(e); } + } - /// - /// - /// - public virtual void Close() - { + /// + /// + /// + /// + /// + public abstract object GetValue(int i); - } + /// + /// + /// + /// + /// + /// + public virtual Task GetValueAsync(int i, CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); - /// - /// - /// - /// - public virtual Task CloseAsync() + try { - try - { - Close(); - return MiniExcelTask.CompletedTask; - } - catch (Exception e) - { - return MiniExcelTask.FromException(e); - } + return Task.FromResult(GetValue(i)); } + catch (Exception e) + { + return Task.FromException(e); + } + } - /// - /// - /// - public void Dispose() + /// + /// + /// + /// + public abstract bool Read(); + + /// + /// + /// + /// + /// + public virtual Task ReadAsync(CancellationToken cancellationToken = default) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + try + { + return Read() ? Task.FromResult(true) : Task.FromResult(false); + } + catch (Exception e) { - Dispose(true); - GC.SuppressFinalize(this); + return Task.FromException(e); } + } -#if NET8_0_OR_GREATER - /// - /// - /// - /// - /// - public virtual ValueTask DisposeAsync() + /// + /// + /// + public virtual void Close() + { + } + + /// + /// + /// + /// + public virtual Task CloseAsync() + { + try { - Dispose(); - return default; + Close(); + return Task.CompletedTask; } + catch (Exception e) + { + return Task.FromException(e); + } + } + + /// + /// + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + +#if NET8_0_OR_GREATER + /// + /// + /// + /// + /// + public virtual ValueTask DisposeAsync() + { + Dispose(); + return default; + } #endif - /// - /// - /// - /// - protected virtual void Dispose(bool disposing) + /// + /// + /// + /// + protected virtual void Dispose(bool disposing) + { + if (disposing) { - if (disposing) - { - Close(); - } + Close(); } } -} +} \ No newline at end of file diff --git a/src/MiniExcel/MiniExcelLibs.csproj b/src/MiniExcel/MiniExcelLibs.csproj index 96e620ec..1911d2cd 100644 --- a/src/MiniExcel/MiniExcelLibs.csproj +++ b/src/MiniExcel/MiniExcelLibs.csproj @@ -1,11 +1,13 @@  net462;netstandard2.0;net8.0;net9.0 - 1.41.2 + 2.0.0-beta.1 + enable - 8 + 13 + MiniExcel Mini-Software @@ -41,21 +43,24 @@ Todo : https://github.com/mini-software/MiniExcel/projects/1?fullscreen=true - - - + + + - - + + - - + + + + - + + diff --git a/src/MiniExcel/MiniExcelTask.cs b/src/MiniExcel/MiniExcelTask.cs deleted file mode 100644 index 2a3fb7b0..00000000 --- a/src/MiniExcel/MiniExcelTask.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace MiniExcelLibs -{ - internal class MiniExcelTask - { -#if NET462 - public static Task CompletedTask = Task.FromResult(0); -#else - public static Task CompletedTask = Task.CompletedTask; -#endif - - public static Task FromException(Exception exception) - { -#if NET462 - var tcs = new TaskCompletionSource(); - tcs.SetException(exception); - return tcs.Task; -#else - return Task.FromException(exception); -#endif - } - - public static Task FromException(Exception exception) - { -#if NET462 - var tcs = new TaskCompletionSource(); - tcs.SetException(exception); - return tcs.Task; -#else - return Task.FromException(exception); -#endif - } - - public static Task FromCanceled(CancellationToken cancellationToken) - { -#if NET462 - var tcs = new TaskCompletionSource(); - cancellationToken.Register(() => tcs.SetCanceled()); - return tcs.Task; -#else - return Task.FromCanceled(cancellationToken); -#endif - } - - public static Task FromCanceled(CancellationToken cancellationToken) - { -#if NET462 - var tcs = new TaskCompletionSource(); - cancellationToken.Register(() => tcs.SetCanceled()); - return tcs.Task; -#else - return Task.FromCanceled(cancellationToken); -#endif - } - } -} diff --git a/src/MiniExcel/OpenXml/Config.cs b/src/MiniExcel/OpenXml/Config.cs deleted file mode 100644 index 32fa409f..00000000 --- a/src/MiniExcel/OpenXml/Config.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace MiniExcelLibs.OpenXml -{ - internal static class Config - { - public const string SpreadsheetmlXmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"; - public const string SpreadsheetmlXmlStrictns = "http://purl.oclc.org/ooxml/spreadsheetml/main"; - public const string SpreadsheetmlXmlRelationshipns = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; - public const string SpreadsheetmlXmlStrictRelationshipns = "http://purl.oclc.org/ooxml/officeDocument/relationships"; - public const string SpreadsheetmlXml_x14ac = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"; - } -} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/Constants/ExcelContentTypes.cs b/src/MiniExcel/OpenXml/Constants/ExcelContentTypes.cs index 52ea48e4..bcb5f9fa 100644 --- a/src/MiniExcel/OpenXml/Constants/ExcelContentTypes.cs +++ b/src/MiniExcel/OpenXml/Constants/ExcelContentTypes.cs @@ -1,12 +1,11 @@ -namespace MiniExcelLibs.OpenXml.Constants +namespace MiniExcelLibs.OpenXml.Constants; + +internal static class ExcelContentTypes { - internal static class ExcelContentTypes - { - internal const string Relationships = "application/vnd.openxmlformats-package.relationships+xml"; - internal const string SharedStrings = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"; - internal const string Worksheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"; - internal const string Styles = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"; - internal const string Drawing = "application/vnd.openxmlformats-officedocument.drawing+xml"; - internal const string Workbook = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"; - } -} + internal const string Relationships = "application/vnd.openxmlformats-package.relationships+xml"; + internal const string SharedStrings = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"; + internal const string Worksheet = "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"; + internal const string Styles = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"; + internal const string Drawing = "application/vnd.openxmlformats-officedocument.drawing+xml"; + internal const string Workbook = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"; +} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/Constants/ExcelFileNames.cs b/src/MiniExcel/OpenXml/Constants/ExcelFileNames.cs index 2ade87fc..982c22eb 100644 --- a/src/MiniExcel/OpenXml/Constants/ExcelFileNames.cs +++ b/src/MiniExcel/OpenXml/Constants/ExcelFileNames.cs @@ -1,17 +1,16 @@ -namespace MiniExcelLibs.OpenXml.Constants +namespace MiniExcelLibs.OpenXml.Constants; + +internal static class ExcelFileNames { - internal static class ExcelFileNames - { - internal const string Rels = "_rels/.rels"; - internal const string SharedStrings = "xl/sharedStrings.xml"; + internal const string Rels = "_rels/.rels"; + internal const string SharedStrings = "xl/sharedStrings.xml"; - internal const string ContentTypes = "[Content_Types].xml"; - internal const string Styles = "xl/styles.xml"; - internal const string Workbook = "xl/workbook.xml"; - internal const string WorkbookRels = "xl/_rels/workbook.xml.rels"; + internal const string ContentTypes = "[Content_Types].xml"; + internal const string Styles = "xl/styles.xml"; + internal const string Workbook = "xl/workbook.xml"; + internal const string WorkbookRels = "xl/_rels/workbook.xml.rels"; - internal static string SheetRels(int sheetId) => $"xl/worksheets/_rels/sheet{sheetId}.xml.rels"; - internal static string Drawing(int sheetIndex) => $"xl/drawings/drawing{sheetIndex + 1}.xml"; - internal static string DrawingRels(int sheetIndex) => $"xl/drawings/_rels/drawing{sheetIndex + 1}.xml.rels"; - } -} + internal static string SheetRels(int sheetId) => $"xl/worksheets/_rels/sheet{sheetId}.xml.rels"; + internal static string Drawing(int sheetIndex) => $"xl/drawings/drawing{sheetIndex + 1}.xml"; + internal static string DrawingRels(int sheetIndex) => $"xl/drawings/_rels/drawing{sheetIndex + 1}.xml.rels"; +} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/Constants/ExcelXml.cs b/src/MiniExcel/OpenXml/Constants/ExcelXml.cs index 35683412..6b489253 100644 --- a/src/MiniExcel/OpenXml/Constants/ExcelXml.cs +++ b/src/MiniExcel/OpenXml/Constants/ExcelXml.cs @@ -1,111 +1,128 @@ using MiniExcelLibs.OpenXml.Models; -namespace MiniExcelLibs.OpenXml.Constants +namespace MiniExcelLibs.OpenXml.Constants; + +internal static class ExcelXml { - internal static class ExcelXml + static ExcelXml() { - static ExcelXml() - { - DefaultRels = ExcelOpenXmlUtils.MinifyXml(DefaultRels); - DefaultWorkbookXml = ExcelOpenXmlUtils.MinifyXml(DefaultWorkbookXml); - DefaultWorkbookXmlRels = ExcelOpenXmlUtils.MinifyXml(DefaultWorkbookXmlRels); - DefaultSheetRelXml = ExcelOpenXmlUtils.MinifyXml(DefaultSheetRelXml); - DefaultDrawing = ExcelOpenXmlUtils.MinifyXml(DefaultDrawing); - } - - - internal const string EmptySheetXml = @""; - - internal static readonly string DefaultRels = @" - - -"; - - internal static readonly string DefaultWorkbookXmlRels = @" - - {{sheets}} - - -"; - - internal static readonly string DefaultWorkbookXml = @" - - - {{sheets}} - -"; + DefaultRels = ExcelOpenXmlUtils.MinifyXml(DefaultRels); + DefaultWorkbookXml = ExcelOpenXmlUtils.MinifyXml(DefaultWorkbookXml); + DefaultWorkbookXmlRels = ExcelOpenXmlUtils.MinifyXml(DefaultWorkbookXmlRels); + DefaultSheetRelXml = ExcelOpenXmlUtils.MinifyXml(DefaultSheetRelXml); + DefaultDrawing = ExcelOpenXmlUtils.MinifyXml(DefaultDrawing); + } - internal static readonly string DefaultSheetRelXml = @" - - {{format}} -"; + internal const string EmptySheetXml = """"""; + + internal static readonly string DefaultRels = + """ + + + + + """; + + internal static readonly string DefaultWorkbookXmlRels = + """ + + + {{sheets}} + + + + """; + + internal static readonly string DefaultWorkbookXml = + """ + + + + {{sheets}} + + + """; + + internal static readonly string DefaultSheetRelXml = + """ + + + {{format}} + + """; - internal static readonly string DefaultDrawing = @" - - {{format}} -"; + internal static readonly string DefaultDrawing = + """ + + + {{format}} + + """; - internal const string DefaultDrawingXmlRels = @" - - {{format}} -"; - - internal const string DefaultSharedString = ""; - - internal const string StartTypes = @""; - internal static string ContentType(string contentType, string partName) => $""; - internal const string EndTypes = ""; - - internal static string WorksheetRelationship(SheetDto sheetDto) - => $@""; - - internal static string ImageRelationship(FileDto image) - => $@""; - - internal static string DrawingRelationship(int sheetId) - => $@""; - - internal static string DrawingXml(FileDto file, int fileIndex) - => $@" - - {file.CellIndex - 1/* why -1 : https://user-images.githubusercontent.com/12729184/150460189-f08ed939-44d4-44e1-be6e-9c533ece6be8.png*/} - 0 - {file.RowIndex - 1} - 0 - - - - - - - - - - - - - - - - - - - - - - - - - - - "; - - internal static string Sheet(SheetDto sheetDto, int sheetId) - => $@""; - - } - -} + internal const string DefaultDrawingXmlRels = + """ + + + {{format}} + + """; + + internal const string DefaultSharedString = ""; + + internal const string StartTypes = """"""; + internal static string ContentType(string contentType, string partName) => $""; + internal const string EndTypes = ""; + + internal static string WorksheetRelationship(SheetDto sheetDto) + => $""""""; + + internal static string ImageRelationship(FileDto image) + => $""""""; + + internal static string DrawingRelationship(int sheetId) + => $""""""; + + internal static string DrawingXml(FileDto file, int fileIndex) + => $""" + + + {file.CellIndex - 1 /* why -1 : https://user-images.githubusercontent.com/12729184/150460189-f08ed939-44d4-44e1-be6e-9c533ece6be8.png*/} + 0 + {file.RowIndex - 1} + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + """; + + internal static string Sheet(SheetDto sheetDto, int sheetId) + => $""""""; + +} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/Constants/Schemas.cs b/src/MiniExcel/OpenXml/Constants/Schemas.cs new file mode 100644 index 00000000..15a2cbe3 --- /dev/null +++ b/src/MiniExcel/OpenXml/Constants/Schemas.cs @@ -0,0 +1,10 @@ +namespace MiniExcelLibs.OpenXml.Constants; + +internal static class Schemas +{ + public const string SpreadsheetmlXmlns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"; + public const string SpreadsheetmlXmlStrictns = "http://purl.oclc.org/ooxml/spreadsheetml/main"; + public const string SpreadsheetmlXmlRelationshipns = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; + public const string SpreadsheetmlXmlStrictRelationshipns = "http://purl.oclc.org/ooxml/officeDocument/relationships"; + public const string SpreadsheetmlXmlX14Ac = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"; +} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/Constants/WorksheetXml.cs b/src/MiniExcel/OpenXml/Constants/WorksheetXml.cs index 50cdcbc7..1ec9e1e8 100644 --- a/src/MiniExcel/OpenXml/Constants/WorksheetXml.cs +++ b/src/MiniExcel/OpenXml/Constants/WorksheetXml.cs @@ -1,68 +1,62 @@ using System.Globalization; using MiniExcelLibs.Attributes; -namespace MiniExcelLibs.OpenXml.Constants -{ - internal static class WorksheetXml - { - internal const string StartWorksheet = @""; - internal const string StartWorksheetWithRelationship = @""; - internal const string EndWorksheet = ""; - - internal const string StartDimension = " $"{StartDimension}{dimensionRef}\" />"; - - internal const string StartSheetViews = ""; - internal const string EndSheetViews = ""; - - internal static string StartSheetView( int tabSelected=0, int workbookViewId=0 ) - => $""; - internal const string EndSheetView = ""; +namespace MiniExcelLibs.OpenXml.Constants; - internal const string StartSheetData = ""; - internal const string EndSheetData = ""; - - internal static string StartPane( int? xSplit, int? ySplit, string topLeftCell, string activePane, string state ) - => string.Concat( - ""); - - internal static string PaneSelection( string pane, string activeCell, string sqref) - => string.Concat( - ""); - - internal static string StartRow(int rowIndex) => $""; - internal const string EndRow = ""; - internal const string StartCols = ""; +internal static class WorksheetXml +{ + internal const string StartWorksheet = """"""; + internal const string StartWorksheetWithRelationship = """"""; + internal const string EndWorksheet = ""; + + internal const string StartDimension = " $"{StartDimension}{dimensionRef}\" />"; + + internal const string StartSheetViews = ""; + internal const string EndSheetViews = ""; + + internal static string StartSheetView( int tabSelected=0, int workbookViewId=0 ) => $""; + internal const string EndSheetView = ""; + + internal const string StartSheetData = ""; + internal const string EndSheetData = ""; + + internal static string StartPane( int? xSplit, int? ySplit, string topLeftCell, string activePane, string state ) => string.Concat( + ""); + + internal static string PaneSelection(string pane, string? activeCell, string? sqref) => string.Concat( + ""); + + internal static string StartRow(int rowIndex) => $""; + internal const string EndRow = ""; + internal const string StartCols = ""; - internal static string Column(int colIndex, double columnWidth) - => $@""; + internal static string Column(int colIndex, double columnWidth) + => $""""""; - private static readonly int _maxColumnLength = Column(int.MaxValue, double.MaxValue).Length; + private static readonly int MaxColumnLength = Column(int.MaxValue, double.MaxValue).Length; - public static int GetColumnPlaceholderLength(int columnCount) - => StartCols.Length + (_maxColumnLength * columnCount) + EndCols.Length; + public static int GetColumnPlaceholderLength(int columnCount) => StartCols.Length + MaxColumnLength * columnCount + EndCols.Length; - internal const string EndCols = ""; + internal const string EndCols = ""; - internal static string EmptyCell(string cellReference, string styleIndex) - => $""; + internal static string EmptyCell(string cellReference, string styleIndex) => $""; - //t check avoid format error ![image](https://user-images.githubusercontent.com/12729184/118770190-9eee3480-b8b3-11eb-9f5a-87a439f5e320.png) - internal static string Cell(string cellReference, string cellType, string styleIndex, string cellValue, bool preserveSpace = false, ColumnType columnType = ColumnType.Value) - => $"{cellValue}"; + //t check avoid format error ![image](https://user-images.githubusercontent.com/12729184/118770190-9eee3480-b8b3-11eb-9f5a-87a439f5e320.png) + internal static string Cell(string cellReference, string? cellType, string styleIndex, string? cellValue, bool preserveSpace = false, ColumnType columnType = ColumnType.Value) + => $"{cellValue}"; - internal static string Autofilter(string dimensionRef) => $""; - internal static string Drawing(int sheetIndex) => $""; - } -} + internal static string Autofilter(string dimensionRef) => $""; + internal static string Drawing(int sheetIndex) => $""; +} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs index 6b868456..e6d123c2 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetReader.cs @@ -1,7 +1,4 @@ -using MiniExcelLibs.OpenXml.Models; -using MiniExcelLibs.Utils; -using MiniExcelLibs.Zip; -using System; +using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Globalization; @@ -12,629 +9,582 @@ using System.Threading; using System.Threading.Tasks; using System.Xml; +using MiniExcelLibs.OpenXml.Constants; +using MiniExcelLibs.OpenXml.Models; +using MiniExcelLibs.Utils; +using MiniExcelLibs.Zip; +using Zomp.SyncMethodGenerator; + +namespace MiniExcelLibs.OpenXml; -namespace MiniExcelLibs.OpenXml +internal partial class ExcelOpenXmlSheetReader : IExcelReader { - internal partial class ExcelOpenXmlSheetReader : IExcelReader + private static readonly string[] Ns = [Schemas.SpreadsheetmlXmlns, Schemas.SpreadsheetmlXmlStrictns]; + private static readonly string[] RelationshiopNs = [Schemas.SpreadsheetmlXmlRelationshipns, Schemas.SpreadsheetmlXmlStrictRelationshipns]; + private readonly OpenXmlConfiguration _config; + + private List? _sheetRecords; + private ExcelOpenXmlStyles? _style; + private bool _disposed; + + internal readonly ExcelOpenXmlZip Archive; + internal IDictionary? SharedStrings; + + private ExcelOpenXmlSheetReader(Stream stream, IMiniExcelConfiguration? configuration) { - private bool _disposed = false; - private static readonly string[] _ns = { Config.SpreadsheetmlXmlns, Config.SpreadsheetmlXmlStrictns }; - private static readonly string[] _relationshiopNs = { Config.SpreadsheetmlXmlRelationshipns, Config.SpreadsheetmlXmlStrictRelationshipns }; - private List _sheetRecords; - internal IDictionary _sharedStrings; - private ExcelOpenXmlStyles _style; - internal readonly ExcelOpenXmlZip _archive; - private readonly OpenXmlConfiguration _config; - - private ExcelOpenXmlSheetReader(Stream stream, IConfiguration configuration, bool isUpdateMode) - { - _archive = new ExcelOpenXmlZip(stream); - _config = (OpenXmlConfiguration)configuration ?? OpenXmlConfiguration.DefaultConfig; - } + Archive = new ExcelOpenXmlZip(stream); + _config = (OpenXmlConfiguration?)configuration ?? OpenXmlConfiguration.DefaultConfig; + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async Task CreateAsync(Stream stream, IConfiguration configuration, bool isUpdateMode = true, CancellationToken cancellationToken = default) - { - var reader = new ExcelOpenXmlSheetReader(stream, configuration, isUpdateMode); - await reader.SetSharedStringsAsync(cancellationToken).ConfigureAwait(false); - return reader; - } + [CreateSyncVersion] + public static async Task CreateAsync(Stream stream, IMiniExcelConfiguration? configuration, CancellationToken cancellationToken = default) + { + var reader = new ExcelOpenXmlSheetReader(stream, configuration); + await reader.SetSharedStringsAsync(cancellationToken).ConfigureAwait(false); + return reader; + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public IAsyncEnumerable> QueryAsync(bool useHeaderRow, string sheetName, string startCell, CancellationToken cancellationToken = default) - { - return QueryRangeAsync(useHeaderRow, sheetName, startCell, "", cancellationToken); - } + [CreateSyncVersion] + public IAsyncEnumerable> QueryAsync(bool useHeaderRow, string? sheetName, string startCell, CancellationToken cancellationToken = default) + { + return QueryRangeAsync(useHeaderRow, sheetName, startCell, "", cancellationToken); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public IAsyncEnumerable QueryAsync(string sheetName, string startCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() - { - if (sheetName == null) - sheetName = CustomPropertyHelper.GetExcellSheetInfo(typeof(T), _config)?.ExcelSheetName; + [CreateSyncVersion] + public IAsyncEnumerable QueryAsync(string? sheetName, string startCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() + { + sheetName ??= CustomPropertyHelper.GetExcellSheetInfo(typeof(T), _config)?.ExcelSheetName; - //Todo: Find a way if possible to remove the 'hasHeader' parameter to check whether or not to include - // the first row in the result set in favor of modifying the already present 'useHeaderRow' to do the same job - return QueryImplAsync(QueryAsync(false, sheetName, startCell, cancellationToken), startCell, hasHeader, _config, cancellationToken); - } + //Todo: Find a way if possible to remove the 'hasHeader' parameter to check whether or not to include + // the first row in the result set in favor of modifying the already present 'useHeaderRow' to do the same job + return QueryImplAsync(QueryAsync(false, sheetName, startCell, cancellationToken), startCell, hasHeader, _config, cancellationToken); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public IAsyncEnumerable> QueryRangeAsync(bool useHeaderRow, string sheetName, string startCell, string endCell, CancellationToken cancellationToken = default) + [CreateSyncVersion] + public IAsyncEnumerable> QueryRangeAsync(bool useHeaderRow, string? sheetName, string startCell, string endCell, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (!ReferenceHelper.ParseReference(startCell, out var startColumnIndex, out var startRowIndex)) + throw new InvalidDataException($"Value {startCell} is not a valid cell reference."); + + // convert to 0-based + startColumnIndex--; + startRowIndex--; + + // endCell is allowed to be empty to query for all rows and columns + int? endColumnIndex = null; + int? endRowIndex = null; + if (!string.IsNullOrWhiteSpace(endCell)) { - cancellationToken.ThrowIfCancellationRequested(); + if (!ReferenceHelper.ParseReference(endCell, out int cIndex, out int rIndex)) + throw new InvalidDataException($"Value {endCell} is not a valid cell reference."); - if (!ReferenceHelper.ParseReference(startCell, out var startColumnIndex, out var startRowIndex)) - { - throw new InvalidDataException($"Value {startCell} is not a valid cell reference."); - } // convert to 0-based - startColumnIndex--; - startRowIndex--; - - // endCell is allowed to be empty to query for all rows and columns - int? endColumnIndex = null; - int? endRowIndex = null; - if (!string.IsNullOrWhiteSpace(endCell)) - { - if (!ReferenceHelper.ParseReference(endCell, out int cIndex, out int rIndex)) - { - throw new InvalidDataException($"Value {endCell} is not a valid cell reference."); - } + endColumnIndex = cIndex - 1; + endRowIndex = rIndex - 1; + } - // convert to 0-based - endColumnIndex = cIndex - 1; - endRowIndex = rIndex - 1; - } + return InternalQueryRangeAsync(useHeaderRow, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, cancellationToken); + } - return InternalQueryRangeAsync(useHeaderRow, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, cancellationToken); - } + [CreateSyncVersion] + public IAsyncEnumerable QueryRangeAsync(string? sheetName, string startCell, string endCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() + { + return QueryImplAsync(QueryRangeAsync(false, sheetName, startCell, endCell, cancellationToken), startCell, hasHeader, _config, cancellationToken); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public IAsyncEnumerable QueryRangeAsync(string sheetName, string startCell, string endCell, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() + [CreateSyncVersion] + public IAsyncEnumerable> QueryRangeAsync(bool useHeaderRow, string? sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (startRowIndex <= 0) + throw new ArgumentOutOfRangeException(nameof(startRowIndex), "Start row index is 1-based and must be greater than 0."); + if (startColumnIndex <= 0) + throw new ArgumentOutOfRangeException(nameof(startColumnIndex), "Start column index is 1-based and must be greater than 0."); + + // convert to 0-based + startColumnIndex--; + startRowIndex--; + + if (endRowIndex.HasValue) { - return QueryImplAsync(QueryRangeAsync(false, sheetName, startCell, endCell, cancellationToken), startCell, hasHeader, this._config, cancellationToken); + if (endRowIndex.Value <= 0) + throw new ArgumentOutOfRangeException(nameof(endRowIndex), "End row index is 1-based and must be greater than 0."); + + // convert to 0-based + endRowIndex--; } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public IAsyncEnumerable> QueryRangeAsync(bool useHeaderRow, string sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, CancellationToken cancellationToken = default) + if (endColumnIndex.HasValue) { - cancellationToken.ThrowIfCancellationRequested(); - - if (startRowIndex <= 0) + if (endColumnIndex.Value > 0) { - throw new ArgumentOutOfRangeException(nameof(startRowIndex), "Start row index is 1-based and must be greater than 0."); - } - if (startColumnIndex <= 0) - { - throw new ArgumentOutOfRangeException(nameof(startColumnIndex), "Start column index is 1-based and must be greater than 0."); - } - // convert to 0-based - startColumnIndex--; - startRowIndex--; - - if (endRowIndex.HasValue) - { - if (endRowIndex.Value <= 0) - { - throw new ArgumentOutOfRangeException(nameof(endRowIndex), "End row index is 1-based and must be greater than 0."); - } // convert to 0-based - endRowIndex--; + endColumnIndex--; } - if (endColumnIndex.HasValue) + else { - if (endColumnIndex.Value <= 0) - { - throw new ArgumentOutOfRangeException(nameof(endColumnIndex), "End column index is 1-based and must be greater than 0."); - } - // convert to 0-based - endColumnIndex--; + throw new ArgumentOutOfRangeException(nameof(endColumnIndex), "End column index is 1-based and must be greater than 0."); } - - return InternalQueryRangeAsync(useHeaderRow, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, cancellationToken); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public IAsyncEnumerable QueryRangeAsync(string sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() - { - return QueryImplAsync(QueryRangeAsync(false, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, cancellationToken), ReferenceHelper.ConvertXyToCell(startColumnIndex, startRowIndex), hasHeader, _config, cancellationToken); - } + return InternalQueryRangeAsync(useHeaderRow, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, cancellationToken); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - internal async IAsyncEnumerable> InternalQueryRangeAsync(bool useHeaderRow, string sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); + [CreateSyncVersion] + public IAsyncEnumerable QueryRangeAsync(string? sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, bool hasHeader, CancellationToken cancellationToken = default) where T : class, new() + { + return QueryImplAsync(QueryRangeAsync(false, sheetName, startRowIndex, startColumnIndex, endRowIndex, endColumnIndex, cancellationToken), ReferenceHelper.ConvertXyToCell(startColumnIndex, startRowIndex), hasHeader, _config, cancellationToken); + } - var xmlSettings = XmlReaderHelper.GetXmlReaderSettings( + [CreateSyncVersion] + internal async IAsyncEnumerable> InternalQueryRangeAsync(bool useHeaderRow, string? sheetName, int startRowIndex, int startColumnIndex, int? endRowIndex, int? endColumnIndex, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + var xmlSettings = XmlReaderHelper.GetXmlReaderSettings( #if SYNC_ONLY - false + false #else - true + true #endif - ); + ); - var sheetEntry = GetSheetEntry(sheetName); + var sheetEntry = GetSheetEntry(sheetName); - // TODO: need to optimize performance - // Q. why need 3 times openstream merge one open read? A. no, zipstream can't use position = 0 + // TODO: need to optimize performance + // Q. why need 3 times openstream merge one open read? A. no, zipstream can't use position = 0 - var mergeCellsContext = new MergeCellsContext(); - if (_config.FillMergedCells && !await TryGetMergeCellsAsync(sheetEntry, mergeCellsContext, cancellationToken).ConfigureAwait(false)) - { - yield break; - } + var mergeCellsContext = new MergeCellsContext(); + if (_config.FillMergedCells && !await TryGetMergeCellsAsync(sheetEntry, mergeCellsContext, cancellationToken).ConfigureAwait(false)) + yield break; - var maxRowColumnIndexResult = await TryGetMaxRowColumnIndexAsync(sheetEntry, cancellationToken).ConfigureAwait(false); - if (!maxRowColumnIndexResult.IsSuccess) - { - yield break; - } + var maxRowColumnIndexResult = await TryGetMaxRowColumnIndexAsync(sheetEntry, cancellationToken).ConfigureAwait(false); + if (!maxRowColumnIndexResult.IsSuccess) + yield break; - var maxRowIndex = maxRowColumnIndexResult.MaxRowIndex; - var maxColumnIndex = maxRowColumnIndexResult.MaxColumnIndex; - var withoutCR = maxRowColumnIndexResult.WithoutCR; + var maxRowIndex = maxRowColumnIndexResult.MaxRowIndex; + var maxColumnIndex = maxRowColumnIndexResult.MaxColumnIndex; + var withoutCr = maxRowColumnIndexResult.WithoutCr; - if (endColumnIndex.HasValue) - { - maxColumnIndex = endColumnIndex.Value; - } + if (endColumnIndex.HasValue) + { + maxColumnIndex = endColumnIndex.Value; + } - using (var sheetStream = sheetEntry.Open()) - using (var reader = XmlReader.Create(sheetStream, xmlSettings)) - { - if (!XmlReaderHelper.IsStartElement(reader, "worksheet", _ns)) - yield break; + using var sheetStream = sheetEntry.Open(); + using var reader = XmlReader.Create(sheetStream, xmlSettings); + if (!XmlReaderHelper.IsStartElement(reader, "worksheet", Ns)) + yield break; + if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) + yield break; + + while (!reader.EOF) + { + if (XmlReaderHelper.IsStartElement(reader, "sheetData", Ns)) + { if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) - yield break; + continue; + int rowIndex = -1; + bool isFirstRow = true; + var headRows = new Dictionary(); while (!reader.EOF) { - if (XmlReaderHelper.IsStartElement(reader, "sheetData", _ns)) + if (XmlReaderHelper.IsStartElement(reader, "row", Ns)) { - if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) - continue; + var nextRowIndex = rowIndex + 1; + if (int.TryParse(reader.GetAttribute("r"), out int arValue)) + rowIndex = arValue - 1; // The row attribute is 1-based + else + rowIndex++; - var headRows = new Dictionary(); - int rowIndex = -1; - bool isFirstRow = true; - while (!reader.EOF) + if (rowIndex < startRowIndex) { - if (XmlReaderHelper.IsStartElement(reader, "row", _ns)) - { - var nextRowIndex = rowIndex + 1; - if (int.TryParse(reader.GetAttribute("r"), out int arValue)) - rowIndex = arValue - 1; // The row attribute is 1-based - else - rowIndex++; + await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken) + .ConfigureAwait(false); + await XmlReaderHelper.SkipToNextSameLevelDomAsync(reader, cancellationToken) + .ConfigureAwait(false); + continue; + } - if (rowIndex < startRowIndex) - { - await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false); - await XmlReaderHelper.SkipToNextSameLevelDomAsync(reader, cancellationToken).ConfigureAwait(false); - continue; - } - if (endRowIndex.HasValue && rowIndex > endRowIndex.Value) - { - break; - } + if (rowIndex > endRowIndex) + { + break; + } - await foreach (var row in QueryRowAsync(reader, isFirstRow, startRowIndex, nextRowIndex, rowIndex, startColumnIndex, endColumnIndex, maxColumnIndex, withoutCR, useHeaderRow, headRows, mergeCellsContext.MergeCells, cancellationToken).ConfigureAwait(false)) - { - if (isFirstRow) - { - isFirstRow = false; // for startcell logic - if (useHeaderRow) - continue; - } - yield return row; - } - } - else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) + await foreach (var row in QueryRowAsync(reader, isFirstRow, startRowIndex, nextRowIndex, + rowIndex, startColumnIndex, endColumnIndex, maxColumnIndex, + withoutCr, + useHeaderRow, headRows, mergeCellsContext.MergeCells, + cancellationToken) + .ConfigureAwait(false)) + { + if (isFirstRow) { - break; + isFirstRow = false; // for startcell logic + if (useHeaderRow) + continue; } + + yield return row; } } - else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) + else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken) + .ConfigureAwait(false)) { break; } } } + else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) + { + break; + } } + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async IAsyncEnumerable> QueryRowAsync( - XmlReader reader, - bool isFirstRow, - int startRowIndex, - int nextRowIndex, - int rowIndex, - int startColumnIndex, - int? endColumnIndex, - int maxColumnIndex, - bool withoutCR, - bool useHeaderRow, - Dictionary headRows, - MergeCells mergeCells, - [EnumeratorCancellation] CancellationToken cancellationToken = default) + [CreateSyncVersion] + private async IAsyncEnumerable> QueryRowAsync( + XmlReader reader, + bool isFirstRow, + int startRowIndex, + int nextRowIndex, + int rowIndex, + int startColumnIndex, + int? endColumnIndex, + int maxColumnIndex, + bool withoutCr, + bool useHeaderRow, + Dictionary headRows, + MergeCells? mergeCells, + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + // fill empty rows + if (!_config.IgnoreEmptyRows) { - // fill empty rows - if (!_config.IgnoreEmptyRows) + var expectedRowIndex = isFirstRow ? startRowIndex : nextRowIndex; + if (startRowIndex <= expectedRowIndex && expectedRowIndex < rowIndex) { - var expectedRowIndex = isFirstRow ? startRowIndex : nextRowIndex; - if (startRowIndex <= expectedRowIndex && expectedRowIndex < rowIndex) + for (int i = expectedRowIndex; i < rowIndex; i++) { - for (int i = expectedRowIndex; i < rowIndex; i++) - { - yield return GetCell(useHeaderRow, maxColumnIndex, headRows, startColumnIndex); - } + yield return GetCell(useHeaderRow, maxColumnIndex, headRows, startColumnIndex); } } + } - // row -> c, must after `if (nextRowIndex < rowIndex)` condition code, eg. The first empty row has no xml element,and the second row xml element is - if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false) && !_config.IgnoreEmptyRows) - { - //Fill in case of self closed empty row tag eg. - yield return GetCell(useHeaderRow, maxColumnIndex, headRows, startColumnIndex); - yield break; - } + // row -> c, must after `if (nextRowIndex < rowIndex)` condition code, eg. The first empty row has no xml element,and the second row xml element is + if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false) && !_config.IgnoreEmptyRows) + { + //Fill in case of self closed empty row tag eg. + yield return GetCell(useHeaderRow, maxColumnIndex, headRows, startColumnIndex); + yield break; + } - var cell = GetCell(useHeaderRow, maxColumnIndex, headRows, startColumnIndex); - var columnIndex = withoutCR ? -1 : 0; - while (!reader.EOF) + var cell = GetCell(useHeaderRow, maxColumnIndex, headRows, startColumnIndex); + var columnIndex = withoutCr ? -1 : 0; + while (!reader.EOF) + { + if (XmlReaderHelper.IsStartElement(reader, "c", Ns)) { - if (XmlReaderHelper.IsStartElement(reader, "c", _ns)) - { - var aS = reader.GetAttribute("s"); - var aR = reader.GetAttribute("r"); - var aT = reader.GetAttribute("t"); - var cellAndColumn = await ReadCellAndSetColumnIndexAsync(reader, columnIndex, withoutCR, startColumnIndex, aR, aT, cancellationToken).ConfigureAwait(false); + var aS = reader.GetAttribute("s"); + var aR = reader.GetAttribute("r"); + var aT = reader.GetAttribute("t"); + var cellAndColumn = await ReadCellAndSetColumnIndexAsync(reader, columnIndex, withoutCr, startColumnIndex, aR, aT, cancellationToken).ConfigureAwait(false); - var cellValue = cellAndColumn.CellValue; - columnIndex = cellAndColumn.ColumnIndex; + var cellValue = cellAndColumn.CellValue; + columnIndex = cellAndColumn.ColumnIndex; - if (_config.FillMergedCells) + if (_config.FillMergedCells) + { + if (mergeCells?.MergesValues.ContainsKey(aR) ?? false) { - if (mergeCells.MergesValues.ContainsKey(aR)) - { - mergeCells.MergesValues[aR] = cellValue; - } - else if (mergeCells.MergesMap.TryGetValue(aR, out var mergeKey)) - { - mergeCells.MergesValues.TryGetValue(mergeKey, out cellValue); - } + mergeCells.MergesValues[aR] = cellValue; } - - if (columnIndex < startColumnIndex || (endColumnIndex.HasValue && columnIndex > endColumnIndex.Value)) - continue; - - if (!string.IsNullOrEmpty(aS)) // if c with s meaning is custom style need to check type by xl/style.xml + else if (mergeCells?.MergesMap.TryGetValue(aR, out var mergeKey) ?? false) { - int xfIndex = -1; - if (int.TryParse(aS, NumberStyles.Any, CultureInfo.InvariantCulture, - out var styleIndex)) - xfIndex = styleIndex; + mergeCells.MergesValues.TryGetValue(mergeKey, out cellValue); + } + } - // only when have s attribute then load styles xml data - if (_style == null) - _style = new ExcelOpenXmlStyles(_archive); + if (columnIndex < startColumnIndex || columnIndex > endColumnIndex) + continue; - cellValue = _style.ConvertValueByStyleFormat(xfIndex, cellValue); - } + if (!string.IsNullOrEmpty(aS)) // if c with s meaning is custom style need to check type by xl/style.xml + { + int xfIndex = -1; + if (int.TryParse(aS, NumberStyles.Any, CultureInfo.InvariantCulture, out var styleIndex)) + xfIndex = styleIndex; - SetCellsValueAndHeaders(cellValue, useHeaderRow, headRows, isFirstRow, cell, columnIndex); + // only when have s attribute then load styles xml data + _style ??= new ExcelOpenXmlStyles(Archive); + cellValue = _style.ConvertValueByStyleFormat(xfIndex, cellValue); } - else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) - break; + + SetCellsValueAndHeaders(cellValue, useHeaderRow, headRows, isFirstRow, cell, columnIndex); + } + else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) + { + break; } - yield return cell; } + yield return cell; + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public static async IAsyncEnumerable QueryImplAsync(IAsyncEnumerable> values, string startCell, bool hasHeader, Configuration configuration, [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, new() - { - cancellationToken.ThrowIfCancellationRequested(); + [CreateSyncVersion] + public static async IAsyncEnumerable QueryImplAsync(IAsyncEnumerable> values, string startCell, bool hasHeader, MiniExcelConfiguration configuration, [EnumeratorCancellation] CancellationToken cancellationToken = default) where T : class, new() + { + cancellationToken.ThrowIfCancellationRequested(); - var type = typeof(T); + var type = typeof(T); - //TODO:need to optimize - List props = null; - Dictionary headersDic = null; - string[] keys = null; - var first = true; - var rowIndex = 0; + //TODO:need to optimize + List props = []; + Dictionary headersDic = []; + string[] keys = []; + var first = true; + var rowIndex = 0; - await foreach (var item in values.ConfigureAwait(false)) + await foreach (var item in values.WithCancellation(cancellationToken).ConfigureAwait(false)) + { + if (first) { - if (first) - { - keys = item.Keys.ToArray(); - var trimColumnNames = (configuration as OpenXmlConfiguration)?.TrimColumnNames ?? false; - headersDic = CustomPropertyHelper.GetHeaders(item, trimColumnNames); + keys = item.Keys.ToArray(); + var trimColumnNames = (configuration as OpenXmlConfiguration)?.TrimColumnNames ?? false; + headersDic = CustomPropertyHelper.GetHeaders(item, trimColumnNames); - //TODO: alert don't duplicate column name - props = CustomPropertyHelper.GetExcelCustomPropertyInfos(type, keys, configuration); - first = false; + //TODO: alert don't duplicate column name + props = CustomPropertyHelper.GetExcelCustomPropertyInfos(type, keys, configuration); + first = false; - if (hasHeader) - continue; - } + if (hasHeader) + continue; + } - var v = new T(); - foreach (var pInfo in props) + var v = new T(); + foreach (var pInfo in props) + { + if (pInfo.ExcelColumnAliases is not null) { - if (pInfo.ExcelColumnAliases != null) + foreach (var alias in pInfo.ExcelColumnAliases) { - foreach (var alias in pInfo.ExcelColumnAliases) + if (headersDic?.TryGetValue(alias, out var columnId) ?? false) { - if (headersDic.TryGetValue(alias, out var columnId)) - { - var columnName = keys[columnId]; - item.TryGetValue(columnName, out var aliasItemValue); + var columnName = keys[columnId]; + item.TryGetValue(columnName, out var aliasItemValue); - if (aliasItemValue != null) - { - var newAliasValue = TypeHelper.TypeMapping(v, pInfo, aliasItemValue, rowIndex, startCell, configuration); - } + if (aliasItemValue is not null) + { + var newAliasValue = TypeHelper.TypeMapping(v, pInfo, aliasItemValue, rowIndex, startCell, configuration); } } } + } - //Q: Why need to check every time? A: it needs to check everytime, because it's dictionary - object itemValue = null; - if (pInfo.ExcelIndexName != null && keys.Contains(pInfo.ExcelIndexName)) - { - item.TryGetValue(pInfo.ExcelIndexName, out itemValue); - } - else if (headersDic.TryGetValue(pInfo.ExcelColumnName, out var columnId)) - { - var columnName = keys[columnId]; - item.TryGetValue(columnName, out itemValue); - } + //Q: Why need to check every time? A: it needs to check everytime, because it's dictionary + object? itemValue = null; + if (pInfo.ExcelIndexName is not null && (keys?.Contains(pInfo.ExcelIndexName) ?? false)) + { + item.TryGetValue(pInfo.ExcelIndexName, out itemValue); + } + else if (pInfo.ExcelColumnName is not null && (headersDic?.TryGetValue(pInfo.ExcelColumnName, out var columnId) ?? false)) + { + var columnName = keys[columnId]; + item.TryGetValue(columnName, out itemValue); + } - if (itemValue != null) - { - var newValue = TypeHelper.TypeMapping(v, pInfo, itemValue, rowIndex, startCell, configuration); - } + if (itemValue is not null) + { + var newValue = TypeHelper.TypeMapping(v, pInfo, itemValue, rowIndex, startCell, configuration); } - rowIndex++; - yield return v; } + + rowIndex++; + yield return v; } + } - private ZipArchiveEntry GetSheetEntry(string sheetName) + private ZipArchiveEntry GetSheetEntry(string? sheetName) + { + // if sheets count > 1 need to read xl/_rels/workbook.xml.rels + var sheets = Archive.EntryCollection + .Where(w => w.FullName.StartsWith("xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase) || + w.FullName.StartsWith("/xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase)) + .ToArray(); + + ZipArchiveEntry sheetEntry; + if (sheetName is not null) { - // if sheets count > 1 need to read xl/_rels/workbook.xml.rels - var sheets = _archive.entries - .Where(w => w.FullName.StartsWith("xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase) || w.FullName.StartsWith("/xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase)) - .ToArray(); - ZipArchiveEntry sheetEntry = null; - if (sheetName != null) + SetWorkbookRels(Archive.EntryCollection); + var sheetRecord = _sheetRecords.SingleOrDefault(s => s.Name == sheetName); + if (sheetRecord is null) { - SetWorkbookRels(_archive.entries); - var sheetRecord = _sheetRecords.SingleOrDefault(s => s.Name == sheetName); - if (sheetRecord == null) - { - if (_config.DynamicSheets == null) - throw new InvalidOperationException("Please check that parameters sheetName/Index are correct"); + if (_config.DynamicSheets is null) + throw new InvalidOperationException("Please check that parameters sheetName/Index are correct"); - var sheetConfig = _config.DynamicSheets.FirstOrDefault(ds => ds.Key == sheetName); - if (sheetConfig != null) - { - sheetRecord = _sheetRecords.SingleOrDefault(s => s.Name == sheetConfig.Name); - } + var sheetConfig = _config.DynamicSheets.FirstOrDefault(ds => ds.Key == sheetName); + if (sheetConfig is not null) + { + sheetRecord = _sheetRecords.SingleOrDefault(s => s.Name == sheetConfig.Name); } - sheetEntry = sheets.Single(w => w.FullName == $"xl/{sheetRecord.Path}" || w.FullName == $"/xl/{sheetRecord.Path}" || w.FullName == sheetRecord.Path || sheetRecord.Path == $"/{w.FullName}"); - } - else if (sheets.Length > 1) - { - SetWorkbookRels(_archive.entries); - var s = _sheetRecords[0]; - sheetEntry = sheets.Single(w => w.FullName == $"xl/{s.Path}" || w.FullName == $"/xl/{s.Path}" || w.FullName.TrimStart('/') == s.Path.TrimStart('/')); } - else - sheetEntry = sheets.Single(); - - return sheetEntry; + sheetEntry = sheets.Single(w => w.FullName == $"xl/{sheetRecord.Path}" || + w.FullName == $"/xl/{sheetRecord.Path}" || + w.FullName == sheetRecord.Path || + $"/{w.FullName}" == sheetRecord.Path); } - - private static IDictionary GetCell(bool useHeaderRow, int maxColumnIndex, Dictionary headRows, int startColumnIndex) + else if (sheets.Length > 1) { - return useHeaderRow ? CustomPropertyHelper.GetEmptyExpandoObject(headRows) : CustomPropertyHelper.GetEmptyExpandoObject(maxColumnIndex, startColumnIndex); + SetWorkbookRels(Archive.EntryCollection); + var s = _sheetRecords[0]; + sheetEntry = sheets.Single(w => w.FullName == $"xl/{s.Path}" || + w.FullName == $"/xl/{s.Path}" || + w.FullName.TrimStart('/') == s.Path.TrimStart('/')); } - - private static void SetCellsValueAndHeaders(object cellValue, bool useHeaderRow, Dictionary headRows, bool isFirstRow, IDictionary cell, int columnIndex) + else { - if (!useHeaderRow) - { - //if not using First Head then using A,B,C as index - cell[ColumnHelper.GetAlphabetColumnName(columnIndex)] = cellValue; - return; - } + sheetEntry = sheets.Single(); + } - if (isFirstRow) // for startcell logic - { - var cellValueString = cellValue?.ToString(); - if (!string.IsNullOrWhiteSpace(cellValueString)) - headRows.Add(columnIndex, cellValueString); - } - else if (headRows.TryGetValue(columnIndex, out var key)) - { - cell[key] = cellValue; - } + return sheetEntry; + } + + private static IDictionary GetCell(bool useHeaderRow, int maxColumnIndex, Dictionary headRows, int startColumnIndex) + { + return useHeaderRow + ? CustomPropertyHelper.GetEmptyExpandoObject(headRows) + : CustomPropertyHelper.GetEmptyExpandoObject(maxColumnIndex, startColumnIndex); + } + + private static void SetCellsValueAndHeaders(object? cellValue, bool useHeaderRow, Dictionary headRows, bool isFirstRow, IDictionary cell, int columnIndex) + { + if (!useHeaderRow) + { + //if not using First Head then using A,B,C as index + cell[ColumnHelper.GetAlphabetColumnName(columnIndex)] = cellValue; + return; } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task SetSharedStringsAsync(CancellationToken cancellationToken = default) + if (isFirstRow) // for startcell logic + { + var cellValueString = cellValue?.ToString(); + if (!string.IsNullOrWhiteSpace(cellValueString)) + headRows.Add(columnIndex, cellValueString); + } + else if (headRows.TryGetValue(columnIndex, out var key)) { - cancellationToken.ThrowIfCancellationRequested(); + cell[key] = cellValue; + } + } - if (_sharedStrings != null) - return; - var sharedStringsEntry = _archive.GetEntry("xl/sharedStrings.xml"); - if (sharedStringsEntry == null) - return; - using (var stream = sharedStringsEntry.Open()) + [CreateSyncVersion] + private async Task SetSharedStringsAsync(CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + + if (SharedStrings is not null) + return; + + var sharedStringsEntry = Archive.GetEntry("xl/sharedStrings.xml"); + if (sharedStringsEntry is null) + return; + + var idx = 0; + using var stream = sharedStringsEntry.Open(); + if (_config.EnableSharedStringCache && sharedStringsEntry.Length >= _config.SharedStringCacheSize) + { + SharedStrings = new SharedStringsDiskCache(); + await foreach (var sharedString in XmlReaderHelper.GetSharedStringsAsync(stream, cancellationToken, Ns).ConfigureAwait(false)) { - var idx = 0; - if (_config.EnableSharedStringCache && sharedStringsEntry.Length >= _config.SharedStringCacheSize) - { - _sharedStrings = new SharedStringsDiskCache(); - await foreach (var sharedString in XmlReaderHelper.GetSharedStringsAsync(stream, cancellationToken, _ns).ConfigureAwait(false)) - _sharedStrings[idx++] = sharedString; - } - else if (_sharedStrings == null) - { - var list = new List(); - await foreach (var str in XmlReaderHelper.GetSharedStringsAsync(stream, cancellationToken, _ns).ConfigureAwait(false)) - { - list.Add(str); - } - _sharedStrings = list.ToDictionary((x) => idx++, x => x); - } + SharedStrings[idx++] = sharedString; } } - - private void SetWorkbookRels(ReadOnlyCollection entries) + else if (SharedStrings is null) { - if (_sheetRecords != null) - return; - _sheetRecords = GetWorkbookRels(entries); + var list = await XmlReaderHelper.GetSharedStringsAsync(stream, cancellationToken, Ns) + .CreateListAsync(cancellationToken) + .ConfigureAwait(false); + + SharedStrings = list.ToDictionary(_ => idx++, x => x); } + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - internal static async IAsyncEnumerable ReadWorkbookAsync(ReadOnlyCollection entries, [EnumeratorCancellation] CancellationToken cancellationToken = default) - { - var xmlSettings = XmlReaderHelper.GetXmlReaderSettings( + private void SetWorkbookRels(ReadOnlyCollection entries) + { + _sheetRecords ??= GetWorkbookRels(entries); + } + + [CreateSyncVersion] + internal static async IAsyncEnumerable ReadWorkbookAsync(ReadOnlyCollection entries, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + var xmlSettings = XmlReaderHelper.GetXmlReaderSettings( #if SYNC_ONLY - false + false #else - true + true #endif - ); - - - using (var stream = entries.Single(w => w.FullName == "xl/workbook.xml").Open()) - using (var reader = XmlReader.Create(stream, xmlSettings)) + ); + + using var stream = entries.Single(w => w.FullName == "xl/workbook.xml").Open(); + using var reader = XmlReader.Create(stream, xmlSettings); + + if (!XmlReaderHelper.IsStartElement(reader, "workbook", Ns)) + yield break; + if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) + yield break; + + var activeSheetIndex = 0; + while (!reader.EOF) + { + if (XmlReaderHelper.IsStartElement(reader, "bookViews", Ns)) { - if (!XmlReaderHelper.IsStartElement(reader, "workbook", _ns)) - yield break; - if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) - yield break; + continue; - var activeSheetIndex = 0; while (!reader.EOF) { - if (XmlReaderHelper.IsStartElement(reader, "bookViews", _ns)) + if (XmlReaderHelper.IsStartElement(reader, "workbookView", Ns)) { - if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) - continue; - - while (!reader.EOF) + var activeSheet = reader.GetAttribute("activeTab"); + if (int.TryParse(activeSheet, out var index)) { - if (XmlReaderHelper.IsStartElement(reader, "workbookView", _ns)) - { - var activeSheet = reader.GetAttribute("activeTab"); - if (int.TryParse(activeSheet, out var index)) - { - activeSheetIndex = index; - } - - await reader.SkipAsync() -#if NET6_0_OR_GREATER - .WaitAsync(cancellationToken) -#endif - .ConfigureAwait(false); - } - else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) - { - break; - } + activeSheetIndex = index; } - } - else if (XmlReaderHelper.IsStartElement(reader, "sheets", _ns)) - { - if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) - continue; - var sheetCount = 0; - while (!reader.EOF) - { - if (XmlReaderHelper.IsStartElement(reader, "sheet", _ns)) - { - yield return new SheetRecord( - reader.GetAttribute("name"), - reader.GetAttribute("state"), - uint.Parse(reader.GetAttribute("sheetId")), - XmlReaderHelper.GetAttribute(reader, "id", _relationshiopNs), - sheetCount == activeSheetIndex - ); - sheetCount++; - await reader.SkipAsync() + await reader.SkipAsync() #if NET6_0_OR_GREATER - .WaitAsync(cancellationToken) + .WaitAsync(cancellationToken) #endif - .ConfigureAwait(false); - } - else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) - { - break; - } - } + .ConfigureAwait(false); } else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) { - yield break; + break; } } } - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - internal async Task> GetWorkbookRelsAsync(ReadOnlyCollection entries, CancellationToken cancellationToken = default) - { - var xmlSettings = XmlReaderHelper.GetXmlReaderSettings( -#if SYNC_ONLY - false -#else - true -#endif - ); - - var sheetRecords = new List(); - await foreach (var sheetRecord in ReadWorkbookAsync(entries, cancellationToken).ConfigureAwait(false)) - { - sheetRecords.Add(sheetRecord); - } - - using (var stream = entries.Single(w => w.FullName == "xl/_rels/workbook.xml.rels").Open()) - using (var reader = XmlReader.Create(stream, xmlSettings)) + else if (XmlReaderHelper.IsStartElement(reader, "sheets", Ns)) { - if (!XmlReaderHelper.IsStartElement(reader, "Relationships", "http://schemas.openxmlformats.org/package/2006/relationships")) - return null; - if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) - return null; + continue; + var sheetCount = 0; while (!reader.EOF) { - if (XmlReaderHelper.IsStartElement(reader, "Relationship", "http://schemas.openxmlformats.org/package/2006/relationships")) + if (XmlReaderHelper.IsStartElement(reader, "sheet", Ns)) { - var rid = reader.GetAttribute("Id"); - foreach (var sheet in sheetRecords) - { - if (sheet.Rid == rid) - { - sheet.Path = reader.GetAttribute("Target"); - break; - } - } - + yield return new SheetRecord( + reader.GetAttribute("name"), + reader.GetAttribute("state"), + uint.Parse(reader.GetAttribute("sheetId")), + XmlReaderHelper.GetAttribute(reader, "id", RelationshiopNs), + sheetCount == activeSheetIndex + ); + sheetCount++; await reader.SkipAsync() #if NET6_0_OR_GREATER - .WaitAsync(cancellationToken) + .WaitAsync(cancellationToken) #endif .ConfigureAwait(false); } @@ -644,284 +594,332 @@ await reader.SkipAsync() } } } + else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) + { + yield break; + } + } + } + + [CreateSyncVersion] + internal async Task?> GetWorkbookRelsAsync(ReadOnlyCollection entries, CancellationToken cancellationToken = default) + { + var xmlSettings = XmlReaderHelper.GetXmlReaderSettings( +#if SYNC_ONLY + false +#else + true +#endif + ); + + var sheetRecords = await ReadWorkbookAsync(entries, cancellationToken) + .CreateListAsync(cancellationToken) + .ConfigureAwait(false); + + using var stream = entries.Single(w => w.FullName == "xl/_rels/workbook.xml.rels").Open(); + using var reader = XmlReader.Create(stream, xmlSettings); + + if (!XmlReaderHelper.IsStartElement(reader, "Relationships", "http://schemas.openxmlformats.org/package/2006/relationships")) + return null; + if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) + return null; + + while (!reader.EOF) + { + if (XmlReaderHelper.IsStartElement(reader, "Relationship", "http://schemas.openxmlformats.org/package/2006/relationships")) + { + var rid = reader.GetAttribute("Id"); + foreach (var sheet in sheetRecords.Where(sh => sh.Rid == rid)) + { + sheet.Path = reader.GetAttribute("Target"); + break; + } + + await reader.SkipAsync() +#if NET6_0_OR_GREATER + .WaitAsync(cancellationToken) +#endif + .ConfigureAwait(false); + } + else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) + { + break; + } + } + + return sheetRecords; + } + + internal class CellAndColumn(object? cellValue, int columnIndex) + { + public object? CellValue { get; } = cellValue; + public int ColumnIndex { get; } = columnIndex; + } + + [CreateSyncVersion] + private async Task ReadCellAndSetColumnIndexAsync(XmlReader reader, int columnIndex, bool withoutCr, int startColumnIndex, string aR, string aT, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); - return sheetRecords; - } + const int xfIndex = -1; + int newColumnIndex; - internal class CellAndColumn + if (withoutCr) { - public object CellValue { get; } - public int ColumnIndex { get; } = -1; - - public CellAndColumn(object cellValue, int columnIndex) - { - CellValue = cellValue; - ColumnIndex = columnIndex; - } + newColumnIndex = columnIndex + 1; } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task ReadCellAndSetColumnIndexAsync(XmlReader reader, int columnIndex, bool withoutCR, int startColumnIndex, string aR, string aT, CancellationToken cancellationToken = default) + else if (ReferenceHelper.ParseReference(aR, out int referenceColumn, out _)) { - cancellationToken.ThrowIfCancellationRequested(); - - const int xfIndex = -1; - int newColumnIndex; - - if (withoutCR) - newColumnIndex = columnIndex + 1; - //TODO:need to check only need nextColumnIndex or columnIndex - else if (ReferenceHelper.ParseReference(aR, out int referenceColumn, out _)) - newColumnIndex = referenceColumn - 1; // ParseReference is 1-based - else - newColumnIndex = columnIndex; - - columnIndex = newColumnIndex; - - if (columnIndex < startColumnIndex) - { - if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) - return new CellAndColumn(null, columnIndex); - - while (!reader.EOF) - if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) - break; + newColumnIndex = referenceColumn - 1; // ParseReference is 1-based + } + else + { + newColumnIndex = columnIndex; + } - return new CellAndColumn(null, columnIndex); - } + columnIndex = newColumnIndex; + if (columnIndex < startColumnIndex) + { if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) return new CellAndColumn(null, columnIndex); - object value = null; while (!reader.EOF) { - if (XmlReaderHelper.IsStartElement(reader, "v", _ns)) - { - var rawValue = await reader.ReadElementContentAsStringAsync() -#if NET6_0_OR_GREATER - .WaitAsync(cancellationToken) -#endif - .ConfigureAwait(false); - if (!string.IsNullOrEmpty(rawValue)) - ConvertCellValue(rawValue, aT, xfIndex, out value); - } - else if (XmlReaderHelper.IsStartElement(reader, "is", _ns)) - { - var rawValue = await StringHelper.ReadStringItemAsync(reader, cancellationToken).ConfigureAwait(false); - if (!string.IsNullOrEmpty(rawValue)) - ConvertCellValue(rawValue, aT, xfIndex, out value); - } - else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) - { + if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) break; - } } - return new CellAndColumn(value, columnIndex); + return new CellAndColumn(null, columnIndex); } - private void ConvertCellValue(string rawValue, string aT, int xfIndex, out object value) - { - const NumberStyles style = NumberStyles.Any; - var invariantCulture = CultureInfo.InvariantCulture; + if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) + return new CellAndColumn(null, columnIndex); - switch (aT) + object? value = null; + while (!reader.EOF) + { + if (XmlReaderHelper.IsStartElement(reader, "v", Ns)) + { + var rawValue = await reader.ReadElementContentAsStringAsync() +#if NET6_0_OR_GREATER + .WaitAsync(cancellationToken) +#endif + .ConfigureAwait(false); + + if (!string.IsNullOrEmpty(rawValue)) + ConvertCellValue(rawValue, aT, xfIndex, out value); + } + else if (XmlReaderHelper.IsStartElement(reader, "is", Ns)) { - case "s": - if (int.TryParse(rawValue, style, invariantCulture, out var sstIndex)) + var rawValue = await StringHelper.ReadStringItemAsync(reader, cancellationToken).ConfigureAwait(false); + if (!string.IsNullOrEmpty(rawValue)) + ConvertCellValue(rawValue, aT, xfIndex, out value); + } + else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) + { + break; + } + } + + return new CellAndColumn(value, columnIndex); + } + + private void ConvertCellValue(string rawValue, string aT, int xfIndex, out object? value) + { + const NumberStyles style = NumberStyles.Any; + var invariantCulture = CultureInfo.InvariantCulture; + + switch (aT) + { + case "s": + if (int.TryParse(rawValue, style, invariantCulture, out var sstIndex)) + { + if (sstIndex >= 0 && sstIndex < SharedStrings?.Count) { - if (sstIndex >= 0 && sstIndex < _sharedStrings.Count) - { - //value = Helpers.ConvertEscapeChars(_SharedStrings[sstIndex]); - value = XmlEncoder.DecodeString(_sharedStrings[sstIndex]); - return; - } + //value = Helpers.ConvertEscapeChars(_SharedStrings[sstIndex]); + value = XmlEncoder.DecodeString(SharedStrings[sstIndex]); + return; } - value = null; - return; + } + value = null; + return; - case "inlineStr": - case "str": - //TODO: it will unbox,box - var v = XmlEncoder.DecodeString(rawValue); - if (_config.EnableConvertByteArray) + case "inlineStr": + case "str": + //TODO: it will unbox,box + var v = XmlEncoder.DecodeString(rawValue); + if (_config.EnableConvertByteArray) + { + //if str start with "data:image/png;base64," then convert to byte[] https://github.com/mini-software/MiniExcel/issues/318 + if (v is not null && v.StartsWith("@@@fileid@@@,", StringComparison.Ordinal)) { - //if str start with "data:image/png;base64," then convert to byte[] https://github.com/mini-software/MiniExcel/issues/318 - if (v != null && v.StartsWith("@@@fileid@@@,", StringComparison.Ordinal)) - { - var path = v.Substring(13); - var entry = _archive.GetEntry(path); - var bytes = new byte[entry.Length]; + var path = v[13..]; + var entry = Archive.GetEntry(path); + var bytes = new byte[entry.Length]; - using (var stream = entry.Open()) - using (var ms = new MemoryStream(bytes)) - { - stream.CopyTo(ms); - } - value = bytes; - } - else + using (var stream = entry.Open()) + using (var ms = new MemoryStream(bytes)) { - value = v; + stream.CopyTo(ms); } + value = bytes; } else { value = v; } - return; - - case "b": - value = rawValue == "1"; - return; + } + else + { + value = v; + } + return; - case "d": - if (DateTime.TryParseExact(rawValue, "yyyy-MM-dd", invariantCulture, DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite, out var date)) - { - value = date; - return; - } + case "b": + value = rawValue == "1"; + return; - value = rawValue; + case "d": + if (DateTime.TryParseExact(rawValue, "yyyy-MM-dd", invariantCulture, DateTimeStyles.AllowLeadingWhite | DateTimeStyles.AllowTrailingWhite, out var date)) + { + value = date; return; + } - case "e": - value = rawValue; - return; + value = rawValue; + return; - default: - if (double.TryParse(rawValue, style, invariantCulture, out var n)) - { - value = n; - return; - } + case "e": + value = rawValue; + return; - value = rawValue; + default: + if (double.TryParse(rawValue, style, invariantCulture, out var n)) + { + value = n; return; - } + } + + value = rawValue; + return; } + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - internal async Task> GetDimensionsAsync(CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); + [CreateSyncVersion] + internal async Task> GetDimensionsAsync(CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); - var xmlSettings = XmlReaderHelper.GetXmlReaderSettings( + var xmlSettings = XmlReaderHelper.GetXmlReaderSettings( #if SYNC_ONLY - false + false #else - true + true #endif - ); + ); - var ranges = new List(); + var ranges = new List(); - var sheets = _archive.entries.Where(e => - e.FullName.StartsWith("xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase) || - e.FullName.StartsWith("/xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase)); + var sheets = Archive.EntryCollection.Where(e => + e.FullName.StartsWith("xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase) || + e.FullName.StartsWith("/xl/worksheets/sheet", StringComparison.OrdinalIgnoreCase)); - foreach (var sheet in sheets) - { - var maxRowIndex = -1; - var maxColumnIndex = -1; + foreach (var sheet in sheets) + { + var maxRowIndex = -1; + var maxColumnIndex = -1; - string startCell = null; - string endCell = null; + string? startCell = null; + string? endCell = null; - var withoutCR = false; + var withoutCr = false; - using (var sheetStream = sheet.Open()) - using (var reader = XmlReader.Create(sheetStream, xmlSettings)) + using (var sheetStream = sheet.Open()) + using (var reader = XmlReader.Create(sheetStream, xmlSettings)) + { + while (await reader.ReadAsync().ConfigureAwait(false)) { - while (await reader.ReadAsync().ConfigureAwait(false)) + if (XmlReaderHelper.IsStartElement(reader, "c", Ns)) { - if (XmlReaderHelper.IsStartElement(reader, "c", _ns)) + var r = reader.GetAttribute("r"); + if (r is not null) { - var r = reader.GetAttribute("r"); - if (r != null) - { - if (ReferenceHelper.ParseReference(r, out var column, out var row)) - { - column--; - row--; - maxRowIndex = Math.Max(maxRowIndex, row); - maxColumnIndex = Math.Max(maxColumnIndex, column); - } - } - else + if (ReferenceHelper.ParseReference(r, out var column, out var row)) { - withoutCR = true; - break; + column--; + row--; + maxRowIndex = Math.Max(maxRowIndex, row); + maxColumnIndex = Math.Max(maxColumnIndex, column); } } - - else if (XmlReaderHelper.IsStartElement(reader, "dimension", _ns)) + else { - var refAttr = reader.GetAttribute("ref"); - if (string.IsNullOrEmpty(refAttr)) - throw new InvalidDataException("No dimension data found for the sheet"); + withoutCr = true; + break; + } + } + + else if (XmlReaderHelper.IsStartElement(reader, "dimension", Ns)) + { + var refAttr = reader.GetAttribute("ref"); + if (string.IsNullOrEmpty(refAttr)) + throw new InvalidDataException("No dimension data found for the sheet"); - var rs = refAttr.Split(':'); - if (!ReferenceHelper.ParseReference(rs.Length == 2 ? rs[1] : rs[0], out var col, out var row)) - throw new InvalidDataException("The dimensions of the sheet are invalid"); + var rs = refAttr.Split(':'); + if (!ReferenceHelper.ParseReference(rs.Length == 2 ? rs[1] : rs[0], out var col, out var row)) + throw new InvalidDataException("The dimensions of the sheet are invalid"); - maxColumnIndex = col; - maxRowIndex = row; + maxColumnIndex = col; + maxRowIndex = row; - startCell = rs[0]; - endCell = rs[1]; + startCell = rs[0]; + endCell = rs[1]; - break; - } + break; } } + } + + if (withoutCr) + { + using var sheetStream = sheet.Open(); + using var reader = XmlReader.Create(sheetStream, xmlSettings); + + if (!XmlReaderHelper.IsStartElement(reader, "worksheet", Ns)) + throw new InvalidDataException("No worksheet data found for the sheet"); - if (withoutCR) + if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) + throw new InvalidOperationException("Excel sheet does not contain any data"); + + while (!reader.EOF) { - using (var sheetStream = sheet.Open()) - using (var reader = XmlReader.Create(sheetStream, xmlSettings)) + if (XmlReaderHelper.IsStartElement(reader, "sheetData", Ns)) { - if (!XmlReaderHelper.IsStartElement(reader, "worksheet", _ns)) - throw new InvalidDataException("No worksheet data found for the sheet"); - if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) - throw new InvalidOperationException("Excel sheet does not contain any data"); + continue; while (!reader.EOF) { - if (XmlReaderHelper.IsStartElement(reader, "sheetData", _ns)) + if (XmlReaderHelper.IsStartElement(reader, "row", Ns)) { + maxRowIndex++; + if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) continue; + var cellIndex = -1; while (!reader.EOF) { - if (XmlReaderHelper.IsStartElement(reader, "row", _ns)) + if (XmlReaderHelper.IsStartElement(reader, "c", Ns)) { - maxRowIndex++; - - if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) - continue; - - var cellIndex = -1; - while (!reader.EOF) - { - if (XmlReaderHelper.IsStartElement(reader, "c", _ns)) - { - cellIndex++; - maxColumnIndex = Math.Max(maxColumnIndex, cellIndex); - } - - if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) - break; - } + cellIndex++; + maxColumnIndex = Math.Max(maxColumnIndex, cellIndex); } - else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) - { + + if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) break; - } } } else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) @@ -930,150 +928,142 @@ internal async Task> GetDimensionsAsync(CancellationToken canc } } } + else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) + { + break; + } } - - var range = new ExcelRange(maxRowIndex, maxColumnIndex) - { - StartCell = startCell, - EndCell = endCell - }; - ranges.Add(range); - } - - return ranges; - } - - internal class GetMaxRowColumnIndexResult - { - public bool IsSuccess { get; } - public bool WithoutCR { get; } - public int MaxRowIndex { get; } = -1; - public int MaxColumnIndex { get; } = -1; - - public GetMaxRowColumnIndexResult(bool isSuccess) - { - IsSuccess = isSuccess; } - - public GetMaxRowColumnIndexResult(bool isSuccess, bool withoutCR, int maxRowIndex, int maxColumnIndex) - : this(isSuccess) + var range = new ExcelRange(maxRowIndex, maxColumnIndex) { - WithoutCR = withoutCR; - MaxRowIndex = maxRowIndex; - MaxColumnIndex = maxColumnIndex; - } + StartCell = startCell, + EndCell = endCell + }; + ranges.Add(range); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - internal static async Task TryGetMaxRowColumnIndexAsync(ZipArchiveEntry sheetEntry, CancellationToken cancellationToken = default) + return ranges; + } + + internal class GetMaxRowColumnIndexResult(bool isSuccess) + { + public bool IsSuccess { get; } = isSuccess; + public bool WithoutCr { get; } + public int MaxRowIndex { get; } = -1; + public int MaxColumnIndex { get; } = -1; + + public GetMaxRowColumnIndexResult(bool isSuccess, bool withoutCr, int maxRowIndex, int maxColumnIndex) + : this(isSuccess) { - cancellationToken.ThrowIfCancellationRequested(); + WithoutCr = withoutCr; + MaxRowIndex = maxRowIndex; + MaxColumnIndex = maxColumnIndex; + } + } + + [CreateSyncVersion] + internal static async Task TryGetMaxRowColumnIndexAsync(ZipArchiveEntry sheetEntry, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); - var xmlSettings = XmlReaderHelper.GetXmlReaderSettings( + var xmlSettings = XmlReaderHelper.GetXmlReaderSettings( #if SYNC_ONLY - false + false #else - true + true #endif - ); + ); - bool withoutCR = false; - int maxRowIndex = -1; - int maxColumnIndex = -1; - using (var sheetStream = sheetEntry.Open()) - using (var reader = XmlReader.Create(sheetStream, xmlSettings)) - { - while (await reader.ReadAsync() + bool withoutCr = false; + int maxRowIndex = -1; + int maxColumnIndex = -1; + using (var sheetStream = sheetEntry.Open()) + using (var reader = XmlReader.Create(sheetStream, xmlSettings)) + { + while (await reader.ReadAsync() #if NET6_0_OR_GREATER - .WaitAsync(cancellationToken) + .WaitAsync(cancellationToken) #endif - .ConfigureAwait(false)) + .ConfigureAwait(false)) + { + if (XmlReaderHelper.IsStartElement(reader, "c", Ns)) { - if (XmlReaderHelper.IsStartElement(reader, "c", _ns)) + var r = reader.GetAttribute("r"); + if (r is not null) { - var r = reader.GetAttribute("r"); - if (r != null) + if (ReferenceHelper.ParseReference(r, out var column, out var row)) { - if (ReferenceHelper.ParseReference(r, out var column, out var row)) - { - column--; - row--; - maxRowIndex = Math.Max(maxRowIndex, row); - maxColumnIndex = Math.Max(maxColumnIndex, column); - } - } - else - { - withoutCR = true; - break; + column--; + row--; + maxRowIndex = Math.Max(maxRowIndex, row); + maxColumnIndex = Math.Max(maxColumnIndex, column); } } - //this method logic depends on dimension to get maxcolumnIndex, if without dimension then it need to foreach all rows first time to get maxColumn and maxRowColumn - else if (XmlReaderHelper.IsStartElement(reader, "dimension", _ns)) + else { - var refAttr = reader.GetAttribute("ref"); - if (string.IsNullOrEmpty(refAttr)) - throw new InvalidDataException("No dimension data found for the sheet"); + withoutCr = true; + break; + } + } + //this method logic depends on dimension to get maxcolumnIndex, if without dimension then it need to foreach all rows first time to get maxColumn and maxRowColumn + else if (XmlReaderHelper.IsStartElement(reader, "dimension", Ns)) + { + var refAttr = reader.GetAttribute("ref"); + if (string.IsNullOrEmpty(refAttr)) + throw new InvalidDataException("No dimension data found for the sheet"); - var rs = refAttr.Split(':'); + var rs = refAttr.Split(':'); - // issue : https://github.com/mini-software/MiniExcel/issues/102 - if (!ReferenceHelper.ParseReference(rs.Length == 2 ? rs[1] : rs[0], out int cIndex, out int rIndex)) - throw new InvalidDataException("The dimensions of the sheet are invalid"); + // issue : https://github.com/mini-software/MiniExcel/issues/102 + if (!ReferenceHelper.ParseReference(rs.Length == 2 ? rs[1] : rs[0], out int cIndex, out int rIndex)) + throw new InvalidDataException("The dimensions of the sheet are invalid"); - maxRowIndex = rIndex - 1; - maxColumnIndex = cIndex - 1; - break; - } + maxRowIndex = rIndex - 1; + maxColumnIndex = cIndex - 1; + break; } } + } + + if (withoutCr) + { + using var sheetStream = sheetEntry.Open(); + using var reader = XmlReader.Create(sheetStream, xmlSettings); + + if (!XmlReaderHelper.IsStartElement(reader, "worksheet", Ns)) + return new GetMaxRowColumnIndexResult(false); + if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) + return new GetMaxRowColumnIndexResult(false); - if (withoutCR) + while (!reader.EOF) { - using (var sheetStream = sheetEntry.Open()) - using (var reader = XmlReader.Create(sheetStream, xmlSettings)) + if (XmlReaderHelper.IsStartElement(reader, "sheetData", Ns)) { - if (!XmlReaderHelper.IsStartElement(reader, "worksheet", _ns)) - return new GetMaxRowColumnIndexResult(false); - if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) - return new GetMaxRowColumnIndexResult(false); + continue; while (!reader.EOF) { - if (XmlReaderHelper.IsStartElement(reader, "sheetData", _ns)) + if (XmlReaderHelper.IsStartElement(reader, "row", Ns)) { + maxRowIndex++; + if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) continue; + // Cells + var cellIndex = -1; while (!reader.EOF) { - if (XmlReaderHelper.IsStartElement(reader, "row", _ns)) + if (XmlReaderHelper.IsStartElement(reader, "c", Ns)) { - maxRowIndex++; - - if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) - continue; - - // Cells - var cellIndex = -1; - while (!reader.EOF) - { - if (XmlReaderHelper.IsStartElement(reader, "c", _ns)) - { - cellIndex++; - maxColumnIndex = Math.Max(maxColumnIndex, cellIndex); - } - - if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) - break; - } + cellIndex++; + maxColumnIndex = Math.Max(maxColumnIndex, cellIndex); } - else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) - { + + if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) break; - } } } else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) @@ -1082,109 +1072,113 @@ internal static async Task TryGetMaxRowColumnIndexAs } } } + else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) + { + break; + } } - - return new GetMaxRowColumnIndexResult(true, withoutCR, maxRowIndex, maxColumnIndex); } - internal class MergeCellsContext - { - public MergeCells MergeCells { get; set; } - } + return new GetMaxRowColumnIndexResult(true, withoutCr, maxRowIndex, maxColumnIndex); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - internal static async Task TryGetMergeCellsAsync(ZipArchiveEntry sheetEntry, MergeCellsContext mergeCellsContext, CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); + internal class MergeCellsContext + { + public MergeCells? MergeCells { get; set; } + } + + + [CreateSyncVersion] + internal static async Task TryGetMergeCellsAsync(ZipArchiveEntry sheetEntry, MergeCellsContext mergeCellsContext, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); - var xmlSettings = XmlReaderHelper.GetXmlReaderSettings( + var xmlSettings = XmlReaderHelper.GetXmlReaderSettings( #if SYNC_ONLY - false + false #else - true + true #endif - ); - var mergeCells = new MergeCells(); - using (var sheetStream = sheetEntry.Open()) - using (XmlReader reader = XmlReader.Create(sheetStream, xmlSettings)) + ); + var mergeCells = new MergeCells(); + + using var sheetStream = sheetEntry.Open(); + using var reader = XmlReader.Create(sheetStream, xmlSettings); + + if (!XmlReaderHelper.IsStartElement(reader, "worksheet", Ns)) + return false; + + while (await reader.ReadAsync().ConfigureAwait(false)) + { + if (!XmlReaderHelper.IsStartElement(reader, "mergeCells", Ns)) + continue; + + if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) + return false; + + while (!reader.EOF) { - if (!XmlReaderHelper.IsStartElement(reader, "worksheet", _ns)) - return false; - while (await reader.ReadAsync().ConfigureAwait(false)) + if (XmlReaderHelper.IsStartElement(reader, "mergeCell", Ns)) { - if (!XmlReaderHelper.IsStartElement(reader, "mergeCells", _ns)) - { + var refAttr = reader.GetAttribute("ref"); + var refs = refAttr.Split(':'); + if (refs.Length == 1) continue; - } - - if (!await XmlReaderHelper.ReadFirstContentAsync(reader, cancellationToken).ConfigureAwait(false)) - return false; - - while (!reader.EOF) - { - if (XmlReaderHelper.IsStartElement(reader, "mergeCell", _ns)) - { - var refAttr = reader.GetAttribute("ref"); - var refs = refAttr.Split(':'); - if (refs.Length == 1) - continue; - ReferenceHelper.ParseReference(refs[0], out var x1, out var y1); - ReferenceHelper.ParseReference(refs[1], out var x2, out var y2); + ReferenceHelper.ParseReference(refs[0], out var x1, out var y1); + ReferenceHelper.ParseReference(refs[1], out var x2, out var y2); - mergeCells.MergesValues.Add(refs[0], null); - - // foreach range - var isFirst = true; - for (int x = x1; x <= x2; x++) - { - for (int y = y1; y <= y2; y++) - { - if (!isFirst) - mergeCells.MergesMap.Add(ReferenceHelper.ConvertXyToCell(x, y), refs[0]); - isFirst = false; - } - } + mergeCells.MergesValues.Add(refs[0], null); - await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false); - } - else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) + // foreach range + var isFirst = true; + for (int x = x1; x <= x2; x++) + { + for (int y = y1; y <= y2; y++) { - break; + if (!isFirst) + mergeCells.MergesMap.Add(ReferenceHelper.ConvertXyToCell(x, y), refs[0]); + isFirst = false; } } - } - mergeCellsContext.MergeCells = mergeCells; - return true; + await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false); + } + else if (!await XmlReaderHelper.SkipContentAsync(reader, cancellationToken).ConfigureAwait(false)) + { + break; + } } } - ~ExcelOpenXmlSheetReader() - { - Dispose(false); - } + mergeCellsContext.MergeCells = mergeCells; + return true; + } - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } + ~ExcelOpenXmlSheetReader() + { + Dispose(false); + } - protected virtual void Dispose(bool disposing) + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposed) { - if (!_disposed) + if (disposing) { - if (disposing) + if (SharedStrings is SharedStringsDiskCache cache) { - if (_sharedStrings is SharedStringsDiskCache cache) - { - cache.Dispose(); - } + cache.Dispose(); } - - _disposed = true; } + + _disposed = true; } } } \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs index 5f887da6..0d625705 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.DefaultOpenXml.cs @@ -1,426 +1,417 @@ -using MiniExcelLibs.Attributes; -using MiniExcelLibs.OpenXml.Constants; -using MiniExcelLibs.OpenXml.Models; -using MiniExcelLibs.OpenXml.Styles; -using MiniExcelLibs.Utils; -using MiniExcelLibs.Zip; using System; using System.Collections.Generic; using System.Data; using System.Globalization; using System.Linq; using System.Text; +using MiniExcelLibs.OpenXml.Constants; +using MiniExcelLibs.OpenXml.Models; +using MiniExcelLibs.Utils; +using MiniExcelLibs.Zip; using static MiniExcelLibs.Utils.ImageHelper; -namespace MiniExcelLibs.OpenXml +namespace MiniExcelLibs.OpenXml; + +internal partial class ExcelOpenXmlSheetWriter : IExcelWriter { - internal partial class ExcelOpenXmlSheetWriter : IExcelWriter - { - private readonly Dictionary _zipDictionary = new Dictionary(); - private Dictionary _cellXfIdMap; + private readonly Dictionary _zipDictionary = []; + private Dictionary _cellXfIdMap; - private IEnumerable> GetSheets() + private IEnumerable> GetSheets() + { + var sheetId = 0; + if (_value is IDictionary dictionary) { - var sheetId = 0; - if (_value is IDictionary dictionary) - { - foreach (var sheet in dictionary) - { - sheetId++; - var sheetInfos = GetSheetInfos(sheet.Key); - yield return Tuple.Create(sheetInfos.ToDto(sheetId), sheet.Value); - } - - yield break; - } - - if (_value is DataSet dataSet) + foreach (var sheet in dictionary) { - foreach (DataTable dt in dataSet.Tables) - { - sheetId++; - var sheetInfos = GetSheetInfos(dt.TableName); - yield return Tuple.Create(sheetInfos.ToDto(sheetId), dt); - } - - yield break; + sheetId++; + var sheetInfos = GetSheetInfos(sheet.Key); + yield return Tuple.Create(sheetInfos.ToDto(sheetId), sheet.Value); } - sheetId++; - var defaultSheetInfo = GetSheetInfos(_defaultSheetName); - yield return Tuple.Create(defaultSheetInfo.ToDto(sheetId), _value); + yield break; } - private ExcellSheetInfo GetSheetInfos(string sheetName) + if (_value is DataSet dataSet) { - var info = new ExcellSheetInfo + foreach (DataTable dt in dataSet.Tables) { - ExcelSheetName = sheetName, - Key = sheetName, - ExcelSheetState = SheetState.Visible - }; + sheetId++; + var sheetInfos = GetSheetInfos(dt.TableName); + yield return Tuple.Create(sheetInfos.ToDto(sheetId), dt); + } - if (_configuration.DynamicSheets == null || _configuration.DynamicSheets.Length <= 0) - return info; + yield break; + } - var dynamicSheet = _configuration.DynamicSheets.SingleOrDefault(_ => _.Key == sheetName); - if (dynamicSheet == null) - return info; + sheetId++; + var defaultSheetInfo = GetSheetInfos(_defaultSheetName); + yield return Tuple.Create(defaultSheetInfo.ToDto(sheetId), _value); + } - info.ExcelSheetState = dynamicSheet.State; - if (dynamicSheet.Name != null) - info.ExcelSheetName = dynamicSheet.Name; + private ExcellSheetInfo GetSheetInfos(string sheetName) + { + var info = new ExcellSheetInfo + { + ExcelSheetName = sheetName, + Key = sheetName, + ExcelSheetState = SheetState.Visible + }; + if (_configuration.DynamicSheets is null or []) return info; - } - private string GetSheetViews() - { - // exit early if no style to write - if (_configuration.FreezeRowCount <= 0 && _configuration.FreezeColumnCount <= 0) - return string.Empty; + var dynamicSheet = _configuration.DynamicSheets.SingleOrDefault(s => s.Key == sheetName); + if (dynamicSheet is null) + return info; - var sb = new StringBuilder(); + info.ExcelSheetState = dynamicSheet.State; + if (dynamicSheet.Name is not null) + info.ExcelSheetName = dynamicSheet.Name; - // start sheetViews - sb.Append(WorksheetXml.StartSheetViews); - sb.Append(WorksheetXml.StartSheetView()); + return info; + } - // Write panes - sb.Append(GetPanes()); + private string GetSheetViews() + { + // exit early if no style to write + if (_configuration is { FreezeRowCount: <= 0, FreezeColumnCount: <= 0 }) + return string.Empty; - // end sheetViews - sb.Append(WorksheetXml.EndSheetView); - sb.Append(WorksheetXml.EndSheetViews); + var sb = new StringBuilder(); - return sb.ToString(); - } + // start sheetViews + sb.Append(WorksheetXml.StartSheetViews); + sb.Append(WorksheetXml.StartSheetView()); - private string GetPanes() - { - var sb = new StringBuilder(); + // Write panes + sb.Append(GetPanes()); - string activePane; - switch (_configuration.FreezeColumnCount > 0) - { - case true when _configuration.FreezeRowCount > 0: - activePane = "bottomRight"; - break; - case true: - activePane = "topRight"; - break; - default: - activePane = "bottomLeft"; - break; - } - sb.Append( - WorksheetXml.StartPane( - xSplit: _configuration.FreezeColumnCount > 0 ? _configuration.FreezeColumnCount : (int?)null, - ySplit: _configuration.FreezeRowCount > 0 ? _configuration.FreezeRowCount : (int?)null, - topLeftCell: ExcelOpenXmlUtils.ConvertXyToCell( - _configuration.FreezeColumnCount + 1, - _configuration.FreezeRowCount + 1 - ), - activePane: activePane, - state: "frozen" - ) - ); - - // write pane selections - if (_configuration.FreezeColumnCount > 0 && _configuration.FreezeRowCount > 0) - { - // freeze row and column - /* - - - - */ - var cellTR = ExcelOpenXmlUtils.ConvertXyToCell(_configuration.FreezeColumnCount + 1, 1); - sb.Append(WorksheetXml.PaneSelection("topRight", cellTR, cellTR)); - - var cellBL = ExcelOpenXmlUtils.ConvertXyToCell(1, _configuration.FreezeRowCount + 1); - sb.Append(WorksheetXml.PaneSelection("bottomLeft", cellBL, cellBL)); - - var cellBR = ExcelOpenXmlUtils.ConvertXyToCell(_configuration.FreezeColumnCount + 1, _configuration.FreezeRowCount + 1); - sb.Append(WorksheetXml.PaneSelection("bottomRight", cellBR, cellBR)); - } - else if (_configuration.FreezeColumnCount > 0) - { - // freeze column - /* - - */ - var cellTR = ExcelOpenXmlUtils.ConvertXyToCell(_configuration.FreezeColumnCount, 1); - sb.Append(WorksheetXml.PaneSelection("topRight", cellTR, cellTR)); + // end sheetViews + sb.Append(WorksheetXml.EndSheetView); + sb.Append(WorksheetXml.EndSheetViews); - } - else - { - // freeze row - /* - - */ - sb.Append(WorksheetXml.PaneSelection("bottomLeft", null, null)); + return sb.ToString(); + } - } + private string GetPanes() + { + var sb = new StringBuilder(); - return sb.ToString(); + string activePane = (_configuration.FreezeColumnCount > 0) switch + { + true when _configuration.FreezeRowCount > 0 => "bottomRight", + true => "topRight", + _ => "bottomLeft" + }; + + sb.Append( + WorksheetXml.StartPane( + xSplit: _configuration.FreezeColumnCount > 0 ? _configuration.FreezeColumnCount : null, + ySplit: _configuration.FreezeRowCount > 0 ? _configuration.FreezeRowCount : null, + topLeftCell: ExcelOpenXmlUtils.ConvertXYToCell( + _configuration.FreezeColumnCount + 1, + _configuration.FreezeRowCount + 1 + ), + activePane: activePane, + state: "frozen" + ) + ); + + // write pane selections + if (_configuration is { FreezeColumnCount: > 0, FreezeRowCount: > 0 }) + { + // freeze row and column + /* + + + + */ + var cellTr = ExcelOpenXmlUtils.ConvertXYToCell(_configuration.FreezeColumnCount + 1, 1); + sb.Append(WorksheetXml.PaneSelection("topRight", cellTr, cellTr)); + + var cellBl = ExcelOpenXmlUtils.ConvertXYToCell(1, _configuration.FreezeRowCount + 1); + sb.Append(WorksheetXml.PaneSelection("bottomLeft", cellBl, cellBl)); + + var cellBr = ExcelOpenXmlUtils.ConvertXYToCell(_configuration.FreezeColumnCount + 1, _configuration.FreezeRowCount + 1); + sb.Append(WorksheetXml.PaneSelection("bottomRight", cellBr, cellBr)); } + else if (_configuration.FreezeColumnCount > 0) + { + // freeze column + /* + + */ + var cellTr = ExcelOpenXmlUtils.ConvertXYToCell(_configuration.FreezeColumnCount, 1); + sb.Append(WorksheetXml.PaneSelection("topRight", cellTr, cellTr)); - private Tuple GetCellValue(int rowIndex, int cellIndex, object value, ExcelColumnInfo columnInfo, bool valueIsNull) + } + else { - if (valueIsNull) - return Tuple.Create("2", "str", string.Empty); + // freeze row + /* + + */ + sb.Append(WorksheetXml.PaneSelection("bottomLeft", null, null)); - if (value is string str) - return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXML(str)); + } - var type = GetValueType(value, columnInfo); + return sb.ToString(); + } - if (columnInfo?.ExcelFormat != null && columnInfo?.ExcelFormatId == -1 && value is IFormattable formattableValue) - { - var formattedStr = formattableValue.ToString(columnInfo.ExcelFormat, _configuration.Culture); - return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXML(formattedStr)); - } + private Tuple GetCellValue(int rowIndex, int cellIndex, object value, ExcelColumnInfo? columnInfo, bool valueIsNull) + { + if (valueIsNull) + return Tuple.Create("2", "str", string.Empty); - if (type == typeof(DateTime)) - return GetDateTimeValue((DateTime)value, columnInfo); + if (value is string str) + return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXml(str)); -#if NET6_0_OR_GREATER - if (type == typeof(DateOnly)) - return GetDateTimeValue(((DateOnly)value).ToDateTime(new TimeOnly()), columnInfo); -#endif - if (type.IsEnum) - { - var description = CustomPropertyHelper.DescriptionAttr(type, value); - return Tuple.Create("2", "str", description ?? value.ToString()); - } + var type = GetValueType(value, columnInfo); - if (TypeHelper.IsNumericType(type)) - { - var cellValue = GetNumericValue(value, type); + if (columnInfo is { ExcelFormat: not null, ExcelFormatId: -1 } && value is IFormattable formattableValue) + { + var formattedStr = formattableValue.ToString(columnInfo.ExcelFormat, _configuration.Culture); + return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXml(formattedStr)); + } - if (columnInfo == null || columnInfo.ExcelFormat == null) - { - var dataType = _configuration.Culture == CultureInfo.InvariantCulture ? "n" : "str"; - return Tuple.Create("2", dataType, cellValue); - } + if (type == typeof(DateTime)) + return GetDateTimeValue((DateTime)value, columnInfo); - return Tuple.Create(columnInfo.ExcelFormatId.ToString(), (string)null, cellValue); - } +#if NET6_0_OR_GREATER + if (type == typeof(DateOnly)) + return GetDateTimeValue(((DateOnly)value).ToDateTime(new TimeOnly()), columnInfo); +#endif + if (type.IsEnum) + { + var description = CustomPropertyHelper.DescriptionAttr(type, value); + return Tuple.Create("2", "str", description ?? value.ToString()); + } - if (type == typeof(bool)) - return Tuple.Create("2", "b", (bool)value ? "1" : "0"); + if (TypeHelper.IsNumericType(type)) + { + var cellValue = GetNumericValue(value, type); - if (type == typeof(byte[]) && _configuration.EnableConvertByteArray) + if (columnInfo?.ExcelFormat is null) { - var base64 = GetFileValue(rowIndex, cellIndex, value); - if (_configuration.EnableWriteFilePath) - { - return Tuple.Create("4", "str", ExcelOpenXmlUtils.EncodeXML(base64)); - } - return Tuple.Create("4", "str", ""); + var dataType = _configuration.Culture == CultureInfo.InvariantCulture ? "n" : "str"; + return Tuple.Create("2", dataType, cellValue); } - return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXML(value.ToString())); + return Tuple.Create(columnInfo.ExcelFormatId.ToString(), (string?)null, cellValue); } - private static Type GetValueType(object value, ExcelColumnInfo columnInfo) + if (type == typeof(bool)) + return Tuple.Create("2", "b", (bool)value ? "1" : "0"); + + if (type == typeof(byte[]) && _configuration.EnableConvertByteArray) { - Type type; - if (columnInfo == null || columnInfo.Key != null) - { - // TODO: need to optimize - // Dictionary need to check type every time, so it's slow.. - type = value.GetType(); - type = Nullable.GetUnderlyingType(type) ?? type; - } - else + var base64 = GetFileValue(rowIndex, cellIndex, value); + if (_configuration.EnableWriteFilePath) { - type = columnInfo.ExcludeNullableType; //sometime it doesn't need to re-get type like prop + return Tuple.Create("4", "str", ExcelOpenXmlUtils.EncodeXml(base64)); } - - return type; + return Tuple.Create("4", "str", ""); } - private string GetNumericValue(object value, Type type) + return Tuple.Create("2", "str", ExcelOpenXmlUtils.EncodeXml(value.ToString())); + } + + private static Type? GetValueType(object value, ExcelColumnInfo? columnInfo) + { + Type type; + if (columnInfo is not { Key: null }) { - if (type.IsAssignableFrom(typeof(decimal))) - return ((decimal)value).ToString(_configuration.Culture); + // TODO: need to optimize + // Dictionary need to check type every time, so it's slow.. + type = value.GetType(); + type = Nullable.GetUnderlyingType(type) ?? type; + } + else + { + type = columnInfo.ExcludeNullableType; //sometime it doesn't need to re-get type like prop + } - if (type.IsAssignableFrom(typeof(int))) - return ((int)value).ToString(_configuration.Culture); + return type; + } - if (type.IsAssignableFrom(typeof(double))) - return ((double)value).ToString(_configuration.Culture); + private string GetNumericValue(object value, Type type) + { + if (type.IsAssignableFrom(typeof(decimal))) + return ((decimal)value).ToString(_configuration.Culture); - if (type.IsAssignableFrom(typeof(long))) - return ((long)value).ToString(_configuration.Culture); + if (type.IsAssignableFrom(typeof(int))) + return ((int)value).ToString(_configuration.Culture); - if (type.IsAssignableFrom(typeof(uint))) - return ((uint)value).ToString(_configuration.Culture); + if (type.IsAssignableFrom(typeof(double))) + return ((double)value).ToString(_configuration.Culture); - if (type.IsAssignableFrom(typeof(ushort))) - return ((ushort)value).ToString(_configuration.Culture); + if (type.IsAssignableFrom(typeof(long))) + return ((long)value).ToString(_configuration.Culture); - if (type.IsAssignableFrom(typeof(ulong))) - return ((ulong)value).ToString(_configuration.Culture); + if (type.IsAssignableFrom(typeof(uint))) + return ((uint)value).ToString(_configuration.Culture); - if (type.IsAssignableFrom(typeof(short))) - return ((short)value).ToString(_configuration.Culture); + if (type.IsAssignableFrom(typeof(ushort))) + return ((ushort)value).ToString(_configuration.Culture); - if (type.IsAssignableFrom(typeof(float))) - return ((float)value).ToString(_configuration.Culture); + if (type.IsAssignableFrom(typeof(ulong))) + return ((ulong)value).ToString(_configuration.Culture); - return (decimal.Parse(value.ToString())).ToString(_configuration.Culture); - } + if (type.IsAssignableFrom(typeof(short))) + return ((short)value).ToString(_configuration.Culture); - private string GetFileValue(int rowIndex, int cellIndex, object value) - { - var bytes = (byte[])value; - - // TODO: Setting configuration because it might have high cost? - var format = GetImageFormat(bytes); - //it can't insert to zip first to avoid cache image to memory - //because sheet xml is opening.. https://github.com/mini-software/MiniExcel/issues/304#issuecomment-1017031691 - //int rowIndex, int cellIndex - var file = new FileDto() - { - Byte = bytes, - RowIndex = rowIndex, - CellIndex = cellIndex, - SheetId = _currentSheetIndex - }; + if (type.IsAssignableFrom(typeof(float))) + return ((float)value).ToString(_configuration.Culture); - if (format != ImageFormat.unknown) - { - file.Extension = format.ToString(); - file.IsImage = true; - } - else - { - file.Extension = "bin"; - } + return (decimal.Parse(value.ToString())).ToString(_configuration.Culture); + } - _files.Add(file); + private string GetFileValue(int rowIndex, int cellIndex, object value) + { + var bytes = (byte[])value; + + // TODO: Setting configuration because it might have high cost? + var format = GetImageFormat(bytes); + //it can't insert to zip first to avoid cache image to memory + //because sheet xml is opening.. https://github.com/mini-software/MiniExcel/issues/304#issuecomment-1017031691 + //int rowIndex, int cellIndex + var file = new FileDto() + { + Byte = bytes, + RowIndex = rowIndex, + CellIndex = cellIndex, + SheetId = _currentSheetIndex + }; - //TODO:Convert to base64 - var base64 = $"@@@fileid@@@,{file.Path}"; - return base64; + if (format != ImageFormat.Unknown) + { + file.Extension = format.ToString(); + file.IsImage = true; } - - private Tuple GetDateTimeValue(DateTime value, ExcelColumnInfo columnInfo) + else { - string cellValue = null; - if (!ReferenceEquals(_configuration.Culture, CultureInfo.InvariantCulture)) - { - cellValue = value.ToString(_configuration.Culture); - return Tuple.Create("2", "str", cellValue); - } + file.Extension = "bin"; + } - var oaDate = CorrectDateTimeValue(value); - cellValue = oaDate.ToString(CultureInfo.InvariantCulture); - var format = columnInfo?.ExcelFormat != null ? columnInfo.ExcelFormatId.ToString() : "3"; + _files.Add(file); - return Tuple.Create(format, (string)null, cellValue); - } + //TODO:Convert to base64 + var base64 = $"@@@fileid@@@,{file.Path}"; + return base64; + } - private static double CorrectDateTimeValue(DateTime value) + private Tuple GetDateTimeValue(DateTime value, ExcelColumnInfo columnInfo) + { + string? cellValue; + if (!ReferenceEquals(_configuration.Culture, CultureInfo.InvariantCulture)) { - var oaDate = value.ToOADate(); + cellValue = value.ToString(_configuration.Culture); + return Tuple.Create("2", (string?)"str", cellValue); + } - // Excel says 1900 was a leap year :( Replicate an incorrect behavior thanks - // to Lotus 1-2-3 decision from 1983... - // https://github.com/ClosedXML/ClosedXML/blob/develop/ClosedXML/Extensions/DateTimeExtensions.cs#L45 - const int nonExistent1900Feb29SerialDate = 60; - if (oaDate <= nonExistent1900Feb29SerialDate) - { - oaDate -= 1; - } + var oaDate = CorrectDateTimeValue(value); + cellValue = oaDate.ToString(CultureInfo.InvariantCulture); + var format = columnInfo?.ExcelFormat is not null ? columnInfo.ExcelFormatId.ToString() : "3"; - return oaDate; - } + return Tuple.Create(format, (string?)null, cellValue); + } - private static string GetDimensionRef(int maxRowIndex, int maxColumnIndex) + private static double CorrectDateTimeValue(DateTime value) + { + var oaDate = value.ToOADate(); + + // Excel says 1900 was a leap year :( Replicate an incorrect behavior thanks + // to Lotus 1-2-3 decision from 1983... + // https://github.com/ClosedXML/ClosedXML/blob/develop/ClosedXML/Extensions/DateTimeExtensions.cs#L45 + const int nonExistent1900Feb29SerialDate = 60; + if (oaDate <= nonExistent1900Feb29SerialDate) { - string dimensionRef; - if (maxRowIndex == 0 && maxColumnIndex == 0) - dimensionRef = "A1"; - else if (maxRowIndex <= 1 && maxColumnIndex == 0) - dimensionRef = "A1"; - else if (maxColumnIndex <= 1) - dimensionRef = $"A1:A{maxRowIndex}"; - else if (maxRowIndex == 0) - dimensionRef = $"A1:{ColumnHelper.GetAlphabetColumnName(maxColumnIndex - 1)}1"; - else - dimensionRef = $"A1:{ColumnHelper.GetAlphabetColumnName(maxColumnIndex - 1)}{maxRowIndex}"; - return dimensionRef; + oaDate -= 1; } - private string GetDrawingRelationshipXml(int sheetIndex) - { - var drawing = new StringBuilder(); - foreach (var image in _files.Where(w => w.IsImage && w.SheetId == sheetIndex + 1)) - { - drawing.AppendLine(ExcelXml.ImageRelationship(image)); - } + return oaDate; + } - return drawing.ToString(); - } + private static string GetDimensionRef(int maxRowIndex, int maxColumnIndex) + { + string dimensionRef; + if (maxRowIndex == 0 && maxColumnIndex == 0) + dimensionRef = "A1"; + else if (maxRowIndex <= 1 && maxColumnIndex == 0) + dimensionRef = "A1"; + else if (maxColumnIndex <= 1) + dimensionRef = $"A1:A{maxRowIndex}"; + else if (maxRowIndex == 0) + dimensionRef = $"A1:{ColumnHelper.GetAlphabetColumnName(maxColumnIndex - 1)}1"; + else + dimensionRef = $"A1:{ColumnHelper.GetAlphabetColumnName(maxColumnIndex - 1)}{maxRowIndex}"; + return dimensionRef; + } - private string GetDrawingXml(int sheetIndex) + private string GetDrawingRelationshipXml(int sheetIndex) + { + var drawing = new StringBuilder(); + foreach (var image in _files.Where(w => w.IsImage && w.SheetId == sheetIndex + 1)) { - var drawing = new StringBuilder(); + drawing.AppendLine(ExcelXml.ImageRelationship(image)); + } - for (int fileIndex = 0; fileIndex < _files.Count; fileIndex++) - { - var file = _files[fileIndex]; - if (file.IsImage && file.SheetId == sheetIndex + 1) - { - drawing.Append(ExcelXml.DrawingXml(file, fileIndex)); - } - } + return drawing.ToString(); + } - return drawing.ToString(); - } + private string GetDrawingXml(int sheetIndex) + { + var drawing = new StringBuilder(); - private void GenerateWorkBookXmls( - out StringBuilder workbookXml, - out StringBuilder workbookRelsXml, - out Dictionary sheetsRelsXml) + for (int fileIndex = 0; fileIndex < _files.Count; fileIndex++) { - workbookXml = new StringBuilder(); - workbookRelsXml = new StringBuilder(); - sheetsRelsXml = new Dictionary(); - var sheetId = 0; - foreach (var sheetDto in _sheets) + var file = _files[fileIndex]; + if (file.IsImage && file.SheetId == sheetIndex + 1) { - sheetId++; - workbookXml.AppendLine(ExcelXml.Sheet(sheetDto, sheetId)); - - workbookRelsXml.AppendLine(ExcelXml.WorksheetRelationship(sheetDto)); - - //TODO: support multiple drawing - //TODO: ../drawings/drawing1.xml or /xl/drawings/drawing1.xml - sheetsRelsXml.Add(sheetDto.SheetIdx, ExcelXml.DrawingRelationship(sheetId)); + drawing.Append(ExcelXml.DrawingXml(file, fileIndex)); } } - private string GetContentTypesXml() + return drawing.ToString(); + } + + private void GenerateWorkBookXmls( + out StringBuilder workbookXml, + out StringBuilder workbookRelsXml, + out Dictionary sheetsRelsXml) + { + workbookXml = new StringBuilder(); + workbookRelsXml = new StringBuilder(); + sheetsRelsXml = new Dictionary(); + var sheetId = 0; + foreach (var sheetDto in _sheets) { - var sb = new StringBuilder(ExcelXml.StartTypes); - foreach (var p in _zipDictionary) - { - sb.Append(ExcelXml.ContentType(p.Value.ContentType, p.Key)); - } + sheetId++; + workbookXml.AppendLine(ExcelXml.Sheet(sheetDto, sheetId)); + + workbookRelsXml.AppendLine(ExcelXml.WorksheetRelationship(sheetDto)); - sb.Append(ExcelXml.EndTypes); - return sb.ToString(); + //TODO: support multiple drawing + //TODO: ../drawings/drawing1.xml or /xl/drawings/drawing1.xml + sheetsRelsXml.Add(sheetDto.SheetIdx, ExcelXml.DrawingRelationship(sheetId)); } + } - private string GetCellXfId(string styleIndex) + private string GetContentTypesXml() + { + var sb = new StringBuilder(ExcelXml.StartTypes); + foreach (var p in _zipDictionary) { - return _cellXfIdMap.TryGetValue(styleIndex, out var cellXfId) ? cellXfId : styleIndex; + sb.Append(ExcelXml.ContentType(p.Value.ContentType, p.Key)); } + + sb.Append(ExcelXml.EndTypes); + return sb.ToString(); + } + + private string GetCellXfId(string styleIndex) + { + return _cellXfIdMap.TryGetValue(styleIndex, out var cellXfId) ? cellXfId : styleIndex; } -} +} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs index 69fc611d..88a69802 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlSheetWriter.cs @@ -1,11 +1,4 @@ -using MiniExcelLibs.Attributes; -using MiniExcelLibs.OpenXml.Constants; -using MiniExcelLibs.OpenXml.Models; -using MiniExcelLibs.OpenXml.Styles; -using MiniExcelLibs.Utils; -using MiniExcelLibs.WriteAdapter; -using MiniExcelLibs.Zip; -using System; +using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; @@ -14,659 +7,671 @@ using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; +using MiniExcelLibs.OpenXml.Constants; +using MiniExcelLibs.OpenXml.Models; +using MiniExcelLibs.OpenXml.Styles; +using MiniExcelLibs.Utils; +using MiniExcelLibs.WriteAdapter; +using MiniExcelLibs.Zip; +using Zomp.SyncMethodGenerator; + +namespace MiniExcelLibs.OpenXml; -namespace MiniExcelLibs.OpenXml +internal partial class ExcelOpenXmlSheetWriter : IExcelWriter { - internal partial class ExcelOpenXmlSheetWriter : IExcelWriter + private static readonly UTF8Encoding Utf8WithBom = new(true); + + private readonly MiniExcelZipArchive _archive; + private readonly OpenXmlConfiguration _configuration; + private readonly Stream _stream; + private readonly List _sheets = []; + private readonly List _files = []; + + private readonly string? _defaultSheetName; + private readonly bool _printHeader; + private readonly object? _value; + + private int _currentSheetIndex = 0; + + public ExcelOpenXmlSheetWriter(Stream stream, object? value, string? sheetName, IMiniExcelConfiguration? configuration, bool printHeader) { - private readonly MiniExcelZipArchive _archive; - private static readonly UTF8Encoding _utf8WithBom = new UTF8Encoding(true); - private readonly OpenXmlConfiguration _configuration; - private readonly Stream _stream; - private readonly bool _printHeader; - private readonly object _value; - private readonly string _defaultSheetName; - private readonly List _sheets = new List(); - private readonly List _files = new List(); - private int _currentSheetIndex = 0; - - public ExcelOpenXmlSheetWriter(Stream stream, object value, string sheetName, IConfiguration configuration, bool printHeader) - { - _stream = stream; - // Why ZipArchiveMode.Update not ZipArchiveMode.Create? - // R : Mode create - ZipArchiveEntry does not support seeking.' - _configuration = configuration as OpenXmlConfiguration ?? OpenXmlConfiguration.DefaultConfig; - if (_configuration.EnableAutoWidth && !_configuration.FastMode) - throw new InvalidOperationException("Auto width requires fast mode to be enabled"); - - var archiveMode = _configuration.FastMode ? ZipArchiveMode.Update : ZipArchiveMode.Create; - _archive = new MiniExcelZipArchive(_stream, archiveMode, true, _utf8WithBom); - - _value = value; - _printHeader = printHeader; - _defaultSheetName = sheetName; - } + _stream = stream; - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public async Task SaveAsAsync(CancellationToken cancellationToken = default) - { - try - { - cancellationToken.ThrowIfCancellationRequested(); + // Why ZipArchiveMode.Update not ZipArchiveMode.Create? + // R : Mode create - ZipArchiveEntry does not support seeking.' + _configuration = configuration as OpenXmlConfiguration ?? OpenXmlConfiguration.DefaultConfig; + if (_configuration is { EnableAutoWidth: true, FastMode: false }) + throw new InvalidOperationException("Auto width requires fast mode to be enabled"); - await GenerateDefaultOpenXmlAsync(cancellationToken).ConfigureAwait(false); + var archiveMode = _configuration.FastMode ? ZipArchiveMode.Update : ZipArchiveMode.Create; + _archive = new MiniExcelZipArchive(_stream, archiveMode, true, Utf8WithBom); - var sheets = GetSheets(); - var rowsWritten = new List(); + _value = value; + _printHeader = printHeader; + _defaultSheetName = sheetName; + } - foreach (var sheet in sheets) - { - cancellationToken.ThrowIfCancellationRequested(); + [CreateSyncVersion] + public async Task SaveAsAsync(CancellationToken cancellationToken = default) + { + try + { + cancellationToken.ThrowIfCancellationRequested(); - _sheets.Add(sheet.Item1); //TODO:remove - _currentSheetIndex = sheet.Item1.SheetIdx; - var rows = await CreateSheetXmlAsync(sheet.Item2, sheet.Item1.Path, cancellationToken).ConfigureAwait(false); - rowsWritten.Add(rows); - } + await GenerateDefaultOpenXmlAsync(cancellationToken).ConfigureAwait(false); - await GenerateEndXmlAsync(cancellationToken).ConfigureAwait(false); - return rowsWritten.ToArray(); - } - finally - { - _archive.Dispose(); - } - } + var sheets = GetSheets(); + var rowsWritten = new List(); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default) - { - try + foreach (var sheet in sheets) { cancellationToken.ThrowIfCancellationRequested(); - if (!_configuration.FastMode) - throw new InvalidOperationException("Insert requires fast mode to be enabled"); - - cancellationToken.ThrowIfCancellationRequested(); - - using var reader = await ExcelOpenXmlSheetReader.CreateAsync(_stream, _configuration, cancellationToken: cancellationToken).ConfigureAwait(false); - var sheetRecords = (await reader.GetWorkbookRelsAsync(_archive.Entries, cancellationToken).ConfigureAwait(false)).ToArray(); - foreach (var sheetRecord in sheetRecords.OrderBy(o => o.Id)) - { - cancellationToken.ThrowIfCancellationRequested(); - _sheets.Add(new SheetDto { Name = sheetRecord.Name, SheetIdx = (int)sheetRecord.Id, State = sheetRecord.State }); - } - var existSheetDto = _sheets.SingleOrDefault(s => s.Name == _defaultSheetName); - if (existSheetDto != null && !overwriteSheet) - throw new Exception($"Sheet “{_defaultSheetName}” already exist"); - - await GenerateStylesXmlAsync(cancellationToken).ConfigureAwait(false);//GenerateStylesXml必须在校验overwriteSheet之后,避免不必要的样式更改 + _sheets.Add(sheet.Item1); //TODO:remove + _currentSheetIndex = sheet.Item1.SheetIdx; + var rows = await CreateSheetXmlAsync(sheet.Item2, sheet.Item1.Path, cancellationToken).ConfigureAwait(false); + rowsWritten.Add(rows); + } - int rowsWritten; - if (existSheetDto == null) - { - _currentSheetIndex = (int)sheetRecords.Max(m => m.Id) + 1; - var insertSheetInfo = GetSheetInfos(_defaultSheetName); - var insertSheetDto = insertSheetInfo.ToDto(_currentSheetIndex); - _sheets.Add(insertSheetDto); - rowsWritten = await CreateSheetXmlAsync(_value, insertSheetDto.Path, cancellationToken).ConfigureAwait(false); - } - else - { - _currentSheetIndex = existSheetDto.SheetIdx; - _archive.Entries.Single(s => s.FullName == existSheetDto.Path).Delete(); - rowsWritten = await CreateSheetXmlAsync(_value, existSheetDto.Path, cancellationToken).ConfigureAwait(false); - } + await GenerateEndXmlAsync(cancellationToken).ConfigureAwait(false); + return rowsWritten.ToArray(); + } + finally + { + _archive.Dispose(); + } + } - await AddFilesToZipAsync(cancellationToken).ConfigureAwait(false); + [CreateSyncVersion] + public async Task InsertAsync(bool overwriteSheet = false, CancellationToken cancellationToken = default) + { + try + { + cancellationToken.ThrowIfCancellationRequested(); - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.DrawingRels(_currentSheetIndex - 1))?.Delete(); - await GenerateDrawinRelXmlAsync(_currentSheetIndex - 1, cancellationToken).ConfigureAwait(false); + if (!_configuration.FastMode) + throw new InvalidOperationException("Insert requires fast mode to be enabled"); - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Drawing(_currentSheetIndex - 1))?.Delete(); - await GenerateDrawingXmlAsync(_currentSheetIndex - 1, cancellationToken).ConfigureAwait(false); + cancellationToken.ThrowIfCancellationRequested(); - GenerateWorkBookXmls(out StringBuilder workbookXml, out StringBuilder workbookRelsXml, out Dictionary sheetsRelsXml); - foreach (var sheetRelsXml in sheetsRelsXml) + using var reader = await ExcelOpenXmlSheetReader.CreateAsync(_stream, _configuration, cancellationToken: cancellationToken).ConfigureAwait(false); + var sheetRecords = (await reader.GetWorkbookRelsAsync(_archive.Entries, cancellationToken).ConfigureAwait(false)).ToArray(); + foreach (var sheetRecord in sheetRecords.OrderBy(o => o.Id)) + { + cancellationToken.ThrowIfCancellationRequested(); + _sheets.Add(new SheetDto { - var sheetRelsXmlPath = ExcelFileNames.SheetRels(sheetRelsXml.Key); - _archive.Entries.SingleOrDefault(s => s.FullName == sheetRelsXmlPath)?.Delete(); - await CreateZipEntryAsync(sheetRelsXmlPath, null, ExcelXml.DefaultSheetRelXml.Replace("{{format}}", sheetRelsXml.Value), cancellationToken).ConfigureAwait(false); - } - - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Workbook)?.Delete(); - await CreateZipEntryAsync(ExcelFileNames.Workbook, ExcelContentTypes.Workbook, ExcelXml.DefaultWorkbookXml.Replace("{{sheets}}", workbookXml.ToString()), cancellationToken).ConfigureAwait(false); - - _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.WorkbookRels)?.Delete(); - await CreateZipEntryAsync(ExcelFileNames.WorkbookRels, null, ExcelXml.DefaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString()), cancellationToken).ConfigureAwait(false); + Name = sheetRecord.Name, + SheetIdx = (int)sheetRecord.Id, + State = sheetRecord.State + }); + } + var existSheetDto = _sheets.SingleOrDefault(s => s.Name == _defaultSheetName); + if (existSheetDto is not null && !overwriteSheet) + throw new Exception($"Sheet “{_defaultSheetName}” already exist"); - await InsertContentTypesXmlAsync(cancellationToken).ConfigureAwait(false); + await GenerateStylesXmlAsync(cancellationToken).ConfigureAwait(false);//GenerateStylesXml必须在校验overwriteSheet之后,避免不必要的样式更改 - return rowsWritten; + int rowsWritten; + if (existSheetDto is null) + { + _currentSheetIndex = (int)sheetRecords.Max(m => m.Id) + 1; + var insertSheetInfo = GetSheetInfos(_defaultSheetName); + var insertSheetDto = insertSheetInfo.ToDto(_currentSheetIndex); + _sheets.Add(insertSheetDto); + rowsWritten = await CreateSheetXmlAsync(_value, insertSheetDto.Path, cancellationToken).ConfigureAwait(false); } - finally + else { - _archive.Dispose(); + _currentSheetIndex = existSheetDto.SheetIdx; + _archive.Entries.Single(s => s.FullName == existSheetDto.Path).Delete(); + rowsWritten = await CreateSheetXmlAsync(_value, existSheetDto.Path, cancellationToken).ConfigureAwait(false); } - } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - internal async Task GenerateDefaultOpenXmlAsync(CancellationToken cancellationToken) - { - await CreateZipEntryAsync(ExcelFileNames.Rels, ExcelContentTypes.Relationships, ExcelXml.DefaultRels, cancellationToken).ConfigureAwait(false); - await CreateZipEntryAsync(ExcelFileNames.SharedStrings, ExcelContentTypes.SharedStrings, ExcelXml.DefaultSharedString, cancellationToken).ConfigureAwait(false); - await GenerateStylesXmlAsync(cancellationToken).ConfigureAwait(false); - } + await AddFilesToZipAsync(cancellationToken).ConfigureAwait(false); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task CreateSheetXmlAsync(object values, string sheetPath, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.DrawingRels(_currentSheetIndex - 1))?.Delete(); + await GenerateDrawinRelXmlAsync(_currentSheetIndex - 1, cancellationToken).ConfigureAwait(false); - var entry = _archive.CreateEntry(sheetPath, CompressionLevel.Fastest); - var rowsWritten = 0; + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Drawing(_currentSheetIndex - 1))?.Delete(); + await GenerateDrawingXmlAsync(_currentSheetIndex - 1, cancellationToken).ConfigureAwait(false); - using (var zipStream = entry.Open()) - using (var writer = new MiniExcelStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize)) + GenerateWorkBookXmls(out StringBuilder workbookXml, out StringBuilder workbookRelsXml, out Dictionary sheetsRelsXml); + foreach (var sheetRelsXml in sheetsRelsXml) { - if (values == null) - { - await WriteEmptySheetAsync(writer).ConfigureAwait(false); - } - else - { - rowsWritten = await WriteValuesAsync(writer, values, cancellationToken).ConfigureAwait(false); - } + var sheetRelsXmlPath = ExcelFileNames.SheetRels(sheetRelsXml.Key); + _archive.Entries.SingleOrDefault(s => s.FullName == sheetRelsXmlPath)?.Delete(); + await CreateZipEntryAsync(sheetRelsXmlPath, null, ExcelXml.DefaultSheetRelXml.Replace("{{format}}", sheetRelsXml.Value), cancellationToken).ConfigureAwait(false); } - _zipDictionary.Add(sheetPath, new ZipPackageInfo(entry, ExcelContentTypes.Worksheet)); + + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.Workbook)?.Delete(); + await CreateZipEntryAsync(ExcelFileNames.Workbook, ExcelContentTypes.Workbook, ExcelXml.DefaultWorkbookXml.Replace("{{sheets}}", workbookXml.ToString()), cancellationToken).ConfigureAwait(false); + + _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.WorkbookRels)?.Delete(); + await CreateZipEntryAsync(ExcelFileNames.WorkbookRels, null, ExcelXml.DefaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString()), cancellationToken).ConfigureAwait(false); + + await InsertContentTypesXmlAsync(cancellationToken).ConfigureAwait(false); + return rowsWritten; } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private static async Task WriteEmptySheetAsync(MiniExcelStreamWriter writer) + finally { - await writer.WriteAsync(ExcelXml.EmptySheetXml).ConfigureAwait(false); + _archive.Dispose(); } + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private static async Task WriteDimensionPlaceholderAsync(MiniExcelStreamWriter writer) - { - var dimensionPlaceholderPostition = await writer.WriteAndFlushAsync(WorksheetXml.StartDimension).ConfigureAwait(false); - await writer.WriteAsync(WorksheetXml.DimensionPlaceholder).ConfigureAwait(false); // end of code will be replaced - - return dimensionPlaceholderPostition; - } + [CreateSyncVersion] + internal async Task GenerateDefaultOpenXmlAsync(CancellationToken cancellationToken) + { + await CreateZipEntryAsync(ExcelFileNames.Rels, ExcelContentTypes.Relationships, ExcelXml.DefaultRels, cancellationToken).ConfigureAwait(false); + await CreateZipEntryAsync(ExcelFileNames.SharedStrings, ExcelContentTypes.SharedStrings, ExcelXml.DefaultSharedString, cancellationToken).ConfigureAwait(false); + await GenerateStylesXmlAsync(cancellationToken).ConfigureAwait(false); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private static async Task WriteDimensionAsync(MiniExcelStreamWriter writer, int maxRowIndex, int maxColumnIndex, long placeholderPosition) - { - // Flush and save position so that we can get back again. - var position = await writer.FlushAsync().ConfigureAwait(false); + [CreateSyncVersion] + private async Task CreateSheetXmlAsync(object? values, string sheetPath, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); - writer.SetPosition(placeholderPosition); - await writer.WriteAndFlushAsync($@"{GetDimensionRef(maxRowIndex, maxColumnIndex)}""").ConfigureAwait(false); + var entry = _archive.CreateEntry(sheetPath, CompressionLevel.Fastest); + var rowsWritten = 0; - writer.SetPosition(position); + using var zipStream = entry.Open(); + using var writer = new MiniExcelStreamWriter(zipStream, Utf8WithBom, _configuration.BufferSize); + + if (values is null) + { + await WriteEmptySheetAsync(writer).ConfigureAwait(false); } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task WriteValuesAsync(MiniExcelStreamWriter writer, object values, CancellationToken cancellationToken) + else { - cancellationToken.ThrowIfCancellationRequested(); + rowsWritten = await WriteValuesAsync(writer, values, cancellationToken).ConfigureAwait(false); + } - IMiniExcelWriteAdapter writeAdapter = null; - if (!MiniExcelWriteAdapterFactory.TryGetAsyncWriteAdapter(values, _configuration, out var asyncWriteAdapter)) - { - writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); - } + _zipDictionary.Add(sheetPath, new ZipPackageInfo(entry, ExcelContentTypes.Worksheet)); + return rowsWritten; + } - var count = 0; - var isKnownCount = writeAdapter != null && writeAdapter.TryGetKnownCount(out count); - List props; -#if SYNC_ONLY - props = writeAdapter?.GetColumns(); -#else - props = writeAdapter != null ? writeAdapter?.GetColumns() : await asyncWriteAdapter.GetColumnsAsync().ConfigureAwait(false); -#endif - if (props == null) - { - await WriteEmptySheetAsync(writer).ConfigureAwait(false); - return 0; - } - int maxRowIndex; - var maxColumnIndex = props.Count(x => x != null && !x.ExcelIgnore); + [CreateSyncVersion] + private static async Task WriteEmptySheetAsync(MiniExcelStreamWriter writer) + { + await writer.WriteAsync(ExcelXml.EmptySheetXml).ConfigureAwait(false); + } - await writer.WriteAsync(WorksheetXml.StartWorksheetWithRelationship, cancellationToken).ConfigureAwait(false); + [CreateSyncVersion] + private static async Task WriteDimensionPlaceholderAsync(MiniExcelStreamWriter writer) + { + var dimensionPlaceholderPostition = await writer.WriteAndFlushAsync(WorksheetXml.StartDimension).ConfigureAwait(false); + await writer.WriteAsync(WorksheetXml.DimensionPlaceholder).ConfigureAwait(false); // end of code will be replaced - long dimensionPlaceholderPostition = 0; + return dimensionPlaceholderPostition; + } - // We can write the dimensions directly if the row count is known - if (isKnownCount) - { - maxRowIndex = _printHeader ? count + 1 : count; - await writer.WriteAsync(WorksheetXml.Dimension(GetDimensionRef(maxRowIndex, props.Count)), cancellationToken).ConfigureAwait(false); - } - else if (_configuration.FastMode) - { - dimensionPlaceholderPostition = await WriteDimensionPlaceholderAsync(writer).ConfigureAwait(false); - } + [CreateSyncVersion] + private static async Task WriteDimensionAsync(MiniExcelStreamWriter writer, int maxRowIndex, int maxColumnIndex, long placeholderPosition) + { + // Flush and save position so that we can get back again. + var position = await writer.FlushAsync().ConfigureAwait(false); - //sheet view - await writer.WriteAsync(GetSheetViews(), cancellationToken).ConfigureAwait(false); + writer.SetPosition(placeholderPosition); + await writer.WriteAndFlushAsync($@"{GetDimensionRef(maxRowIndex, maxColumnIndex)}""").ConfigureAwait(false); - //cols:width - ExcelWidthCollection widths = null; - long columnWidthsPlaceholderPosition = 0; - if (_configuration.EnableAutoWidth) - { - columnWidthsPlaceholderPosition = await WriteColumnWidthPlaceholdersAsync(writer, maxColumnIndex, cancellationToken).ConfigureAwait(false); - widths = new ExcelWidthCollection(_configuration.MinWidth, _configuration.MaxWidth, props); - } - else - { - await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props), cancellationToken).ConfigureAwait(false); - } + writer.SetPosition(position); + } - //header - await writer.WriteAsync(WorksheetXml.StartSheetData, cancellationToken).ConfigureAwait(false); - var currentRowIndex = 0; - if (_printHeader) - { - await PrintHeaderAsync(writer, props, cancellationToken).ConfigureAwait(false); - currentRowIndex++; - } + [CreateSyncVersion] + private async Task WriteValuesAsync(MiniExcelStreamWriter writer, object values, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); - if (writeAdapter != null) - { - foreach (var row in writeAdapter.GetRows(props, cancellationToken)) - { - cancellationToken.ThrowIfCancellationRequested(); + IMiniExcelWriteAdapter? writeAdapter = null; + if (!MiniExcelWriteAdapterFactory.TryGetAsyncWriteAdapter(values, _configuration, out var asyncWriteAdapter)) + { + writeAdapter = MiniExcelWriteAdapterFactory.GetWriteAdapter(values, _configuration); + } - await writer.WriteAsync(WorksheetXml.StartRow(++currentRowIndex), cancellationToken).ConfigureAwait(false); - foreach (var cellValue in row) - { - cancellationToken.ThrowIfCancellationRequested(); - await WriteCellAsync(writer, currentRowIndex, cellValue.CellIndex, cellValue.Value, cellValue.Prop, widths).ConfigureAwait(false); - } - await writer.WriteAsync(WorksheetXml.EndRow, cancellationToken).ConfigureAwait(false); - } - } - else - { -#if !SYNC_ONLY - await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken).ConfigureAwait(false)) - { - cancellationToken.ThrowIfCancellationRequested(); - await writer.WriteAsync(WorksheetXml.StartRow(++currentRowIndex), cancellationToken).ConfigureAwait(false); - - await foreach (var cellValue in row.ConfigureAwait(false)) - { - cancellationToken.ThrowIfCancellationRequested(); - await WriteCellAsync(writer, currentRowIndex, cellValue.CellIndex, cellValue.Value, cellValue.Prop, widths).ConfigureAwait(false); - } - await writer.WriteAsync(WorksheetXml.EndRow, cancellationToken).ConfigureAwait(false); - } + var count = 0; + var isKnownCount = writeAdapter is not null && writeAdapter.TryGetKnownCount(out count); + + List props; +#if SYNC_ONLY + props = writeAdapter?.GetColumns(); +#else + props = writeAdapter is not null ? writeAdapter?.GetColumns() : await asyncWriteAdapter.GetColumnsAsync().ConfigureAwait(false); #endif - } - maxRowIndex = currentRowIndex; + + if (props is null) + { + await WriteEmptySheetAsync(writer).ConfigureAwait(false); + return 0; + } + + int maxRowIndex; + var maxColumnIndex = props.Count(x => x is { ExcelIgnore: false }); - await writer.WriteAsync(WorksheetXml.EndSheetData, cancellationToken).ConfigureAwait(false); + await writer.WriteAsync(WorksheetXml.StartWorksheetWithRelationship, cancellationToken).ConfigureAwait(false); - if (_configuration.AutoFilter) - { - await writer.WriteAsync(WorksheetXml.Autofilter(GetDimensionRef(maxRowIndex, maxColumnIndex)), cancellationToken).ConfigureAwait(false); - } + long dimensionPlaceholderPostition = 0; - await writer.WriteAsync(WorksheetXml.Drawing(_currentSheetIndex), cancellationToken).ConfigureAwait(false); - await writer.WriteAsync(WorksheetXml.EndWorksheet, cancellationToken).ConfigureAwait(false); - - if (_configuration.FastMode && dimensionPlaceholderPostition != 0) - { - await WriteDimensionAsync(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition).ConfigureAwait(false); - } - if (_configuration.EnableAutoWidth) - { - await OverwriteColumnWidthPlaceholdersAsync(writer, columnWidthsPlaceholderPosition, widths?.Columns, cancellationToken).ConfigureAwait(false); - } + // We can write the dimensions directly if the row count is known + if (isKnownCount) + { + maxRowIndex = _printHeader ? count + 1 : count; + await writer.WriteAsync(WorksheetXml.Dimension(GetDimensionRef(maxRowIndex, props.Count)), cancellationToken).ConfigureAwait(false); + } + else if (_configuration.FastMode) + { + dimensionPlaceholderPostition = await WriteDimensionPlaceholderAsync(writer).ConfigureAwait(false); + } - if (_printHeader) - maxRowIndex--; + //sheet view + await writer.WriteAsync(GetSheetViews(), cancellationToken).ConfigureAwait(false); - return maxRowIndex; + //cols:width + ExcelWidthCollection? widths = null; + long columnWidthsPlaceholderPosition = 0; + if (_configuration.EnableAutoWidth) + { + columnWidthsPlaceholderPosition = await WriteColumnWidthPlaceholdersAsync(writer, maxColumnIndex, cancellationToken).ConfigureAwait(false); + widths = new ExcelWidthCollection(_configuration.MinWidth, _configuration.MaxWidth, props); } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private static async Task WriteColumnWidthPlaceholdersAsync(MiniExcelStreamWriter writer, int count, CancellationToken cancellationToken = default) + else { - cancellationToken.ThrowIfCancellationRequested(); - - var placeholderPosition = await writer.FlushAsync(cancellationToken).ConfigureAwait(false); - await writer.WriteWhitespaceAsync(WorksheetXml.GetColumnPlaceholderLength(count)).ConfigureAwait(false); - return placeholderPosition; + await WriteColumnsWidthsAsync(writer, ExcelColumnWidth.FromProps(props), cancellationToken).ConfigureAwait(false); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private static async Task OverwriteColumnWidthPlaceholdersAsync(MiniExcelStreamWriter writer, long placeholderPosition, IEnumerable columnWidths, CancellationToken cancellationToken = default) + //header + await writer.WriteAsync(WorksheetXml.StartSheetData, cancellationToken).ConfigureAwait(false); + var currentRowIndex = 0; + if (_printHeader) { - cancellationToken.ThrowIfCancellationRequested(); - - var position = await writer.FlushAsync(cancellationToken).ConfigureAwait(false); - - writer.SetPosition(placeholderPosition); - await WriteColumnsWidthsAsync(writer, columnWidths, cancellationToken).ConfigureAwait(false); - - await writer.FlushAsync(cancellationToken).ConfigureAwait(false); - writer.SetPosition(position); + await PrintHeaderAsync(writer, props, cancellationToken).ConfigureAwait(false); + currentRowIndex++; } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private static async Task WriteColumnsWidthsAsync(MiniExcelStreamWriter writer, IEnumerable columnWidths, CancellationToken cancellationToken = default) + if (writeAdapter is not null) { - var hasWrittenStart = false; - foreach (var column in columnWidths) + foreach (var row in writeAdapter.GetRows(props, cancellationToken)) { cancellationToken.ThrowIfCancellationRequested(); - if (!hasWrittenStart) + await writer.WriteAsync(WorksheetXml.StartRow(++currentRowIndex), cancellationToken).ConfigureAwait(false); + foreach (var cellValue in row) { - await writer.WriteAsync(WorksheetXml.StartCols, cancellationToken).ConfigureAwait(false); - hasWrittenStart = true; + cancellationToken.ThrowIfCancellationRequested(); + await WriteCellAsync(writer, currentRowIndex, cellValue.CellIndex, cellValue.Value, cellValue.Prop, widths).ConfigureAwait(false); } - await writer.WriteAsync(WorksheetXml.Column(column.Index, column.Width), cancellationToken).ConfigureAwait(false); + await writer.WriteAsync(WorksheetXml.EndRow, cancellationToken).ConfigureAwait(false); } - - if (!hasWrittenStart) - return; - - await writer.WriteAsync(WorksheetXml.EndCols, cancellationToken).ConfigureAwait(false); } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task PrintHeaderAsync(MiniExcelStreamWriter writer, List props, CancellationToken cancellationToken = default) + else { - const int yIndex = 1; - await writer.WriteAsync(WorksheetXml.StartRow(yIndex), cancellationToken).ConfigureAwait(false); - - var xIndex = 1; - foreach (var p in props) +#if !SYNC_ONLY + await foreach (var row in asyncWriteAdapter.GetRowsAsync(props, cancellationToken).ConfigureAwait(false)) { - //reason : https://github.com/mini-software/MiniExcel/issues/142 - if (p != null) - { - if (p.ExcelIgnore) - continue; + cancellationToken.ThrowIfCancellationRequested(); + await writer.WriteAsync(WorksheetXml.StartRow(++currentRowIndex), cancellationToken).ConfigureAwait(false); - var r = ExcelOpenXmlUtils.ConvertXyToCell(xIndex, yIndex); - await WriteCellAsync(writer, r, columnName: p.ExcelColumnName).ConfigureAwait(false); + await foreach (var cellValue in row.ConfigureAwait(false).WithCancellation(cancellationToken)) + { + await WriteCellAsync(writer, currentRowIndex, cellValue.CellIndex, cellValue.Value, cellValue.Prop, widths).ConfigureAwait(false); } - xIndex++; + await writer.WriteAsync(WorksheetXml.EndRow, cancellationToken).ConfigureAwait(false); } - - await writer.WriteAsync(WorksheetXml.EndRow, cancellationToken).ConfigureAwait(false); +#endif } + maxRowIndex = currentRowIndex; + + await writer.WriteAsync(WorksheetXml.EndSheetData, cancellationToken).ConfigureAwait(false); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task WriteCellAsync(MiniExcelStreamWriter writer, string cellReference, string columnName) + if (_configuration.AutoFilter) { - await writer.WriteAsync(WorksheetXml.Cell(cellReference, "str", GetCellXfId("1"), ExcelOpenXmlUtils.EncodeXML(columnName))).ConfigureAwait(false); + await writer.WriteAsync(WorksheetXml.Autofilter(GetDimensionRef(maxRowIndex, maxColumnIndex)), cancellationToken).ConfigureAwait(false); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task WriteCellAsync(MiniExcelStreamWriter writer, int rowIndex, int cellIndex, object value, ExcelColumnInfo columnInfo, ExcelWidthCollection widthCollection) + await writer.WriteAsync(WorksheetXml.Drawing(_currentSheetIndex), cancellationToken).ConfigureAwait(false); + await writer.WriteAsync(WorksheetXml.EndWorksheet, cancellationToken).ConfigureAwait(false); + + if (_configuration.FastMode && dimensionPlaceholderPostition != 0) { - if (columnInfo?.CustomFormatter != null) - { - try - { - value = columnInfo.CustomFormatter(value); - } - catch - { - //ignored - } - } + await WriteDimensionAsync(writer, maxRowIndex, maxColumnIndex, dimensionPlaceholderPostition).ConfigureAwait(false); + } + if (_configuration.EnableAutoWidth) + { + await OverwriteColumnWidthPlaceholdersAsync(writer, columnWidthsPlaceholderPosition, widths?.Columns, cancellationToken).ConfigureAwait(false); + } - var columnReference = ExcelOpenXmlUtils.ConvertXyToCell(cellIndex, rowIndex); - var valueIsNull = value is null || - value is DBNull || - (_configuration.WriteEmptyStringAsNull && value is string vs && vs == string.Empty); + if (_printHeader) + maxRowIndex--; - if (_configuration.EnableWriteNullValueCell && valueIsNull) - { - await writer.WriteAsync(WorksheetXml.EmptyCell(columnReference, GetCellXfId("2"))).ConfigureAwait(false); - return; - } + return maxRowIndex; + } - var tuple = GetCellValue(rowIndex, cellIndex, value, columnInfo, valueIsNull); + [CreateSyncVersion] + private static async Task WriteColumnWidthPlaceholdersAsync(MiniExcelStreamWriter writer, int count, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); - var styleIndex = tuple.Item1; - var dataType = tuple.Item2; - var cellValue = tuple.Item3; - var columnType = columnInfo.ExcelColumnType; + var placeholderPosition = await writer.FlushAsync(cancellationToken).ConfigureAwait(false); + await writer.WriteWhitespaceAsync(WorksheetXml.GetColumnPlaceholderLength(count)).ConfigureAwait(false); + return placeholderPosition; + } - /*Prefix and suffix blank space will lost after SaveAs #294*/ - var preserveSpace = cellValue != null && (cellValue.StartsWith(" ", StringComparison.Ordinal) || - cellValue.EndsWith(" ", StringComparison.Ordinal)); + [CreateSyncVersion] + private static async Task OverwriteColumnWidthPlaceholdersAsync(MiniExcelStreamWriter writer, long placeholderPosition, IEnumerable? columnWidths, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); - await writer.WriteAsync(WorksheetXml.Cell(columnReference, dataType, GetCellXfId(styleIndex), cellValue, preserveSpace: preserveSpace, columnType: columnType)).ConfigureAwait(false); - widthCollection?.AdjustWidth(cellIndex, cellValue); - } + var position = await writer.FlushAsync(cancellationToken).ConfigureAwait(false); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task GenerateEndXmlAsync(CancellationToken cancellationToken) - { - await AddFilesToZipAsync(cancellationToken).ConfigureAwait(false); - await GenerateDrawinRelXmlAsync(cancellationToken).ConfigureAwait(false); - await GenerateDrawingXmlAsync(cancellationToken).ConfigureAwait(false); - await GenerateWorkbookXmlAsync(cancellationToken).ConfigureAwait(false); - await GenerateContentTypesXmlAsync(cancellationToken).ConfigureAwait(false); - } + writer.SetPosition(placeholderPosition); + await WriteColumnsWidthsAsync(writer, columnWidths, cancellationToken).ConfigureAwait(false); + + await writer.FlushAsync(cancellationToken).ConfigureAwait(false); + writer.SetPosition(position); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task AddFilesToZipAsync(CancellationToken cancellationToken) + [CreateSyncVersion] + private static async Task WriteColumnsWidthsAsync(MiniExcelStreamWriter writer, IEnumerable? columnWidths, CancellationToken cancellationToken = default) + { + var hasWrittenStart = false; + + columnWidths ??= []; + foreach (var column in columnWidths) { - foreach (var item in _files) + cancellationToken.ThrowIfCancellationRequested(); + + if (!hasWrittenStart) { - cancellationToken.ThrowIfCancellationRequested(); - await CreateZipEntryAsync(item.Path, item.Byte, cancellationToken).ConfigureAwait(false); + await writer.WriteAsync(WorksheetXml.StartCols, cancellationToken).ConfigureAwait(false); + hasWrittenStart = true; } + await writer.WriteAsync(WorksheetXml.Column(column.Index, column.Width), cancellationToken).ConfigureAwait(false); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task GenerateStylesXmlAsync(CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); + if (!hasWrittenStart) + return; - using (var context = new SheetStyleBuildContext(_zipDictionary, _archive, _utf8WithBom, _configuration.DynamicColumns)) + await writer.WriteAsync(WorksheetXml.EndCols, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + private async Task PrintHeaderAsync(MiniExcelStreamWriter writer, List props, CancellationToken cancellationToken = default) + { + const int yIndex = 1; + await writer.WriteAsync(WorksheetXml.StartRow(yIndex), cancellationToken).ConfigureAwait(false); + + var xIndex = 1; + foreach (var p in props) + { + //reason : https://github.com/mini-software/MiniExcel/issues/142 + if (p is not null) { - ISheetStyleBuilder builder = null; - switch (_configuration.TableStyles) - { - case TableStyles.None: - builder = new MinimalSheetStyleBuilder(context); - break; - case TableStyles.Default: - builder = new DefaultSheetStyleBuilder(context, _configuration.StyleOptions); - break; - } - var result = await builder.BuildAsync(cancellationToken).ConfigureAwait(false); - _cellXfIdMap = result.CellXfIdMap; + if (p.ExcelIgnore) + continue; + + var r = ExcelOpenXmlUtils.ConvertXYToCell(xIndex, yIndex); + await WriteCellAsync(writer, r, columnName: p.ExcelColumnName).ConfigureAwait(false); } + xIndex++; } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task GenerateDrawinRelXmlAsync(CancellationToken cancellationToken) + await writer.WriteAsync(WorksheetXml.EndRow, cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + private async Task WriteCellAsync(MiniExcelStreamWriter writer, string cellReference, string columnName) + { + await writer.WriteAsync(WorksheetXml.Cell(cellReference, "str", GetCellXfId("1"), ExcelOpenXmlUtils.EncodeXml(columnName))).ConfigureAwait(false); + } + + [CreateSyncVersion] + private async Task WriteCellAsync(MiniExcelStreamWriter writer, int rowIndex, int cellIndex, object value, ExcelColumnInfo columnInfo, ExcelWidthCollection? widthCollection) + { + if (columnInfo?.CustomFormatter is not null) { - for (int sheetIndex = 0; sheetIndex < _sheets.Count; sheetIndex++) + try { - cancellationToken.ThrowIfCancellationRequested(); - await GenerateDrawinRelXmlAsync(sheetIndex, cancellationToken).ConfigureAwait(false); + value = columnInfo.CustomFormatter(value); + } + catch + { + //ignored } } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task GenerateDrawinRelXmlAsync(int sheetIndex, CancellationToken cancellationToken) + var columnReference = ExcelOpenXmlUtils.ConvertXYToCell(cellIndex, rowIndex); + var valueIsNull = value is null || + value is DBNull || + (_configuration.WriteEmptyStringAsNull && value is string vs && vs == string.Empty); + + if (_configuration.EnableWriteNullValueCell && valueIsNull) { - var drawing = GetDrawingRelationshipXml(sheetIndex); - await CreateZipEntryAsync( - ExcelFileNames.DrawingRels(sheetIndex), - string.Empty, - ExcelXml.DefaultDrawingXmlRels.Replace("{{format}}", drawing), - cancellationToken).ConfigureAwait(false); + await writer.WriteAsync(WorksheetXml.EmptyCell(columnReference, GetCellXfId("2"))).ConfigureAwait(false); + return; } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task GenerateDrawingXmlAsync(CancellationToken cancellationToken) + var tuple = GetCellValue(rowIndex, cellIndex, value, columnInfo, valueIsNull); + + var styleIndex = tuple.Item1; + var dataType = tuple.Item2; + string? cellValue = tuple.Item3; + var columnType = columnInfo.ExcelColumnType; + + /*Prefix and suffix blank space will lost after SaveAs #294*/ + var preserveSpace = cellValue is not null && ( + cellValue.StartsWith(" ") || cellValue.EndsWith(" ")); + + await writer.WriteAsync(WorksheetXml.Cell(columnReference, dataType, GetCellXfId(styleIndex), cellValue, preserveSpace: preserveSpace, columnType: columnType)).ConfigureAwait(false); + widthCollection?.AdjustWidth(cellIndex, cellValue); + } + + [CreateSyncVersion] + private async Task GenerateEndXmlAsync(CancellationToken cancellationToken) + { + await AddFilesToZipAsync(cancellationToken).ConfigureAwait(false); + await GenerateDrawinRelXmlAsync(cancellationToken).ConfigureAwait(false); + await GenerateDrawingXmlAsync(cancellationToken).ConfigureAwait(false); + await GenerateWorkbookXmlAsync(cancellationToken).ConfigureAwait(false); + await GenerateContentTypesXmlAsync(cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + private async Task AddFilesToZipAsync(CancellationToken cancellationToken) + { + foreach (var item in _files) { - for (int sheetIndex = 0; sheetIndex < _sheets.Count; sheetIndex++) - { - cancellationToken.ThrowIfCancellationRequested(); - await GenerateDrawingXmlAsync(sheetIndex, cancellationToken).ConfigureAwait(false); - } + cancellationToken.ThrowIfCancellationRequested(); + await CreateZipEntryAsync(item.Path, item.Byte, cancellationToken).ConfigureAwait(false); } + } + + [CreateSyncVersion] + private async Task GenerateStylesXmlAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task GenerateDrawingXmlAsync(int sheetIndex, CancellationToken cancellationToken) + using var context = new SheetStyleBuildContext(_zipDictionary, _archive, Utf8WithBom, _configuration.DynamicColumns); + ISheetStyleBuilder? builder = _configuration.TableStyles switch { - var drawing = GetDrawingXml(sheetIndex); - await CreateZipEntryAsync( - ExcelFileNames.Drawing(sheetIndex), - ExcelContentTypes.Drawing, - ExcelXml.DefaultDrawing.Replace("{{format}}", drawing), - cancellationToken).ConfigureAwait(false); + TableStyles.None => new MinimalSheetStyleBuilder(context), + TableStyles.Default => new DefaultSheetStyleBuilder(context, _configuration.StyleOptions), + _ => null! + }; + + var result = await builder.BuildAsync(cancellationToken).ConfigureAwait(false); + _cellXfIdMap = result.CellXfIdMap; + } + + [CreateSyncVersion] + private async Task GenerateDrawinRelXmlAsync(CancellationToken cancellationToken) + { + for (int sheetIndex = 0; sheetIndex < _sheets.Count; sheetIndex++) + { + cancellationToken.ThrowIfCancellationRequested(); + await GenerateDrawinRelXmlAsync(sheetIndex, cancellationToken).ConfigureAwait(false); } + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task GenerateWorkbookXmlAsync(CancellationToken cancellationToken) + [CreateSyncVersion] + private async Task GenerateDrawinRelXmlAsync(int sheetIndex, CancellationToken cancellationToken) + { + var drawing = GetDrawingRelationshipXml(sheetIndex); + await CreateZipEntryAsync( + ExcelFileNames.DrawingRels(sheetIndex), + string.Empty, + ExcelXml.DefaultDrawingXmlRels.Replace("{{format}}", drawing), + cancellationToken).ConfigureAwait(false); + } + + [CreateSyncVersion] + private async Task GenerateDrawingXmlAsync(CancellationToken cancellationToken) + { + for (int sheetIndex = 0; sheetIndex < _sheets.Count; sheetIndex++) { cancellationToken.ThrowIfCancellationRequested(); + await GenerateDrawingXmlAsync(sheetIndex, cancellationToken).ConfigureAwait(false); + } + } - GenerateWorkBookXmls( - out StringBuilder workbookXml, - out StringBuilder workbookRelsXml, - out Dictionary sheetsRelsXml); + [CreateSyncVersion] + private async Task GenerateDrawingXmlAsync(int sheetIndex, CancellationToken cancellationToken) + { + var drawing = GetDrawingXml(sheetIndex); + await CreateZipEntryAsync( + ExcelFileNames.Drawing(sheetIndex), + ExcelContentTypes.Drawing, + ExcelXml.DefaultDrawing.Replace("{{format}}", drawing), + cancellationToken).ConfigureAwait(false); + } - foreach (var sheetRelsXml in sheetsRelsXml) - { - await CreateZipEntryAsync( - ExcelFileNames.SheetRels(sheetRelsXml.Key), - null, - ExcelXml.DefaultSheetRelXml.Replace("{{format}}", sheetRelsXml.Value), - cancellationToken).ConfigureAwait(false); - } + [CreateSyncVersion] + private async Task GenerateWorkbookXmlAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); - await CreateZipEntryAsync( - ExcelFileNames.Workbook, - ExcelContentTypes.Workbook, - ExcelXml.DefaultWorkbookXml.Replace("{{sheets}}", workbookXml.ToString()), - cancellationToken).ConfigureAwait(false); + GenerateWorkBookXmls( + out StringBuilder workbookXml, + out StringBuilder workbookRelsXml, + out Dictionary sheetsRelsXml); + foreach (var sheetRelsXml in sheetsRelsXml) + { await CreateZipEntryAsync( - ExcelFileNames.WorkbookRels, + ExcelFileNames.SheetRels(sheetRelsXml.Key), null, - ExcelXml.DefaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString()), - cancellationToken).ConfigureAwait(false); + ExcelXml.DefaultSheetRelXml.Replace("{{format}}", sheetRelsXml.Value), + cancellationToken).ConfigureAwait(false); } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task GenerateContentTypesXmlAsync(CancellationToken cancellationToken) - { - var contentTypes = GetContentTypesXml(); - await CreateZipEntryAsync(ExcelFileNames.ContentTypes, null, contentTypes, cancellationToken).ConfigureAwait(false); - } + await CreateZipEntryAsync( + ExcelFileNames.Workbook, + ExcelContentTypes.Workbook, + ExcelXml.DefaultWorkbookXml.Replace("{{sheets}}", workbookXml.ToString()), + cancellationToken).ConfigureAwait(false); + + await CreateZipEntryAsync( + ExcelFileNames.WorkbookRels, + null, + ExcelXml.DefaultWorkbookXmlRels.Replace("{{sheets}}", workbookRelsXml.ToString()), + cancellationToken).ConfigureAwait(false); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task InsertContentTypesXmlAsync(CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); + [CreateSyncVersion] + private async Task GenerateContentTypesXmlAsync(CancellationToken cancellationToken) + { + var contentTypes = GetContentTypesXml(); + await CreateZipEntryAsync(ExcelFileNames.ContentTypes, null, contentTypes, cancellationToken).ConfigureAwait(false); + } - var contentTypesZipEntry = _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.ContentTypes); - if (contentTypesZipEntry == null) - { - await GenerateContentTypesXmlAsync(cancellationToken).ConfigureAwait(false); - return; - } + [CreateSyncVersion] + private async Task InsertContentTypesXmlAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + + var contentTypesZipEntry = _archive.Entries.SingleOrDefault(s => s.FullName == ExcelFileNames.ContentTypes); + if (contentTypesZipEntry is null) + { + await GenerateContentTypesXmlAsync(cancellationToken).ConfigureAwait(false); + return; + } #if NET5_0_OR_GREATER #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - await using (var stream = contentTypesZipEntry.Open()) + await using var stream = contentTypesZipEntry.Open(); #pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task #else - using (var stream = contentTypesZipEntry.Open()) + using var stream = contentTypesZipEntry.Open(); #endif - { #if NETCOREAPP2_0_OR_GREATER - var doc = await XDocument.LoadAsync(stream, LoadOptions.None, cancellationToken).ConfigureAwait(false); + var doc = await XDocument.LoadAsync(stream, LoadOptions.None, cancellationToken).ConfigureAwait(false); #else - var doc = XDocument.Load(stream); + var doc = XDocument.Load(stream); #endif - var ns = doc.Root?.GetDefaultNamespace(); - var typesElement = doc.Descendants(ns + "Types").Single(); + var ns = doc.Root?.GetDefaultNamespace(); + var typesElement = doc.Descendants(ns + "Types").Single(); - var partNames = new HashSet(StringComparer.InvariantCultureIgnoreCase); - foreach (var partName in typesElement.Elements(ns + "Override").Select(s => s.Attribute("PartName").Value)) - { - partNames.Add(partName); - } + var partNames = new HashSet(StringComparer.InvariantCultureIgnoreCase); + foreach (var partName in typesElement.Elements(ns + "Override").Select(s => s.Attribute("PartName").Value)) + { + partNames.Add(partName); + } - foreach (var p in _zipDictionary) - { - cancellationToken.ThrowIfCancellationRequested(); + foreach (var p in _zipDictionary) + { + cancellationToken.ThrowIfCancellationRequested(); - var partName = $"/{p.Key}"; - if (!partNames.Contains(partName)) - { - var newElement = new XElement(ns + "Override", new XAttribute("ContentType", p.Value.ContentType), new XAttribute("PartName", partName)); - typesElement.Add(newElement); - } - } + var partName = $"/{p.Key}"; + if (!partNames.Contains(partName)) + { + var newElement = new XElement(ns + "Override", new XAttribute("ContentType", p.Value.ContentType), new XAttribute("PartName", partName)); + typesElement.Add(newElement); + } + } - stream.Position = 0; + stream.Position = 0; #if NETCOREAPP2_0_OR_GREATER - await doc.SaveAsync(stream, SaveOptions.None, cancellationToken).ConfigureAwait(false); + await doc.SaveAsync(stream, SaveOptions.None, cancellationToken).ConfigureAwait(false); #else - doc.Save(stream); + doc.Save(stream); #endif - } - } + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task CreateZipEntryAsync(string path, string contentType, string content, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); + [CreateSyncVersion] + private async Task CreateZipEntryAsync(string path, string? contentType, string content, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); - var entry = _archive.CreateEntry(path, CompressionLevel.Fastest); + var entry = _archive.CreateEntry(path, CompressionLevel.Fastest); #if NET5_0_OR_GREATER #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - await using (var zipStream = entry.Open()) + await using (var zipStream = entry.Open()) #pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task #else - using (var zipStream = entry.Open()) + using (var zipStream = entry.Open()) #endif - using (var writer = new MiniExcelStreamWriter(zipStream, _utf8WithBom, _configuration.BufferSize)) - await writer.WriteAsync(content, cancellationToken).ConfigureAwait(false); + using (var writer = new MiniExcelStreamWriter(zipStream, Utf8WithBom, _configuration.BufferSize)) + await writer.WriteAsync(content, cancellationToken).ConfigureAwait(false); - if (!string.IsNullOrEmpty(contentType)) - _zipDictionary.Add(path, new ZipPackageInfo(entry, contentType)); - } + if (!string.IsNullOrEmpty(contentType)) + _zipDictionary.Add(path, new ZipPackageInfo(entry, contentType)); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - private async Task CreateZipEntryAsync(string path, byte[] content, CancellationToken cancellationToken) - { - cancellationToken.ThrowIfCancellationRequested(); + [CreateSyncVersion] + private async Task CreateZipEntryAsync(string path, byte[] content, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); - var entry = _archive.CreateEntry(path, CompressionLevel.Fastest); + var entry = _archive.CreateEntry(path, CompressionLevel.Fastest); #if NET5_0_OR_GREATER #pragma warning disable CA2007 // Consider calling ConfigureAwait on the awaited task - await using (var zipStream = entry.Open()) - await zipStream.WriteAsync(content, cancellationToken).ConfigureAwait(false); + await using var zipStream = entry.Open(); + await zipStream.WriteAsync(content, cancellationToken).ConfigureAwait(false); #pragma warning restore CA2007 // Consider calling ConfigureAwait on the awaited task #else - using (var zipStream = entry.Open()) - await zipStream.WriteAsync(content, 0, content.Length, cancellationToken).ConfigureAwait(false); + using var zipStream = entry.Open(); + await zipStream.WriteAsync(content, 0, content.Length, cancellationToken).ConfigureAwait(false); #endif - } } -} +} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlStyles.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlStyles.cs index 20c5f123..24ccb242 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlStyles.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlStyles.cs @@ -1,98 +1,95 @@ -using MiniExcelLibs.Utils; -using MiniExcelLibs.Zip; -using System; +using System; using System.Collections.Generic; -using System.Diagnostics; +using MiniExcelLibs.OpenXml.Constants; +using MiniExcelLibs.Utils; +using MiniExcelLibs.Zip; -namespace MiniExcelLibs.OpenXml +namespace MiniExcelLibs.OpenXml; + +internal class ExcelOpenXmlStyles { - internal class ExcelOpenXmlStyles - { - private static readonly string[] _ns = { Config.SpreadsheetmlXmlns, Config.SpreadsheetmlXmlStrictns }; - private readonly Dictionary _cellXfs = new Dictionary(); - private readonly Dictionary _cellStyleXfs = new Dictionary(); - private readonly Dictionary _customFormats = new Dictionary(); + private static readonly string[] Ns = [Schemas.SpreadsheetmlXmlns, Schemas.SpreadsheetmlXmlStrictns]; + + private readonly Dictionary _cellXfs = new(); + private readonly Dictionary _cellStyleXfs = new(); + private readonly Dictionary _customFormats = new(); - public ExcelOpenXmlStyles(ExcelOpenXmlZip zip) + public ExcelOpenXmlStyles(ExcelOpenXmlZip zip) + { + using var reader = zip.GetXmlReader("xl/styles.xml"); + + if (!XmlReaderHelper.IsStartElement(reader, "styleSheet", Ns)) + return; + if (!XmlReaderHelper.ReadFirstContent(reader)) + return; + + while (!reader.EOF) { - using (var reader = zip.GetXmlReader(@"xl/styles.xml")) + if (XmlReaderHelper.IsStartElement(reader, "cellXfs", Ns)) { - if (!XmlReaderHelper.IsStartElement(reader, "styleSheet", _ns)) - return; if (!XmlReaderHelper.ReadFirstContent(reader)) - return; - + continue; + + var index = 0; while (!reader.EOF) { - if (XmlReaderHelper.IsStartElement(reader, "cellXfs", _ns)) + if (XmlReaderHelper.IsStartElement(reader, "xf", Ns)) { - if (!XmlReaderHelper.ReadFirstContent(reader)) - continue; - - var index = 0; - while (!reader.EOF) - { - if (XmlReaderHelper.IsStartElement(reader, "xf", _ns)) - { - int.TryParse(reader.GetAttribute("xfId"), out var xfId); - int.TryParse(reader.GetAttribute("numFmtId"), out var numFmtId); - _cellXfs.Add(index, new StyleRecord() { XfId = xfId, NumFmtId = numFmtId }); - reader.Skip(); - index++; - } - else if (!XmlReaderHelper.SkipContent(reader)) - break; - } + int.TryParse(reader.GetAttribute("xfId"), out var xfId); + int.TryParse(reader.GetAttribute("numFmtId"), out var numFmtId); + _cellXfs.Add(index, new StyleRecord() { XfId = xfId, NumFmtId = numFmtId }); + reader.Skip(); + index++; } - else if (XmlReaderHelper.IsStartElement(reader, "cellStyleXfs", _ns)) + else if (!XmlReaderHelper.SkipContent(reader)) + break; + } + } + else if (XmlReaderHelper.IsStartElement(reader, "cellStyleXfs", Ns)) + { + if (!XmlReaderHelper.ReadFirstContent(reader)) + continue; + + var index = 0; + while (!reader.EOF) + { + if (XmlReaderHelper.IsStartElement(reader, "xf", Ns)) { - if (!XmlReaderHelper.ReadFirstContent(reader)) - continue; + int.TryParse(reader.GetAttribute("xfId"), out var xfId); + int.TryParse(reader.GetAttribute("numFmtId"), out var numFmtId); - var index = 0; - while (!reader.EOF) - { - if (XmlReaderHelper.IsStartElement(reader, "xf", _ns)) - { - int.TryParse(reader.GetAttribute("xfId"), out var xfId); - int.TryParse(reader.GetAttribute("numFmtId"), out var numFmtId); - - _cellStyleXfs.Add(index, new StyleRecord() { XfId = xfId, NumFmtId = numFmtId }); - reader.Skip(); - index++; - } - else if (!XmlReaderHelper.SkipContent(reader)) - break; - } + _cellStyleXfs.Add(index, new StyleRecord() { XfId = xfId, NumFmtId = numFmtId }); + reader.Skip(); + index++; } - else if (XmlReaderHelper.IsStartElement(reader, "numFmts", _ns)) + else if (!XmlReaderHelper.SkipContent(reader)) { - if (!XmlReaderHelper.ReadFirstContent(reader)) - continue; + break; + } + } + } + else if (XmlReaderHelper.IsStartElement(reader, "numFmts", Ns)) + { + if (!XmlReaderHelper.ReadFirstContent(reader)) + continue; - while (!reader.EOF) - { - if (XmlReaderHelper.IsStartElement(reader, "numFmt", _ns)) - { - int.TryParse(reader.GetAttribute("numFmtId"), out var numFmtId); - var formatCode = reader.GetAttribute("formatCode"); + while (!reader.EOF) + { + if (XmlReaderHelper.IsStartElement(reader, "numFmt", Ns)) + { + int.TryParse(reader.GetAttribute("numFmtId"), out var numFmtId); + var formatCode = reader.GetAttribute("formatCode"); - //TODO: determine the type according to the format - var type = typeof(string); - if (DateTimeHelper.IsDateTimeFormat(formatCode)) - { - type = typeof(DateTime?); - } - - if (!_customFormats.ContainsKey(numFmtId)) - _customFormats.Add(numFmtId, new NumberFormatString(formatCode, type)); - reader.Skip(); - } - else if (!XmlReaderHelper.SkipContent(reader)) - { - break; - } + //TODO: determine the type according to the format + var type = typeof(string); + if (DateTimeHelper.IsDateTimeFormat(formatCode)) + { + type = typeof(DateTime?); } + + if (!_customFormats.ContainsKey(numFmtId)) + _customFormats.Add(numFmtId, new NumberFormatString(formatCode, type)); + reader.Skip(); } else if (!XmlReaderHelper.SkipContent(reader)) { @@ -100,108 +97,99 @@ public ExcelOpenXmlStyles(ExcelOpenXmlZip zip) } } } + else if (!XmlReaderHelper.SkipContent(reader)) + { + break; + } } + } - public NumberFormatString GetStyleFormat(int index) - { - if (!_cellXfs.TryGetValue(index, out var styleRecord)) - return null; + public NumberFormatString? GetStyleFormat(int index) + { + if (!_cellXfs.TryGetValue(index, out var styleRecord)) + return null; - if (Formats.TryGetValue(styleRecord.NumFmtId, out var numberFormat)) - return numberFormat; + if (Formats.TryGetValue(styleRecord.NumFmtId, out var numberFormat)) + return numberFormat; - if (_customFormats.TryGetValue(styleRecord.NumFmtId, out var customNumberFormat)) - return customNumberFormat; + if (_customFormats.TryGetValue(styleRecord.NumFmtId, out var customNumberFormat)) + return customNumberFormat; - return null; - } - - public object ConvertValueByStyleFormat(int index, object value) - { - var sf = GetStyleFormat(index); - if (sf?.Type == null) - return value; - - if (sf.Type == typeof(DateTime?)) - { - if (double.TryParse(value?.ToString(), out var s)) - { - return DateTimeHelper.IsValidOADateTime(s) ? DateTime.FromOADate(s) : value; - } - } - else if (sf.Type == typeof(TimeSpan?)) - { - if (double.TryParse(value?.ToString(), out var number)) - return TimeSpan.FromDays(number); - } + return null; + } + public object? ConvertValueByStyleFormat(int index, object? value) + { + var sf = GetStyleFormat(index); + + if (sf?.Type is null) return value; - } - private static Dictionary Formats { get; } = new Dictionary - { - { 0, new NumberFormatString("General",typeof(string)) }, - { 1, new NumberFormatString("0",typeof(decimal?)) }, - { 2, new NumberFormatString("0.00",typeof(decimal?)) }, - { 3, new NumberFormatString("#,##0",typeof(decimal?)) }, - { 4, new NumberFormatString("#,##0.00",typeof(decimal?)) }, - { 5, new NumberFormatString("\"$\"#,##0_);(\"$\"#,##0)",typeof(decimal?)) }, - { 6, new NumberFormatString("\"$\"#,##0_);[Red](\"$\"#,##0)",typeof(decimal?)) }, - { 7, new NumberFormatString("\"$\"#,##0.00_);(\"$\"#,##0.00)",typeof(decimal?)) }, - { 8, new NumberFormatString("\"$\"#,##0.00_);[Red](\"$\"#,##0.00)",typeof(string)) }, - { 9, new NumberFormatString("0%",typeof(decimal?)) }, - { 10, new NumberFormatString("0.00%",typeof(string)) }, - { 11, new NumberFormatString("0.00E+00",typeof(string)) }, - { 12, new NumberFormatString("# ?/?",typeof(string)) }, - { 13, new NumberFormatString("# ??/??",typeof(string)) }, - { 14, new NumberFormatString("d/m/yyyy",typeof(DateTime?)) }, - { 15, new NumberFormatString("d-mmm-yy",typeof(DateTime?)) }, - { 16, new NumberFormatString("d-mmm",typeof(DateTime?)) }, - { 17, new NumberFormatString("mmm-yy",typeof(TimeSpan)) }, - { 18, new NumberFormatString("h:mm AM/PM",typeof(TimeSpan?)) }, - { 19, new NumberFormatString("h:mm:ss AM/PM",typeof(TimeSpan?)) }, - { 20, new NumberFormatString("h:mm",typeof(TimeSpan?)) }, - { 21, new NumberFormatString("h:mm:ss",typeof(TimeSpan?)) }, - { 22, new NumberFormatString("m/d/yy h:mm",typeof(DateTime?)) }, - // 23..36 international/unused - { 37, new NumberFormatString("#,##0_);(#,##0)",typeof(string)) }, - { 38, new NumberFormatString("#,##0_);[Red](#,##0)",typeof(string)) }, - { 39, new NumberFormatString("#,##0.00_);(#,##0.00)",typeof(string)) }, - { 40, new NumberFormatString("#,##0.00_);[Red](#,##0.00)",typeof(string)) }, - { 41, new NumberFormatString("_(\"$\"* #,##0_);_(\"$\"* (#,##0);_(\"$\"* \"-\"_);_(@_)",typeof(string)) }, - { 42, new NumberFormatString("_(* #,##0_);_(* (#,##0);_(* \"-\"_);_(@_)",typeof(string)) }, - { 43, new NumberFormatString("_(\"$\"* #,##0.00_);_(\"$\"* (#,##0.00);_(\"$\"* \"-\"??_);_(@_)",typeof(string)) }, - { 44, new NumberFormatString("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)",typeof(string)) }, - { 45, new NumberFormatString("mm:ss",typeof(TimeSpan?)) }, - { 46, new NumberFormatString("[h]:mm:ss",typeof(TimeSpan?)) }, - { 47, new NumberFormatString("mm:ss.0",typeof(TimeSpan?)) }, - { 48, new NumberFormatString("##0.0E+0",typeof(string)) }, - { 49, new NumberFormatString("@",typeof(string)) }, - - // issue 222 - { 58, new NumberFormatString("m/d",typeof(DateTime?)) }, - - // custom format start with 176 - }; - } + if (sf.Type == typeof(DateTime?) && double.TryParse(value?.ToString(), out var s)) + return DateTimeHelper.IsValidOADateTime(s) ? DateTime.FromOADate(s) : value; - internal class NumberFormatString - { - public string FormatCode { get; } - public Type Type { get; set; } - public bool NeedConvertToString { get; } + if (sf.Type == typeof(TimeSpan?) && double.TryParse(value?.ToString(), out var number)) + return TimeSpan.FromDays(number); - public NumberFormatString(string formatCode, Type type, bool needConvertToString = false) - { - FormatCode = formatCode; - Type = type; - NeedConvertToString = needConvertToString; - } + return value; } - internal class StyleRecord + private static Dictionary Formats { get; } = new() { - public int XfId { get; set; } - public int NumFmtId { get; set; } - } + { 0, new NumberFormatString("General", typeof(string)) }, + { 1, new NumberFormatString("0", typeof(decimal?)) }, + { 2, new NumberFormatString("0.00", typeof(decimal?)) }, + { 3, new NumberFormatString("#,##0", typeof(decimal?)) }, + { 4, new NumberFormatString("#,##0.00", typeof(decimal?)) }, + { 5, new NumberFormatString("\"$\"#,##0_);(\"$\"#,##0)", typeof(decimal?)) }, + { 6, new NumberFormatString("\"$\"#,##0_);[Red](\"$\"#,##0)", typeof(decimal?)) }, + { 7, new NumberFormatString("\"$\"#,##0.00_);(\"$\"#,##0.00)", typeof(decimal?)) }, + { 8, new NumberFormatString("\"$\"#,##0.00_);[Red](\"$\"#,##0.00)", typeof(string)) }, + { 9, new NumberFormatString("0%", typeof(decimal?)) }, + { 10, new NumberFormatString("0.00%", typeof(string)) }, + { 11, new NumberFormatString("0.00E+00", typeof(string)) }, + { 12, new NumberFormatString("# ?/?", typeof(string)) }, + { 13, new NumberFormatString("# ??/??", typeof(string)) }, + { 14, new NumberFormatString("d/m/yyyy", typeof(DateTime?)) }, + { 15, new NumberFormatString("d-mmm-yy", typeof(DateTime?)) }, + { 16, new NumberFormatString("d-mmm", typeof(DateTime?)) }, + { 17, new NumberFormatString("mmm-yy", typeof(TimeSpan)) }, + { 18, new NumberFormatString("h:mm AM/PM", typeof(TimeSpan?)) }, + { 19, new NumberFormatString("h:mm:ss AM/PM", typeof(TimeSpan?)) }, + { 20, new NumberFormatString("h:mm", typeof(TimeSpan?)) }, + { 21, new NumberFormatString("h:mm:ss", typeof(TimeSpan?)) }, + { 22, new NumberFormatString("m/d/yy h:mm", typeof(DateTime?)) }, + // 23..36 international/unused + { 37, new NumberFormatString("#,##0_);(#,##0)", typeof(string)) }, + { 38, new NumberFormatString("#,##0_);[Red](#,##0)", typeof(string)) }, + { 39, new NumberFormatString("#,##0.00_);(#,##0.00)", typeof(string)) }, + { 40, new NumberFormatString("#,##0.00_);[Red](#,##0.00)", typeof(string)) }, + { 41, new NumberFormatString("_(\"$\"* #,##0_);_(\"$\"* (#,##0);_(\"$\"* \"-\"_);_(@_)", typeof(string)) }, + { 42, new NumberFormatString("_(* #,##0_);_(* (#,##0);_(* \"-\"_);_(@_)", typeof(string)) }, + { 43, new NumberFormatString("_(\"$\"* #,##0.00_);_(\"$\"* (#,##0.00);_(\"$\"* \"-\"??_);_(@_)", typeof(string)) }, + { 44, new NumberFormatString("_(* #,##0.00_);_(* (#,##0.00);_(* \"-\"??_);_(@_)", typeof(string)) }, + { 45, new NumberFormatString("mm:ss", typeof(TimeSpan?)) }, + { 46, new NumberFormatString("[h]:mm:ss", typeof(TimeSpan?)) }, + { 47, new NumberFormatString("mm:ss.0", typeof(TimeSpan?)) }, + { 48, new NumberFormatString("##0.0E+0", typeof(string)) }, + { 49, new NumberFormatString("@", typeof(string)) }, + + // issue 222 + { 58, new NumberFormatString("m/d",typeof(DateTime?)) }, + + // custom format start with 176 + }; +} + +internal class NumberFormatString(string formatCode, Type type, bool needConvertToString = false) +{ + public string FormatCode { get; } = formatCode; + public Type Type { get; set; } = type; + public bool NeedConvertToString { get; } = needConvertToString; +} + +internal class StyleRecord +{ + public int XfId { get; set; } + public int NumFmtId { get; set; } } \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/ExcelOpenXmlUtils.cs b/src/MiniExcel/OpenXml/ExcelOpenXmlUtils.cs index f58ac5fb..c1c74cfc 100644 --- a/src/MiniExcel/OpenXml/ExcelOpenXmlUtils.cs +++ b/src/MiniExcel/OpenXml/ExcelOpenXmlUtils.cs @@ -2,112 +2,111 @@ using System.Runtime.CompilerServices; using MiniExcelLibs.Utils; -namespace MiniExcelLibs.OpenXml +namespace MiniExcelLibs.OpenXml; + +internal static class ExcelOpenXmlUtils { - internal static class ExcelOpenXmlUtils - { - public static string MinifyXml(string xml) => xml - .Replace("\r", "") - .Replace("\n", "") - .Replace("\t", "") - .Trim(); + public static string MinifyXml(string xml) => xml + .Replace("\r", "") + .Replace("\n", "") + .Replace("\t", "") + .Trim(); - /// - /// Encode to XML (special characteres: ' " > < &) - /// - public static string EncodeXML(string value) => value == null ? string.Empty - : XmlEncoder.EncodeString(value) - .Replace("&", "&") - .Replace("<", "<") - .Replace(">", ">") - .Replace("\"", """) - .Replace("'", "'") - .ToString(); + /// + /// Encode to XML (special characteres: ' " > < &) + /// + public static string EncodeXml(string? value) => value is null ? "" + : XmlEncoder.EncodeString(value) + ?.Replace("&", "&") + .Replace("<", "<") + .Replace(">", ">") + .Replace("\"", """) + .Replace("'", "'") + .ToString() ?? ""; - /// X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2) - public static string ConvertXyToCell(int x, int y) - { - int dividend = x; - string columnName = string.Empty; + /// X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2) + public static string ConvertXYToCell(int x, int y) + { + int dividend = x; + string columnName = string.Empty; - while (dividend > 0) - { - var modulo = (dividend - 1) % 26; - columnName = Convert.ToChar(65 + modulo) + columnName; - dividend = (dividend - modulo) / 26; - } - return $"{columnName}{y}"; + while (dividend > 0) + { + var modulo = (dividend - 1) % 26; + columnName = Convert.ToChar(65 + modulo) + columnName; + dividend = (dividend - modulo) / 26; } + return $"{columnName}{y}"; + } - /// X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2) - public static string ConvertXyToCell(Tuple xy) => ConvertXyToCell(xy.Item1, xy.Item2); + /// X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2) + public static string ConvertXYToCell(Tuple xy) => ConvertXYToCell(xy.Item1, xy.Item2); - /// X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2) - public static Tuple ConvertCellToXY(string cell) => Tuple.Create(GetCellColumnIndex(cell), GetCellRowNumber(cell)); + /// X=CellLetter,Y=CellNumber,ex:A1=(1,1),B2=(2,2) + public static Tuple ConvertCellToXY(string cell) => Tuple.Create(GetCellColumnIndex(cell), GetCellRowNumber(cell)); - public static int GetColumnNumber(string name) + public static int GetColumnNumber(string name) + { + int number = -1; + int pow = 1; + for (int i = name.Length - 1; i >= 0; i--) { - int number = -1; - int pow = 1; - for (int i = name.Length - 1; i >= 0; i--) - { - number += (name[i] - 'A' + 1) * pow; - pow *= 26; - } - - return number; + number += (name[i] - 'A' + 1) * pow; + pow *= 26; } - public static int GetCellColumnIndex(string cell) - { - const string keys = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - const int mode = 26; + return number; + } - var x = 0; - var cellLetter = GetCellColumnLetter(cell); - //AA=27,ZZ=702 - foreach (var t in cellLetter) - x = x * mode + keys.IndexOf(t); + public static int GetCellColumnIndex(string cell) + { + const string keys = " ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + const int mode = 26; - return x; - } + var x = 0; + var cellLetter = GetCellColumnLetter(cell); + //AA=27,ZZ=702 + foreach (var t in cellLetter) + x = x * mode + keys.IndexOf(t); + + return x; + } - public static int GetCellRowNumber(string cell) + public static int GetCellRowNumber(string cell) + { + if (string.IsNullOrEmpty(cell)) + throw new Exception("cell is null or empty"); + var cellNumber = string.Empty; + foreach (var t in cell) { - if (string.IsNullOrEmpty(cell)) - throw new Exception("cell is null or empty"); - var cellNumber = string.Empty; - foreach (var t in cell) - { - if (char.IsDigit(t)) - cellNumber += t; - } - return int.Parse(cellNumber); + if (char.IsDigit(t)) + cellNumber += t; } + return int.Parse(cellNumber); + } - public static string GetCellColumnLetter(string cell) + public static string GetCellColumnLetter(string cell) + { + string GetCellLetter = string.Empty; + foreach (var t in cell) { - string GetCellLetter = string.Empty; - foreach (var t in cell) - { - if (char.IsLetter(t)) - GetCellLetter += t; - } - return GetCellLetter; + if (char.IsLetter(t)) + GetCellLetter += t; } + return GetCellLetter; + } - public static string ConvertColumnName(int x) - { - int dividend = x; - string columnName = string.Empty; + public static string ConvertColumnName(int x) + { + int dividend = x; + string columnName = string.Empty; - while (dividend > 0) - { - var modulo = (dividend - 1) % 26; - columnName = Convert.ToChar(65 + modulo) + columnName; - dividend = (dividend - modulo) / 26; - } - return columnName; + while (dividend > 0) + { + var modulo = (dividend - 1) % 26; + columnName = Convert.ToChar(65 + modulo) + columnName; + dividend = (dividend - modulo) / 26; } + return columnName; } -} +} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/ExcelWidthCollection.cs b/src/MiniExcel/OpenXml/ExcelWidthCollection.cs index 494c1ade..a8db103d 100644 --- a/src/MiniExcel/OpenXml/ExcelWidthCollection.cs +++ b/src/MiniExcel/OpenXml/ExcelWidthCollection.cs @@ -1,71 +1,69 @@ -using MiniExcelLibs.Utils; -using System; +using System; using System.Collections.Generic; using System.Linq; +using MiniExcelLibs.Utils; -namespace MiniExcelLibs.OpenXml +namespace MiniExcelLibs.OpenXml; + +public sealed class ExcelColumnWidth { - public sealed class ExcelColumnWidth - { - public int Index { get; set; } - public double Width { get; set; } + public int Index { get; set; } + public double Width { get; set; } - internal static IEnumerable FromProps(ICollection props, double? minWidth = null) + internal static IEnumerable FromProps(ICollection props, double? minWidth = null) + { + var i = 1; + foreach (var p in props) { - var i = 1; - foreach (var p in props) + if (p?.ExcelColumnWidth is not null || minWidth is not null) { - if (p?.ExcelColumnWidth != null || minWidth != null) + var colIndex = p?.ExcelColumnIndex + 1; + yield return new ExcelColumnWidth { - var colIndex = p?.ExcelColumnIndex + 1; - yield return new ExcelColumnWidth - { - Index = colIndex ?? i, - Width = p?.ExcelColumnWidth ?? minWidth.Value - }; - } - - i++; + Index = colIndex ?? i, + Width = p?.ExcelColumnWidth ?? minWidth.Value + }; } + + i++; } } +} - public sealed class ExcelWidthCollection - { - private readonly Dictionary _columnWidths; - private readonly double _maxWidth; +public sealed class ExcelWidthCollection +{ + private readonly Dictionary _columnWidths; + private readonly double _maxWidth; - public IEnumerable Columns => _columnWidths.Values; + public IEnumerable Columns => _columnWidths.Values; - internal ExcelWidthCollection(double minWidth, double maxWidth, ICollection props) - { - _maxWidth = maxWidth; - _columnWidths = ExcelColumnWidth.FromProps(props, minWidth).ToDictionary(x => x.Index); - } + internal ExcelWidthCollection(double minWidth, double maxWidth, ICollection props) + { + _maxWidth = maxWidth; + _columnWidths = ExcelColumnWidth.FromProps(props, minWidth).ToDictionary(x => x.Index); + } - public void AdjustWidth(int columnIndex, string columnValue) + public void AdjustWidth(int columnIndex, string columnValue) + { + if (!string.IsNullOrEmpty(columnValue) && _columnWidths.TryGetValue(columnIndex, out var currentWidth)) { - if (!string.IsNullOrEmpty(columnValue) - && _columnWidths.TryGetValue(columnIndex, out var currentWidth)) - { - var adjustedWidth = Math.Max(currentWidth.Width, GetApproximateTextWidth(columnValue.Length)); - currentWidth.Width = Math.Min(_maxWidth, adjustedWidth); - } + var adjustedWidth = Math.Max(currentWidth.Width, GetApproximateTextWidth(columnValue.Length)); + currentWidth.Width = Math.Min(_maxWidth, adjustedWidth); } + } - /// - /// Get the approximate width of the given text for Calibri 11pt - /// - /// - /// Rounds the result to 2 decimal places. - /// - public static double GetApproximateTextWidth(int textLength) - { - const double characterWidthFactor = 1.2; // Estimated factor for Calibri, 11pt - const double padding = 2; // Add some padding for extra spacing + /// + /// Get the approximate width of the given text for Calibri 11pt + /// + /// + /// Rounds the result to 2 decimal places. + /// + public static double GetApproximateTextWidth(int textLength) + { + const double characterWidthFactor = 1.2; // Estimated factor for Calibri, 11pt + const double padding = 2; // Add some padding for extra spacing - var excelColumnWidth = (textLength * characterWidthFactor) + padding; - return Math.Round(excelColumnWidth, 2); - } + var excelColumnWidth = textLength * characterWidthFactor + padding; + return Math.Round(excelColumnWidth, 2); } -} +} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/MergeCells.cs b/src/MiniExcel/OpenXml/MergeCells.cs index 51ad3490..a1a62d78 100644 --- a/src/MiniExcel/OpenXml/MergeCells.cs +++ b/src/MiniExcel/OpenXml/MergeCells.cs @@ -1,10 +1,9 @@ using System.Collections.Generic; -namespace MiniExcelLibs.OpenXml +namespace MiniExcelLibs.OpenXml; + +internal class MergeCells { - internal class MergeCells - { - public Dictionary MergesValues { get; set; } = new Dictionary(); - public Dictionary MergesMap { get; set; } = new Dictionary(); - } -} + public Dictionary MergesValues { get; set; } = []; + public Dictionary MergesMap { get; set; } = []; +} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs b/src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs index f6cc53a0..6f8509cb 100644 --- a/src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs +++ b/src/MiniExcel/OpenXml/MiniExcelStreamWriter.cs @@ -4,73 +4,68 @@ using System.Threading; using System.Threading.Tasks; -namespace MiniExcelLibs.OpenXml +namespace MiniExcelLibs.OpenXml; + +internal partial class MiniExcelStreamWriter(Stream stream, Encoding encoding, int bufferSize) : IDisposable { - internal partial class MiniExcelStreamWriter : IDisposable - { - private readonly StreamWriter _streamWriter; - private bool disposedValue; + private readonly StreamWriter _streamWriter = new(stream, encoding, bufferSize); + private bool _disposedValue; - public MiniExcelStreamWriter(Stream stream, Encoding encoding, int bufferSize) - { - _streamWriter = new StreamWriter(stream, encoding, bufferSize); - } + [Zomp.SyncMethodGenerator.CreateSyncVersion] + public async Task WriteAsync(string content, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public async Task WriteAsync(string content, CancellationToken cancellationToken = default) + if (!string.IsNullOrEmpty(content)) { - cancellationToken.ThrowIfCancellationRequested(); - - if (string.IsNullOrEmpty(content)) - return; await _streamWriter.WriteAsync(content).ConfigureAwait(false); } + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public async Task WriteAndFlushAsync(string content, CancellationToken cancellationToken = default) - { - cancellationToken.ThrowIfCancellationRequested(); + [Zomp.SyncMethodGenerator.CreateSyncVersion] + public async Task WriteAndFlushAsync(string content, CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); - await WriteAsync(content, cancellationToken).ConfigureAwait(false); - return await FlushAsync(cancellationToken).ConfigureAwait(false); - } + await WriteAsync(content, cancellationToken).ConfigureAwait(false); + return await FlushAsync(cancellationToken).ConfigureAwait(false); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public async Task WriteWhitespaceAsync(int length) - { - await _streamWriter.WriteAsync(new string(' ', length)).ConfigureAwait(false); - } + [Zomp.SyncMethodGenerator.CreateSyncVersion] + public async Task WriteWhitespaceAsync(int length) + { + await _streamWriter.WriteAsync(new string(' ', length)).ConfigureAwait(false); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - public async Task FlushAsync(CancellationToken cancellationToken = default) - { - await _streamWriter.FlushAsync( + [Zomp.SyncMethodGenerator.CreateSyncVersion] + public async Task FlushAsync(CancellationToken cancellationToken = default) + { + await _streamWriter.FlushAsync( #if NET8_0_OR_GREATER - cancellationToken + cancellationToken #endif - ).ConfigureAwait(false); - return _streamWriter.BaseStream.Position; - } + ).ConfigureAwait(false); + return _streamWriter.BaseStream.Position; + } - public void SetPosition(long position) - { - _streamWriter.BaseStream.Position = position; - } + public void SetPosition(long position) + { + _streamWriter.BaseStream.Position = position; + } - protected virtual void Dispose(bool disposing) + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) { - if (!disposedValue) - { - _streamWriter?.Dispose(); - disposedValue = true; - } + _streamWriter?.Dispose(); + _disposedValue = true; } + } - public void Dispose() - { - // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method - Dispose(disposing: true); - GC.SuppressFinalize(this); - } + public void Dispose() + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose(disposing: true); + GC.SuppressFinalize(this); } } \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/Models/DrawingDto.cs b/src/MiniExcel/OpenXml/Models/DrawingDto.cs index 6d8df6cd..672ae8b3 100644 --- a/src/MiniExcel/OpenXml/Models/DrawingDto.cs +++ b/src/MiniExcel/OpenXml/Models/DrawingDto.cs @@ -1,9 +1,8 @@ using System; -namespace MiniExcelLibs.OpenXml.Models +namespace MiniExcelLibs.OpenXml.Models; + +internal class DrawingDto { - internal class DrawingDto - { - internal string ID { get; set; } = $"R{Guid.NewGuid():N}"; - } + internal string ID { get; set; } = $"R{Guid.NewGuid():N}"; } \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/Models/ExcelRange.cs b/src/MiniExcel/OpenXml/Models/ExcelRange.cs index a8f784dd..0ecb16cb 100644 --- a/src/MiniExcel/OpenXml/Models/ExcelRange.cs +++ b/src/MiniExcel/OpenXml/Models/ExcelRange.cs @@ -1,36 +1,29 @@ using System; -namespace MiniExcelLibs.OpenXml.Models +namespace MiniExcelLibs.OpenXml.Models; + +public class ExcelRangeElement { - public class ExcelRangeElement + internal ExcelRangeElement(int startIndex, int endIndex) { - internal ExcelRangeElement(int startIndex, int endIndex) - { - if (startIndex > endIndex) - throw new ArgumentException("StartIndex value cannot be greater than EndIndex value."); + if (startIndex > endIndex) + throw new ArgumentException("StartIndex value cannot be greater than EndIndex value."); - StartIndex = startIndex; - EndIndex = endIndex; - } + StartIndex = startIndex; + EndIndex = endIndex; + } - public int StartIndex { get; } - public int EndIndex { get; } + public int StartIndex { get; } + public int EndIndex { get; } - public int Count => EndIndex - StartIndex + 1; - } + public int Count => EndIndex - StartIndex + 1; +} - public class ExcelRange - { - public ExcelRange(int maxRow, int maxColumn) - { - Rows = new ExcelRangeElement(1, maxRow); - Columns = new ExcelRangeElement(1, maxColumn); - } - - public string StartCell { get; internal set; } - public string EndCell { get; internal set; } +public class ExcelRange(int maxRow, int maxColumn) +{ + public string StartCell { get; internal set; } + public string EndCell { get; internal set; } - public ExcelRangeElement Rows { get; } - public ExcelRangeElement Columns { get; } - } + public ExcelRangeElement Rows { get; } = new(1, maxRow); + public ExcelRangeElement Columns { get; } = new(1, maxColumn); } \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/Models/FileDto.cs b/src/MiniExcel/OpenXml/Models/FileDto.cs index 57e4feb8..b9fd5667 100644 --- a/src/MiniExcel/OpenXml/Models/FileDto.cs +++ b/src/MiniExcel/OpenXml/Models/FileDto.cs @@ -1,17 +1,16 @@ using System; -namespace MiniExcelLibs.OpenXml.Models +namespace MiniExcelLibs.OpenXml.Models; + +internal class FileDto { - internal class FileDto - { - internal string ID { get; set; } = $"R{Guid.NewGuid():N}"; - internal string Extension { get; set; } - internal string Path => $"xl/media/{ID}.{Extension}"; - internal string Path2 => $"/xl/media/{ID}.{Extension}"; - internal byte[] Byte { get; set; } - internal int RowIndex { get; set; } - internal int CellIndex { get; set; } - internal bool IsImage { get; set; } - internal int SheetId { get; set; } - } + internal string ID { get; set; } = $"R{Guid.NewGuid():N}"; + internal string Extension { get; set; } + internal string Path => $"xl/media/{ID}.{Extension}"; + internal string Path2 => $"/xl/media/{ID}.{Extension}"; + internal byte[] Byte { get; set; } + internal int RowIndex { get; set; } + internal int CellIndex { get; set; } + internal bool IsImage { get; set; } + internal int SheetId { get; set; } } \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/Models/SheetDto.cs b/src/MiniExcel/OpenXml/Models/SheetDto.cs index c75c69f6..2d6da7cc 100644 --- a/src/MiniExcel/OpenXml/Models/SheetDto.cs +++ b/src/MiniExcel/OpenXml/Models/SheetDto.cs @@ -1,14 +1,13 @@ using System; -namespace MiniExcelLibs.OpenXml.Models +namespace MiniExcelLibs.OpenXml.Models; + +internal class SheetDto { - internal class SheetDto - { - internal string ID { get; set; } = $"R{Guid.NewGuid():N}"; - internal string Name { get; set; } - internal int SheetIdx { get; set; } - internal string Path => $"xl/worksheets/sheet{SheetIdx}.xml"; + internal string ID { get; set; } = $"R{Guid.NewGuid():N}"; + internal string? Name { get; set; } + internal int SheetIdx { get; set; } + internal string Path => $"xl/worksheets/sheet{SheetIdx}.xml"; - internal string State { get; set; } - } + internal string State { get; set; } } \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs b/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs index 23095a2a..0b956425 100644 --- a/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs +++ b/src/MiniExcel/OpenXml/OpenXmlConfiguration.cs @@ -1,33 +1,32 @@ using MiniExcelLibs.Attributes; -namespace MiniExcelLibs.OpenXml -{ - public class OpenXmlConfiguration : Configuration - { - internal static readonly OpenXmlConfiguration DefaultConfig = new OpenXmlConfiguration(); - public bool FillMergedCells { get; set; } - public TableStyles TableStyles { get; set; } = TableStyles.Default; - public bool AutoFilter { get; set; } = true; - public int FreezeRowCount { get; set; } = 1; - public int FreezeColumnCount { get; set; } = 0; - public bool EnableConvertByteArray { get; set; } = true; - public bool IgnoreTemplateParameterMissing { get; set; } = true; - public bool EnableWriteNullValueCell { get; set; } = true; - public bool WriteEmptyStringAsNull { get; set; } = false; - public bool TrimColumnNames { get; set; } = true; - public bool IgnoreEmptyRows { get; set; } = false; - public bool EnableSharedStringCache { get; set; } = true; - public long SharedStringCacheSize { get; set; } = 5 * 1024 * 1024; - public OpenXmlStyleOptions StyleOptions { get; set; } = new OpenXmlStyleOptions(); - public DynamicExcelSheet[] DynamicSheets { get; set; } - public bool EnableWriteFilePath{ get; set; } = true; - /// - /// Calculate column widths automatically from each column value. - /// - public bool EnableAutoWidth { get; set; } - - public double MinWidth { get; set; } = 9.28515625; +namespace MiniExcelLibs.OpenXml; - public double MaxWidth { get; set; } = 200; - } +public class OpenXmlConfiguration : MiniExcelConfiguration +{ + internal static readonly OpenXmlConfiguration DefaultConfig = new(); + + public bool FillMergedCells { get; set; } + public TableStyles TableStyles { get; set; } = TableStyles.Default; + public bool AutoFilter { get; set; } = true; + public int FreezeRowCount { get; set; } = 1; + public int FreezeColumnCount { get; set; } = 0; + public bool EnableConvertByteArray { get; set; } = true; + public bool IgnoreTemplateParameterMissing { get; set; } = true; + public bool EnableWriteNullValueCell { get; set; } = true; + public bool WriteEmptyStringAsNull { get; set; } = false; + public bool TrimColumnNames { get; set; } = true; + public bool IgnoreEmptyRows { get; set; } = false; + public bool EnableSharedStringCache { get; set; } = true; + public long SharedStringCacheSize { get; set; } = 5 * 1024 * 1024; + public OpenXmlStyleOptions StyleOptions { get; set; } = new(); + public DynamicExcelSheet[]? DynamicSheets { get; set; } + public bool EnableWriteFilePath{ get; set; } = true; + + /// + /// Calculate column widths automatically from each column value. + /// + public bool EnableAutoWidth { get; set; } + public double MinWidth { get; set; } = 9.28515625; + public double MaxWidth { get; set; } = 200; } \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/OpenXmlStyleOptions.cs b/src/MiniExcel/OpenXml/OpenXmlStyleOptions.cs index 225dde75..48e3e490 100644 --- a/src/MiniExcel/OpenXml/OpenXmlStyleOptions.cs +++ b/src/MiniExcel/OpenXml/OpenXmlStyleOptions.cs @@ -1,7 +1,6 @@ -namespace MiniExcelLibs.OpenXml +namespace MiniExcelLibs.OpenXml; + +public class OpenXmlStyleOptions { - public class OpenXmlStyleOptions - { - public bool WrapCellContents { get; set; } - } + public bool WrapCellContents { get; set; } } \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/SharedStringsDiskCache.cs b/src/MiniExcel/OpenXml/SharedStringsDiskCache.cs index 34163c2d..a02cd3f2 100644 --- a/src/MiniExcel/OpenXml/SharedStringsDiskCache.cs +++ b/src/MiniExcel/OpenXml/SharedStringsDiskCache.cs @@ -4,155 +4,162 @@ using System.IO; using System.Text; -namespace MiniExcelLibs.OpenXml +namespace MiniExcelLibs.OpenXml; + +internal class SharedStringsDiskCache : IDictionary, IDisposable { - internal class SharedStringsDiskCache : IDictionary, IDisposable - { - private static readonly Encoding _encoding = new UTF8Encoding(true); + private static readonly Encoding Encoding = new UTF8Encoding(true); - private readonly FileStream _positionFs; - private readonly FileStream _lengthFs; - private readonly FileStream _valueFs; - private bool _disposedValue; + private readonly FileStream _positionFs; + private readonly FileStream _lengthFs; + private readonly FileStream _valueFs; + + private long _maxIndx = -1; - private long _maxIndx = -1; - public int Count => checked((int)(_maxIndx + 1)); - public string this[int key] { get => GetValue(key); set => Add(key, value); } - public bool ContainsKey(int key) - { - return key <= _maxIndx; - } + private bool _disposedValue; + + public int Count => checked((int)(_maxIndx + 1)); - public SharedStringsDiskCache() - { - var path = $"{Guid.NewGuid().ToString()}_miniexcelcache"; - _positionFs = new FileStream($"{path}_position", FileMode.OpenOrCreate); - _lengthFs = new FileStream($"{path}_length", FileMode.OpenOrCreate); - _valueFs = new FileStream($"{path}_data", FileMode.OpenOrCreate); - } + public string this[int key] + { + get => GetValue(key); + set => Add(key, value); + } + + public bool ContainsKey(int key) => key <= _maxIndx; - // index must start with 0-N - internal void Add(int index, string value) - { - if (index > _maxIndx) - _maxIndx = index; + public SharedStringsDiskCache() + { + var path = $"{Guid.NewGuid().ToString()}_miniexcelcache"; + + _positionFs = new FileStream($"{path}_position", FileMode.OpenOrCreate); + _lengthFs = new FileStream($"{path}_length", FileMode.OpenOrCreate); + _valueFs = new FileStream($"{path}_data", FileMode.OpenOrCreate); + } + + // index must start with 0-N + internal void Add(int index, string value) + { + if (index > _maxIndx) + _maxIndx = index; - var valueBs = _encoding.GetBytes(value); - if (value.Length > 32767) //check info length, becasue cell string max length is 47483647 - throw new ArgumentOutOfRangeException("", "Excel one cell max length is 32,767 characters"); + var valueBs = Encoding.GetBytes(value); + if (value.Length > 32767) //check info length, becasue cell string max length is 47483647 + throw new ArgumentOutOfRangeException("", "Excel one cell max length is 32,767 characters"); - _positionFs.Write(BitConverter.GetBytes(_valueFs.Position), 0, 4); - _lengthFs.Write(BitConverter.GetBytes(valueBs.Length), 0, 4); - _valueFs.Write(valueBs, 0, valueBs.Length); - } + _positionFs.Write(BitConverter.GetBytes(_valueFs.Position), 0, 4); + _lengthFs.Write(BitConverter.GetBytes(valueBs.Length), 0, 4); + _valueFs.Write(valueBs, 0, valueBs.Length); + } - private string GetValue(int index) - { - _positionFs.Position = index * 4; - var bytes = new byte[4]; - _ = _positionFs.Read(bytes, 0, 4); - var position = BitConverter.ToInt32(bytes, 0); + private string GetValue(int index) + { + _positionFs.Position = index * 4; + var bytes = new byte[4]; + _ = _positionFs.Read(bytes, 0, 4); + var position = BitConverter.ToInt32(bytes, 0); - bytes = new byte[4]; - _lengthFs.Position = index * 4; - _ = _lengthFs.Read(bytes, 0, 4); - var length = BitConverter.ToInt32(bytes, 0); + bytes = new byte[4]; + _lengthFs.Position = index * 4; + _ = _lengthFs.Read(bytes, 0, 4); + var length = BitConverter.ToInt32(bytes, 0); - bytes = new byte[length]; - _valueFs.Position = position; - _ = _valueFs.Read(bytes, 0, length); + bytes = new byte[length]; + _valueFs.Position = position; + _ = _valueFs.Read(bytes, 0, length); - return _encoding.GetString(bytes); - } - - protected virtual void Dispose(bool disposing) - { - if (!_disposedValue) - { - if (disposing) - { - // TODO: dispose managed state (managed objects) - } - - _positionFs.Dispose(); - if (File.Exists(_positionFs.Name)) - File.Delete(_positionFs.Name); - - _lengthFs.Dispose(); - if (File.Exists(_lengthFs.Name)) - File.Delete(_lengthFs.Name); - - _valueFs.Dispose(); - if (File.Exists(_valueFs.Name)) - File.Delete(_valueFs.Name); - - _disposedValue = true; - } - } + return Encoding.GetString(bytes); + } - ~SharedStringsDiskCache() - { - Dispose(disposing: false); - } + public ICollection Keys => throw new NotImplementedException(); + public ICollection Values => throw new NotImplementedException(); + public bool IsReadOnly => throw new NotImplementedException(); + public bool Remove(int key) + { + throw new NotImplementedException(); + } - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - public ICollection Keys => throw new NotImplementedException(); - public ICollection Values => throw new NotImplementedException(); - public bool IsReadOnly => throw new NotImplementedException(); - public bool Remove(int key) - { - throw new NotImplementedException(); - } + public bool TryGetValue(int key, out string value) + { + throw new NotImplementedException(); + } - public bool TryGetValue(int key, out string value) - { - throw new NotImplementedException(); - } + public void Add(KeyValuePair item) + { + throw new NotImplementedException(); + } - public void Add(KeyValuePair item) - { - throw new NotImplementedException(); - } + public void Clear() + { + throw new NotImplementedException(); + } - public void Clear() - { - throw new NotImplementedException(); - } + public bool Contains(KeyValuePair item) + { + throw new NotImplementedException(); + } - public bool Contains(KeyValuePair item) - { - throw new NotImplementedException(); - } + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + throw new NotImplementedException(); + } - public void CopyTo(KeyValuePair[] array, int arrayIndex) - { - throw new NotImplementedException(); - } + public bool Remove(KeyValuePair item) + { + throw new NotImplementedException(); + } - public bool Remove(KeyValuePair item) - { - throw new NotImplementedException(); - } + public IEnumerator> GetEnumerator() + { + for (int i = 0; i < _maxIndx; i++) + yield return new KeyValuePair(i, this[i]); + } - public IEnumerator> GetEnumerator() - { - for (int i = 0; i < _maxIndx; i++) - yield return new KeyValuePair(i, this[i]); - } + IEnumerator IEnumerable.GetEnumerator() + { + for (int i = 0; i < _maxIndx; i++) + yield return this[i]; + } - IEnumerator IEnumerable.GetEnumerator() - { - for (int i = 0; i < _maxIndx; i++) - yield return this[i]; - } + void IDictionary.Add(int key, string value) + { + throw new NotImplementedException(); + } + + + ~SharedStringsDiskCache() + { + Dispose(disposing: false); + } - void IDictionary.Add(int key, string value) + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) { - throw new NotImplementedException(); + if (disposing) + { + // TODO: dispose managed state (managed objects) + } + + _positionFs.Dispose(); + if (File.Exists(_positionFs.Name)) + File.Delete(_positionFs.Name); + + _lengthFs.Dispose(); + if (File.Exists(_lengthFs.Name)) + File.Delete(_lengthFs.Name); + + _valueFs.Dispose(); + if (File.Exists(_valueFs.Name)) + File.Delete(_valueFs.Name); + + _disposedValue = true; } } -} +} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/SheetInfo.cs b/src/MiniExcel/OpenXml/SheetInfo.cs index 11606ddd..a75d05f3 100644 --- a/src/MiniExcel/OpenXml/SheetInfo.cs +++ b/src/MiniExcel/OpenXml/SheetInfo.cs @@ -1,37 +1,31 @@ -namespace MiniExcelLibs.OpenXml +namespace MiniExcelLibs.OpenXml; + +public class SheetInfo(uint id, uint index, string name, SheetState sheetState, bool active) { - public class SheetInfo - { - public SheetInfo(uint id, uint index, string name, SheetState sheetState, bool active) - { - Id = id; - Index = index; - Name = name; - State = sheetState; - Active = active; - } + /// + /// Internal sheet id - depends on the order in which the sheet is added + /// + public uint Id { get; } = id; + + /// + /// Next sheet index - numbered from 0 + /// + public uint Index { get; } = index; - /// - /// Internal sheet id - depends on the order in which the sheet is added - /// - public uint Id { get; } - /// - /// Next sheet index - numbered from 0 - /// - public uint Index { get; } - /// - /// Sheet name - /// - public string Name { get; } - /// - /// Sheet visibility state - /// - public SheetState State { get; } - /// - /// Indicates whether the sheet is active - /// - public bool Active { get; } - } + /// + /// Sheet name + /// + public string Name { get; } = name; - public enum SheetState { Visible, Hidden, VeryHidden } + /// + /// Sheet visibility state + /// + public SheetState State { get; } = sheetState; + + /// + /// Indicates whether the sheet is active + /// + public bool Active { get; } = active; } + +public enum SheetState { Visible, Hidden, VeryHidden } \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/SheetRecord.cs b/src/MiniExcel/OpenXml/SheetRecord.cs index e301b932..ae85edc3 100644 --- a/src/MiniExcel/OpenXml/SheetRecord.cs +++ b/src/MiniExcel/OpenXml/SheetRecord.cs @@ -1,41 +1,24 @@ using System; -namespace MiniExcelLibs.OpenXml +namespace MiniExcelLibs.OpenXml; + +internal sealed class SheetRecord(string name, string state, uint id, string rid, bool active) { - internal sealed class SheetRecord + public string Name { get; } = name; + public string State { get; set; } = state; + public uint Id { get; } = id; + public string Rid { get; set; } = rid; + public string Path { get; set; } + public bool Active { get; } = active; + + public SheetInfo ToSheetInfo(uint index) { - public SheetRecord(string name, string state, uint id, string rid, bool active) - { - Name = name; - State = state; - Id = id; - Rid = rid; - Active = active; - } - - public string Name { get; } - - public string State { get; set; } - - public uint Id { get; } - - public string Rid { get; set; } - - public string Path { get; set; } - - public bool Active { get; } - - public SheetInfo ToSheetInfo(uint index) - { - if (string.IsNullOrEmpty(State)) - { - return new SheetInfo(Id, index, Name, SheetState.Visible, Active); - } - if (Enum.TryParse(State, true, out SheetState stateEnum)) - { - return new SheetInfo(Id, index, Name, stateEnum, Active); - } - throw new ArgumentException($"Unable to parse sheet state. Sheet name: {Name}"); - } + if (string.IsNullOrEmpty(State)) + return new SheetInfo(Id, index, Name, SheetState.Visible, Active); + + if (Enum.TryParse(State, true, out SheetState stateEnum)) + return new SheetInfo(Id, index, Name, stateEnum, Active); + + throw new ArgumentException($"Unable to parse sheet state. Sheet name: {Name}"); } -} +} \ No newline at end of file diff --git a/src/MiniExcel/OpenXml/Styles/DefaultSheetStyleBuilder.cs b/src/MiniExcel/OpenXml/Styles/DefaultSheetStyleBuilder.cs index dbb95e0d..399c2624 100644 --- a/src/MiniExcel/OpenXml/Styles/DefaultSheetStyleBuilder.cs +++ b/src/MiniExcel/OpenXml/Styles/DefaultSheetStyleBuilder.cs @@ -1,416 +1,474 @@ using System.Threading.Tasks; -namespace MiniExcelLibs.OpenXml.Styles +namespace MiniExcelLibs.OpenXml.Styles; + +internal partial class DefaultSheetStyleBuilder : SheetStyleBuilderBase { - internal partial class DefaultSheetStyleBuilder : SheetStyleBuilderBase + private static readonly SheetStyleElementInfos GenerateElementInfos = new SheetStyleElementInfos { - private static readonly SheetStyleElementInfos GenerateElementInfos = new SheetStyleElementInfos - { - NumFmtCount = 0,//The default NumFmt number is 0, but there will be NumFmt dynamically generated based on ColumnsToApply - FontCount = 2, - FillCount = 3, - BorderCount = 2, - CellStyleXfCount = 3, - CellXfCount = 5 - }; + NumFmtCount = 0,//The default NumFmt number is 0, but there will be NumFmt dynamically generated based on ColumnsToApply + FontCount = 2, + FillCount = 3, + BorderCount = 2, + CellStyleXfCount = 3, + CellXfCount = 5 + }; - private readonly SheetStyleBuildContext _context; - private OpenXmlStyleOptions _styleOptions; + private readonly SheetStyleBuildContext _context; + private OpenXmlStyleOptions _styleOptions; - public DefaultSheetStyleBuilder(SheetStyleBuildContext context, OpenXmlStyleOptions styleOptions) : base(context) - { - _context = context; - _styleOptions = styleOptions; - } - - protected override SheetStyleElementInfos GetGenerateElementInfos() - { - return GenerateElementInfos; - } - - [Zomp.SyncMethodGenerator.CreateSyncVersion] - protected override async Task GenerateNumFmtAsync() - { - const int numFmtIndex = 166; - var index = 0; - foreach (var item in _context.ColumnsToApply) - { - index++; + public DefaultSheetStyleBuilder(SheetStyleBuildContext context, OpenXmlStyleOptions styleOptions) : base(context) + { + _context = context; + _styleOptions = styleOptions; + } - /* - * - * - * - * - * - * - * - */ - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "font", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "vertAlign", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "val", null, "baseline").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "sz", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "val", null, "11").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "name", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "val", null, "Calibri").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "family", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "val", null, "2").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + index++; /* - * - * - * - * - * - * - * + * - * - * - */ - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "fill", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "patternFill", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "patternType", null, "none").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + [Zomp.SyncMethodGenerator.CreateSyncVersion] + protected override async Task GenerateFontAsync() + { + /* + * + * + * + * + * + * + * + */ + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "font", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "vertAlign", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "val", null, "baseline").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "sz", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "val", null, "11").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "name", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "val", null, "Calibri").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "family", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "val", null, "2").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - /* - * - * - * - */ - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "fill", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "patternFill", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "patternType", null, "gray125").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + /* + * + * + * + * + * + * + * + */ + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "font", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "vertAlign", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "val", null, "baseline").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "sz", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "val", null, "11").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FFFFFFFF").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "name", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "val", null, "Calibri").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "family", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "val", null, "2").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + } - /* - * - * - * - * - * - */ - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "fill", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "patternFill", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "patternType", null, "solid").ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "fgColor", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "284472C4").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - } + [Zomp.SyncMethodGenerator.CreateSyncVersion] + protected override async Task GenerateFillAsync() + { + /* + * + * + * + */ + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "fill", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "patternFill", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "patternType", null, "none").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - protected override async Task GenerateBorderAsync() - { - /* - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - */ - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "border", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "diagonalUp", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "diagonalDown", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "left", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "none").ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "right", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "none").ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "top", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "none").ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "bottom", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "none").ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "diagonal", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "none").ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + /* + * + * + * + */ + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "fill", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "patternFill", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "patternType", null, "gray125").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - /* - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - */ - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "border", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "diagonalUp", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "diagonalDown", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "left", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "thin").ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "right", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "thin").ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "top", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "thin").ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "bottom", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "thin").ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "diagonal", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "none").ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - } + /* + * + * + * + * + * + */ + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "fill", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "patternFill", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "patternType", null, "solid").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "fgColor", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "284472C4").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + } - [Zomp.SyncMethodGenerator.CreateSyncVersion] - protected override async Task GenerateCellStyleXfAsync() - { - /* - * - * - */ - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "xf", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "numFmtId", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fontId", null, $"{_context.OldElementInfos.FontCount + 0}").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fillId", null, $"{_context.OldElementInfos.FillCount + 0}").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "borderId", null, $"{_context.OldElementInfos.BorderCount + 0}").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyNumberFormat", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyFill", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyBorder", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyAlignment", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyProtection", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "protection", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "locked", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "hidden", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + [Zomp.SyncMethodGenerator.CreateSyncVersion] + protected override async Task GenerateBorderAsync() + { + /* + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "border", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "diagonalUp", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "diagonalDown", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "left", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "none").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "right", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "none").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "top", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "none").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "bottom", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "none").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "diagonal", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "none").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - /* - * - * - */ - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "xf", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "numFmtId", null, "14").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fontId", null, $"{_context.OldElementInfos.FontCount + 1}").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fillId", null, $"{_context.OldElementInfos.FillCount + 2}").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "borderId", null, $"{_context.OldElementInfos.BorderCount + 1}").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyNumberFormat", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyFill", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyBorder", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyAlignment", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyProtection", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "protection", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "locked", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "hidden", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + /* + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "border", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "diagonalUp", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "diagonalDown", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "left", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "thin").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "right", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "thin").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "top", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "thin").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "bottom", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "thin").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "diagonal", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "style", null, "none").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "color", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "rgb", null, "FF000000").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + } - /* - * - * - */ - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "xf", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "numFmtId", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fontId", null, $"{_context.OldElementInfos.FontCount + 0}").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fillId", null, $"{_context.OldElementInfos.FillCount + 0}").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "borderId", null, $"{_context.OldElementInfos.BorderCount + 1}").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyNumberFormat", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyFill", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyBorder", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyAlignment", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyProtection", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "protection", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "locked", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "hidden", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - } + [Zomp.SyncMethodGenerator.CreateSyncVersion] + protected override async Task GenerateCellStyleXfAsync() + { + /* + * + * + */ + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "xf", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "numFmtId", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fontId", null, $"{_context.OldElementInfos.FontCount + 0}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fillId", null, $"{_context.OldElementInfos.FillCount + 0}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "borderId", null, $"{_context.OldElementInfos.BorderCount + 0}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyNumberFormat", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyFill", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyBorder", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyAlignment", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyProtection", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "protection", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "locked", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "hidden", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - [Zomp.SyncMethodGenerator.CreateSyncVersion] - protected override async Task GenerateCellXfAsync() - { - /* - * - */ - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "xf", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + /* + * + * + */ + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "xf", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "numFmtId", null, "14").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fontId", null, $"{_context.OldElementInfos.FontCount + 1}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fillId", null, $"{_context.OldElementInfos.FillCount + 2}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "borderId", null, $"{_context.OldElementInfos.BorderCount + 1}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyNumberFormat", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyFill", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyBorder", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyAlignment", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyProtection", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "protection", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "locked", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "hidden", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - /* - * - * - * - */ - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "xf", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "numFmtId", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fontId", null, $"{_context.OldElementInfos.FontCount + 1}").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fillId", null, $"{_context.OldElementInfos.FillCount + 2}").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "borderId", null, $"{_context.OldElementInfos.BorderCount + 1}").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "xfId", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyNumberFormat", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyFill", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyBorder", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyAlignment", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyProtection", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "alignment", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "horizontal", null, "left").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "vertical", null, "bottom").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "textRotation", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "wrapText", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "indent", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "relativeIndent", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "justifyLastLine", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "shrinkToFit", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "readingOrder", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "protection", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "locked", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "hidden", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + /* + * + * + */ + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "xf", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "numFmtId", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fontId", null, $"{_context.OldElementInfos.FontCount + 0}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fillId", null, $"{_context.OldElementInfos.FillCount + 0}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "borderId", null, $"{_context.OldElementInfos.BorderCount + 1}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyNumberFormat", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyFill", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyBorder", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyAlignment", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyProtection", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "protection", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "locked", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "hidden", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + } - /* - * - * - * - */ - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "xf", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "numFmtId", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fontId", null, $"{_context.OldElementInfos.FontCount + 0}").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fillId", null, $"{_context.OldElementInfos.FillCount + 0}").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "borderId", null, $"{_context.OldElementInfos.BorderCount + 1}").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "xfId", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyNumberFormat", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyFill", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyBorder", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyAlignment", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyProtection", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "alignment", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "horizontal", null, "general").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "vertical", null, "bottom").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "textRotation", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "wrapText", null, _styleOptions.WrapCellContents ? "1" : "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "indent", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "relativeIndent", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "justifyLastLine", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "shrinkToFit", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "readingOrder", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "protection", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "locked", null, "1").ConfigureAwait(false); - await _context.NewXmlWriter.WriteAttributeStringAsync(null, "hidden", null, "0").ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); - await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + [Zomp.SyncMethodGenerator.CreateSyncVersion] + protected override async Task GenerateCellXfAsync() + { + /* + * + */ + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "xf", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + + /* + * + * + * + */ + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "xf", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "numFmtId", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fontId", null, $"{_context.OldElementInfos.FontCount + 1}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fillId", null, $"{_context.OldElementInfos.FillCount + 2}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "borderId", null, $"{_context.OldElementInfos.BorderCount + 1}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "xfId", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyNumberFormat", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyFill", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyBorder", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyAlignment", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyProtection", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "alignment", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "horizontal", null, "left").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "vertical", null, "bottom").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "textRotation", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "wrapText", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "indent", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "relativeIndent", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "justifyLastLine", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "shrinkToFit", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "readingOrder", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "protection", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "locked", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "hidden", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + + /* + * + * + * + */ + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "xf", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "numFmtId", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fontId", null, $"{_context.OldElementInfos.FontCount + 0}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fillId", null, $"{_context.OldElementInfos.FillCount + 0}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "borderId", null, $"{_context.OldElementInfos.BorderCount + 1}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "xfId", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyNumberFormat", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyFill", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyBorder", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyAlignment", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyProtection", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "alignment", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "horizontal", null, "general").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "vertical", null, "bottom").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "textRotation", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "wrapText", null, _styleOptions.WrapCellContents ? "1" : "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "indent", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "relativeIndent", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "justifyLastLine", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "shrinkToFit", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "readingOrder", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "protection", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "locked", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "hidden", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + + /* + * + * + * + */ + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "xf", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "numFmtId", null, "14").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fontId", null, $"{_context.OldElementInfos.FontCount + 0}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fillId", null, $"{_context.OldElementInfos.FillCount + 0}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "borderId", null, $"{_context.OldElementInfos.BorderCount + 1}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "xfId", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyNumberFormat", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyFill", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyBorder", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyAlignment", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyProtection", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "alignment", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "horizontal", null, "general").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "vertical", null, "bottom").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "textRotation", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "wrapText", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "indent", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "relativeIndent", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "justifyLastLine", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "shrinkToFit", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "readingOrder", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "protection", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "locked", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "hidden", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + + /* + * + * + * + */ + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "xf", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "numFmtId", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fontId", null, $"{_context.OldElementInfos.FontCount + 0}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "fillId", null, $"{_context.OldElementInfos.FillCount + 0}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "borderId", null, $"{_context.OldElementInfos.BorderCount + 1}").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "xfId", null, "0").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyBorder", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "applyAlignment", null, "1").ConfigureAwait(false); + await _context.NewXmlWriter.WriteStartElementAsync(_context.OldXmlReader.Prefix, "alignment", _context.OldXmlReader.NamespaceURI).ConfigureAwait(false); + await _context.NewXmlWriter.WriteAttributeStringAsync(null, "horizontal", null, "fill").ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + await _context.NewXmlWriter.WriteEndElementAsync().ConfigureAwait(false); + + const int numFmtIndex = 166; + var index = 0; + foreach (var item in _context.ColumnsToApply) + { + index++; /* - * - * - *