├── .gitattributes ├── .github └── workflows │ └── cibuild.yml ├── .gitignore ├── LICENSE.txt ├── NuGet.config ├── README.md ├── USAGE.md ├── doc ├── formatting.png ├── patternstyles.png ├── repeatrows.png ├── simplexcelxml.png └── titleauthor.png ├── package.png ├── scripts ├── build.ps1 ├── buildFunctions.ps1 └── buildProperties.txt └── src ├── Simplexcel.MvcTestApp ├── ExcelResultBase.cs ├── ExcelTestActionResult.cs ├── Global.asax ├── Global.asax.cs ├── HomeController.cs ├── Properties │ └── AssemblyInfo.cs ├── Simplexcel.MvcTestApp.csproj ├── Views │ ├── Home │ │ └── Index.cshtml │ └── Web.config ├── Web.config ├── favicon.ico └── packages.config ├── Simplexcel.TestApp ├── Program.cs └── Simplexcel.TestApp.csproj ├── Simplexcel.Tests ├── BugTests.cs ├── CellAddressTests.cs ├── CellCollectionTests.cs ├── CellTests.cs ├── ColRowCountTests.cs ├── Simplexcel.Tests.csproj └── WorksheetTests.cs ├── Simplexcel.sln ├── Simplexcel ├── CellAddressHelper.cs ├── Cells │ ├── BuiltInCellFormat.cs │ ├── Cell.cs │ ├── CellAddress.cs │ ├── CellBorder.cs │ ├── CellCollection.cs │ ├── CellType.cs │ ├── GradientFill.cs │ ├── GradientStop.cs │ ├── HorizontalAlign.cs │ ├── IgnoredError.cs │ ├── PatternFill.cs │ ├── PatternType.cs │ ├── VerticalAlign.cs │ └── XlsxColumnAttribute.cs ├── Color.cs ├── ColumnWidthCollection.cs ├── ExtensionMethods.Internal.cs ├── ExtensionMethods.cs ├── GlobalSuppressions.cs ├── InternalsVisibleTo.cs ├── LargeNumberHandlingMode.cs ├── PageSetup │ ├── Orientation.cs │ ├── PageBreak.cs │ └── PageSetup.cs ├── Simplexcel.csproj ├── SimplexcelVersion.cs ├── Workbook.cs ├── Worksheet.Populate.cs ├── Worksheet.cs ├── WorksheetView │ ├── Pane.cs │ ├── PaneState.cs │ ├── Panes.cs │ ├── Selection.cs │ └── SheetView.cs └── XlsxInternal │ ├── Namespaces.cs │ ├── Relationship.cs │ ├── RelationshipCounter.cs │ ├── SharedStrings.cs │ ├── SheetPackageInfo.cs │ ├── StyleWriter.cs │ ├── XlsxCell.cs │ ├── XlsxCellStyle.cs │ ├── XlsxCellTypes.cs │ ├── XlsxExtensionMethods.cs │ ├── XlsxFont.cs │ ├── XlsxIgnoredError.cs │ ├── XlsxIgnoredErrorCollection.cs │ ├── XlsxPackage.cs │ ├── XlsxRow.cs │ ├── XlsxWriter.cs │ ├── XmlFile.cs │ └── ZipPackage.cs ├── simplexcel_oss.snk └── simplexcel_oss.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.github/workflows/cibuild.yml: -------------------------------------------------------------------------------- 1 | name: CI Build 2 | 3 | on: 4 | pull_request: 5 | push: 6 | paths: 7 | - "src/**" 8 | - ".github/workflows/**" 9 | - "scripts/**" 10 | 11 | # To avoid vendor lock-in, this is mostly driven by a build script 12 | jobs: 13 | build-linux: # Sanity check build 14 | name: Linux 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v2 19 | with: 20 | fetch-depth: 0 21 | - name: Run build script 22 | shell: pwsh 23 | run: | 24 | $Env:GIT_BRANCH=$Env:GITHUB_REF 25 | $Env:build_artifactsDir=$Env:RUNNER_TEMP 26 | cd scripts 27 | ./build.ps1 28 | Get-ChildItem Env: | Where-Object {$_.Name -Match "^MH_"} | %{ echo "::set-output name=$($_.Name)::$($_.Value)" } 29 | build: # Actual prod build 30 | name: Windows 31 | runs-on: windows-latest 32 | steps: 33 | - name: Checkout code 34 | uses: actions/checkout@v2 35 | with: 36 | fetch-depth: 0 37 | # To avoid vendor lock-in, this is mostly driven by a build script 38 | - name: Run build script 39 | id: pwshbuild 40 | shell: pwsh 41 | run: | 42 | $Env:GIT_BRANCH=$Env:GITHUB_REF 43 | $Env:build_artifactsDir=$Env:RUNNER_TEMP 44 | cd scripts 45 | ./build.ps1 46 | Get-ChildItem Env: | Where-Object {$_.Name -Match "^MH_"} | %{ echo "::set-output name=$($_.Name)::$($_.Value)" } 47 | - name: Publish to GPR (Development) 48 | if: success() && !startsWith(github.ref, 'refs/pull') && steps.pwshbuild.outputs.MH_IS_PROD_BUILD == 'False' 49 | shell: pwsh 50 | run: | 51 | $mhPath = "${{ steps.pwshbuild.outputs.MH_BUILD_OUTPUT }}" 52 | $nupkgName = "simplexcel.${{ steps.pwshbuild.outputs.MH_BUILD_VERSION_PACKAGE }}.nupkg" 53 | $nupkgPath = Join-Path -Path $mhPath -ChildPath $nupkgName 54 | 55 | # Publish to GitHub Package Repository 56 | Write-Host "Dev build, pushing to GPR: $nupkgPath" 57 | dotnet nuget push "$nupkgPath" --source https://nuget.pkg.github.com/mstum --api-key ${{ secrets.GITHUB_TOKEN }} --skip-duplicate --no-symbols 58 | - name: Publish to Nuget.org (Release) 59 | if: success() && steps.pwshbuild.outputs.MH_IS_PROD_BUILD == 'True' 60 | shell: pwsh 61 | run: | 62 | $mhPath = "${{ steps.pwshbuild.outputs.MH_BUILD_OUTPUT }}" 63 | $nupkgName = "simplexcel.${{ steps.pwshbuild.outputs.MH_BUILD_VERSION_PACKAGE }}.nupkg" 64 | $nupkgPath = Join-Path -Path $mhPath -ChildPath $nupkgName 65 | 66 | # Publish to Nuget.org 67 | Write-Host "Release build, pushing to Nuget.org: $nupkgPath" 68 | dotnet nuget push "$nupkgPath" -k ${{ secrets.NUGET_KEY }} -s https://api.nuget.org/v3/index.json --no-symbols 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.userprefs 2 | doc/out 3 | *resharper.user 4 | [Dd]ebug/ 5 | [Rr]elease/ 6 | build/ 7 | [Bb]in/ 8 | [Oo]bj/ 9 | pkgobj/ 10 | pkg/ 11 | *.suo 12 | *.sln.cache 13 | _ReSharper.*/ 14 | *.user 15 | ~$* 16 | TestResults/ 17 | *.testsettings 18 | *.vsmdi 19 | _Debug 20 | *.swp 21 | .vs 22 | .vscode 23 | .idea 24 | packages/ 25 | nuget/ 26 | .DS_Store 27 | Thumbs.db 28 | Desktop.ini 29 | BenchmarkDotNet.Artifacts 30 | src/Simplexcel.TestApp/*.xlsx 31 | artifacts/ -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2017 Michael Stum, http://www.Stum.de 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /NuGet.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simplexcel 2 | 3 | ## This project is discontinued 4 | 5 | Please see https://github.com/mstum/Simplexcel/issues/43 6 | 7 | # Old README.md for 3.1.0.195 8 | 9 | | master | release | 10 | | ------ | ------- | 11 | | [![Actions Status](https://github.com/mstum/Simplexcel/workflows/CI%20Build/badge.svg?branch=master)](https://github.com/mstum/Simplexcel/actions) | [![Actions Status](https://github.com/mstum/Simplexcel/workflows/CI%20Build/badge.svg?branch=release)](https://github.com/mstum/Simplexcel/actions) | 12 | 13 | This is a simple .xlsx generator library for .net 4.5, .NET Standard 2.0, and .NET Standard 2.1. 14 | 15 | It does not aim to implement the entirety of the Office Open XML Workbook format and all the small and big features Excel offers. 16 | Instead, it is meant as a way to handle common tasks that can't be handled by other workarounds (e.g., CSV Files or HTML Tables) and is fully supported under ASP.net (unlike, say, COM Interop which Microsoft explicitly doesn't support on a server). 17 | 18 | # Features 19 | * You can store numbers as numbers, so no more unwanted conversion to scientific notation on large numbers! 20 | * You can store text that looks like a number as text, so no more truncation of leading zeroes because Excel thinks it's a number 21 | * You can have multiple Worksheets 22 | * You have basic formatting: Font Name/Size, Bold/Underline/Italic, Color, Border around a cell 23 | * You can specify the size of cells 24 | * Workbooks can be saved compressed or uncompressed (CPU Load vs. File Size) 25 | * You can specify repeating rows and columns (from the top and left respectively), useful when printing. 26 | * Fully supported in ASP.net and Windows Services 27 | * Supports .net Core 28 | 29 | # Usage 30 | See [USAGE.md](https://github.com/mstum/Simplexcel/blob/master/USAGE.md) for instructions how to use. 31 | 32 | # Changelog 33 | ## 3.1.0 (2022-04-30) 34 | * Support for Standard built-in number formats, which localize properly (PR [#36](https://github.com/mstum/Simplexcel/pull/36) and [#37](https://github.com/mstum/Simplexcel/pull/37)) 35 | * Support for [AutoFilter](https://support.microsoft.com/en-us/office/use-autofilter-to-filter-your-data-7d87d63e-ebd0-424b-8106-e2ab61133d92) (PR [#35](https://github.com/mstum/Simplexcel/pull/35)) 36 | * Thanks to [0xced](https://github.com/0xced) for these! 37 | 38 | ## 3.0.2 (2020-12-25) 39 | * Add [`SourceLink`](https://docs.microsoft.com/en-us/dotnet/standard/library-guidance/sourcelink) support 40 | 41 | ## 3.0.1 (2019-11-20) 42 | * Fix `TypeInitializationException` in SimplexcelVersion in some contexts (e.g., UWP, Xamarin) ([Issue #30](https://github.com/mstum/Simplexcel/issues/30)) 43 | 44 | ## 3.0.0 (2019-11-08) 45 | * Remove targeting netstandard1.3, add targeting for netstandard2.1 46 | * The library is now signed and strongly named for improved compatibility 47 | * The AssemblyVersion is `3.0.0.0` and will not change in the future 48 | * The AssemblyFileVersion and AssemblyInformationalVersion will contain the actual version number 49 | * The actual signing key is checked in as [`simplexcel_oss.snk`](src/simplexcel_oss.snk), and a dump of the public key and token is in [`simplexcel_oss.txt`](src/simplexcel_oss.txt) 50 | * There is a static `SimplexcelVersion` class with some helpers: 51 | * PublicKeyToken is the public key token for the assembly (e.g., 65e777c740a5d92a) 52 | * PublicKey is the full public key token for the assembly (e.g., 0024000004800000940000000602000000240000525341310...) 53 | * VersionString is the AssemblyInformationalVersion as a string, which may include a suffix if it's a development version (e.g., 2.3.0.177-v3-dev) 54 | * Version is the AssemblyFileVersion as a Version object, which does include any suffix (e.g., 2.3.0.177) 55 | * No functional changes, just making sure that this is independent of future changes 56 | 57 | ## 2.3.0 (2019-11-02) 58 | * Add `Worksheet.FreezeTopLeft` method (by @bcopeland in PR #26) to freeze more than just the top row/left column 59 | * Support for Formulas, for example: `sheet.Cells["B4"] = Cell.Formula("MEDIAN(A:A)");`. See [the test app](https://github.com/mstum/Simplexcel/blob/0e22dddfcb26b9672ba3ccab6d229da7535127e7/src/Simplexcel.TestApp/Program.cs#L167) for some examples 60 | 61 | ## 2.2.1 (2018-09-19) 62 | * Fixed bug where Background Color wasn't correctly applied to a Fill. ([Issue 23](https://github.com/mstum/Simplexcel/issues/23)) 63 | 64 | ## 2.2.0 (2018-02-24) 65 | * Add `IgnoredErrors` to a `Cell`, to disable Excel warnings (like "Number stored as text"). 66 | * If `LargeNumberHandlingMode.StoreAsText` is set on a sheet, the "Number stored as Text" warning is automatically disabled for that cell. 67 | * Add `Cell.Fill` property, which allows setting the Fill of the cell, including the background color, pattern type (diagonal, crosshatch, grid, etc.) and pattern color 68 | * Add `netstandard2.0` version, on top of the existing `netstandard1.3` and `net45` versions. 69 | 70 | ## 2.1.0 (2017-09-25) 71 | * **Functional Change:** Numbers with more than 11 digits are forced as Text by Default, because [of a limitation in Excel](https://support.microsoft.com/en-us/help/2643223/long-numbers-are-displayed-incorrectly-in-excel). To restore the previous functionality, you can set `Worksheet.LargeNumberHandlingMode` to `LargeNumberHandlingMode.None`. You can also use `Cell.IsLargeNumber` to check if a given number would be affected by this. 72 | * **Functional Change:** `Worksheet.Populate`/`Worksheet.FromData` now also reads properties from base classes. 73 | * `Worksheet.Populate`/`Worksheet.FromData` accept a new argument, `cacheTypeColumns` which defaults to false. If set to true, then Simplexcel will cache the Reflection-based lookup of object properties. This is useful for if you have a few types that you create sheets from a lot. 74 | * You can add `[XlsxColumn]` to a Property so that `Worksheet.Populate`/`Worksheet.FromData` can set the column name and a given column order. *Caveat:* If you set `ColumnIndex` on some, but not all Properties, the properties without a `ColumnIndex` will be on the right of the last assigned column, even if that means gaps. I recommend that you either set `ColumnIndex` on all properties or none. 75 | * You can add `[XlsxIgnoreColumn]` to a Property so that `Worksheet.Populate`/`Worksheet.FromData` ignores it. 76 | * Added `Cell.HorizontalAlignment` and `Cell.VerticalAlignment` to allow setting the alignment of a cell (left/center/right/justify, top/middle/bottom/justify). 77 | * Added XmlDoc to Nuget package, so you should get Intellisense with proper comments now. 78 | 79 | ## 2.0.5 (2017-09-23) 80 | * Add support for manual page breaks. Call `Worksheet.InsertManualPageBreakAfterRow` or `Worksheet.InsertManualPageBreakAfterColumn` with either the zero-based index of the row/column after which to create the break, or with a cell address (e.g., B5) to create the break below or to the left of that cell. 81 | 82 | ## 2.0.4 (2017-09-17) 83 | * Support for [freezing panes](https://support.office.com/en-us/article/Freeze-panes-to-lock-rows-and-columns-dab2ffc9-020d-4026-8121-67dd25f2508f). Right now, this is being kept simple: call either `Worksheet.FreezeTopRow` or `Worksheet.FreezeLeftColumn` to freeze either the first row (1) or the leftmost column (A). 84 | * If a Stream is not seekable (e.g., HttpContext.Response.OutputStream), Simplexcel automatically creates a temporary MemoryStream as an intermediate. 85 | * Add `Cell.FromObject` to make Cell creation easier by guessing the correct type. 86 | * Support `DateTime` cells, thanks to @mguinness and PR #16. 87 | 88 | ## 2.0.3 (2017-09-08) 89 | * Add `Worksheet.Populate` method to fill a sheet with data. Caveats: Does not loot at inherited members, doesn't look at complex types. 90 | * Also add static `Worksheet.FromData` method to create and populate the sheet in one. 91 | 92 | ## 2.0.2 (2017-06-17) 93 | * Add additional validation when saving to a Stream. The stream must be seekable (and of course writeable), otherwise an Exception is thrown. 94 | 95 | ## 2.0.1 (2017-05-18) 96 | * Fix [Issue #12](https://github.com/mstum/Simplexcel/issues/12): Sanitizing Regex stripped out too many characters (like the Ampersand or Emojis). Note that certain Unicode characters only work on newer versions of Excel (e.g., Emojis work in Excel 2013 but not 2007 or 2010). 97 | 98 | ## 2.0.0 (2017-04-22) 99 | * Re-target to .net Framework 4.5 and .NET Standard 1.3. 100 | * No longer use `System.Drawing.Color` but new type `Simplexcel.Color` should work. 101 | * Classes no longer use Data Contract serializer, hence no more `[DataContract]`, `[DataMember]`, etc. attributes. 102 | * Remove `CompressionLevel` - the entire creation of the actual .xlsx file is re-done (no more dependency on `System.IO.Packaging`) and compression is now a simple bool. 103 | 104 | ## 1.0.5 (2014-01-30) 105 | * SharedStrings are sanitized to avoid XML Errors when using Escape chars (like 0x1B). 106 | 107 | ## 1.0.4 (2014-01-21) 108 | * Workbook.Save throws an InvalidOperationException if there are no sheets. 109 | 110 | ## 1.0.3 (2013-08-20) 111 | * Added support for external hyperlinks. 112 | * Made Workbooks serializable using the .net DataContractSerializer. 113 | 114 | ## 1.0.2 (2013-01-10) 115 | * Initial Public Release. 116 | 117 | # License 118 | http://mstum.mit-license.org/ 119 | 120 | The MIT License (MIT) 121 | 122 | Copyright (c) 2013-2017 Michael Stum, http://www.Stum.de 123 | Contains contributions by [@mguinness](https://github.com/mguinness) 124 | 125 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 126 | 127 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 128 | 129 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 130 | -------------------------------------------------------------------------------- /USAGE.md: -------------------------------------------------------------------------------- 1 | ## Install Nuget Package 2 | The Library is available on NuGet and can be installed by searching for Simplexcel or using the NuGet command prompt: 3 | 4 | Install-Package simplexcel 5 | 6 | For more information on NuGet see http://www.nuget.org 7 | 8 | ## Examples 9 | ### Creating a Workbook 10 | The simple example of creating a workbook only requires you to create a few Worksheets, populate their Cells, and add them to a new Workbook. 11 | 12 | ```cs 13 | // using Simplexcel; 14 | var sheet = new Worksheet("Hello, world!"); 15 | sheet.Cells[0, 0] = "Hello,"; 16 | sheet.Cells["B1"] = "World!"; 17 | 18 | var workbook = new Workbook(); 19 | workbook.Add(sheet); 20 | workbook.Save(@"d:\test.xlsx"); 21 | ``` 22 | 23 | ### Cell creation 24 | Cells can be created implicitly from strings, int64's, double and decimal (and anything that's implicitly convertible to those, e.g. int32), DateTime, or explicitly through the Cell constructor. 25 | 26 | ```cs 27 | Cell textCell = "fromString"; 28 | Cell intCell = 4; // will be formatted as number without decimal places "0" 29 | Cell doubleCell = 123.456; // will be formatted with 2 decimal places, "0.00" 30 | Cell decimalCell = 987.654321m; // will be formatted with 2 decimal places, "0.00" 31 | decimalCell.Format = "0.000"; // override the cell format with an Excel Formatting string 32 | Cell dateCell = DateTime.Now; // will be formatted with date and time portions 33 | Cell cellFromStaticFactory = Cell.FromObject(myObj.SomeProp); // will try to guess the cell type based on the type of the object - note that this causes boxing 34 | 35 | // Explicit constructor, specifying the type, value and format (Excel will display this as 88.75%) 36 | Cell decimalWithFormat = new Cell(CellType.Number, 0.8875m, BuiltInCellFormat.PercentTwoDecimalPlaces); 37 | ``` 38 | 39 | ### Cell formatting 40 | After you've added a cell, you can set formatting like Bold or Font Size or Border on it. 41 | 42 | ```cs 43 | // Addressing cells via zero-based row and column index... 44 | sheet.Cells[0, 0] = "Hello,"; 45 | sheet.Cells[0, 0].Bold = true; 46 | // ...or Excel-style references, B1 = [0,1] 47 | sheet.Cells["B1"] = "World!"; 48 | sheet.Cells["B1"].Border = CellBorder.Bottom | CellBorder.Right; 49 | 50 | Cell myCell = "Test Cell"; 51 | myCell.FontName = "Comic Sans MS"; 52 | myCell.FontSize = 18; 53 | myCell.TextColor = Color.Violet; 54 | myCell.Bold = true; 55 | myCell.Italic = true; 56 | myCell.Underline = true; 57 | myCell.Border = CellBorder.All; // Left | Right | Top | Bottom 58 | sheet.Cells[0, 2] = myCell; 59 | 60 | // To change the width of a column, specify a value. Specifying NULL will use the default 61 | sheet.ColumnWidths[2] = 30; 62 | ``` 63 | 64 | In Excel, this will create a beautiful sheet: 65 | ![Cell formatting](/doc/formatting.png?raw=true "Cell formatting") 66 | 67 | ### Page Setup 68 | A Worksheet has a PageSetup property that contains the orientation (portrait or landscape) and a setting to repeat a number of rows or columns (starting from the top and left respectively) on every page when printing. 69 | 70 | ```cs 71 | var sheet = new Worksheet("Hello, world!"); 72 | sheet.PageSetup.PrintRepeatRows = 2; // How many rows (starting with the top one) 73 | sheet.PageSetup.PrintRepeatColumns = 0; // How many columns (starting with the left one, 0 is default) 74 | sheet.PageSetup.Orientation = Orientation.Landscape; 75 | 76 | 77 | sheet.Cells["A1"] = "Title!"; 78 | sheet.Cells["A1"].Bold = true; 79 | sheet.Cells["A2"] = "Subtitle!"; 80 | sheet.Cells["A2"].Bold = true; 81 | sheet.Cells["A2"].TextColor = Color.Magenta; 82 | for (int i = 0; i < 100; i++) 83 | { 84 | sheet.Cells[i + 2, 0] = "Entry Number " + (i + 1); 85 | } 86 | ``` 87 | 88 | This looks like this when printing: 89 | ![Page Setup](/doc/repeatrows.png?raw=true "Page Setup") 90 | 91 | ### Hyperlinks 92 | You can create Hyperlinks for a cell. 93 | 94 | ```cs 95 | sheet.Cells["A1"] = "Click me now!"; 96 | sheet.Cells["A1"].Hyperlink = "https://github.com/mstum/Simplexcel/"; 97 | ``` 98 | 99 | This will NOT automatically format it as a Hyperlink (blue/underlined) to give you freedom to format as desired. 100 | 101 | ### Misc. 102 | You can specify if the .xlsx file should be compressed. 103 | 104 | ```cs 105 | workbook.Save(@"d:\testCompressed.xlsx", true); // Smaller file, more CPU usage, Default 106 | workbook.Save(@"d:\testUncompressed.xlsx", false); // Larger file, less CPU usage 107 | ``` 108 | 109 | You can also save to a Stream. The Stream must be writeable. 110 | ``` 111 | workbook.Save(HttpContext.Current.Response.OutputStream); 112 | ``` 113 | 114 | The workbook class allows you to add an Author and Title, which will show up in the Properties pane. 115 | 116 | ```cs 117 | var workbook = new Workbook(); 118 | workbook.Title = "Hello, World!"; 119 | workbook.Author = "My Application Version 1.0.0.5"; 120 | ``` 121 | ![Title and Author](/doc/titleauthor.png?raw=true "Title and Author") 122 | 123 | To help troubleshooting issues with generated xlsx files, they contain a file called simplexcel.xml which contains version information and the date the document was generated. 124 | 125 | ![simplexcel.xml](/doc/simplexcelxml.png?raw=true "simplexcel.xml") 126 | 127 | ```xml 128 | 129 | 130 | 2017-04-07T21:04:18.5220876Z 131 | 132 | ``` 133 | 134 | ### Caveats and Exceptions 135 | Here's a list of things to be aware of when working with Simplexcel 136 | 137 | ```cs 138 | // Sheet names cannot be NULL or empty 139 | new Worksheet(null); // ArgumentException 140 | new Worksheet(""); // ArgumentException 141 | 142 | // Sheet names cannot be longer than 31 chars 143 | new Worksheet(new string('a', 32)); // ArgumentException 144 | 145 | // Sheet names cannot contain these chars: \ / ? * [ ] 146 | new Worksheet("Data for 09/22/2012"); // ArgumentException 147 | 148 | // There are static properties on the Worksheet class to help you create valid names 149 | int maxLength = Worksheet.MaxSheetNameLength; 150 | char[] invalidChars = Worksheet.InvalidSheetNameChars; 151 | 152 | // Within a workbook, sheet names must be unique 153 | var wb = new Workbook(); 154 | var sheet1 = new Worksheet("Hello!"); 155 | var sheet2 = new Worksheet("Hello!"); 156 | wb.Add(sheet1); 157 | wb.Add(sheet2); // ArgumentException 158 | 159 | // Accessing a cell before it's been assigned returns NULL 160 | sheet1.Cells["A1"].Bold = true; // NullReferenceException 161 | ``` 162 | 163 | ## ActionResult for ASP.net MVC 164 | If you want to generate Excel Sheets as part of an ASP.net MVC action, you can use this abstract class as an ActionResult. A derived class would simply override GenerateWorkbook. This assumes that the controller merely creates a model, and that the actual workbook generation happens after the action is executed and ActionResult.ExecuteResult is called. Alternatively, you can change it to take a Workbook in the constructor and create the Workbook in the MVC Controller Action. 165 | 166 | ```cs 167 | public abstract class ExcelResultBase : ActionResult 168 | { 169 | private readonly string _filename; 170 | 171 | protected ExcelResultBase(string filename) 172 | { 173 | _filename = filename; 174 | } 175 | 176 | protected abstract Workbook GenerateWorkbook(); 177 | 178 | public override void ExecuteResult(ControllerContext context) 179 | { 180 | if (context == null) 181 | { 182 | throw new ArgumentNullException("context"); 183 | } 184 | 185 | var workbook = GenerateWorkbook(); 186 | if (workbook == null) 187 | { 188 | context.HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; 189 | return; 190 | } 191 | 192 | context.HttpContext.Response.ContentType = "application/octet-stream"; 193 | context.HttpContext.Response.StatusCode = (int)HttpStatusCode.OK; 194 | context.HttpContext.Response.AppendHeader("content-disposition", "attachment; filename=\"" + _filename + "\""); 195 | 196 | workbook.Save(context.HttpContext.Response.OutputStream); 197 | } 198 | } 199 | ``` 200 | 201 | ## Fills (since 2.2.0) 202 | You can set the Fill of a cell, for example: 203 | 204 | ```cs 205 | // Specify all parameters of a PatternFill 206 | sheet.Cells["C5"].Fill = new PatternFill { PatternType = PatternType.ThinDiagonalCrosshatch, BackgroundColor = Color.Yellow, PatternColor = Color.Violet }; 207 | 208 | // Setting only a background color will set the PatternColor to be automatically chosen by Excel 209 | sheet.Cells["C7"].Fill = new PatternFill { PatternType = PatternType.DiagonalCrosshatch, BackgroundColor = Color.Violet }; 210 | 211 | // Only set the background color. This automatically sets the PatternType to Solid the first time. 212 | sheet.Cells["C9"].Fill.BackgroundColor = Color.Navy; 213 | ``` 214 | 215 | The `PatternType` follows the naming convention in Excel: 216 | 217 | ![Pattern Styles](/doc/patternstyles.png?raw=true "Pattern Styles") 218 | 219 | * Solid, Gray750, Gray500, Gray250, Gray125, Gray0625 (That's 75%, 50%, 25%, 12.5% and 6.25% Gray) 220 | * HorizontalStripe, VerticalStripe, ReverseDiagonalStripe, DiagonalStripe, DiagonalCrosshatch, ThickDiagonalCrosshatch 221 | * ThinHorizontalStripe, ThinVerticalStripe, ThinReverseDiagonalStripe, ThinDiagonalStripe, ThinHorizontalCrosshatch, ThinDiagonalCrosshatch 222 | 223 | ## Formulas (since 2.3.0) 224 | You can assign a formula to a cell, for example: 225 | 226 | ```cs 227 | sheet.Cells["A1"] = 5; 228 | sheet.Cells["A2"] = 10; 229 | sheet.Cells["A3"] = 15; 230 | sheet.Cells["B1"] = Cell.Formula("SUM(A:A)"); 231 | sheet.Cells["B2"] = Cell.Formula("SUM(A1:A2)"); 232 | sheet.Cells["B3"] = Cell.Formula("AVERAGE(A:A)"); 233 | sheet.Cells["B4"] = Cell.Formula("MEDIAN(A:A)"); 234 | sheet.Cells["C1"] = Cell.Formula("A1+B1"); 235 | sheet.Cells["C2"] = Cell.Formula("=SUM(B:B)"); 236 | sheet.Cells["C3"] = Cell.Formula("C5+A1"); 237 | sheet.Cells["C5"] = Cell.Formula("SUM(B:B)+C1"); 238 | ``` 239 | 240 | Formulas are interpreted by the application (Excel, LibreOffice Calc, etc.) and functionality might this differ between 241 | different applications or versions (e.g., XLOOKUP was added to Excel in late 2019 and is not available on most versions). -------------------------------------------------------------------------------- /doc/formatting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mstum/Simplexcel/a41b291104d14c85d9f497695d85a79f477e6196/doc/formatting.png -------------------------------------------------------------------------------- /doc/patternstyles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mstum/Simplexcel/a41b291104d14c85d9f497695d85a79f477e6196/doc/patternstyles.png -------------------------------------------------------------------------------- /doc/repeatrows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mstum/Simplexcel/a41b291104d14c85d9f497695d85a79f477e6196/doc/repeatrows.png -------------------------------------------------------------------------------- /doc/simplexcelxml.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mstum/Simplexcel/a41b291104d14c85d9f497695d85a79f477e6196/doc/simplexcelxml.png -------------------------------------------------------------------------------- /doc/titleauthor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mstum/Simplexcel/a41b291104d14c85d9f497695d85a79f477e6196/doc/titleauthor.png -------------------------------------------------------------------------------- /package.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mstum/Simplexcel/a41b291104d14c85d9f497695d85a79f477e6196/package.png -------------------------------------------------------------------------------- /scripts/build.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [switch] $Verbose = $false 3 | ) 4 | try { 5 | if ($Verbose -eq $true) { 6 | $PSDefaultParameterValues['*:Verbose'] = $true 7 | $VerbosePreference = "Continue" 8 | $DebugPreference = "Continue" 9 | } 10 | 11 | $InformationPreference = "Continue" 12 | $WarningPreference = "Continue" 13 | $ErrorActionPreference = "Stop" 14 | 15 | Import-module .\buildFunctions.ps1 -Force 16 | 17 | Build-WriteTitle "Reading Build Properties and Environment" 18 | $buildProps = Build-ReadProperties 19 | #Build-Log-Hashtable $buildProps 20 | 21 | $solution = Build-GetRequiredProperty $buildProps "build_solution" 22 | $version = Build-GetRequiredProperty $buildProps "build_version" 23 | $artifactsDir = Build-GetPropertyOrDefault $buildProps "build_artifactsDir" "../artifacts" # Build Server Env 24 | $Env:MH_BUILD_OUTPUT = $artifactsDir 25 | Build-Log-Information "Artifacts Output Directory: $artifactsDir" 26 | 27 | $productionBranch = Build-GetPropertyOrDefault $buildProps "build_prod_branch" "master" 28 | $buildConfig = Build-GetPropertyOrDefault $buildProps "build_config" "Release" 29 | 30 | Build-WriteTitle "Getting git branch information" 31 | $commitCount = & git rev-list --count HEAD 32 | $branchName = Build-GetPropertyOrDefault $buildProps "GIT_BRANCH" "$(& git rev-parse --abbrev-ref HEAD)" 33 | $branchName = $branchName -ireplace "refs/heads/", "" 34 | $branchName = $branchName -ireplace "origin/", "" 35 | if ($branchName -imatch "^refs/pull/(\d+)/merge$") { 36 | $branchName = "pr-$($Matches[1])" 37 | } 38 | $isProdBuild = $branchName -eq $productionBranch 39 | Build-Log-Information "Git branch: $branchName, commit count: $commitCount, is production branch: $isProdBuild" 40 | $Env:MH_IS_PROD_BUILD = $isProdBuild 41 | 42 | Build-WriteTitle "Determining Version Number" 43 | # Don't need two version parts unless we get over 65535 commits 44 | #$part3 = [math]::Floor($commitCount / 65535) 45 | $part4 = $commitCount % 65535 46 | $versionSuffix = $(If ($isProdBuild) { "" } Else { "-$branchName" }) 47 | $version = "$version.$part4" 48 | Build-Log-Information "Version: $version$versionSuffix" 49 | $Env:MH_BUILD_VERSION = $version 50 | $Env:MH_BUILD_VERSION_SUFFIX = $versionSuffix 51 | $Env:MH_BUILD_VERSION_PACKAGE = "$version$versionSuffix" 52 | 53 | Build-WriteTitle "Remove TestApp Projects from sln" 54 | dotnet sln $solution remove ../src/Simplexcel.MvcTestApp/Simplexcel.MvcTestApp.csproj 55 | dotnet sln $solution remove ../src/Simplexcel.TestApp/Simplexcel.TestApp.csproj 56 | 57 | Build-WriteTitle "dotnet restore" 58 | dotnet restore $solution 59 | 60 | Build-WriteTitle "dotnet pack" 61 | # if is set in the project file, this will not allow the use of a version suffix. 62 | # => Set 1.2.3 to only specify the version number. 63 | # The SDK will then create a Version property based on VersionPrefix and VersionSuffix 64 | # AssemblyVersion is fixed at 3.0.0.0 due to strong naming since Simplexcel 3.0 65 | dotnet pack -c $buildConfig -o $artifactsDir -p:PackageVersion=$version$versionSuffix -p:Version=$version$versionSuffix -p:AssemblyVersion="3.0.0.0" -p:FileVersion=$version $solution 66 | 67 | Build-WriteTitle "dotnet test" 68 | dotnet test "../src/Simplexcel.Tests" -c $buildConfig -r "$artifactsDir/testoutput" 69 | } 70 | finally { 71 | $PSDefaultParameterValues.Remove('*:Verbose') 72 | } 73 | -------------------------------------------------------------------------------- /scripts/buildFunctions.ps1: -------------------------------------------------------------------------------- 1 | # [CmdletBinding()] 2 | # -Verbose:($PSCmdlet.MyInvocation.BoundParameters["Verbose"].IsPresent -eq $true) 3 | 4 | function Build-ReadProperties { 5 | <# 6 | .SYNOPSIS 7 | Reads a text file with Key-Value pairs into a Hashtable 8 | 9 | .DESCRIPTION 10 | Expects a text file in the form 11 | 12 | key1=value1 13 | key2=value2 14 | 15 | Will return a Hashtable with these associations 16 | 17 | .EXAMPLE 18 | PS> $props = Build-ReadProperties "propsForBuildServer.txt" 19 | PS> Write-Host "Value of key1: $props.key1" 20 | 21 | .LINK 22 | ConvertFrom-StringData 23 | 24 | .PARAMETER propFilename 25 | The file name to read. 26 | #> 27 | [CmdletBinding()] 28 | [OutputType([Hashtable])] 29 | param ( 30 | [Parameter(Position=0,mandatory=$false)] 31 | [string]$propFilename="buildProperties.txt", 32 | 33 | [Parameter(Position=1,mandatory=$false)] 34 | [switch]$mergeEnvironment=$true 35 | ) 36 | 37 | Process { 38 | $buildProps = Get-Content -Raw $propFilename | ConvertFrom-StringData 39 | 40 | if ($mergeEnvironment) { 41 | Get-ChildItem Env: | ForEach-Object -Process { $buildProps[$_.Key] = $_.Value } 42 | } 43 | 44 | return $buildProps 45 | } 46 | } 47 | 48 | function Build-WriteTitle { 49 | param ( 50 | [AllowEmptyString()] 51 | [Parameter(Position=0,mandatory=$true)] 52 | [string]$message 53 | ) 54 | Write-Host $message -ForegroundColor Green 55 | } 56 | 57 | function Build-Log-Verbose { 58 | param ( 59 | [AllowEmptyString()] 60 | [Parameter(Position=0,mandatory=$true)] 61 | [string]$message 62 | ) 63 | Write-Verbose $message 64 | } 65 | 66 | function Build-Log-Information { 67 | param ( 68 | [AllowEmptyString()] 69 | [Parameter(Position=0,mandatory=$true,ValueFromPipeline)] 70 | [string]$message 71 | ) 72 | Write-Information $message 73 | } 74 | 75 | function Build-Log-Warning { 76 | param ( 77 | [AllowEmptyString()] 78 | [Parameter(Position=0,mandatory=$true)] 79 | [string]$message 80 | ) 81 | Write-Warning $message 82 | } 83 | 84 | function Build-Log-Error { 85 | param ( 86 | [AllowEmptyString()] 87 | [Parameter(Position=0,mandatory=$true)] 88 | [string]$message, 89 | 90 | [AllowEmptyString()] 91 | [Parameter(Position=1,mandatory=$false)] 92 | [string]$id="BuildError" 93 | ) 94 | 95 | Write-Error $message -ErrorId $id 96 | } 97 | 98 | function Build-Log-Hashtable { 99 | param ( 100 | [AllowEmptyString()] 101 | [Parameter(Position=0,mandatory=$true,ValueFromPipeline)] 102 | [Hashtable]$props 103 | ) 104 | $props | Format-Table | Out-String | Build-Log-Information 105 | } 106 | 107 | function Build-GetPropertyOrDefault { 108 | param ( 109 | [Parameter(Position=0,mandatory=$true)] 110 | [Hashtable]$props, 111 | 112 | [AllowEmptyString()] 113 | [Parameter(Position=1,mandatory=$true)] 114 | [string]$propName, 115 | 116 | [AllowEmptyString()] 117 | [Parameter(Position=2,mandatory=$true)] 118 | [string]$defaultValue 119 | ) 120 | 121 | $value = $props.$propName 122 | 123 | If ([string]::IsNullOrWhiteSpace($value)) { 124 | Build-Log-Verbose "$propName was empty, returning '$defaultValue'" 125 | return $defaultValue 126 | } Else { 127 | Build-Log-Verbose "$propName was '$value'" 128 | return $value 129 | } 130 | } 131 | 132 | function Build-GetRequiredProperty { 133 | param ( 134 | [Parameter(Position=0,mandatory=$true)] 135 | [Hashtable]$props, 136 | 137 | [AllowEmptyString()] 138 | [Parameter(Position=1,mandatory=$true)] 139 | [string]$propName 140 | ) 141 | 142 | $value = $props.$propName 143 | 144 | If ([string]::IsNullOrWhiteSpace($value)) { 145 | Build-Log-Error "$propName requires a value, but none was given" 146 | exit 147 | 148 | } Else { 149 | return $value 150 | } 151 | } -------------------------------------------------------------------------------- /scripts/buildProperties.txt: -------------------------------------------------------------------------------- 1 | build_version=3.1.0 2 | build_prod_branch=release 3 | build_config=Release 4 | build_solution=../src/Simplexcel.sln 5 | -------------------------------------------------------------------------------- /src/Simplexcel.MvcTestApp/ExcelResultBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Net; 4 | using System.Web.Mvc; 5 | 6 | namespace Simplexcel.MvcTestApp 7 | { 8 | public abstract class ExcelResultBase : ActionResult 9 | { 10 | private readonly string _filename; 11 | 12 | protected ExcelResultBase(string filename) 13 | { 14 | _filename = filename; 15 | } 16 | 17 | protected abstract Workbook GenerateWorkbook(); 18 | 19 | public override void ExecuteResult(ControllerContext context) 20 | { 21 | if (context == null) 22 | { 23 | throw new ArgumentNullException("context"); 24 | } 25 | 26 | var workbook = GenerateWorkbook(); 27 | if (workbook == null) 28 | { 29 | context.HttpContext.Response.StatusCode = (int)HttpStatusCode.NotFound; 30 | return; 31 | } 32 | 33 | context.HttpContext.Response.ContentType = "application/octet-stream"; 34 | context.HttpContext.Response.StatusCode = (int)HttpStatusCode.OK; 35 | context.HttpContext.Response.AppendHeader("content-disposition", "attachment; filename=\"" + _filename + "\""); 36 | 37 | workbook.Save(context.HttpContext.Response.OutputStream); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /src/Simplexcel.MvcTestApp/ExcelTestActionResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Simplexcel.MvcTestApp 5 | { 6 | public class ExcelTestActionResult : ExcelResultBase 7 | { 8 | private readonly List _data; 9 | 10 | public ExcelTestActionResult(string filename, List data) : base(filename) 11 | { 12 | _data = data; 13 | } 14 | 15 | protected override Workbook GenerateWorkbook() 16 | { 17 | var wb = new Workbook 18 | { 19 | Title = "Workbook Title", 20 | Author = "The Author" 21 | }; 22 | 23 | var sheet = new Worksheet("Test"); 24 | sheet.PageSetup.Orientation = Orientation.Landscape; 25 | sheet.PageSetup.PrintRepeatRows = 2; 26 | sheet.PageSetup.PrintRepeatColumns = 2; 27 | 28 | sheet.ColumnWidths[0] = 24.6; 29 | 30 | sheet.Cells["A1"] = "Test"; 31 | sheet.Cells["A1"].FontName = "Arial Black"; 32 | 33 | sheet.Cells[0, 1] = "Another Test"; 34 | sheet.Cells[0, 1].Border = CellBorder.Bottom | CellBorder.Right; 35 | 36 | sheet.Cells[0, 2] = "Bold Red"; 37 | sheet.Cells[0, 2].Bold = true; 38 | sheet.Cells[0, 2].TextColor = Color.Red; 39 | 40 | Cell cell = "BIU & Big & Blue"; 41 | cell.Bold = true; 42 | cell.Underline = true; 43 | cell.Italic = true; 44 | cell.TextColor = Color.Blue; 45 | cell.FontSize = 18; 46 | cell.Hyperlink = "https://github.com/mstum/Simplexcel"; 47 | sheet.Cells[0, 3] = cell; 48 | sheet.ColumnWidths[3] = 40; 49 | 50 | 51 | sheet.Cells[0, 4] = 13; 52 | sheet.Cells[0, 5] = 13.58; 53 | 54 | Cell cell2 = "Orange"; 55 | cell2.Bold = true; 56 | cell2.Italic = true; 57 | cell2.TextColor = Color.Orange; 58 | cell2.FontSize = 18; 59 | sheet.Cells[0, 2] = cell2; 60 | 61 | sheet.Cells[0, 6] = "👪"; 62 | sheet.Cells[0, 7] = "👨‍👩‍👧‍👦"; 63 | 64 | var i = 0; 65 | foreach(var datum in _data ?? Enumerable.Empty()) 66 | { 67 | i++; 68 | sheet.Cells[i, 0] = datum; 69 | } 70 | 71 | wb.Add(sheet); 72 | 73 | var sheet2 = new Worksheet("Sheet 2"); 74 | sheet2[0, 0] = "Sheet Number 2"; 75 | sheet2[0, 0].Bold = true; 76 | wb.Add(sheet2); 77 | 78 | return wb; 79 | } 80 | } 81 | } -------------------------------------------------------------------------------- /src/Simplexcel.MvcTestApp/Global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Codebehind="Global.asax.cs" Inherits="Simplexcel.MvcTestApp.MvcApplication" Language="C#" %> 2 | -------------------------------------------------------------------------------- /src/Simplexcel.MvcTestApp/Global.asax.cs: -------------------------------------------------------------------------------- 1 | using System.Web.Mvc; 2 | using System.Web.Routing; 3 | 4 | namespace Simplexcel.MvcTestApp 5 | { 6 | public class MvcApplication : System.Web.HttpApplication 7 | { 8 | protected void Application_Start() 9 | { 10 | RouteTable.Routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 11 | 12 | RouteTable.Routes.MapRoute( 13 | name: "Default", 14 | url: "{controller}/{action}/{id}", 15 | defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional } 16 | ); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Simplexcel.MvcTestApp/HomeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Web.Mvc; 4 | 5 | namespace Simplexcel.MvcTestApp.Controllers 6 | { 7 | public class HomeController : Controller 8 | { 9 | public ActionResult Index() 10 | { 11 | return View(); 12 | } 13 | 14 | public ActionResult ExcelTest() 15 | { 16 | var businessData = new List(); 17 | for (int i = 1; i < 55; i++) 18 | { 19 | businessData.Add(Guid.NewGuid().ToString()); 20 | } 21 | 22 | return new ExcelTestActionResult("test.xlsx", businessData); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/Simplexcel.MvcTestApp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Simplexcel.MvcTestApp")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Simplexcel.MvcTestApp")] 13 | [assembly: AssemblyCopyright("Copyright © 2017")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("02275a57-537d-47a0-bf4a-d2e2d5c3700c")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /src/Simplexcel.MvcTestApp/Simplexcel.MvcTestApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8 | 9 | 2.0 10 | {26E384B1-4991-4B7B-B68C-4DDB500AE37C} 11 | {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} 12 | Library 13 | Properties 14 | Simplexcel.MvcTestApp 15 | Simplexcel.MvcTestApp 16 | v4.7 17 | false 18 | true 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | true 29 | full 30 | false 31 | bin\ 32 | DEBUG;TRACE 33 | prompt 34 | 4 35 | 36 | 37 | true 38 | pdbonly 39 | true 40 | bin\ 41 | TRACE 42 | prompt 43 | 4 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | True 67 | ..\packages\Microsoft.Web.Infrastructure.1.0.0.0\lib\net40\Microsoft.Web.Infrastructure.dll 68 | 69 | 70 | 71 | 72 | 73 | 74 | True 75 | ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.Helpers.dll 76 | 77 | 78 | True 79 | ..\packages\Microsoft.AspNet.Mvc.5.2.3\lib\net45\System.Web.Mvc.dll 80 | 81 | 82 | True 83 | ..\packages\Microsoft.AspNet.Razor.3.2.3\lib\net45\System.Web.Razor.dll 84 | 85 | 86 | True 87 | ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.dll 88 | 89 | 90 | True 91 | ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Deployment.dll 92 | 93 | 94 | True 95 | ..\packages\Microsoft.AspNet.WebPages.3.2.3\lib\net45\System.Web.WebPages.Razor.dll 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | Global.asax 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | {39afae0e-34d0-43ad-acd7-39fbe03a57f5} 117 | Simplexcel 118 | 119 | 120 | 121 | 122 | 123 | 124 | 10.0 125 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | True 138 | True 139 | 59984 140 | / 141 | http://localhost:59984/ 142 | False 143 | False 144 | 145 | 146 | False 147 | 148 | 149 | 150 | 151 | 157 | -------------------------------------------------------------------------------- /src/Simplexcel.MvcTestApp/Views/Home/Index.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | Simplexcel ASP.net MVC Test App 4 | 5 | 6 | Download Excel Sheet 7 | 8 | -------------------------------------------------------------------------------- /src/Simplexcel.MvcTestApp/Views/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 |
7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/Simplexcel.MvcTestApp/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/Simplexcel.MvcTestApp/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mstum/Simplexcel/a41b291104d14c85d9f497695d85a79f477e6196/src/Simplexcel.MvcTestApp/favicon.ico -------------------------------------------------------------------------------- /src/Simplexcel.MvcTestApp/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Simplexcel.TestApp/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Simplexcel.TestApp 6 | { 7 | class Program 8 | { 9 | static void Main() 10 | { 11 | var wb = new Workbook 12 | { 13 | Title = "Workbook Title", 14 | Author = "The Author" 15 | }; 16 | 17 | var sheet = new Worksheet("Test"); 18 | sheet.PageSetup.Orientation = Orientation.Landscape; 19 | sheet.PageSetup.PrintRepeatRows = 2; 20 | sheet.PageSetup.PrintRepeatColumns = 2; 21 | 22 | sheet.ColumnWidths[0] = 24.6; 23 | 24 | // Version Info 25 | sheet["F11"] = "Version"; 26 | sheet["G11"] = SimplexcelVersion.Version.ToString(); 27 | sheet["F12"] = "VersionString"; 28 | sheet["G12"] = SimplexcelVersion.VersionString; 29 | sheet["F13"] = "PublicKeyToken"; 30 | sheet["G13"] = SimplexcelVersion.PublicKeyToken; 31 | sheet["F14"] = "PublicKey"; 32 | sheet["G14"] = SimplexcelVersion.PublicKey; 33 | 34 | sheet.Cells["A1"] = "Test"; 35 | sheet.Cells["A1"].FontName = "Arial Black"; 36 | 37 | sheet.Cells[0, 1] = "Another Test"; 38 | sheet.Cells[0, 1].Border = CellBorder.Bottom | CellBorder.Right; 39 | 40 | sheet.Cells[0, 2] = "Bold Red"; 41 | sheet.Cells[0, 2].Bold = true; 42 | sheet.Cells[0, 2].TextColor = Color.Red; 43 | 44 | Cell cell = "BIU & Big & Blue"; 45 | cell.Bold = true; 46 | cell.Underline = true; 47 | cell.Italic = true; 48 | cell.TextColor = Color.Blue; 49 | cell.FontSize = 18; 50 | cell.Hyperlink = "https://github.com/mstum/Simplexcel"; 51 | sheet.Cells[0, 3] = cell; 52 | sheet.ColumnWidths[3] = 40; 53 | 54 | sheet.Cells[0, 4] = 13; 55 | sheet.Cells[0, 5] = 13.58; 56 | 57 | for (int i = 1; i < 55; i++) 58 | { 59 | sheet.Cells[1, i] = Guid.NewGuid().ToString("N"); 60 | sheet.Cells[i, 0] = Guid.NewGuid().ToString("N"); 61 | } 62 | 63 | Cell cell2 = "Orange"; 64 | cell2.Bold = true; 65 | cell2.Italic = true; 66 | cell2.TextColor = Color.Orange; 67 | cell2.FontSize = 18; 68 | sheet.Cells[0, 2] = cell2; 69 | 70 | // Note that Emoji only work in Excel 2013 or newer, not 2007 or 2010. This is an Excel limitation. 71 | sheet.Cells[0, 6] = "👪"; 72 | sheet.Cells[0, 7] = "👨‍👩‍👧‍👦"; 73 | 74 | var dateTime = new DateTime(2022, 4, 26, 13, 14, 15); 75 | sheet.Cells["D4"] = dateTime; 76 | sheet.Cells["D5"] = new Cell(CellType.Date, dateTime, BuiltInCellFormat.DateOnly); 77 | sheet.Cells["D6"] = new Cell(CellType.Date, dateTime, BuiltInCellFormat.TimeOnly); 78 | sheet.Cells["D7"] = new Cell(CellType.Date, dateTime, "yyyy\"_\"mm\"_\"dd\"_\"hh\"_\"mm\"_\"ss"); 79 | sheet.Cells["D8"] = long.MaxValue; 80 | sheet.Cells["D9"] = long.MinValue; 81 | sheet.Cells["D10"] = decimal.MaxValue; 82 | sheet.Cells["D11"] = decimal.MinValue; 83 | 84 | sheet.Cells["D12"] = 9999999999L; 85 | sheet.Cells["D13"] = 99999999999L; 86 | sheet.Cells["D14"] = 100000000000L; 87 | sheet.Cells["D15"] = 100000000001L; 88 | sheet.Cells["D16"] = 1000000000000L; 89 | sheet.Cells["D17"] = 1000000000001L; 90 | sheet.Cells["D18"] = Cell.LargeNumberPositiveLimit; 91 | sheet.Cells["D19"] = Cell.LargeNumberPositiveLimit + 1; 92 | sheet.Cells["D20"] = Cell.LargeNumberPositiveLimit - 1; 93 | 94 | sheet.Cells["D21"] = -9999999999L; 95 | sheet.Cells["D22"] = -99999999999L; 96 | sheet.Cells["D23"] = -100000000000L; 97 | sheet.Cells["D24"] = -100000000001L; 98 | sheet.Cells["D25"] = -1000000000000L; 99 | sheet.Cells["D26"] = -1000000000001L; 100 | sheet.Cells["D27"] = Cell.LargeNumberNegativeLimit; 101 | sheet.Cells["D28"] = Cell.LargeNumberNegativeLimit + 1; 102 | sheet.Cells["D29"] = Cell.LargeNumberNegativeLimit - 1; 103 | sheet.LargeNumberHandlingMode = LargeNumberHandlingMode.StoreAsText; 104 | 105 | sheet.Cells["C5"] = "ThinDiagonalCrosshatch"; 106 | sheet.Cells["C5"].Fill = new PatternFill { PatternType = PatternType.ThinDiagonalCrosshatch, PatternColor = Color.Yellow, BackgroundColor = Color.Violet }; 107 | sheet.Cells["C5"].TextColor = Color.Green; 108 | 109 | sheet.Cells["C6"] = "ThickDiagonalCrosshatch"; 110 | sheet.Cells["C6"].Fill = new PatternFill { PatternType = PatternType.ThickDiagonalCrosshatch, PatternColor = Color.Violet }; 111 | sheet.Cells["C6"].TextColor = Color.Green; 112 | 113 | sheet.Cells["C7"] = "DiagonalCrosshatch"; 114 | sheet.Cells["C7"].Fill = new PatternFill { PatternType = PatternType.DiagonalCrosshatch, BackgroundColor = Color.Violet }; 115 | sheet.Cells["C7"].TextColor = Color.Green; 116 | 117 | sheet.Cells["C8"] = "FillSolid"; 118 | sheet.Cells["C8"].Fill = new PatternFill { PatternType = PatternType.Solid, PatternColor = Color.Yellow, BackgroundColor = Color.Violet }; 119 | sheet.Cells["C8"].TextColor = Color.Green; 120 | 121 | sheet.Cells["C9"] = "SetFillBgColor"; 122 | sheet.Cells["C9"].Fill.BackgroundColor = Color.Navy; 123 | sheet.Cells["C9"].TextColor = Color.Green; 124 | 125 | sheet.Cells["F5"] = "Hello,"; 126 | sheet.Cells["G5"] = "World!"; 127 | sheet.Cells["F5"].Fill.BackgroundColor = Color.Blue; 128 | sheet.Cells["G5"].Fill.BackgroundColor = Color.Red; 129 | 130 | wb.Add(sheet); 131 | 132 | // Prime the Cache... 133 | var populatedSheet = new Worksheet("Populate"); 134 | populatedSheet.Populate(EnumeratePopulateTestData(), cacheTypeColumns: true); 135 | 136 | // ...and use the cache 137 | populatedSheet = new Worksheet("Populate"); 138 | populatedSheet.Populate(EnumeratePopulateTestData(), cacheTypeColumns: true); 139 | wb.Add(populatedSheet); 140 | 141 | var frozenTopRowSheet = new Worksheet("Frozen Top Row") { AutoFilter = true }; 142 | frozenTopRowSheet.Cells[0, 0] = "Header 1"; 143 | frozenTopRowSheet.Cells[0, 1] = "Header 2"; 144 | frozenTopRowSheet.Cells[0, 2] = "Header 3"; 145 | foreach (var i in Enumerable.Range(1, 100)) 146 | { 147 | frozenTopRowSheet.Cells[i, 0] = "Value 1-" + i; 148 | frozenTopRowSheet.Cells[i, 1] = "Value 2-" + i; 149 | frozenTopRowSheet.Cells[i, 2] = "Value 3-" + i; 150 | } 151 | frozenTopRowSheet.FreezeTopRow(); 152 | wb.Add(frozenTopRowSheet); 153 | 154 | var frozenLeftColumnSheet = new Worksheet("Frozen Left Column"); 155 | frozenLeftColumnSheet.Cells[0, 0] = "Header 1"; 156 | frozenLeftColumnSheet.Cells[1, 0] = "Header 2"; 157 | frozenLeftColumnSheet.Cells[2, 0] = "Header 3"; 158 | foreach (var i in Enumerable.Range(1, 100)) 159 | { 160 | frozenLeftColumnSheet.Cells[0, i] = "Value 1-" + i; 161 | frozenLeftColumnSheet.Cells[1, i] = "Value 2-" + i; 162 | frozenLeftColumnSheet.Cells[2, i] = "Value 3-" + i; 163 | } 164 | frozenLeftColumnSheet.FreezeLeftColumn(); 165 | wb.Add(frozenLeftColumnSheet); 166 | 167 | var pageBreakSheet = new Worksheet("Page Breaks"); 168 | for (var row = 0; row < 20; row++) 169 | { 170 | for (var col = 0; col < 20; col++) 171 | { 172 | pageBreakSheet.Cells[row, col] = $"Row {row} /Col {col}"; 173 | } 174 | } 175 | pageBreakSheet.InsertManualPageBreakAfterRow("B1"); 176 | pageBreakSheet.InsertManualPageBreakAfterRow(5); 177 | pageBreakSheet.InsertManualPageBreakAfterColumn("B8"); 178 | pageBreakSheet.InsertManualPageBreakAfterColumn(16); 179 | wb.Add(pageBreakSheet); 180 | 181 | var formulaSheet = new Worksheet("Formula"); 182 | formulaSheet.Cells["A1"] = 5; 183 | formulaSheet.Cells["A2"] = 10; 184 | formulaSheet.Cells["A3"] = 15; 185 | formulaSheet.Cells["B1"] = Cell.Formula("SUM(A:A)"); 186 | formulaSheet.Cells["B2"] = Cell.Formula("SUM(A1:A2)"); 187 | formulaSheet.Cells["B3"] = Cell.Formula("AVERAGE(A:A)"); 188 | formulaSheet.Cells["B4"] = Cell.Formula("MEDIAN(A:A)"); 189 | formulaSheet.Cells["C1"] = Cell.Formula("A1+B1"); 190 | formulaSheet.Cells["C2"] = Cell.Formula("=SUM(B:B)"); 191 | formulaSheet.Cells["C3"] = Cell.Formula("C5+A1"); 192 | formulaSheet.Cells["C5"] = Cell.Formula("SUM(B:B)+C1"); 193 | wb.Add(formulaSheet); 194 | 195 | var freezeTopLeftSheet = new Worksheet("FreezeTopLeft"); 196 | for (var row = 0; row < 10; row++) 197 | for (var col = 0; col < 10; col++) 198 | { 199 | freezeTopLeftSheet[row, col] = $"{row},{col}"; 200 | } 201 | freezeTopLeftSheet.FreezeTopLeft(2, 2); 202 | wb.Add(freezeTopLeftSheet); 203 | 204 | wb.Save("compressed.xlsx", compress: true); 205 | wb.Save("uncompressed.xlsx", compress: false); 206 | } 207 | 208 | private static IEnumerable EnumeratePopulateTestData() 209 | { 210 | var r = new Random(); 211 | 212 | for (int i = 0; i < 500; i++) 213 | { 214 | yield return new PopulateTestData 215 | { 216 | Id = i, 217 | CreatedUtc = new DateTime(2015, 1, 1, 1, 1, 1).AddDays(r.Next(1, 852)), 218 | Name = "Item Number " + i, 219 | Value = int.MaxValue - i, 220 | Price = r.Next(800, 9400) * 0.52m, 221 | Quantity = r.Next(4, 75) * 0.5m, 222 | IgnoreMe = Guid.NewGuid().ToString() 223 | }; 224 | } 225 | } 226 | 227 | private abstract class PopulateTestDataBase 228 | { 229 | public int Id { get; set; } 230 | 231 | [XlsxColumn("Unit Price")] 232 | public decimal Price { get; set; } 233 | 234 | [XlsxColumn(null)] 235 | public decimal Quantity { get; set; } 236 | } 237 | 238 | private class PopulateTestData : PopulateTestDataBase 239 | { 240 | public string Name { get; set; } 241 | 242 | [XlsxColumn("")] 243 | public long Value { get; set; } 244 | 245 | [XlsxColumn("Total")] 246 | public decimal TotalPrice { get { return Price * Quantity; } } 247 | public DateTime CreatedUtc { get; set; } 248 | 249 | [XlsxIgnoreColumn] 250 | public string IgnoreMe { get; set; } 251 | } 252 | } 253 | } 254 | -------------------------------------------------------------------------------- /src/Simplexcel.TestApp/Simplexcel.TestApp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/Simplexcel.Tests/BugTests.cs: -------------------------------------------------------------------------------- 1 | using Simplexcel.XlsxInternal; 2 | using System.Linq; 3 | using Xunit; 4 | 5 | namespace Simplexcel.Tests 6 | { 7 | public class BugTests 8 | { 9 | /// 10 | /// https://github.com/mstum/Simplexcel/issues/12 11 | /// 12 | /// SharedStrings _sanitizeRegex makes "&" in CellStrings impossible 13 | /// 14 | [Fact] 15 | public void Issue12_AmpersandInCellValues() 16 | { 17 | var sharedStrings = new SharedStrings(); 18 | sharedStrings.GetStringIndex("Here & Now"); 19 | sharedStrings.GetStringIndex("&"); 20 | var xmlFile = sharedStrings.ToXmlFile(); 21 | 22 | var nodeValues = xmlFile.Content.Descendants(Namespaces.x + "t").Select(x => x.Value).ToList(); 23 | 24 | Assert.Contains("Here & Now", nodeValues); 25 | Assert.Contains("&", nodeValues); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Simplexcel.Tests/CellAddressTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Simplexcel.Tests 4 | { 5 | public class CellAddressTests 6 | { 7 | [Fact] 8 | public void CellAddress_ToString_ProperlyConverts() 9 | { 10 | Assert.Equal("A1", new CellAddress(0, 0).ToString()); 11 | Assert.Equal("B1", new CellAddress(0, 1).ToString()); 12 | Assert.Equal("A2", new CellAddress(1, 0).ToString()); 13 | Assert.Equal("B2", new CellAddress(1, 1).ToString()); 14 | Assert.Equal("Z1", new CellAddress(0, 25).ToString()); 15 | Assert.Equal("AA1", new CellAddress(0, 26).ToString()); 16 | Assert.Equal("BA1", new CellAddress(0, 52).ToString()); 17 | Assert.Equal("BZ1", new CellAddress(0, 77).ToString()); 18 | Assert.Equal("CA1", new CellAddress(0, 78).ToString()); 19 | Assert.Equal("IV1", new CellAddress(0, 255).ToString()); 20 | Assert.Equal("CA1", new CellAddress(0, 78).ToString()); 21 | Assert.Equal("ZZ1", new CellAddress(0, 701).ToString()); 22 | Assert.Equal("AAA1", new CellAddress(0, 702).ToString()); 23 | Assert.Equal("AAZ1", new CellAddress(0, 727).ToString()); 24 | Assert.Equal("ABA1", new CellAddress(0, 728).ToString()); 25 | Assert.Equal("XFD1", new CellAddress(0, 16383).ToString()); 26 | Assert.Equal("ABA1000", new CellAddress(999, 728).ToString()); 27 | } 28 | 29 | [Fact] 30 | public void CellAdress_FromString_ProperlyParses() 31 | { 32 | Assert.Equal(0, new CellAddress("A1").Column); 33 | Assert.Equal(0, new CellAddress("A1").Row); 34 | 35 | Assert.Equal(1, new CellAddress("B1").Column); 36 | Assert.Equal(0, new CellAddress("B1").Row); 37 | 38 | Assert.Equal(0, new CellAddress("A2").Column); 39 | Assert.Equal(1, new CellAddress("A2").Row); 40 | 41 | Assert.Equal(1, new CellAddress("B2").Column); 42 | Assert.Equal(1, new CellAddress("B2").Row); 43 | 44 | Assert.Equal(25, new CellAddress("Z1").Column); 45 | Assert.Equal(0, new CellAddress("Z1").Row); 46 | 47 | Assert.Equal(26, new CellAddress("AA1").Column); 48 | Assert.Equal(0, new CellAddress("AA1").Row); 49 | 50 | Assert.Equal(52, new CellAddress("BA1").Column); 51 | Assert.Equal(0, new CellAddress("BA1").Row); 52 | 53 | Assert.Equal(77, new CellAddress("BZ1").Column); 54 | Assert.Equal(0, new CellAddress("BZ1").Row); 55 | 56 | Assert.Equal(78, new CellAddress("CA1").Column); 57 | Assert.Equal(0, new CellAddress("CA1").Row); 58 | 59 | Assert.Equal(701, new CellAddress("ZZ1").Column); 60 | Assert.Equal(0, new CellAddress("ZZ1").Row); 61 | 62 | Assert.Equal(702, new CellAddress("AAA1").Column); 63 | Assert.Equal(0, new CellAddress("AAA1").Row); 64 | 65 | Assert.Equal(727, new CellAddress("AAZ1").Column); 66 | Assert.Equal(0, new CellAddress("AAZ1").Row); 67 | 68 | Assert.Equal(728, new CellAddress("ABA1").Column); 69 | Assert.Equal(0, new CellAddress("ABA1").Row); 70 | 71 | Assert.Equal(16383, new CellAddress("XFD1").Column); 72 | Assert.Equal(0, new CellAddress("XFD1").Row); 73 | 74 | Assert.Equal(728, new CellAddress("ABA1000").Column); 75 | Assert.Equal(999, new CellAddress("ABA1000").Row); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Simplexcel.Tests/CellCollectionTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Simplexcel.Tests 4 | { 5 | public class CellCollectionTests 6 | { 7 | [Fact] 8 | public void EmptyCellCollection_AccessUninitializedCell_ImplicitlyCreatesCell() 9 | { 10 | var cc = new CellCollection(); 11 | 12 | cc[0, 0].Bold = true; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Simplexcel.Tests/CellTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Simplexcel.Tests 4 | { 5 | public class CellTests 6 | { 7 | [Fact] 8 | public void ImplicitCreation_FromLong_NoDecimalPlaces() 9 | { 10 | Cell cell = 1234; 11 | Assert.Equal(BuiltInCellFormat.NumberNoDecimalPlaces, cell.Format); 12 | Assert.Equal(CellType.Number, cell.CellType); 13 | } 14 | 15 | [Fact] 16 | public void ImplicitCreation_FromDouble_TwoDecimalPlaces() 17 | { 18 | Cell cell = 1234.56; 19 | Assert.Equal(BuiltInCellFormat.NumberTwoDecimalPlaces, cell.Format); 20 | Assert.Equal(CellType.Number, cell.CellType); 21 | 22 | } 23 | 24 | [Fact] 25 | public void ImplicitCreation_FromDecimal_TwoDecimalPlaces() 26 | { 27 | Cell cell = 1234.56m; 28 | Assert.Equal(BuiltInCellFormat.NumberTwoDecimalPlaces, cell.Format); 29 | Assert.Equal(CellType.Number, cell.CellType); 30 | } 31 | 32 | [Fact] 33 | public void ImplicitCreation_FromString_CellTypeText() 34 | { 35 | Cell cell = "1234"; 36 | Assert.Equal(CellType.Text, cell.CellType); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Simplexcel.Tests/ColRowCountTests.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | namespace Simplexcel.Tests 4 | { 5 | public class ColRowCountTests 6 | { 7 | [Fact] 8 | public void EmptySheet_ColumnCount_Zero() 9 | { 10 | var sheet = new Worksheet("test"); 11 | Assert.Equal(0, sheet.Cells.ColumnCount); 12 | } 13 | 14 | [Fact] 15 | public void EmptySheet_RowCount_Zero() 16 | { 17 | var sheet = new Worksheet("test"); 18 | Assert.Equal(0, sheet.Cells.RowCount); 19 | } 20 | 21 | [Fact] 22 | public void NoOffByOneError() 23 | { 24 | var sheet = new Worksheet("Test"); 25 | sheet.Cells[0, 0] = "Test"; 26 | Assert.Equal(1, sheet.Cells.RowCount); 27 | Assert.Equal(1, sheet.Cells.ColumnCount); 28 | } 29 | 30 | [Fact] 31 | public void EmptyRows_RowCount_CountsEmpty() 32 | { 33 | var sheet = new Worksheet("Test"); 34 | sheet.Cells[0, 0] = "Test"; 35 | sheet.Cells[6, 0] = "Row Seven"; 36 | Assert.Equal(7, sheet.Cells.RowCount); 37 | } 38 | 39 | [Fact] 40 | public void EmptyRows_ColumnCount_CountsEmpty() 41 | { 42 | var sheet = new Worksheet("Test"); 43 | sheet.Cells[0, 0] = "Test"; 44 | sheet.Cells[0, 6] = "Column Seven"; 45 | Assert.Equal(7, sheet.Cells.ColumnCount); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Simplexcel.Tests/Simplexcel.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | false 6 | true 7 | ../simplexcel_oss.snk 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/Simplexcel.Tests/WorksheetTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace Simplexcel.Tests 5 | { 6 | public class WorksheetTests 7 | { 8 | [Fact] 9 | public void Worksheet_NameTooLong_ThrowsArgumentException() 10 | { 11 | var name = new string('x', Worksheet.MaxSheetNameLength + 1); 12 | 13 | Assert.Throws(() => new Worksheet(name)); 14 | } 15 | 16 | [Fact] 17 | public void Worksheet_NameInvalidChars_ThrowsArgumentException() 18 | { 19 | foreach (var invalid in Worksheet.InvalidSheetNameChars) 20 | { 21 | var name = "x" + invalid + "y"; 22 | Assert.Throws(() => new Worksheet(name)); 23 | } 24 | } 25 | 26 | [Fact] 27 | public void Worksheet_EmptyName_ThrowsArgumentException() 28 | { 29 | Assert.Throws(() => new Worksheet(string.Empty)); 30 | } 31 | 32 | [Fact] 33 | public void Worksheet_NullName_ThrowsArgumentException() 34 | { 35 | Assert.Throws(() => new Worksheet(null)); 36 | } 37 | 38 | [Fact] 39 | public void FreezeTopLeft_NegativeRows_Throws() 40 | { 41 | var ws = new Worksheet("Foo"); 42 | Assert.Throws("rows", () => ws.FreezeTopLeft(-1, 9)); 43 | } 44 | 45 | [Fact] 46 | public void FreezeTopLeft_NegativeColumns_Throws() 47 | { 48 | var ws = new Worksheet("Foo"); 49 | Assert.Throws("columns", () => ws.FreezeTopLeft(9, -1)); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Simplexcel.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 16 3 | VisualStudioVersion = 16.0.29424.173 4 | MinimumVisualStudioVersion = 15.0.26124.0 5 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Simplexcel", "Simplexcel\Simplexcel.csproj", "{39AFAE0E-34D0-43AD-ACD7-39FBE03A57F5}" 6 | EndProject 7 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Simplexcel.TestApp", "Simplexcel.TestApp\Simplexcel.TestApp.csproj", "{C015203B-2EA6-48D7-8571-E3EAC963A583}" 8 | EndProject 9 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Simplexcel.Tests", "Simplexcel.Tests\Simplexcel.Tests.csproj", "{1A651F47-91E3-40EC-93EF-DAD0326BD995}" 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Simplexcel.MvcTestApp", "Simplexcel.MvcTestApp\Simplexcel.MvcTestApp.csproj", "{26E384B1-4991-4B7B-B68C-4DDB500AE37C}" 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{12C68E3A-847B-44A1-9492-026905E2FDBA}" 14 | ProjectSection(SolutionItems) = preProject 15 | simplexcel_oss.snk = simplexcel_oss.snk 16 | EndProjectSection 17 | EndProject 18 | Global 19 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 20 | Debug|Any CPU = Debug|Any CPU 21 | Release|Any CPU = Release|Any CPU 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {39AFAE0E-34D0-43AD-ACD7-39FBE03A57F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {39AFAE0E-34D0-43AD-ACD7-39FBE03A57F5}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {39AFAE0E-34D0-43AD-ACD7-39FBE03A57F5}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {39AFAE0E-34D0-43AD-ACD7-39FBE03A57F5}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {C015203B-2EA6-48D7-8571-E3EAC963A583}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {C015203B-2EA6-48D7-8571-E3EAC963A583}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {C015203B-2EA6-48D7-8571-E3EAC963A583}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {C015203B-2EA6-48D7-8571-E3EAC963A583}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {1A651F47-91E3-40EC-93EF-DAD0326BD995}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {1A651F47-91E3-40EC-93EF-DAD0326BD995}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {1A651F47-91E3-40EC-93EF-DAD0326BD995}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {1A651F47-91E3-40EC-93EF-DAD0326BD995}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {26E384B1-4991-4B7B-B68C-4DDB500AE37C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {26E384B1-4991-4B7B-B68C-4DDB500AE37C}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {26E384B1-4991-4B7B-B68C-4DDB500AE37C}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {26E384B1-4991-4B7B-B68C-4DDB500AE37C}.Release|Any CPU.Build.0 = Release|Any CPU 40 | EndGlobalSection 41 | GlobalSection(SolutionProperties) = preSolution 42 | HideSolutionNode = FALSE 43 | EndGlobalSection 44 | GlobalSection(ExtensibilityGlobals) = postSolution 45 | SolutionGuid = {B4AFD13E-5383-4FDE-BA21-BA7BAA4C7D7E} 46 | EndGlobalSection 47 | EndGlobal 48 | -------------------------------------------------------------------------------- /src/Simplexcel/CellAddressHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text.RegularExpressions; 5 | 6 | namespace Simplexcel 7 | { 8 | internal static class CellAddressHelper 9 | { 10 | private readonly static Regex _cellAddressRegex = new Regex(@"(?[a-z]+)(?[0-9]+)", RegexOptions.Compiled | RegexOptions.IgnoreCase); 11 | private readonly static char[] _chars = new[] { 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' }; 12 | 13 | /// 14 | /// Convert a zero-based row and column to an Excel Cell Reference, e.g. A1 for [0,0] 15 | /// 16 | /// 17 | /// 18 | /// 19 | internal static string ColRowToReference(int col, int row) 20 | { 21 | return ColToReference(col) + (row + 1); 22 | } 23 | 24 | /// 25 | /// Convert a zero-based column number to the proper Reference in Excel (e.g, 0 = A) 26 | /// 27 | /// 28 | /// 29 | internal static string ColToReference(int col) 30 | { 31 | var dividend = (col + 1); 32 | var columnName = string.Empty; 33 | while (dividend > 0) 34 | { 35 | var modifier = (dividend - 1) % 26; 36 | columnName = Convert.ToChar(65 + modifier) + columnName; 37 | dividend = ((dividend - modifier) / 26); 38 | } 39 | return columnName; 40 | } 41 | 42 | /// 43 | /// Convert an Excel Cell Reference to a zero-based column/row representation (e.g., A1 => [0,0]) 44 | /// 45 | internal static void ReferenceToColRow(string reference, out int row, out int column) 46 | { 47 | row = column = 0; 48 | 49 | var parts = _cellAddressRegex.Match(reference); 50 | if (parts.Success) 51 | { 52 | var rowMatch = parts.Groups["Row"]; 53 | var colMatch = parts.Groups["Column"]; 54 | if (rowMatch.Success && colMatch.Success) 55 | { 56 | row = Convert.ToInt32(rowMatch.Value) - 1; 57 | 58 | string colString = colMatch.Value.ToLower(); 59 | int col = 0; 60 | for (int i = 0; i < colString.Length; i++) 61 | { 62 | char c = colString[colString.Length - i - 1]; 63 | var ix = Array.IndexOf(_chars, c); 64 | if (ix == -1) 65 | { 66 | throw new ArgumentException("Cell Reference needs to be in Excel format, e.g. A1 or BA321. Invalid: " + reference, nameof(reference)); 67 | } 68 | 69 | var ixo = ix + 1; 70 | var multiplier = Math.Pow(26, i); 71 | col += (int)(ixo * multiplier); 72 | } 73 | column = col - 1; 74 | } 75 | } 76 | else 77 | { 78 | throw new ArgumentException("Cell Reference needs to be in Excel format, e.g. A1 or BA321. Invalid: " + reference, nameof(reference)); 79 | } 80 | } 81 | 82 | internal static IList DetermineRanges(ICollection cellAddresses) 83 | { 84 | var result = new List(); 85 | 86 | // TODO: Actually support Ranges. Ranges are Rectangular, e.g., A1:B5 (TopLeft:BottomRight) 87 | foreach (var ca in cellAddresses ?? Enumerable.Empty()) 88 | { 89 | result.Add(ca.ToString()); 90 | } 91 | 92 | return result; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Simplexcel/Cells/BuiltInCellFormat.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel 2 | { 3 | /// 4 | /// Excel Built-In Cell formats 5 | /// 6 | public static class BuiltInCellFormat 7 | { 8 | /// 9 | /// General 10 | /// 11 | public const string General = "General"; 12 | 13 | /// 14 | /// 0 15 | /// 16 | public const string NumberNoDecimalPlaces = "0"; 17 | 18 | /// 19 | /// 0.00 20 | /// 21 | public const string NumberTwoDecimalPlaces = "0.00"; 22 | 23 | /// 24 | /// 0% 25 | /// 26 | public const string PercentNoDecimalPlaces = "0%"; 27 | 28 | /// 29 | /// 0.00% 30 | /// 31 | public const string PercentTwoDecimalPlaces = "0.00%"; 32 | 33 | /// 34 | /// @ 35 | /// 36 | public const string Text = "@"; 37 | 38 | /// 39 | /// m/d/yy h:mm 40 | /// 41 | public const string DateAndTime = "m/d/yy h:mm"; 42 | 43 | /// 44 | /// mm-dd-yy 45 | /// 46 | public const string DateOnly = "mm-dd-yy"; 47 | 48 | /// 49 | /// h:mm 50 | /// 51 | public const string TimeOnly = "h:mm"; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Simplexcel/Cells/Cell.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Simplexcel.XlsxInternal; 3 | 4 | namespace Simplexcel 5 | { 6 | /// 7 | /// A cell inside a Worksheet 8 | /// 9 | public sealed class Cell 10 | { 11 | internal XlsxCellStyle XlsxCellStyle { get; } 12 | 13 | /// 14 | /// Create a new Cell of the given . 15 | /// You can also implicitly create a cell from a string or number. 16 | /// 17 | /// 18 | public Cell(CellType cellType) : this(cellType, null, BuiltInCellFormat.General) { } 19 | 20 | /// 21 | /// Create a new Cell of the given , with the given value and format. For some common formats, see . 22 | /// You can also implicitly create a cell from a string or number. 23 | /// 24 | /// 25 | /// 26 | /// 27 | public Cell(CellType type, object value, string format) 28 | { 29 | XlsxCellStyle = new XlsxCellStyle 30 | { 31 | Format = format 32 | }; 33 | 34 | Value = value; 35 | CellType = type; 36 | IgnoredErrors = new IgnoredError(); 37 | } 38 | 39 | /// 40 | /// The Excel Format for the cell, see 41 | /// 42 | public string Format 43 | { 44 | get { return XlsxCellStyle.Format; } 45 | set { XlsxCellStyle.Format = value; } 46 | } 47 | 48 | /// 49 | /// The border around the cell 50 | /// 51 | public CellBorder Border 52 | { 53 | get { return XlsxCellStyle.Border; } 54 | set { XlsxCellStyle.Border = value; } 55 | } 56 | 57 | /// 58 | /// The name of the Font (Default: Calibri) 59 | /// 60 | public string FontName 61 | { 62 | get { return XlsxCellStyle.Font.Name; } 63 | set { XlsxCellStyle.Font.Name = value; } 64 | } 65 | 66 | /// 67 | /// The Size of the Font (Default: 11) 68 | /// 69 | public int FontSize 70 | { 71 | get { return XlsxCellStyle.Font.Size; } 72 | set { XlsxCellStyle.Font.Size = value; } 73 | } 74 | 75 | /// 76 | /// Should the text be bold? 77 | /// 78 | public bool Bold 79 | { 80 | get { return XlsxCellStyle.Font.Bold; } 81 | set { XlsxCellStyle.Font.Bold = value; } 82 | } 83 | 84 | /// 85 | /// Should the text be italic? 86 | /// 87 | public bool Italic 88 | { 89 | get { return XlsxCellStyle.Font.Italic; } 90 | set { XlsxCellStyle.Font.Italic = value; } 91 | } 92 | 93 | /// 94 | /// Should the text be underlined? 95 | /// 96 | public bool Underline 97 | { 98 | get { return XlsxCellStyle.Font.Underline; } 99 | set { XlsxCellStyle.Font.Underline = value; } 100 | } 101 | 102 | /// 103 | /// The font color. 104 | /// 105 | public Color TextColor 106 | { 107 | get { return XlsxCellStyle.Font.TextColor; } 108 | set { XlsxCellStyle.Font.TextColor = value; } 109 | } 110 | 111 | /// 112 | /// The interior/fill color. 113 | /// 114 | public PatternFill Fill 115 | { 116 | get { return XlsxCellStyle.Fill; } 117 | set { XlsxCellStyle.Fill = value; } 118 | } 119 | 120 | /// 121 | /// The Horizontal Alignment of content within a Cell 122 | /// 123 | public HorizontalAlign HorizontalAlignment 124 | { 125 | get { return XlsxCellStyle.HorizontalAlignment; } 126 | set { XlsxCellStyle.HorizontalAlignment = value; } 127 | } 128 | 129 | /// 130 | /// The Vertical Alignment of content within this Cell 131 | /// 132 | public VerticalAlign VerticalAlignment 133 | { 134 | get { return XlsxCellStyle.VerticalAlignment; } 135 | set { XlsxCellStyle.VerticalAlignment = value; } 136 | } 137 | 138 | /// 139 | /// Any errors that are ignored in this Cell 140 | /// 141 | public IgnoredError IgnoredErrors { get; } 142 | 143 | /// 144 | /// The Type of the cell. 145 | /// 146 | public CellType CellType { get; } 147 | 148 | /// 149 | /// The Content of the cell. 150 | /// 151 | public object Value { get; set; } 152 | 153 | /// 154 | /// Should this cell be a Hyperlink to something? 155 | /// 156 | public string Hyperlink { get; set; } 157 | 158 | /// 159 | /// Create a new that includes a Formula (e.g., SUM(A1:A5)). Do not include the initial = sign! 160 | /// 161 | /// The formula, without the initial = sign (so "SUM(A1:A5)", not "=SUM(A1:A5)") 162 | /// 163 | public static Cell Formula(string formula) 164 | { 165 | return new Cell(CellType.Formula, formula, BuiltInCellFormat.General); 166 | } 167 | 168 | /// 169 | /// Create a new with a of Text from a string. 170 | /// 171 | /// 172 | /// 173 | public static implicit operator Cell(string value) 174 | { 175 | return new Cell(CellType.Text, value, BuiltInCellFormat.Text); 176 | } 177 | 178 | /// 179 | /// Create a new with a of Number (formatted without decimal places) from an integer. 180 | /// 181 | /// 182 | /// 183 | public static implicit operator Cell(long value) 184 | { 185 | return new Cell(CellType.Number, Convert.ToDecimal(value), BuiltInCellFormat.NumberNoDecimalPlaces); 186 | } 187 | 188 | /// 189 | /// Create a new with a of Number (formatted with 2 decimal places) places from a decimal. 190 | /// 191 | /// 192 | /// 193 | public static implicit operator Cell(Decimal value) 194 | { 195 | return new Cell(CellType.Number, value, BuiltInCellFormat.NumberTwoDecimalPlaces); 196 | } 197 | 198 | /// 199 | /// Create a new with a of Number (formatted with 2 decimal places) places from a double. 200 | /// 201 | /// 202 | /// 203 | public static implicit operator Cell(double value) 204 | { 205 | return new Cell(CellType.Number, Convert.ToDecimal(value), BuiltInCellFormat.NumberTwoDecimalPlaces); 206 | } 207 | 208 | /// 209 | /// Create a new with a of Date, formatted as DateAndTime. 210 | /// 211 | /// 212 | /// 213 | public static implicit operator Cell(DateTime value) 214 | { 215 | return new Cell(CellType.Date, value, BuiltInCellFormat.DateAndTime); 216 | } 217 | 218 | /// 219 | /// Create a Cell from the given object, trying to determine the best cell type/format. 220 | /// 221 | /// 222 | /// 223 | public static Cell FromObject(object val) 224 | { 225 | Cell cell; 226 | if (val is sbyte || val is short || val is int || val is long || val is byte || val is uint || val is ushort || val is ulong) 227 | { 228 | cell = new Cell(CellType.Number, Convert.ToDecimal(val), BuiltInCellFormat.NumberNoDecimalPlaces); 229 | } 230 | else if (val is float || val is double || val is decimal) 231 | { 232 | cell = new Cell(CellType.Number, Convert.ToDecimal(val), BuiltInCellFormat.General); 233 | } 234 | else if (val is DateTime) 235 | { 236 | cell = new Cell(CellType.Date, val, BuiltInCellFormat.DateAndTime); 237 | } 238 | else 239 | { 240 | cell = new Cell(CellType.Text, (val ?? String.Empty).ToString(), BuiltInCellFormat.Text); 241 | } 242 | return cell; 243 | } 244 | 245 | /// 246 | /// The largest positive number Excel can handle before applies 247 | /// 248 | public static decimal LargeNumberPositiveLimit => 99999999999m; 249 | 250 | /// 251 | /// The largest negative number Excel can handle before applies 252 | /// 253 | public static decimal LargeNumberNegativeLimit => -99999999999m; 254 | 255 | /// 256 | /// Check if the given number is so large that would apply to it 257 | /// 258 | /// The number to check 259 | /// 260 | public static bool IsLargeNumber(decimal? number) => number.HasValue && (number.Value < LargeNumberNegativeLimit || number.Value > LargeNumberPositiveLimit); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /src/Simplexcel/Cells/CellAddress.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel 2 | { 3 | /// 4 | /// The zero-based Address of a cell 5 | /// 6 | /// 7 | /// This is zero-based, so cell A1 is Row,Column [0,0] 8 | /// 9 | public struct CellAddress 10 | { 11 | /// 12 | /// The zero-based row 13 | /// 14 | public readonly int Row; 15 | 16 | /// 17 | /// The zero-based column 18 | /// 19 | public readonly int Column; 20 | 21 | /// 22 | /// Create a Cell Adress given the zero-based row and column 23 | /// 24 | /// 25 | /// 26 | public CellAddress(int row, int column) 27 | { 28 | Row = row; 29 | Column = column; 30 | } 31 | 32 | /// 33 | /// Create a CellAddress from a Reference like A1 34 | /// 35 | /// 36 | public CellAddress(string cellAddress) 37 | { 38 | CellAddressHelper.ReferenceToColRow(cellAddress, out Row, out Column); 39 | } 40 | 41 | /// 42 | /// Convert this CellAddress into a Cell Reference, e.g. A1 for [0,0] 43 | /// 44 | /// 45 | public override string ToString() 46 | { 47 | return CellAddressHelper.ColRowToReference(Column, Row); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/Simplexcel/Cells/CellBorder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Simplexcel 4 | { 5 | /// 6 | /// Border around a cell 7 | /// 8 | [Flags] 9 | public enum CellBorder 10 | { 11 | /// 12 | /// No Border 13 | /// 14 | None = 0, 15 | 16 | /// 17 | /// Border at the top 18 | /// 19 | Top = 1, 20 | 21 | /// 22 | /// Border on the Right side 23 | /// 24 | Right = 2, 25 | 26 | /// 27 | /// Border at the bottom 28 | /// 29 | Bottom = 4, 30 | 31 | /// 32 | /// Border on the Left Side 33 | /// 34 | Left = 8, 35 | 36 | /// 37 | /// Borders on all four sides 38 | /// 39 | All = Top + Right + Bottom + Left 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Simplexcel/Cells/CellCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace Simplexcel 5 | { 6 | /// 7 | /// A Collection of Cells 8 | /// 9 | public sealed class CellCollection : IEnumerable> 10 | { 11 | private readonly Dictionary _cells = new Dictionary(); 12 | 13 | /// 14 | /// How many Rows are in the sheet? (This counts the maximum row index, so empty rows are counted if they are followed by another row) 15 | /// 16 | public int RowCount 17 | { 18 | get { return _cells.Count > 0 ? _cells.Keys.Max(k => k.Row) + 1 : 0; } 19 | } 20 | 21 | /// 22 | /// How many Columns are in the sheet? (This counts the rightmost column, so empty columns are counted if they are followed by another column) 23 | /// 24 | public int ColumnCount 25 | { 26 | get { return _cells.Count > 0 ? _cells.Keys.Max(k => k.Column) + 1 : 0; } 27 | } 28 | 29 | /// 30 | /// Get the cell with the given cell reference, e.g. Get the cell "A1". May return NULL. 31 | /// 32 | /// 33 | /// The Cell, or NULL of the Cell hasn't been created yet. 34 | public Cell this[string address] 35 | { 36 | get { return this[new CellAddress(address)]; } 37 | set { this[new CellAddress(address)] = value; } 38 | } 39 | 40 | /// 41 | /// Get the cell with the given zero based row and column, e.g. [0,0] returns the A1 cell. May return NULL. 42 | /// 43 | /// 44 | /// 45 | /// The Cell, or NULL of the Cell hasn't been created yet. 46 | public Cell this[int row, int column] 47 | { 48 | get { return this[new CellAddress(row, column)]; } 49 | set { this[new CellAddress(row, column)] = value; } 50 | } 51 | 52 | private Cell this[CellAddress key] 53 | { 54 | get 55 | { 56 | if (!_cells.ContainsKey(key)) 57 | _cells[key] = string.Empty; 58 | 59 | return _cells[key]; 60 | } 61 | set { _cells[key] = value; } 62 | } 63 | 64 | /// 65 | /// Enumerate over the Cells in this Collection 66 | /// 67 | /// 68 | public IEnumerator> GetEnumerator() 69 | { 70 | return _cells.GetEnumerator(); 71 | } 72 | 73 | /// 74 | /// Enumerate over the Cells in this Collection 75 | /// 76 | /// 77 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 78 | { 79 | return GetEnumerator(); 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Simplexcel/Cells/CellType.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel 2 | { 3 | /// 4 | /// The Type of a Cell, important for handling the values corrently (e.g., Excel not converting long numbers into scientific notation or removing leading zeroes) 5 | /// 6 | public enum CellType 7 | { 8 | /// 9 | /// Cell contains text, so use it exactly as specified 10 | /// 11 | Text, 12 | 13 | /// 14 | /// Cell contains a number (may strip leading zeroes but sorts properly) 15 | /// 16 | Number, 17 | 18 | /// 19 | /// Cell contains a date, must be greater than 01/01/0100 20 | /// 21 | Date, 22 | 23 | /// 24 | /// Cell contains a Formula (e.g., "SUM(A1:A10)") 25 | /// 26 | Formula 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Simplexcel/Cells/GradientFill.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace Simplexcel 6 | { 7 | // TODO: Not Yet Implemented 8 | internal class GradientFill : IEquatable 9 | { 10 | public IList Stops { get; } 11 | 12 | public GradientFill() 13 | { 14 | Stops = new List(); 15 | } 16 | 17 | public override bool Equals(object obj) 18 | { 19 | if (obj is null) return false; 20 | if (ReferenceEquals(this, obj)) return true; 21 | if (obj.GetType() != typeof(GradientFill)) return false; 22 | return Equals((GradientFill)obj); 23 | } 24 | 25 | public bool Equals(GradientFill other) 26 | { 27 | if (other is null) return false; 28 | if (ReferenceEquals(this, other)) return true; 29 | return other.Stops.SequenceEqual(Stops); 30 | } 31 | 32 | public override int GetHashCode() 33 | { 34 | unchecked 35 | { 36 | int result = Stops.GetCollectionHashCode(); 37 | return result; 38 | } 39 | } 40 | 41 | public static bool operator ==(GradientFill left, GradientFill right) 42 | { 43 | return Equals(left, right); 44 | } 45 | 46 | public static bool operator !=(GradientFill left, GradientFill right) 47 | { 48 | return !Equals(left, right); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /src/Simplexcel/Cells/GradientStop.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Simplexcel 4 | { 5 | internal class GradientStop : IEquatable 6 | { 7 | /// 8 | /// The position indicated here indicates the point where the color is pure. 9 | /// 10 | public double Position { get; set; } 11 | 12 | /// 13 | /// The pure color of this Gradient Stop 14 | /// 15 | public Color Color { get; set; } 16 | 17 | public override bool Equals(object obj) 18 | { 19 | if (obj is null) return false; 20 | if (ReferenceEquals(this, obj)) return true; 21 | if (obj.GetType() != typeof(GradientStop)) return false; 22 | return Equals((GradientStop)obj); 23 | } 24 | 25 | public bool Equals(GradientStop other) 26 | { 27 | if (other is null) return false; 28 | if (ReferenceEquals(this, other)) return true; 29 | return Equals(other.Position, Position) 30 | && Equals(other.Color, Color); 31 | } 32 | 33 | public override int GetHashCode() 34 | { 35 | unchecked 36 | { 37 | int result = Position.GetHashCode(); 38 | result = (result * 397) ^ Color.GetHashCode(); 39 | return result; 40 | } 41 | } 42 | 43 | public static bool operator ==(GradientStop left, GradientStop right) 44 | { 45 | return Equals(left, right); 46 | } 47 | 48 | public static bool operator !=(GradientStop left, GradientStop right) 49 | { 50 | return !Equals(left, right); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Simplexcel/Cells/HorizontalAlign.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel 2 | { 3 | /// 4 | /// The Horizontal Alignment of content within a Cell 5 | /// 6 | public enum HorizontalAlign 7 | { 8 | /// 9 | /// No specific alignment, use Excel's default 10 | /// 11 | None = 0, 12 | 13 | /// 14 | /// The horizontal alignment is general-aligned. Text data 15 | /// is left-aligned. Numbers, dates, and times are rightaligned. 16 | /// Boolean types are centered. Changing the 17 | /// alignment does not change the type of data. 18 | /// 19 | General, 20 | 21 | /// 22 | /// The horizontal alignment is left-aligned, even in Right-to-Left mode. 23 | /// 24 | Left, 25 | 26 | /// 27 | /// The horizontal alignment is centered, meaning the text is centered across the cell. 28 | /// 29 | Center, 30 | 31 | /// 32 | /// The horizontal alignment is right-aligned, meaning that 33 | /// cell contents are aligned at the right edge of the cell, 34 | /// even in Right-to-Left mode. 35 | /// 36 | Right, 37 | 38 | //Fill, 39 | 40 | /// 41 | /// The horizontal alignment is justified (flush left and 42 | /// right). For each line of text, aligns each line of the 43 | /// wrapped text in a cell to the right and left (except the 44 | /// last line). If no single line of text wraps in the cell, then 45 | /// the text is not justified. 46 | /// 47 | Justify, 48 | 49 | //CenterContinuous, 50 | 51 | //Distributed 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Simplexcel/Cells/IgnoredError.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel 2 | { 3 | /// 4 | /// Information about Errors that are being ignored in a cell 5 | /// 6 | public sealed class IgnoredError 7 | { 8 | /// 9 | /// Ignore errors when numbers are formatted as text or are preceded by an apostrophe. 10 | /// 11 | public bool NumberStoredAsText { get; set; } 12 | 13 | /// 14 | /// Ignore errors when cells contain a value different from a calculated column formula. 15 | /// In other words, for a calculated column, a cell in that column is considered to have 16 | /// an error if its formula is different from the calculated column formula, or doesn't 17 | /// contain a formula at all. 18 | /// 19 | public bool CalculatedColumn { get; set; } 20 | 21 | /// 22 | /// Ignore errors when formulas refer to empty cells 23 | /// 24 | public bool EmptyCellReference { get; set; } 25 | 26 | /// 27 | /// Ignore errors when cells contain formulas that result in an error. 28 | /// 29 | public bool EvalError { get; set; } 30 | 31 | /// 32 | /// Ignore errors when a formula in a region of your worksheet differs from other formulas in the same region. 33 | /// 34 | public bool Formula { get; set; } 35 | 36 | /// 37 | /// Ignore errors when formulas omit certain cells in a region. 38 | /// 39 | public bool FormulaRange { get; set; } 40 | 41 | /// 42 | /// Ignore errors when a cell's value in a Table does not comply with the Data Validation rules specified. 43 | /// 44 | public bool ListDataValidation { get; set; } 45 | 46 | /// 47 | /// Ignore errors when formulas contain text formatted cells with years represented as 2 digits. 48 | /// 49 | public bool TwoDigitTextYear { get; set; } 50 | 51 | /// 52 | /// Ignore errors when unlocked cells contain formulas. 53 | /// 54 | public bool UnlockedFormula { get; set; } 55 | 56 | /// 57 | /// Is this instance different from the default (does it need an ignoredError element?) 58 | /// 59 | internal bool IsDifferentFromDefault => GetHashCode() != 0; 60 | 61 | /// 62 | /// Get the HashCode for this instance, which is a unique combination based on the boolean properties 63 | /// 64 | /// 65 | public override int GetHashCode() 66 | { 67 | // This is like a bit field, since an int is 32-Bit and this class is all booleans 68 | var result = 0; 69 | if (EvalError) { result |= (1 << 0); } 70 | if (TwoDigitTextYear) { result |= (1 << 1); } 71 | if (NumberStoredAsText) { result |= (1 << 2); } 72 | if (Formula) { result |= (1 << 3); } 73 | if (FormulaRange) { result |= (1 << 4); } 74 | if (UnlockedFormula) { result |= (1 << 5); } 75 | if (EmptyCellReference) { result |= (1 << 6); } 76 | if (ListDataValidation) { result |= (1 << 7); } 77 | if (CalculatedColumn) { result |= (1 << 8); } 78 | return result; 79 | } 80 | 81 | /// 82 | /// Compare this to another 83 | /// 84 | /// 85 | /// 86 | public override bool Equals(object obj) 87 | { 88 | if (obj is null) return false; 89 | if (ReferenceEquals(this, obj)) return true; 90 | if (obj.GetType() != typeof(IgnoredError)) return false; 91 | return Equals((IgnoredError)obj); 92 | } 93 | 94 | /// 95 | /// Compare this to another 96 | /// 97 | /// 98 | /// 99 | public bool Equals(IgnoredError other) 100 | { 101 | if (other is null) return false; 102 | if (ReferenceEquals(this, other)) return true; 103 | 104 | var thisId = GetHashCode(); 105 | var otherId = other.GetHashCode(); 106 | 107 | return otherId.Equals(thisId); 108 | } 109 | 110 | /// 111 | /// Compare a to another 112 | /// 113 | /// 114 | /// 115 | /// 116 | public static bool operator ==(IgnoredError left, IgnoredError right) 117 | { 118 | return Equals(left, right); 119 | } 120 | 121 | /// 122 | /// Compare a to another 123 | /// 124 | /// 125 | /// 126 | /// 127 | public static bool operator !=(IgnoredError left, IgnoredError right) 128 | { 129 | return !Equals(left, right); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/Simplexcel/Cells/PatternFill.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Simplexcel 4 | { 5 | /// 6 | /// A Pattern Fill of a Cell. 7 | /// 8 | public class PatternFill : IEquatable 9 | { 10 | // TODO: Add a Gradient Fill to this. 11 | // This isn't the structure in the XML, but that's how Excel Presents it, as a "Fill Effect" 12 | 13 | bool firstTimeSet = false; 14 | private Color? _bgColor; 15 | 16 | /// 17 | /// The type of Fill Pattern to use. 18 | /// Refer to the documentation for a list of each pattern. 19 | /// 20 | public PatternType PatternType { get; set; } 21 | 22 | /// 23 | /// The Background Color of the cell 24 | /// 25 | public Color? PatternColor { get; set; } 26 | 27 | /// 28 | /// The Background Color of the Fill. 29 | /// (No effect if is ) 30 | /// 31 | public Color? BackgroundColor 32 | { 33 | get => _bgColor; 34 | set 35 | { 36 | _bgColor = value; 37 | 38 | // PatternType defaults to None, but the first time we set a background color, 39 | // set it to solid as the user likely wants the background color to show. 40 | // Further modifications won't change the PatternType, as this is now a delibrate setting 41 | if (_bgColor.HasValue && PatternType == PatternType.None && !firstTimeSet) 42 | { 43 | PatternType = PatternType.Solid; 44 | firstTimeSet = true; 45 | } 46 | } 47 | } 48 | 49 | /// 50 | /// Compare to another object. 51 | /// 52 | public override bool Equals(object obj) 53 | => Equals(obj as PatternFill); 54 | 55 | /// 56 | /// Compare to another object. 57 | /// 58 | public bool Equals(PatternFill other) 59 | { 60 | if (other is null) return false; 61 | if (ReferenceEquals(this, other)) return true; 62 | return Equals(other.PatternType, PatternType) 63 | && Equals(other.PatternColor, PatternColor) 64 | && Equals(other.BackgroundColor, BackgroundColor); 65 | } 66 | 67 | /// 68 | public override int GetHashCode() 69 | { 70 | unchecked 71 | { 72 | int result = PatternType.GetHashCode(); 73 | if (PatternColor.HasValue) 74 | { 75 | result = (result * 397) ^ PatternColor.GetHashCode(); 76 | } 77 | if (BackgroundColor.HasValue) 78 | { 79 | result = (result * 397) ^ BackgroundColor.GetHashCode(); 80 | } 81 | return result; 82 | } 83 | } 84 | 85 | /// 86 | /// Check whether the objects and are Equal. 87 | /// 88 | public static bool operator ==(PatternFill left, PatternFill right) => Equals(left, right); 89 | 90 | /// 91 | /// Check whether the objects and are not Equal. 92 | /// 93 | public static bool operator !=(PatternFill left, PatternFill right) => !Equals(left, right); 94 | } 95 | } -------------------------------------------------------------------------------- /src/Simplexcel/Cells/PatternType.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel 2 | { 3 | /// 4 | /// The Type of the pattern in a . 5 | /// 6 | public enum PatternType 7 | { 8 | /// 9 | /// none 10 | /// 11 | None = 0, 12 | 13 | /// 14 | /// solid 15 | /// 16 | Solid, 17 | 18 | /// 19 | /// 75% Gray 20 | /// darkGray 21 | /// 22 | Gray750, 23 | 24 | /// 25 | /// 50% Gray 26 | /// mediumGray 27 | /// 28 | Gray500, 29 | 30 | /// 31 | /// 25% Gray 32 | /// lightGray 33 | /// 34 | Gray250, 35 | 36 | /// 37 | /// 12.5% Gray 38 | /// gray125 39 | /// 40 | Gray125, 41 | 42 | /// 43 | /// 6.25% Gray 44 | /// gray0625 45 | /// 46 | Gray0625, 47 | 48 | /// 49 | /// Horizontal Stripe 50 | /// darkHorizontal 51 | /// 52 | HorizontalStripe, 53 | 54 | /// 55 | /// Vertical Stripe 56 | /// darkVertical 57 | /// 58 | VerticalStripe, 59 | 60 | /// 61 | /// Reverse Diagonal Stripe 62 | /// darkDown 63 | /// 64 | ReverseDiagonalStripe, 65 | 66 | /// 67 | /// Diagonal Stripe 68 | /// darkUp 69 | /// 70 | DiagonalStripe, 71 | 72 | /// 73 | /// Diagonal Crosshatch 74 | /// darkGrid 75 | /// 76 | DiagonalCrosshatch, 77 | 78 | /// 79 | /// Thick Diagonal Crosshatch 80 | /// darkTrellis 81 | /// 82 | ThickDiagonalCrosshatch, 83 | 84 | /// 85 | /// Thin Horizontal Stripe 86 | /// lightHorizontal 87 | /// 88 | ThinHorizontalStripe, 89 | 90 | /// 91 | /// Thin Vertical Stripe 92 | /// lightVertical 93 | /// 94 | ThinVerticalStripe, 95 | 96 | /// 97 | /// Thin Reverse Diagonal Stripe 98 | /// lightDown 99 | /// 100 | ThinReverseDiagonalStripe, 101 | 102 | /// 103 | /// Thin Diagonal Stripe 104 | /// lightUp 105 | /// 106 | ThinDiagonalStripe, 107 | 108 | /// 109 | /// Thin Horizontal Crosshatch 110 | /// lightGrid 111 | /// 112 | ThinHorizontalCrosshatch, 113 | 114 | /// 115 | /// Thin Diagonal Crosshatch 116 | /// lightTrellis 117 | /// 118 | ThinDiagonalCrosshatch 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/Simplexcel/Cells/VerticalAlign.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel 2 | { 3 | /// 4 | /// The Vertical Alignment of content within a Cell 5 | /// 6 | public enum VerticalAlign 7 | { 8 | /// 9 | /// No specific alignment, use Excel's default 10 | /// 11 | None = 0, 12 | 13 | /// 14 | /// The vertical alignment is aligned-to-top. 15 | /// 16 | Top, 17 | 18 | /// 19 | /// The vertical alignment is centered across the height of the cell. 20 | /// 21 | Middle, 22 | 23 | /// 24 | /// The vertical alignment is aligned-to-bottom. 25 | /// 26 | Bottom, 27 | 28 | /// 29 | /// When text direction is horizontal: the vertical alignment of lines of text is 30 | /// distributed vertically, where each line of text inside the cell is evenly 31 | /// distributed across the height of the cell, with flush top and bottom margins. 32 | /// 33 | /// When text direction is vertical: similar behavior as horizontal justification. 34 | /// The alignment is justified (flush top and bottom in this case). For each line 35 | /// of text, each line of the wrapped text in a cell is aligned to the top and 36 | /// bottom (except the last line). If no single line of text wraps in the cell, 37 | /// then the text is not justified. 38 | /// 39 | Justify, 40 | 41 | //Distributed 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Simplexcel/Cells/XlsxColumnAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Simplexcel 4 | { 5 | /// 6 | /// Used with or , allows setting how object properties are handled. 7 | /// 8 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] 9 | public class XlsxColumnAttribute : Attribute 10 | { 11 | /// 12 | /// The name of the Column, used as the Header row. 13 | /// If this is NULL, the Property Name will be used. 14 | /// If this is an Empty string, then no text will be in the Cell header. 15 | /// 16 | public string Name { get; set; } 17 | 18 | /// 19 | /// The Index of the Column, e.g., "0" for "A". 20 | /// If there are Columns with and without an Index, the columns 21 | /// without an Index will be added after the last column with an Index. 22 | /// 23 | /// It is recommended that either all object properties or none specify Column Indexes 24 | /// 25 | public int ColumnIndex { get; set; } 26 | 27 | /// 28 | /// Create a new XlsxColumnAttribute 29 | /// 30 | public XlsxColumnAttribute() 31 | { 32 | ColumnIndex = -1; 33 | } 34 | 35 | /// 36 | /// Create a new XlsxColumnAttribute with the given name 37 | /// 38 | /// 39 | public XlsxColumnAttribute(string name) 40 | { 41 | Name = name; 42 | ColumnIndex = -1; 43 | } 44 | } 45 | 46 | /// 47 | /// This attribute causes to ignore the property completely 48 | /// 49 | [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] 50 | public sealed class XlsxIgnoreColumnAttribute : Attribute 51 | { 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Simplexcel/ColumnWidthCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace Simplexcel 5 | { 6 | /// 7 | /// Custom Column Widths within a worksheet 8 | /// 9 | public sealed class ColumnWidthCollection : IEnumerable> 10 | { 11 | private readonly Dictionary _columnWidths = new Dictionary(); 12 | 13 | /// 14 | /// Get or set the width of a column (Zero-based column index, null value = auto) 15 | /// 16 | /// Zero-based column index 17 | /// 18 | public double? this[int column] 19 | { 20 | get { return _columnWidths.ContainsKey(column) ? _columnWidths[column] : (double?)null; } 21 | set 22 | { 23 | if (!value.HasValue) 24 | { 25 | _columnWidths.Remove(column); 26 | } 27 | else 28 | { 29 | _columnWidths[column] = value.Value; 30 | } 31 | } 32 | } 33 | 34 | /// 35 | /// Enumerate over the custom column widths. The Key is the zero-based column, the value is the custom column width. 36 | /// 37 | /// 38 | public IEnumerator> GetEnumerator() 39 | { 40 | return _columnWidths.GetEnumerator(); 41 | } 42 | 43 | /// 44 | /// Enumerate over the custom column widths. The Key is the zero-based column, the value is the custom column width. 45 | /// 46 | /// 47 | IEnumerator IEnumerable.GetEnumerator() 48 | { 49 | return GetEnumerator(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Simplexcel/ExtensionMethods.Internal.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection; 4 | 5 | namespace Simplexcel 6 | { 7 | internal static class ExtensionMethods 8 | { 9 | internal static IEnumerable GetAllProperties(this TypeInfo typeInfo) => GetAllForType(typeInfo, ti => ti.DeclaredProperties); 10 | 11 | private static IEnumerable GetAllForType(TypeInfo typeInfo, Func> accessor) 12 | { 13 | if (typeInfo == null) 14 | { 15 | yield break; 16 | } 17 | 18 | // The Stack is to make sure that we fetch Base Type Properties first 19 | var baseTypes = new Stack(); 20 | while (typeInfo != null) 21 | { 22 | baseTypes.Push(typeInfo); 23 | typeInfo = typeInfo.BaseType?.GetTypeInfo(); 24 | } 25 | 26 | while (baseTypes.Count > 0) 27 | { 28 | var ti = baseTypes.Pop(); 29 | foreach (var t in accessor(ti)) 30 | { 31 | yield return t; 32 | } 33 | } 34 | } 35 | 36 | internal static int GetCollectionHashCode(this IEnumerable input) 37 | { 38 | var hashCode = 0; 39 | if (input != null) 40 | { 41 | foreach (var item in input) 42 | { 43 | var itemHashCode = item == null ? 0 : item.GetHashCode(); 44 | hashCode = (hashCode * 397) ^ itemHashCode; 45 | } 46 | } 47 | return hashCode; 48 | } 49 | 50 | private static readonly char[] HexEncodingTable = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; 51 | 52 | /// 53 | /// Taken from Bouncy Castle and slightly changed to create a string rather than write to a stream. 54 | /// https://www.bouncycastle.org/ 55 | /// 56 | /// Licensed under MIT License 57 | /// https://www.bouncycastle.org/csharp/licence.html 58 | /// 59 | internal static string ToHexString(this IReadOnlyList bytes, int offset = 0, int length = -1) 60 | { 61 | if (bytes == null || bytes.Count == 0) { return string.Empty; } 62 | if (length == -1) { length = bytes.Count; } 63 | 64 | var result = new char[2 * length]; 65 | var index = offset; 66 | for (int i = offset; i < (offset + length); i++) 67 | { 68 | var v = bytes[i]; 69 | result[index++] = HexEncodingTable[v >> 4]; 70 | result[index++] = HexEncodingTable[v & 0xf]; 71 | } 72 | return new string(result); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Simplexcel/ExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace Simplexcel 6 | { 7 | /// 8 | /// Some utility methods that don't need to be in their respective classes 9 | /// 10 | public static class SimplexcelExtensionMethods 11 | { 12 | /// 13 | /// Insert a manual page break after the row specified by the cell address (e.g., B5) 14 | /// 15 | /// 16 | /// 17 | public static void InsertManualPageBreakAfterRow(this Worksheet sheet, string cellAddress) 18 | { 19 | CellAddressHelper.ReferenceToColRow(cellAddress, out int row, out _); 20 | sheet.InsertManualPageBreakAfterRow(row+1); 21 | } 22 | 23 | /// 24 | /// Insert a manual page break to the left of the column specified by the cell address (e.g., B5) 25 | /// 26 | /// 27 | /// 28 | public static void InsertManualPageBreakAfterColumn(this Worksheet sheet, string cellAddress) 29 | { 30 | CellAddressHelper.ReferenceToColRow(cellAddress, out _, out int col); 31 | sheet.InsertManualPageBreakAfterColumn(col+1); 32 | } 33 | 34 | private readonly static Type XlsxIgnoreColumnType = typeof(XlsxIgnoreColumnAttribute); 35 | private readonly static Type XlsxColumnType = typeof(XlsxColumnAttribute); 36 | internal static bool HasXlsxIgnoreAttribute(this PropertyInfo prop) 37 | { 38 | if(prop == null) 39 | { 40 | throw new ArgumentNullException(nameof(prop)); 41 | } 42 | 43 | return prop.GetCustomAttributes(XlsxIgnoreColumnType).Any(); 44 | } 45 | 46 | internal static XlsxColumnAttribute GetXlsxColumnAttribute(this PropertyInfo prop) 47 | { 48 | if (prop == null) 49 | { 50 | throw new ArgumentNullException(nameof(prop)); 51 | } 52 | 53 | return prop.GetCustomAttributes(XlsxColumnType).Cast().FirstOrDefault(); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Simplexcel/GlobalSuppressions.cs: -------------------------------------------------------------------------------- 1 | // This file is used by Code Analysis to maintain SuppressMessage 2 | // attributes that are applied to this project. 3 | // Project-level suppressions either have no target or are given 4 | // a specific target and scoped to a namespace, type, member, etc. 5 | 6 | [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0063:Use simple 'using' statement", Justification = "I hate it.")] 7 | -------------------------------------------------------------------------------- /src/Simplexcel/InternalsVisibleTo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | [assembly: InternalsVisibleTo("Simplexcel.Tests,PublicKey=00240000048000009400000006020000002400005253413100040000010001004131a505299be2e17e97ea1d04b7383b88ff53e68698071513c8698718a3804409c1051da9447b2c96da55d46d89bb7c587f8fbbba151aac898f13a7327992e8a3a306702db2e702ec84d7d0ae6c69526f01f1eeeaeedb3e2a98a4c151e1978945b5a76e0ab2e149151ef6f27da20ca71b3b857a5216660f83c8f8521ca0e3ac")] -------------------------------------------------------------------------------- /src/Simplexcel/LargeNumberHandlingMode.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel 2 | { 3 | /// 4 | /// Excel doesn't want to display large numbers (more than 11 digits) properly: 5 | /// https://support.microsoft.com/en-us/help/2643223/long-numbers-are-displayed-incorrectly-in-excel 6 | /// 7 | /// This decides how to handle larger numbers. 8 | /// See and for the actual limits. 9 | /// 10 | public enum LargeNumberHandlingMode 11 | { 12 | /// 13 | /// Force the number to be stored as Text (Default) 14 | /// 15 | StoreAsText = 0, 16 | 17 | /// 18 | /// Do not do anything different, and store numbers as-is. 19 | /// This may cause Excel to truncate the number. 20 | /// This was the behavior before Simplexcel Version 2.1.0. 21 | /// 22 | None 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Simplexcel/PageSetup/Orientation.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel 2 | { 3 | /// 4 | /// The Orientation of a page 5 | /// 6 | public enum Orientation 7 | { 8 | /// 9 | /// Portrait (default) 10 | /// 11 | Portrait = 0, 12 | /// 13 | /// Landscape 14 | /// 15 | Landscape 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Simplexcel/PageSetup/PageBreak.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel 2 | { 3 | /// 4 | /// The brk element, for Page Breaks 5 | /// 6 | public sealed class PageBreak 7 | { 8 | /// 9 | /// Zero-based row or column Id of the page break. 10 | /// Breaks occur above the specified row and left of the specified column. 11 | /// 12 | public int Id { get; set; } 13 | 14 | /// 15 | /// Manual Break flag. true means the break is a manually inserted break. 16 | /// 17 | public bool IsManualBreak { get; set; } 18 | 19 | /// 20 | /// Zero-based index of end row or column of the break. 21 | /// For row breaks, specifies column index; 22 | /// for column breaks, specifies row index. 23 | /// 24 | public int Max { get; set; } 25 | 26 | /// 27 | /// Zero-based index of start row or column of the break. 28 | /// For row breaks, specifies column index; 29 | /// for column breaks, specifies row index. 30 | /// 31 | public int Min { get; set; } 32 | 33 | /// 34 | /// Flag indicating that a PivotTable created this break. 35 | /// 36 | public bool IsPivotCreatedPageBreak { get; set; } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Simplexcel/PageSetup/PageSetup.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel 2 | { 3 | /// 4 | /// Page Setup for a Sheet 5 | /// 6 | public sealed class PageSetup 7 | { 8 | /// 9 | /// How many rows to repeat on each page when printing? 10 | /// 11 | public int PrintRepeatRows { get; set; } 12 | 13 | /// 14 | /// How many columns to repeat on each page when printing? 15 | /// 16 | public int PrintRepeatColumns { get; set; } 17 | 18 | /// 19 | /// The Orientation of the page, Portrait (default) or Landscape 20 | /// 21 | public Orientation Orientation { get; set; } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Simplexcel/Simplexcel.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0;netstandard2.1;net45 4 | 3.0.0 5 | Simplexcel 6 | Michael Stum <opensource@stum.de> 7 | 3.0.0.0 8 | 3.0.0.0 9 | en 10 | Simplexcel .xlsx library 11 | A 100% managed code library to generate Excel .xlsx Workbooks. Can be safely used on a server, no COM Interop or other unsafe/unsupported operations. 12 | © 2013-2020 Michael Stum 13 | MIT 14 | false 15 | https://github.com/mstum/simplexcel 16 | https://github.com/mstum/simplexcel.git 17 | package.png 18 | git 19 | xlsx excel ooxml netcore netstandard 20 | simplexcel 21 | true 22 | true 23 | ../simplexcel_oss.snk 24 | false 25 | 8.0 26 | 27 | 28 | 29 | embedded 30 | true 31 | true 32 | True 33 | true 34 | true 35 | true 36 | snupkg 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | all 51 | runtime; build; native; contentfiles; analyzers; buildtransitive 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/Simplexcel/SimplexcelVersion.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Reflection; 4 | 5 | namespace Simplexcel 6 | { 7 | /// 8 | /// The version of the Simplexcel Library. 9 | /// 10 | public static class SimplexcelVersion 11 | { 12 | /// 13 | /// The version of the Simplexcel Library. 14 | /// This might include a suffix for development versions, e.g., "2.3.0.177-v3-dev" 15 | /// 16 | public static string VersionString { get; } 17 | 18 | /// 19 | /// The version of the Simplexcel Library. 20 | /// This does not indicate if this is a development version. 21 | /// 22 | public static Version Version { get; } 23 | 24 | /// 25 | /// The Public Key that was used when signing Simplexcel 26 | /// 27 | public static string PublicKey { get; } 28 | 29 | /// 30 | /// The Public Key Token that was used when signing Simplexcel 31 | /// 32 | public static string PublicKeyToken { get; } 33 | 34 | static SimplexcelVersion() 35 | { 36 | // AssemblyVersion is always 3.0.0.0 due to strong naming 37 | // [assembly: AssemblyVersion("3.0.0.0")] 38 | // [assembly: AssemblyFileVersion("3.0.0.177")] 39 | // [assembly: AssemblyInformationalVersion("3.0.0.177-v3-dev")] 40 | var assembly = typeof(SimplexcelVersion).Assembly; 41 | var asmName = assembly.GetName(); 42 | PublicKey = asmName.GetPublicKey().ToHexString(); 43 | PublicKeyToken = asmName.GetPublicKeyToken().ToHexString(); 44 | 45 | var infoVersion = assembly.GetCustomAttribute(); 46 | var fileVersion = assembly.GetCustomAttribute(); 47 | Version = fileVersion != null ? new Version(fileVersion.Version) : asmName.Version; 48 | VersionString = infoVersion?.InformationalVersion ?? asmName.Version.ToString(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Simplexcel/Workbook.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using Simplexcel.XlsxInternal; 6 | 7 | namespace Simplexcel 8 | { 9 | /// 10 | /// An Excel Workbook 11 | /// 12 | public sealed class Workbook 13 | { 14 | private readonly List _sheets = new List(); 15 | 16 | /// 17 | /// The Worksheets in this Workbook 18 | /// 19 | public IEnumerable Sheets { get { return _sheets.AsEnumerable(); } } 20 | 21 | /// 22 | /// The title of the Workbook 23 | /// 24 | public string Title { get; set; } 25 | 26 | /// 27 | /// The author of the Workbook 28 | /// 29 | public string Author { get; set; } 30 | 31 | /// 32 | /// How many sheets are in the Workbook currently? 33 | /// 34 | public int SheetCount 35 | { 36 | get { return _sheets.Count; } 37 | } 38 | 39 | /// 40 | /// Add a worksheet to this workbook. Sheet names must be unique. 41 | /// 42 | /// Thrown if a sheet with the same Name already exists 43 | /// 44 | public void Add(Worksheet sheet) 45 | { 46 | // According to ECMA-376, sheet names must be unique 47 | if (_sheets.Any(s => s.Name == sheet.Name)) 48 | { 49 | throw new ArgumentException("This workbook already contains a sheet named " + sheet.Name); 50 | } 51 | 52 | _sheets.Add(sheet); 53 | } 54 | 55 | /// 56 | /// Save this workbook to a file, overwriting the file if it exists 57 | /// 58 | /// 59 | /// Use compression? (Smaller files/higher CPU Usage) 60 | /// Thrown if there are no sheets in the workbook. 61 | public void Save(string filename, bool compress = true) 62 | { 63 | if(string.IsNullOrEmpty(filename)) 64 | { 65 | throw new ArgumentException("Invalid Filename.", nameof(filename)); 66 | } 67 | 68 | using (var fs = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.None)) 69 | { 70 | Save(fs, compress); 71 | } 72 | } 73 | 74 | /// 75 | /// Save this workbook to the given stream 76 | /// 77 | /// 78 | /// Use compression? (Smaller files/higher CPU Usage) 79 | /// Thrown if there are no sheets in the workbook. 80 | public void Save(Stream stream, bool compress = true) 81 | { 82 | if(stream == null) 83 | { 84 | throw new ArgumentNullException(nameof(stream)); 85 | } 86 | 87 | if(!stream.CanWrite) 88 | { 89 | throw new InvalidOperationException("Stream to save to is not writeable."); 90 | } 91 | 92 | if (stream.CanSeek) 93 | { 94 | XlsxWriter.Save(this, stream, compress); 95 | } 96 | else 97 | { 98 | // ZipArchive needs a seekable stream. If a stream is not seekable (e.g., HttpContext.Response.OutputStream), wrap it in a MemoryStream instead. 99 | // TODO: Can we guess the required capacity? 100 | using(var ms = new MemoryStream()) 101 | { 102 | XlsxWriter.Save(this, ms, compress); 103 | ms.CopyTo(stream); 104 | } 105 | } 106 | } 107 | } 108 | } -------------------------------------------------------------------------------- /src/Simplexcel/Worksheet.Populate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Reflection; 6 | 7 | namespace Simplexcel 8 | { 9 | public sealed partial class Worksheet 10 | { 11 | private readonly static Lazy>> PopulateCache 12 | = new Lazy>>( 13 | () => new ConcurrentDictionary>(), 14 | System.Threading.LazyThreadSafetyMode.ExecutionAndPublication); 15 | 16 | /// 17 | /// Create Worksheet with the provided data. 18 | /// 19 | /// Will use the Object Property Names as Column Headers (First Row) and then populate the cells with data. 20 | /// You can use and to control the output. 21 | /// 22 | /// The name of the Sheet 23 | /// The data, can be empty or null 24 | /// If true, the Column info for the given type is being cached in memory 25 | public static Worksheet FromData(string sheetName, IEnumerable data, bool cacheTypeColumns = false) where T : class 26 | { 27 | var sheet = new Worksheet(sheetName); 28 | sheet.Populate(data, cacheTypeColumns); 29 | return sheet; 30 | } 31 | 32 | /// 33 | /// Populate Worksheet with the provided data. 34 | /// 35 | /// Will use the Object Property Names as Column Headers (First Row) and then populate the cells with data. 36 | /// You can use and to control the output. 37 | /// 38 | /// The data, can be empty or null 39 | /// If true, the Column info for the given type is being cached in memory 40 | public void Populate(IEnumerable data, bool cacheTypeColumns = false) where T : class 41 | { 42 | data ??= Enumerable.Empty(); 43 | 44 | var type = typeof(T); 45 | 46 | // Key = TempColumnIndex, Value = Attribute 47 | var cols = cacheTypeColumns ? TryGetFromCache(type) : null; 48 | if (cols == null) 49 | { 50 | cols = GetColumsFromType(type); 51 | if (cacheTypeColumns) 52 | { 53 | AddToPopulateCache(type, cols); 54 | } 55 | } 56 | 57 | foreach (var pi in cols.Values) 58 | { 59 | Cells[0, pi.ColumnIndex] = pi.Name; 60 | Cells[0, pi.ColumnIndex].Bold = true; 61 | } 62 | 63 | var row = 0; 64 | foreach (var item in data) 65 | { 66 | row++; 67 | 68 | foreach (var pi in cols.Values) 69 | { 70 | object val = pi.Property.GetValue(item); 71 | var cell = Cell.FromObject(val); 72 | Cells[row, pi.ColumnIndex] = cell; 73 | } 74 | } 75 | } 76 | 77 | private static Dictionary GetColumsFromType(Type type) 78 | { 79 | var cols = new Dictionary(); 80 | var props = type.GetTypeInfo().GetAllProperties() 81 | .Where(p => p.GetIndexParameters().Length == 0) 82 | .ToList(); 83 | 84 | int tempCol = 0; // Just a counter to keep the order of Properties the same 85 | int maxCol = -1; // Largest Column that has XlsxColumnAttribute.ColumnIndex specified 86 | foreach (var prop in props) 87 | { 88 | if (prop.HasXlsxIgnoreAttribute()) 89 | { 90 | continue; 91 | } 92 | 93 | var pi = new PopulateCellInfo(); 94 | var colInfo = prop.GetXlsxColumnAttribute(); 95 | pi.Name = colInfo?.Name == null ? prop.Name : colInfo.Name; 96 | pi.ColumnIndex = colInfo?.ColumnIndex != null ? colInfo.ColumnIndex : -1; // -1 will later be reassigned 97 | pi.TempColumnIndex = tempCol++; 98 | 99 | if (pi.ColumnIndex > maxCol) 100 | { 101 | maxCol = pi.ColumnIndex; 102 | } 103 | 104 | pi.Property = prop; 105 | cols[pi.TempColumnIndex] = pi; 106 | } 107 | 108 | // Slot any Columns without an order after maxCol 109 | for (int i = 0; i < tempCol; i++) 110 | { 111 | var pi = cols[i]; 112 | if (pi.ColumnIndex == -1) 113 | { 114 | pi.ColumnIndex = ++maxCol; 115 | } 116 | } 117 | 118 | return cols; 119 | } 120 | 121 | private static void AddToPopulateCache(Type type, Dictionary cols) 122 | { 123 | PopulateCache.Value.AddOrUpdate(type, cols, (u1, u2) => cols); 124 | } 125 | 126 | private static Dictionary TryGetFromCache(Type type) 127 | { 128 | if (!PopulateCache.IsValueCreated) 129 | { 130 | return null; 131 | } 132 | 133 | if (PopulateCache.Value.TryGetValue(type, out var cached)) 134 | { 135 | return cached; 136 | } 137 | 138 | return null; 139 | } 140 | 141 | private class PopulateCellInfo 142 | { 143 | public string Name { get; set; } 144 | public int ColumnIndex { get; set; } 145 | public int TempColumnIndex { get; set; } 146 | public PropertyInfo Property { get; set; } 147 | } 148 | } 149 | } -------------------------------------------------------------------------------- /src/Simplexcel/Worksheet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Simplexcel 5 | { 6 | /// 7 | /// A single Worksheet in an Excel Document 8 | /// 9 | public sealed partial class Worksheet 10 | { 11 | private readonly CellCollection _cells = new CellCollection(); 12 | 13 | private readonly ColumnWidthCollection _columnWidth = new ColumnWidthCollection(); 14 | 15 | private readonly PageSetup _pageSetup = new PageSetup(); 16 | 17 | private List _sheetViews; 18 | private List _rowBreaks; 19 | private List _columnBreaks; 20 | 21 | /// 22 | /// Get a list of characters that are invalid to use in the Sheet Name 23 | /// 24 | /// 25 | /// These chars are not part of the ECMA-376 standard, but imposed by Excel 26 | /// 27 | public static readonly char[] InvalidSheetNameChars = new[] { '\\', '/', '?', '*', '[', ']' }; 28 | 29 | /// 30 | /// Get the maximum allowable length for a sheet name 31 | /// 32 | /// 33 | /// This limit is not part of the ECMA-376 standard, but imposed by Excel 34 | /// 35 | public static readonly int MaxSheetNameLength = 31; 36 | 37 | /// 38 | /// Create a new Worksheet with the given name 39 | /// 40 | /// Thrown if the name is null, empty, longer than characters or contains any character in 41 | /// 42 | public Worksheet(string name) 43 | { 44 | if (string.IsNullOrEmpty(name)) 45 | { 46 | throw new ArgumentException("Sheet name is null or empty"); 47 | } 48 | 49 | if (name.Length > MaxSheetNameLength) 50 | { 51 | throw new ArgumentException("Sheet name is longer than " + MaxSheetNameLength + " characters."); 52 | } 53 | 54 | if (name.IndexOfAny(InvalidSheetNameChars) > -1) 55 | { 56 | throw new ArgumentException("Sheet Names can not contain any of these characters: " + string.Join(" ", InvalidSheetNameChars)); 57 | } 58 | 59 | Name = name; 60 | LargeNumberHandlingMode = LargeNumberHandlingMode.StoreAsText; 61 | } 62 | 63 | /// 64 | /// Gets or sets the Name of the Worksheet 65 | /// 66 | public string Name { get; } 67 | 68 | /// 69 | /// The Page Orientation and some other related values 70 | /// 71 | public PageSetup PageSetup 72 | { 73 | get { return _pageSetup; } 74 | } 75 | 76 | /// 77 | /// The Cells of the Worksheet (zero based, [0,0] = A1) 78 | /// 79 | public CellCollection Cells 80 | { 81 | get { return _cells; } 82 | } 83 | 84 | /// 85 | /// The Width of individual columns (Zero-based, in Excel's Units) 86 | /// 87 | public ColumnWidthCollection ColumnWidths 88 | { 89 | get { return _columnWidth; } 90 | } 91 | 92 | /// 93 | /// How to handle numbers that are larger than or smaller than ? 94 | /// 95 | public LargeNumberHandlingMode LargeNumberHandlingMode { get; set; } 96 | 97 | /// 98 | /// Whether to enable the AutoFilter feature on the first non-empty row. 99 | /// 100 | /// Defaults to . 101 | /// 102 | /// See Use AutoFilter to filter your data 103 | public bool AutoFilter { get; set; } 104 | 105 | /// 106 | /// Get the cell with the given cell reference, e.g. Get the cell "A1". May return NULL. 107 | /// 108 | /// 109 | /// The Cell, or NULL of the Cell hasn't been created yet. 110 | public Cell this[string address] 111 | { 112 | get { return Cells[address]; } 113 | set { Cells[address] = value; } 114 | } 115 | 116 | /// 117 | /// Get the cell with the given zero based row and column, e.g. [0,0] returns the A1 cell. May return NULL. 118 | /// 119 | /// 120 | /// 121 | /// The Cell, or NULL of the Cell hasn't been created yet. 122 | public Cell this[int row, int column] 123 | { 124 | get { return Cells[row, column]; } 125 | set { Cells[row, column] = value; } 126 | } 127 | 128 | /// 129 | /// Freeze the top row, that is, create a that splits the first row (A) into a pane. 130 | /// 131 | public void FreezeTopRow() => FreezeTopLeft(1, 0, Panes.BottomLeft); 132 | 133 | /// 134 | /// Freeze the first column, that is, create a that splits the first column (1) into a pane. 135 | /// 136 | public void FreezeLeftColumn() => FreezeTopLeft(0, 1, Panes.TopRight); 137 | 138 | /// 139 | /// Freezes rows at the top and columns on the left. 140 | /// 141 | /// The number of rows to freeze. 142 | /// The number of columns to freeze. 143 | /// The pane that's selected in the sheet. Leave null to automatically determine this. 144 | public void FreezeTopLeft(int rows, int columns, Panes? activePane = null) 145 | { 146 | // TODO: Eventually, support more SheetView functionality, right now, keep it simple. 147 | if (_sheetViews != null) 148 | { 149 | throw new InvalidOperationException("You have already frozen rows and/or columns on this Worksheet."); 150 | } 151 | 152 | if (rows < 0) 153 | { 154 | throw new ArgumentOutOfRangeException(nameof(rows), "Rows cannot be negative."); 155 | } 156 | 157 | if (columns < 0) 158 | { 159 | throw new ArgumentOutOfRangeException(nameof(columns), "Columns cannot be negative."); 160 | } 161 | 162 | if (columns == 0 && rows == 0) 163 | { 164 | return; 165 | } 166 | 167 | if (!activePane.HasValue) 168 | { 169 | if (columns == 0) 170 | { 171 | activePane = Panes.BottomLeft; 172 | } 173 | else if (rows == 0) 174 | { 175 | activePane = Panes.TopRight; 176 | } 177 | else 178 | { 179 | activePane = Panes.BottomRight; 180 | } 181 | } 182 | 183 | var sheetView = new SheetView 184 | { 185 | Pane = new Pane 186 | { 187 | ActivePane = activePane.Value, 188 | XSplit = columns > 0 ? (int?)columns : null, 189 | YSplit = rows > 0 ? (int?)rows : null, 190 | TopLeftCell = CellAddressHelper.ColRowToReference(columns, rows), 191 | State = PaneState.Frozen 192 | } 193 | }; 194 | sheetView.AddSelection(new Selection { ActivePane = activePane.Value }); 195 | AddSheetView(sheetView); 196 | } 197 | 198 | private void AddSheetView(SheetView sheetView) 199 | { 200 | if (_sheetViews == null) 201 | { 202 | _sheetViews = new List(); 203 | } 204 | _sheetViews.Add(sheetView); 205 | } 206 | 207 | internal ICollection GetSheetViews() 208 | { 209 | return _sheetViews; 210 | } 211 | 212 | internal ICollection GetRowBreaks() 213 | { 214 | return _rowBreaks; 215 | } 216 | 217 | internal ICollection GetColumnBreaks() 218 | { 219 | return _columnBreaks; 220 | } 221 | 222 | /// 223 | /// Insert a manual page break after the row specified by the zero-based index (e.g., 0 for the first row) 224 | /// 225 | /// The zero-based row index (e.g., 0 for the first row) 226 | public void InsertManualPageBreakAfterRow(int row) 227 | { 228 | if (_rowBreaks == null) 229 | { 230 | _rowBreaks = new List(); 231 | } 232 | 233 | _rowBreaks.Add(new PageBreak 234 | { 235 | Id = row, 236 | IsManualBreak = true 237 | }); 238 | } 239 | 240 | /// 241 | /// Insert a manual page break to the left of the column specified by the zero-based index (e.g., 0 for column A) 242 | /// 243 | /// The zero-based index of the column (e.g., 0 for column A) 244 | public void InsertManualPageBreakAfterColumn(int col) 245 | { 246 | if (_columnBreaks == null) 247 | { 248 | _columnBreaks = new List(); 249 | } 250 | 251 | _columnBreaks.Add(new PageBreak 252 | { 253 | Id = col, 254 | IsManualBreak = true 255 | }); 256 | } 257 | } 258 | } -------------------------------------------------------------------------------- /src/Simplexcel/WorksheetView/Pane.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel 2 | { 3 | /// 4 | /// Worksheet view pane 5 | /// 6 | public sealed class Pane 7 | { 8 | internal const int DefaultXSplit = 0; 9 | internal const int DefaultYSplit = 0; 10 | internal const Panes DefaultActivePane = Panes.TopLeft; 11 | internal const PaneState DefaultState = PaneState.Split; 12 | 13 | /// 14 | /// Horizontal position of the split, in 1/20th of a point; 0 (zero) if none. If the pane is frozen, 15 | /// this value indicates the number of columns visible in the top pane. 16 | /// 17 | public int? XSplit { get; set; } 18 | 19 | /// 20 | /// Vertical position of the split, in 1/20th of a point; 0 (zero) if none. If the pane is frozen, 21 | /// this value indicates the number of rows visible in the left pane 22 | /// 23 | public int? YSplit { get; set; } 24 | 25 | /// 26 | /// The pane that is active. 27 | /// 28 | public Panes? ActivePane { get; set; } 29 | 30 | /// 31 | /// Indicates whether the pane has horizontal / vertical splits, and whether those splits are frozen. 32 | /// 33 | public PaneState? State { get; set; } 34 | 35 | /// 36 | /// Location of the top left visible cell in the bottom right pane (when in Left-To-Right mode) 37 | /// 38 | public string TopLeftCell { get; set; } 39 | } 40 | } -------------------------------------------------------------------------------- /src/Simplexcel/WorksheetView/PaneState.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel 2 | { 3 | /// 4 | /// State of the sheet's pane. 5 | /// 6 | public enum PaneState 7 | { 8 | /// 9 | /// Panes are split, but not frozen. In this state, the split bars are adjustable by the user. 10 | /// 11 | Split, 12 | 13 | /// 14 | /// Panes are frozen, but were not split being frozen. In 15 | /// this state, when the panes are unfrozen again, a single 16 | /// pane results, with no split. 17 | /// 18 | Frozen, 19 | 20 | /// 21 | /// Panes are frozen and were split before being frozen. In 22 | /// this state, when the panes are unfrozen again, the split 23 | /// remains, but is adjustable. 24 | /// 25 | FrozenSplit 26 | } 27 | } -------------------------------------------------------------------------------- /src/Simplexcel/WorksheetView/Panes.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel 2 | { 3 | /// 4 | /// Defines the names of the four possible panes into which the view of a workbook in the application can be split. 5 | /// 6 | public enum Panes 7 | { 8 | /// 9 | /// Bottom right pane, when both vertical and horizontal splits are applied. 10 | /// 11 | BottomRight, 12 | 13 | /// 14 | /// Top right pane, when both vertical and horizontal splits are applied. 15 | /// 16 | /// This value is also used when only a vertical split has 17 | /// been applied, dividing the pane into right and left 18 | /// regions. In that case, this value specifies the right pane. 19 | /// 20 | TopRight, 21 | 22 | /// 23 | /// Bottom left pane, when both vertical and horizontal splits are applied. 24 | /// 25 | /// This value is also used when only a horizontal split has 26 | /// been applied, dividing the pane into upper and lower 27 | /// regions. In that case, this value specifies the bottom pane 28 | /// 29 | BottomLeft, 30 | 31 | /// 32 | /// Top left pane, when both vertical and horizontal splits are applied. 33 | /// 34 | /// This value is also used when only a horizontal split has 35 | /// been applied, dividing the pane into upper and lower 36 | /// regions. In that case, this value specifies the top pane. 37 | /// 38 | /// This value is also used when only a vertical split has 39 | /// been applied, dividing the pane into right and left 40 | /// regions. In that case, this value specifies the left pane 41 | /// 42 | TopLeft 43 | } 44 | } -------------------------------------------------------------------------------- /src/Simplexcel/WorksheetView/Selection.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel 2 | { 3 | /// 4 | /// Worksheet view selection. 5 | /// 6 | public sealed class Selection 7 | { 8 | /// 9 | /// The pane to which this selection belongs. 10 | /// 11 | public Panes ActivePane { get; set; } 12 | 13 | /// 14 | /// Location of the active cell. E.g., "A1" 15 | /// 16 | public string ActiveCell { get; set; } 17 | 18 | // activeCellId 19 | // sqref 20 | 21 | /// 22 | /// Create a new selection 23 | /// 24 | public Selection() 25 | { 26 | ActivePane = Panes.TopLeft; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Simplexcel/WorksheetView/SheetView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Simplexcel 5 | { 6 | /// 7 | /// A single sheet view definition. When more than one sheet view is defined in the file, it means that when opening 8 | /// the workbook, each sheet view corresponds to a separate window within the spreadsheet application, where 9 | /// each window is showing the particular sheet containing the same workbookViewId value, the last sheetView 10 | /// definition is loaded, and the others are discarded. 11 | /// 12 | /// When multiple windows are viewing the same sheet, multiple 13 | /// sheetView elements (with corresponding workbookView entries) are saved. 14 | /// 15 | public sealed class SheetView 16 | { 17 | private List _selections; 18 | 19 | internal ICollection Selections 20 | { 21 | get { return _selections; } 22 | } 23 | 24 | /// 25 | /// Flag indicating whether this sheet is selected. 26 | /// When only 1 sheet is selected and active, this value should be in synch with the activeTab value. 27 | /// In case of a conflict, the Start Part setting wins and sets the active sheet tab. 28 | /// Multiple sheets can be selected, but only one sheet shall be active at one time. 29 | /// 30 | public bool? TabSelected { get; set; } 31 | 32 | /// 33 | /// Show the ruler in page layout view 34 | /// 35 | public bool? ShowRuler { get; set; } 36 | 37 | /// 38 | /// Zero-based index of this workbook view, pointing to a workbookView element in the bookViews collection. 39 | /// 40 | public int WorkbookViewId { get { return 0; } } 41 | 42 | /// 43 | /// The pane that this SheetView applies to 44 | /// 45 | public Pane Pane { get; set; } 46 | 47 | /// 48 | /// Add the given selection to the SheetView 49 | /// 50 | /// 51 | /// If true, will throw an if there is already a selection with the same . If false, overwrite the previous selection for that pane. 52 | public void AddSelection(Selection sel, bool throwOnDuplicatePane = true) 53 | { 54 | if (sel == null) { throw new ArgumentNullException(nameof(sel)); } 55 | if (!Enum.IsDefined(typeof(Panes), sel.ActivePane)) 56 | { 57 | throw new ArgumentOutOfRangeException(nameof(sel), "Not a valid ActivePane: " + sel.ActivePane); 58 | } 59 | 60 | // Up to 4 Selections, and the Selection Pane cannot already be in use 61 | if (_selections == null) 62 | { 63 | _selections = new List(4); 64 | } 65 | 66 | foreach (var s in _selections) 67 | { 68 | if (s.ActivePane == sel.ActivePane) 69 | { 70 | if (throwOnDuplicatePane) 71 | { 72 | throw new InvalidOperationException("There is already a Selection for ActivePane " + sel.ActivePane); 73 | } 74 | else 75 | { 76 | _selections.Remove(s); 77 | break; 78 | } 79 | } 80 | } 81 | 82 | _selections.Add(sel); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/Simplexcel/XlsxInternal/Namespaces.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace Simplexcel.XlsxInternal 4 | { 5 | /// 6 | /// Various xmlns namespaces 7 | /// 8 | internal static class Namespaces 9 | { 10 | internal static readonly XNamespace simplexcel = "http://stum.de/simplexcel/v1"; 11 | internal static readonly XNamespace coreProperties = "http://schemas.openxmlformats.org/package/2006/metadata/core-properties"; 12 | internal static readonly XNamespace dc = "http://purl.org/dc/elements/1.1/"; 13 | internal static readonly XNamespace dcterms = "http://purl.org/dc/terms/"; 14 | internal static readonly XNamespace extendedProperties = "http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"; 15 | internal static readonly XNamespace mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"; 16 | internal static readonly XNamespace officeRelationships = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; 17 | internal static readonly XNamespace relationship = "http://schemas.openxmlformats.org/package/2006/relationships"; 18 | internal static readonly XNamespace vt = "http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes"; 19 | internal static readonly XNamespace workbook = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"; 20 | internal static readonly XNamespace workbookRelationship = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; 21 | internal static readonly XNamespace x = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"; 22 | internal static readonly XNamespace x14ac = "http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac"; 23 | internal static readonly XNamespace xsi = "http://www.w3.org/2001/XMLSchema-instance"; 24 | internal static readonly XNamespace contenttypes = "http://schemas.openxmlformats.org/package/2006/content-types"; 25 | } 26 | } -------------------------------------------------------------------------------- /src/Simplexcel/XlsxInternal/Relationship.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel.XlsxInternal 2 | { 3 | /// 4 | /// A Relationship inside the Package 5 | /// 6 | internal class Relationship 7 | { 8 | public string Id { get; set; } 9 | public XmlFile Target { get; set; } 10 | public string Type { get; set; } 11 | 12 | public Relationship(RelationshipCounter counter) 13 | { 14 | Id = "r" + counter.GetNextId(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Simplexcel/XlsxInternal/RelationshipCounter.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel.XlsxInternal 2 | { 3 | internal class RelationshipCounter 4 | { 5 | private int _count; 6 | 7 | internal int GetNextId() 8 | { 9 | _count++; 10 | return _count; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Simplexcel/XlsxInternal/SharedStrings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.Collections.Generic; 5 | using System.Xml.Linq; 6 | using System.Text.RegularExpressions; 7 | 8 | namespace Simplexcel.XlsxInternal 9 | { 10 | /// 11 | /// The Shared Strings table in an Excel document 12 | /// 13 | internal class SharedStrings 14 | { 15 | private readonly Dictionary _sharedStrings = new Dictionary(StringComparer.Ordinal); 16 | 17 | /// 18 | /// The number of Unique Strings 19 | /// 20 | internal int UniqueCount { get { return _sharedStrings.Count; } } 21 | /// 22 | /// The number of total Strings (incl. duplicate uses of the same string) 23 | /// 24 | internal int Count { get; private set; } 25 | 26 | /// 27 | /// Add a string to the shared strings table and return the index of that string 28 | /// 29 | /// 30 | /// 31 | internal int GetStringIndex(string input) 32 | { 33 | // NULL is treated as an empty string 34 | if (input == null) 35 | { 36 | input = string.Empty; 37 | } 38 | 39 | Count++; 40 | if (!_sharedStrings.ContainsKey(input)) 41 | { 42 | _sharedStrings[input] = _sharedStrings.Count; 43 | } 44 | return _sharedStrings[input]; 45 | } 46 | 47 | /// 48 | /// Create the XmlFile for the Package. 49 | /// 50 | /// 51 | internal XmlFile ToXmlFile() 52 | { 53 | var file = new XmlFile 54 | { 55 | ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml", 56 | Path = "xl/sharedStrings.xml" 57 | }; 58 | 59 | var sst = new XDocument(new XElement(Namespaces.x + "sst", 60 | new XAttribute("xmlns", Namespaces.x), 61 | new XAttribute("count", Count), 62 | new XAttribute("uniqueCount", UniqueCount) 63 | )); 64 | 65 | foreach (var kvp in _sharedStrings.OrderBy(k => k.Value)) 66 | { 67 | var tElem = new XElement(Namespaces.x + "t") 68 | { 69 | Value = kvp.Key 70 | }; 71 | 72 | var siElem = new XElement(Namespaces.x + "si", tElem); 73 | sst.Root.Add(siElem); 74 | } 75 | 76 | file.Content = sst; 77 | 78 | return file; 79 | } 80 | 81 | internal Relationship ToRelationship(RelationshipCounter relationshipCounter) 82 | { 83 | return new Relationship(relationshipCounter) 84 | { 85 | Target = ToXmlFile(), 86 | Type = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings" 87 | }; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Simplexcel/XlsxInternal/SheetPackageInfo.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel.XlsxInternal 2 | { 3 | /// 4 | /// Information about a Worksheet in the Package 5 | /// 6 | internal class SheetPackageInfo 7 | { 8 | internal int SheetId { get; set; } 9 | internal string RelationshipId { get; set; } 10 | internal string SheetName { get; set; } 11 | internal string RepeatRows { get; set; } 12 | internal string RepeatCols { get; set; } 13 | 14 | internal SheetPackageInfo() 15 | { 16 | RelationshipId = string.Empty; 17 | SheetName = string.Empty; 18 | RepeatRows = string.Empty; 19 | RepeatCols = string.Empty; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/Simplexcel/XlsxInternal/StyleWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Xml.Linq; 4 | 5 | namespace Simplexcel.XlsxInternal 6 | { 7 | internal static class StyleWriter 8 | { 9 | // There are up to 164 built in number formats (0-163), all else are custom (164 and above) 10 | private const int CustomFormatIndex = 164; 11 | 12 | /// 13 | /// Standard format codes as defined in ECMA-376, 3rd Edition, Part 1, 18.8.30 numFmt (Number Format) 14 | /// 15 | private static Dictionary StandardFormatIds = new Dictionary 16 | { 17 | ["General"] = 0, 18 | ["0"] = 1, 19 | ["0.00"] = 2, 20 | ["#,##0"] = 3, 21 | ["#,##0.00"] = 4, 22 | ["0%"] = 9, 23 | ["0.00%"] = 10, 24 | ["0.00E+00"] = 11, 25 | ["# ?/?"] = 12, 26 | ["# ??/??"] = 13, 27 | ["mm-dd-yy"] = 14, 28 | ["d-mmm-yy"] = 15, 29 | ["d-mmm"] = 16, 30 | ["mmm-yy"] = 17, 31 | ["h:mm AM/PM"] = 18, 32 | ["h:mm:ss AM/PM"] = 19, 33 | ["h:mm"] = 20, 34 | ["h:mm:ss"] = 21, 35 | ["m/d/yy h:mm"] = 22, 36 | ["#,##0 ;(#,##0)"] = 37, 37 | ["#,##0 ;[Red](#,##0)"] = 38, 38 | ["#,##0.00;(#,##0.00)"] = 39, 39 | ["#,##0.00;[Red](#,##0.00)"] = 40, 40 | ["mm:ss"] = 45, 41 | ["[h]:mm:ss"] = 46, 42 | ["mmss.0"] = 47, 43 | ["##0.0E+0"] = 48, 44 | ["@"] = 49, 45 | }; 46 | 47 | /// 48 | /// Create a styles.xml file 49 | /// 50 | /// 51 | /// 52 | internal static XmlFile CreateStyleXml(IList styles) 53 | { 54 | var numberFormats = new List(); 55 | var uniqueBorders = new List { CellBorder.None }; 56 | var uniqueFonts = new List { new XlsxFont() }; 57 | // These two fills MUST exist as fill 0 (None) and 1 (Gray125) 58 | var uniqueFills = new List { new PatternFill { PatternType = PatternType.None }, new PatternFill { PatternType = PatternType.Gray125 } }; 59 | 60 | foreach (var style in styles) 61 | { 62 | if (style.Border != CellBorder.None && !uniqueBorders.Contains(style.Border)) 63 | { 64 | uniqueBorders.Add(style.Border); 65 | } 66 | 67 | if (!numberFormats.Contains(style.Format) && !StandardFormatIds.ContainsKey(style.Format)) 68 | { 69 | numberFormats.Add(style.Format); 70 | } 71 | 72 | if (style.Font != null && !uniqueFonts.Contains(style.Font)) 73 | { 74 | uniqueFonts.Add(style.Font); 75 | } 76 | 77 | if (style.Fill != null && !uniqueFills.Contains(style.Fill)) 78 | { 79 | uniqueFills.Add(style.Fill); 80 | } 81 | } 82 | 83 | var file = new XmlFile 84 | { 85 | ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml", 86 | Path = "xl/styles.xml" 87 | }; 88 | 89 | var doc = new XDocument(new XElement(Namespaces.workbook + "styleSheet", new XAttribute("xmlns", Namespaces.workbook))); 90 | 91 | StyleAddNumFmtsElement(doc, numberFormats); 92 | StyleAddFontsElement(doc, uniqueFonts); 93 | StyleAddFillsElement(doc, uniqueFills); 94 | StyleAddBordersElement(doc, uniqueBorders); 95 | StyleAddCellStyleXfsElement(doc); 96 | StyleAddCellXfsElement(doc, styles, uniqueBorders, numberFormats, uniqueFonts, uniqueFills); 97 | 98 | file.Content = doc; 99 | 100 | return file; 101 | } 102 | 103 | private static void StyleAddCellXfsElement(XDocument doc, IList styles, IList uniqueBorders, IList numberFormats, IList fontInfos, IList fills) 104 | { 105 | var cellXfs = new XElement(Namespaces.workbook + "cellXfs", new XAttribute("count", styles.Count)); 106 | 107 | // Default Cell Style, used to make sure the row numbers don't appear broken 108 | cellXfs.Add(new XElement(Namespaces.workbook + "xf", 109 | new XAttribute("numFmtId", 0), 110 | new XAttribute("fontId", 0), 111 | new XAttribute("fillId", 0), 112 | new XAttribute("borderId", 0), 113 | new XAttribute("xfId", 0))); 114 | 115 | foreach (var style in styles) 116 | { 117 | var numFmtId = StandardFormatIds.TryGetValue(style.Format, out var standardNumFmtId) ? standardNumFmtId : numberFormats.IndexOf(style.Format) + CustomFormatIndex; 118 | var fontId = fontInfos.IndexOf(style.Font); 119 | var fillId = fills.IndexOf(style.Fill); 120 | var borderId = uniqueBorders.IndexOf(style.Border); 121 | var xfId = 0; 122 | 123 | var elem = new XElement(Namespaces.workbook + "xf", 124 | new XAttribute("numFmtId", numFmtId), 125 | new XAttribute("fontId", fontId), 126 | new XAttribute("fillId", fillId), 127 | new XAttribute("borderId", borderId), 128 | new XAttribute("xfId", xfId)); 129 | 130 | if (numFmtId > 0) 131 | { 132 | elem.Add(new XAttribute("applyNumberFormat", 1)); 133 | } 134 | if (fillId > 0) 135 | { 136 | elem.Add(new XAttribute("applyFill", 1)); 137 | } 138 | if (fontId > 0) 139 | { 140 | elem.Add(new XAttribute("applyFont", 1)); 141 | } 142 | if (borderId > 0) 143 | { 144 | elem.Add(new XAttribute("applyBorder", 1)); 145 | } 146 | 147 | AddAlignmentElement(elem, style); 148 | 149 | cellXfs.Add(elem); 150 | } 151 | doc.Root.Add(cellXfs); 152 | } 153 | 154 | private static void AddAlignmentElement(XElement elem, XlsxCellStyle style) 155 | { 156 | if(elem == null) 157 | { 158 | throw new ArgumentNullException(nameof(elem)); 159 | } 160 | 161 | if (style == null) 162 | { 163 | throw new ArgumentNullException(nameof(style)); 164 | } 165 | 166 | if (style.HorizontalAlignment == HorizontalAlign.None && style.VerticalAlignment == VerticalAlign.None) 167 | { 168 | return; 169 | } 170 | 171 | var alignElem = new XElement(Namespaces.workbook + "alignment"); 172 | if(style.HorizontalAlignment != HorizontalAlign.None) 173 | { 174 | var value = style.HorizontalAlignment switch 175 | { 176 | HorizontalAlign.General => "general", 177 | HorizontalAlign.Left => "left", 178 | HorizontalAlign.Center => "center", 179 | HorizontalAlign.Right => "right", 180 | HorizontalAlign.Justify => "justify", 181 | _ => throw new InvalidOperationException("Unhandled HorizontalAlignment in Cell Style: " + style.HorizontalAlignment), 182 | }; 183 | alignElem.Add(new XAttribute("horizontal", value)); 184 | } 185 | 186 | if (style.VerticalAlignment != VerticalAlign.None) 187 | { 188 | var value = style.VerticalAlignment switch 189 | { 190 | VerticalAlign.Top => "top", 191 | VerticalAlign.Middle => "center", 192 | VerticalAlign.Bottom => "bottom", 193 | VerticalAlign.Justify => "justify", 194 | _ => throw new InvalidOperationException("Unhandled VerticalAlignment in Cell Style: " + style.VerticalAlignment), 195 | }; 196 | alignElem.Add(new XAttribute("vertical", value)); 197 | } 198 | 199 | elem.Add(alignElem); 200 | } 201 | 202 | private static void StyleAddCellStyleXfsElement(XDocument doc) 203 | { 204 | var cellStyleXfs = new XElement(Namespaces.workbook + "cellStyleXfs", new XAttribute("count", 1)); 205 | cellStyleXfs.Add(new XElement(Namespaces.workbook + "xf", new XAttribute("numFmtId", 0), new XAttribute("fontId", 0), new XAttribute("fillId", 0), new XAttribute("borderId", 0))); 206 | doc.Root.Add(cellStyleXfs); 207 | } 208 | 209 | private static void StyleAddNumFmtsElement(XDocument doc, List numberFormats) 210 | { 211 | var numFmtsElem = new XElement(Namespaces.workbook + "numFmts", new XAttribute("count", numberFormats.Count)); 212 | 213 | for (int i = 0; i < numberFormats.Count; i++) 214 | { 215 | var fmtElem = new XElement(Namespaces.workbook + "numFmt", 216 | new XAttribute("numFmtId", i + CustomFormatIndex), 217 | new XAttribute("formatCode", numberFormats[i])); 218 | numFmtsElem.Add(fmtElem); 219 | } 220 | doc.Root.Add(numFmtsElem); 221 | } 222 | 223 | private static void StyleAddBordersElement(XDocument doc, List uniqueBorders) 224 | { 225 | var borders = new XElement(Namespaces.workbook + "borders", new XAttribute("count", uniqueBorders.Count)); 226 | foreach (var border in uniqueBorders) 227 | { 228 | var bex = new XElement(Namespaces.workbook + "border"); 229 | 230 | var left = new XElement(Namespaces.workbook + "left"); 231 | if (border.HasFlag(CellBorder.Left)) 232 | { 233 | left.Add(new XAttribute("style", "thin")); 234 | left.Add(new XElement(Namespaces.workbook + "color", new XAttribute("auto", 1))); 235 | } 236 | bex.Add(left); 237 | 238 | var right = new XElement(Namespaces.workbook + "right"); 239 | if (border.HasFlag(CellBorder.Right)) 240 | { 241 | right.Add(new XAttribute("style", "thin")); 242 | right.Add(new XElement(Namespaces.workbook + "color", new XAttribute("auto", 1))); 243 | } 244 | bex.Add(right); 245 | 246 | var top = new XElement(Namespaces.workbook + "top"); 247 | if (border.HasFlag(CellBorder.Top)) 248 | { 249 | top.Add(new XAttribute("style", "thin")); 250 | top.Add(new XElement(Namespaces.workbook + "color", new XAttribute("auto", 1))); 251 | } 252 | bex.Add(top); 253 | 254 | var bottom = new XElement(Namespaces.workbook + "bottom"); 255 | if (border.HasFlag(CellBorder.Bottom)) 256 | { 257 | bottom.Add(new XAttribute("style", "thin")); 258 | bottom.Add(new XElement(Namespaces.workbook + "color", new XAttribute("auto", 1))); 259 | } 260 | bex.Add(bottom); 261 | 262 | bex.Add(new XElement(Namespaces.workbook + "diagonal")); 263 | 264 | borders.Add(bex); 265 | } 266 | doc.Root.Add(borders); 267 | } 268 | 269 | private static void StyleAddFillsElement(XDocument doc, List uniqueFills) 270 | { 271 | var fills = new XElement(Namespaces.workbook + "fills", new XAttribute("count", uniqueFills.Count)); 272 | 273 | foreach (var fill in uniqueFills) 274 | { 275 | var fe = new XElement(Namespaces.workbook + "fill"); 276 | if (fill is PatternFill pf) 277 | { 278 | var pfe = new XElement(Namespaces.workbook + "patternFill"); 279 | string patternType = "none"; 280 | switch (pf.PatternType) 281 | { 282 | case PatternType.Solid: patternType = "solid"; break; 283 | case PatternType.Gray750: patternType = "darkGray"; break; 284 | case PatternType.Gray500: patternType = "mediumGray"; break; 285 | case PatternType.Gray250: patternType = "lightGray"; break; 286 | case PatternType.Gray125: patternType = "gray125"; break; 287 | case PatternType.Gray0625: patternType = "gray0625"; break; 288 | case PatternType.HorizontalStripe: patternType = "darkHorizontal"; break; 289 | case PatternType.VerticalStripe: patternType = "darkVertical"; break; 290 | case PatternType.ReverseDiagonalStripe: patternType = "darkDown"; break; 291 | case PatternType.DiagonalStripe: patternType = "darkUp"; break; 292 | case PatternType.DiagonalCrosshatch: patternType = "darkGrid"; break; 293 | case PatternType.ThickDiagonalCrosshatch: patternType = "darkTrellis"; break; 294 | case PatternType.ThinHorizontalStripe: patternType = "lightHorizontal"; break; 295 | case PatternType.ThinVerticalStripe: patternType = "lightVertical"; break; 296 | case PatternType.ThinReverseDiagonalStripe: patternType = "lightDown"; break; 297 | case PatternType.ThinDiagonalStripe: patternType = "lightUp"; break; 298 | case PatternType.ThinHorizontalCrosshatch: patternType = "lightGrid"; break; 299 | case PatternType.ThinDiagonalCrosshatch: patternType = "lightTrellis"; break; 300 | } 301 | pfe.Add(new XAttribute("patternType", patternType)); 302 | 303 | if (pf.BackgroundColor.HasValue) 304 | { 305 | // Not a typo. Excel calls the fgColor "Background Color" in the UI, and the bgColor "Pattern Color" 306 | pfe.Add(new XElement(Namespaces.workbook + "fgColor", new XAttribute("rgb", pf.BackgroundColor.Value))); 307 | } 308 | if (pf.PatternColor.HasValue) 309 | { 310 | pfe.Add(new XElement(Namespaces.workbook + "bgColor", new XAttribute("rgb", pf.PatternColor.Value))); 311 | } 312 | else if (pf.BackgroundColor.HasValue) 313 | { 314 | // fgColor without bgColor causes Excel to show an error 315 | pfe.Add(new XElement(Namespaces.workbook + "bgColor", new XAttribute("auto", 1))); 316 | } 317 | 318 | fe.Add(pfe); 319 | } 320 | //else if (fill is GradientFill gf) 321 | //{ 322 | // TODO: Not Yet Implemented 323 | //} 324 | fills.Add(fe); 325 | } 326 | doc.Root.Add(fills); 327 | } 328 | 329 | private static void StyleAddFontsElement(XDocument doc, IList fontInfos) 330 | { 331 | var fonts = new XElement(Namespaces.workbook + "fonts", new XAttribute("count", fontInfos.Count)); 332 | 333 | foreach (var font in fontInfos) 334 | { 335 | var elem = new XElement(Namespaces.workbook + "font", 336 | new XElement(Namespaces.workbook + "sz", new XAttribute("val", font.Size)), 337 | new XElement(Namespaces.workbook + "name", new XAttribute("val", font.Name)), 338 | new XElement(Namespaces.workbook + "color", new XAttribute("rgb", font.TextColor)) 339 | ); 340 | 341 | if (font.Bold) { elem.Add(new XElement(Namespaces.workbook + "b")); } 342 | if (font.Italic) { elem.Add(new XElement(Namespaces.workbook + "i")); } 343 | if (font.Underline) { elem.Add(new XElement(Namespaces.workbook + "u")); } 344 | 345 | fonts.Add(elem); 346 | } 347 | doc.Root.Add(fonts); 348 | } 349 | } 350 | } 351 | -------------------------------------------------------------------------------- /src/Simplexcel/XlsxInternal/XlsxCell.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel.XlsxInternal 2 | { 3 | /// 4 | /// A Cell inside the sheet.xml file 5 | /// ECMA-376, 3rd Edition, Part 1, 18.3.1.4 c (Cell) 6 | /// 7 | internal class XlsxCell 8 | { 9 | /// 10 | internal string CellType { get; set; } 11 | 12 | /// 13 | /// r (Reference) An "A1" style reference to the location of this cell 14 | /// The possible values for this attribute are defined by the ST_CellRef simple type (§18.18.7). 15 | /// 16 | internal string Reference { get; set; } 17 | 18 | /// 19 | /// s (StyleIndex) 20 | /// 21 | internal int StyleIndex { get; set; } 22 | 23 | /// 24 | /// The v element 25 | /// 26 | internal object Value { get; set; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Simplexcel/XlsxInternal/XlsxCellStyle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Simplexcel.XlsxInternal 4 | { 5 | /// 6 | /// The Style Information about a cell. Isolated from the Cell object because we need to compare styles when building the styles.xml file 7 | /// 8 | internal class XlsxCellStyle : IEquatable 9 | { 10 | internal XlsxCellStyle() 11 | { 12 | Font = new XlsxFont(); 13 | Fill = new PatternFill(); 14 | } 15 | 16 | internal XlsxFont Font { get; set; } 17 | 18 | internal CellBorder Border { get; set; } 19 | 20 | internal string Format { get; set; } 21 | 22 | internal VerticalAlign VerticalAlignment { get; set; } 23 | 24 | internal HorizontalAlign HorizontalAlignment { get; set; } 25 | 26 | internal PatternFill Fill { get; set; } 27 | 28 | /// 29 | /// Compare this to another 30 | /// 31 | /// 32 | /// 33 | public override bool Equals(object obj) 34 | { 35 | if (obj is null) return false; 36 | if (ReferenceEquals(this, obj)) return true; 37 | if (obj.GetType() != typeof(XlsxCellStyle)) return false; 38 | return Equals((XlsxCellStyle)obj); 39 | } 40 | 41 | /// 42 | /// Compare this to another 43 | /// 44 | /// 45 | /// 46 | public bool Equals(XlsxCellStyle other) 47 | { 48 | if (other is null) return false; 49 | if (ReferenceEquals(this, other)) return true; 50 | 51 | return Equals(other.Border, Border) 52 | && other.Font.Equals(Font) 53 | && other.Format.Equals(Format) 54 | && Equals(other.VerticalAlignment, VerticalAlignment) 55 | && Equals(other.HorizontalAlignment, HorizontalAlignment) 56 | && Equals(other.Fill, Fill) 57 | ; 58 | } 59 | 60 | public override int GetHashCode() 61 | { 62 | unchecked 63 | { 64 | int result = Border.GetHashCode(); 65 | result = (result * 397) ^ Font.GetHashCode(); 66 | result = (result * 397) ^ Format.GetHashCode(); 67 | result = (result * 397) ^ VerticalAlignment.GetHashCode(); 68 | result = (result * 397) ^ HorizontalAlignment.GetHashCode(); 69 | result = (result * 397) ^ Fill.GetHashCode(); 70 | return result; 71 | } 72 | } 73 | 74 | /// 75 | /// Compare a to another 76 | /// 77 | /// 78 | /// 79 | /// 80 | public static bool operator ==(XlsxCellStyle left, XlsxCellStyle right) 81 | { 82 | return Equals(left, right); 83 | } 84 | 85 | /// 86 | /// Compare a to another 87 | /// 88 | /// 89 | /// 90 | /// 91 | public static bool operator !=(XlsxCellStyle left, XlsxCellStyle right) 92 | { 93 | return !Equals(left, right); 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /src/Simplexcel/XlsxInternal/XlsxCellTypes.cs: -------------------------------------------------------------------------------- 1 | namespace Simplexcel.XlsxInternal 2 | { 3 | /// 4 | /// ECMA-376, 3rd Edition, Part 1, 18.18.11 ST_CellType (Cell Type) 5 | /// 6 | internal static class XlsxCellTypes 7 | { 8 | /// 9 | /// b (Boolean) Cell containing a boolean. 10 | /// 11 | internal static readonly string Boolean = "b"; 12 | 13 | /// 14 | /// d (Date) Cell contains a date in the ISO 8601 format. 15 | /// 16 | internal static readonly string Date = "d"; 17 | 18 | /// 19 | /// e (Error) Cell containing an error. 20 | /// 21 | internal static readonly string Error = "e"; 22 | 23 | /// 24 | /// inlineStr (Inline String) Cell containing an (inline) rich string, i.e., one not in the shared string table. 25 | /// Note: Excel expects "str" instead of "inlineStr" 26 | /// 27 | internal static readonly string InlineString = "str"; 28 | 29 | /// 30 | /// n (Number) Cell containing a number. 31 | /// 32 | internal static readonly string Number = "n"; 33 | 34 | /// 35 | /// s (Shared String) Cell containing a shared string. 36 | /// 37 | internal static readonly string SharedString = "s"; 38 | 39 | /// 40 | /// str (String) Cell containing a formula string. 41 | /// 42 | internal static readonly string FormulaString = "str"; 43 | } 44 | } -------------------------------------------------------------------------------- /src/Simplexcel/XlsxInternal/XlsxExtensionMethods.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace Simplexcel.XlsxInternal 3 | { 4 | internal static class XlsxEnumFormatter 5 | { 6 | internal static string GetXmlValue(this Panes pane) => pane switch 7 | { 8 | Panes.BottomLeft => "bottomLeft", 9 | Panes.BottomRight => "bottomRight", 10 | Panes.TopLeft => "topLeft", 11 | Panes.TopRight => "topRight", 12 | _ => throw new ArgumentOutOfRangeException(nameof(pane), "Invalid Pane: " + pane), 13 | }; 14 | 15 | internal static string GetXmlValue(this PaneState state) => state switch 16 | { 17 | PaneState.Split => "split", 18 | PaneState.Frozen => "frozen", 19 | PaneState.FrozenSplit => "frozenSplit", 20 | _ => throw new ArgumentOutOfRangeException(nameof(state), "Invalid Pane State: " + state), 21 | }; 22 | } 23 | } -------------------------------------------------------------------------------- /src/Simplexcel/XlsxInternal/XlsxFont.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Simplexcel.XlsxInternal 4 | { 5 | internal class XlsxFont : IEquatable 6 | { 7 | internal string Name { get; set; } 8 | internal int Size { get; set; } 9 | internal bool Bold { get; set; } 10 | internal bool Italic { get; set; } 11 | internal bool Underline { get; set; } 12 | internal Color TextColor { get; set; } 13 | 14 | internal XlsxFont() 15 | { 16 | Name = "Calibri"; 17 | Size = 11; 18 | } 19 | 20 | public override bool Equals(object obj) 21 | { 22 | if (obj is null) return false; 23 | if (ReferenceEquals(this, obj)) return true; 24 | if (obj.GetType() != typeof(XlsxFont)) return false; 25 | return Equals((XlsxFont)obj); 26 | } 27 | 28 | public bool Equals(XlsxFont other) 29 | { 30 | if (other is null) return false; 31 | if (ReferenceEquals(this, other)) return true; 32 | return Equals(other.Name, Name) 33 | && other.Size == Size 34 | && other.Bold.Equals(Bold) 35 | && other.Italic.Equals(Italic) 36 | && other.Underline.Equals(Underline) 37 | && other.TextColor.Equals(TextColor); 38 | } 39 | 40 | public override int GetHashCode() 41 | { 42 | unchecked 43 | { 44 | int result = (Name != null ? Name.GetHashCode() : 0); 45 | result = (result*397) ^ Size; 46 | result = (result*397) ^ Bold.GetHashCode(); 47 | result = (result*397) ^ Italic.GetHashCode(); 48 | result = (result*397) ^ Underline.GetHashCode(); 49 | result = (result*397) ^ TextColor.GetHashCode(); 50 | return result; 51 | } 52 | } 53 | 54 | public static bool operator ==(XlsxFont left, XlsxFont right) 55 | { 56 | return Equals(left, right); 57 | } 58 | 59 | public static bool operator !=(XlsxFont left, XlsxFont right) 60 | { 61 | return !Equals(left, right); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Simplexcel/XlsxInternal/XlsxIgnoredError.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Simplexcel.XlsxInternal 5 | { 6 | internal sealed class XlsxIgnoredError 7 | { 8 | private readonly IgnoredError _ignoredError; 9 | internal HashSet Cells { get; } 10 | 11 | public XlsxIgnoredError() 12 | { 13 | Cells = new HashSet(); 14 | _ignoredError = new IgnoredError(); 15 | } 16 | 17 | internal IgnoredError IgnoredError 18 | { 19 | get 20 | { 21 | // Note: This is a mutable reference, but changing it would be... bad. 22 | return _ignoredError; 23 | } 24 | set 25 | { 26 | if (value == null) 27 | { 28 | throw new ArgumentNullException(nameof(value)); 29 | } 30 | 31 | // And because the reference is mutable, this stores a copy so that modifications to the Worksheet don't break this 32 | _ignoredError.NumberStoredAsText = value.NumberStoredAsText; 33 | IgnoredErrorId = _ignoredError.GetHashCode(); 34 | } 35 | } 36 | 37 | internal int IgnoredErrorId { get; private set; } 38 | 39 | internal string GetSqRef() 40 | { 41 | var ranges = CellAddressHelper.DetermineRanges(Cells); 42 | return string.Join(" ", ranges); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Simplexcel/XlsxInternal/XlsxIgnoredErrorCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Simplexcel.XlsxInternal 4 | { 5 | internal sealed class XlsxIgnoredErrorCollection 6 | { 7 | internal IList DistinctIgnoredErrors { get; } 8 | 9 | public XlsxIgnoredErrorCollection() 10 | { 11 | DistinctIgnoredErrors = new List(); 12 | } 13 | 14 | public void AddIgnoredError(CellAddress cellAddress, IgnoredError ignoredErrors) 15 | { 16 | if (!ignoredErrors.IsDifferentFromDefault) 17 | { 18 | return; 19 | } 20 | 21 | var id = ignoredErrors.GetHashCode(); 22 | 23 | foreach (var distinctIgnored in DistinctIgnoredErrors) 24 | { 25 | if (distinctIgnored.IgnoredErrorId == id) 26 | { 27 | distinctIgnored.Cells.Add(cellAddress); 28 | return; 29 | } 30 | } 31 | 32 | var newIgnoredError = new XlsxIgnoredError 33 | { 34 | IgnoredError = ignoredErrors 35 | }; 36 | newIgnoredError.Cells.Add(cellAddress); 37 | DistinctIgnoredErrors.Add(newIgnoredError); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Simplexcel/XlsxInternal/XlsxPackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Xml.Linq; 6 | 7 | namespace Simplexcel.XlsxInternal 8 | { 9 | /// 10 | /// A Wrapper for an .xlsx file, containing all the contents and relations and logic to create the file from that content 11 | /// 12 | internal class XlsxPackage 13 | { 14 | /// 15 | /// All XML Files in this package 16 | /// 17 | internal IList XmlFiles { get; } 18 | 19 | /// 20 | /// Package-level relationships (/_rels/.rels) 21 | /// 22 | internal IList Relationships { get; } 23 | 24 | /// 25 | /// Workbook-level relationships (/xl/_rels/workbook.xml.rels) 26 | /// 27 | internal IList WorkbookRelationships { get; } 28 | 29 | 30 | internal XlsxPackage() 31 | { 32 | Relationships = new List(); 33 | WorkbookRelationships = new List(); 34 | XmlFiles = new List(); 35 | } 36 | 37 | /// 38 | /// Save the Xlsx Package to a new Stream (that the caller owns and has to dispose) 39 | /// 40 | /// 41 | internal void SaveToStream(Stream outputStream, bool compress) 42 | { 43 | if(outputStream == null || !outputStream.CanWrite || !outputStream.CanSeek) 44 | { 45 | throw new InvalidOperationException("Stream to save to must be writeable and seekable."); 46 | } 47 | 48 | using (var pkg = new ZipPackage(outputStream, compress)) 49 | { 50 | WriteInfoXmlFile(pkg); 51 | 52 | foreach (var file in XmlFiles) 53 | { 54 | pkg.WriteXmlFile(file); 55 | 56 | var relations = Relationships.Where(r => r.Target == file); 57 | foreach (var rel in relations) 58 | { 59 | pkg.AddRelationship(rel); 60 | } 61 | } 62 | 63 | if (WorkbookRelationships.Count > 0) 64 | { 65 | pkg.WriteXmlFile(WorkbookRelsXml()); 66 | } 67 | } 68 | outputStream.Seek(0, SeekOrigin.Begin); 69 | } 70 | 71 | private void WriteInfoXmlFile(ZipPackage pkg) 72 | { 73 | var infoXml = new XmlFile 74 | { 75 | Path = "simplexcel.xml", 76 | Content = new XDocument(new XElement(Namespaces.simplexcel + "docInfo", new XAttribute("xmlns", Namespaces.simplexcel))) 77 | }; 78 | 79 | infoXml.Content.Root.Add(new XElement(Namespaces.simplexcel + "version", 80 | new XAttribute("major", SimplexcelVersion.Version.Major), 81 | new XAttribute("minor", SimplexcelVersion.Version.Minor), 82 | new XAttribute("build", SimplexcelVersion.Version.Build), 83 | new XAttribute("revision", SimplexcelVersion.Version.Revision), 84 | new XText(SimplexcelVersion.VersionString) 85 | )); 86 | 87 | infoXml.Content.Root.Add(new XElement(Namespaces.simplexcel + "created", DateTime.UtcNow)); 88 | 89 | pkg.WriteXmlFile(infoXml); 90 | } 91 | 92 | /// 93 | /// Create the xl/_rels/workbook.xml.rels file 94 | /// 95 | /// 96 | internal XmlFile WorkbookRelsXml() 97 | { 98 | var file = new XmlFile 99 | { 100 | ContentType = "application/vnd.openxmlformats-package.relationships+xml", 101 | Path = "xl/_rels/workbook.xml.rels" 102 | }; 103 | 104 | var content = new XDocument(new XElement(Namespaces.relationship + "Relationships", new XAttribute("xmlns", Namespaces.relationship))); 105 | foreach (var rel in WorkbookRelationships) 106 | { 107 | var elem = new XElement(Namespaces.relationship + "Relationship", 108 | new XAttribute("Target", "/" + rel.Target.Path), 109 | new XAttribute("Type", rel.Type), 110 | new XAttribute("Id", rel.Id)); 111 | 112 | content.Root.Add(elem); 113 | } 114 | file.Content = content; 115 | 116 | return file; 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/Simplexcel/XlsxInternal/XlsxRow.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace Simplexcel.XlsxInternal 4 | { 5 | /// 6 | /// ECMA-376, 3rd Edition, Part 1, 18.3.1.73 row (Row) 7 | /// 8 | internal class XlsxRow 9 | { 10 | /// 11 | /// The c elements 12 | /// 13 | internal IList Cells { get; set; } 14 | 15 | /// 16 | /// This implicitly sets customHeight to 1 and ht to the value 17 | /// 18 | internal double? CustomRowHeight { get; set; } 19 | 20 | /// 21 | /// r (Row Index) Row index. Indicates to which row in the sheet this row definition corresponds. 22 | /// 23 | internal int RowIndex { get; set; } 24 | 25 | public XlsxRow() 26 | { 27 | Cells = new List(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Simplexcel/XlsxInternal/XmlFile.cs: -------------------------------------------------------------------------------- 1 | using System.Xml.Linq; 2 | 3 | namespace Simplexcel.XlsxInternal 4 | { 5 | /// 6 | /// An XML File in the package 7 | /// 8 | internal class XmlFile 9 | { 10 | /// 11 | /// The path to the file within the package, without leading / 12 | /// 13 | internal string Path { get; set; } 14 | 15 | /// 16 | /// The Content Type of the file (default: application/xml) 17 | /// 18 | internal string ContentType { get; set; } 19 | 20 | /// 21 | /// The actual file content 22 | /// 23 | internal XDocument Content { get; set; } 24 | 25 | public XmlFile() 26 | { 27 | ContentType = "application/xml"; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Simplexcel/XlsxInternal/ZipPackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.IO.Compression; 5 | using System.Text; 6 | using System.Xml.Linq; 7 | 8 | namespace Simplexcel.XlsxInternal 9 | { 10 | internal sealed class ZipPackage : IDisposable 11 | { 12 | private readonly ZipArchive _archive; 13 | private readonly IDictionary _contentTypes; 14 | private readonly IList _relationships; 15 | private bool _closed; 16 | private readonly bool _compress; 17 | 18 | internal ZipPackage(Stream underlyingStream, bool useCompression) 19 | { 20 | if (underlyingStream == null) 21 | { 22 | throw new ArgumentNullException(nameof(underlyingStream)); 23 | } 24 | 25 | _archive = new ZipArchive(underlyingStream, ZipArchiveMode.Create, true); 26 | _contentTypes = new Dictionary(); 27 | _relationships = new List(); 28 | _compress = useCompression; 29 | } 30 | 31 | internal void AddRelationship(Relationship rel) 32 | { 33 | CheckClosed(); 34 | _relationships.Add(rel); 35 | } 36 | 37 | internal void Close() 38 | { 39 | CheckClosed(); 40 | WriteContentTypes(); 41 | WriteRelationships(); 42 | _archive.Dispose(); 43 | _closed = true; 44 | } 45 | 46 | public void Dispose() 47 | { 48 | if (!_closed) 49 | { 50 | Close(); 51 | } 52 | } 53 | 54 | private void CheckClosed() 55 | { 56 | if (_closed) 57 | { 58 | throw new InvalidOperationException("This " + nameof(ZipPackage) + " is already closed."); 59 | } 60 | } 61 | 62 | internal void WriteXmlFile(XmlFile file) 63 | { 64 | CheckClosed(); 65 | _contentTypes["/" + file.Path] = file.ContentType; 66 | AddFile(file.Path, file.Content); 67 | } 68 | 69 | private void AddFile(string relativeFileName, XDocument xdoc) 70 | { 71 | CheckClosed(); 72 | 73 | if (xdoc == null) 74 | { 75 | throw new ArgumentNullException(nameof(xdoc)); 76 | } 77 | 78 | byte[] content = Encoding.UTF8.GetBytes(xdoc.ToString()); 79 | AddFile(relativeFileName, content); 80 | } 81 | 82 | private void AddFile(string relativeFileName, byte[] content) 83 | { 84 | CheckClosed(); 85 | if (string.IsNullOrEmpty(relativeFileName)) 86 | { 87 | throw new ArgumentException("Invalid " + nameof(relativeFileName)); 88 | } 89 | if (content == null) 90 | { 91 | throw new ArgumentNullException(nameof(content)); 92 | } 93 | 94 | var entry = _archive.CreateEntry(relativeFileName, _compress ? CompressionLevel.Optimal : CompressionLevel.NoCompression); 95 | 96 | using (var stream = entry.Open()) 97 | { 98 | stream.Write(content, 0, content.Length); 99 | } 100 | } 101 | 102 | private void WriteContentTypes() 103 | { 104 | CheckClosed(); 105 | const string defaultXmlContentType = "application/xml"; 106 | var root = new XElement(Namespaces.contenttypes + "Types"); 107 | root.Add(new XElement(Namespaces.contenttypes + "Default", new XAttribute("Extension", "xml"), new XAttribute("ContentType", defaultXmlContentType))); 108 | root.Add(new XElement(Namespaces.contenttypes + "Default", new XAttribute("Extension", "rels"), new XAttribute("ContentType", "application/vnd.openxmlformats-package.relationships+xml"))); 109 | 110 | foreach (var ct in _contentTypes) 111 | { 112 | if (string.Equals(Path.GetExtension(ct.Key), ".xml", StringComparison.OrdinalIgnoreCase)) 113 | { 114 | if (!string.Equals(ct.Value, defaultXmlContentType)) 115 | { 116 | var ovr = new XElement(Namespaces.contenttypes + "Override"); 117 | ovr.Add(new XAttribute("PartName", ct.Key)); 118 | ovr.Add(new XAttribute("ContentType", ct.Value)); 119 | root.Add(ovr); 120 | } 121 | } 122 | } 123 | 124 | AddFile("[Content_Types].xml", new XDocument(root)); 125 | } 126 | 127 | private void WriteRelationships() 128 | { 129 | CheckClosed(); 130 | var root = new XElement(Namespaces.relationship + "Relationships"); 131 | foreach(var rel in _relationships) 132 | { 133 | var re = new XElement(Namespaces.relationship + "Relationship"); 134 | re.Add(new XAttribute("Type", rel.Type)); 135 | re.Add(new XAttribute("Target", "/" + rel.Target.Path)); 136 | re.Add(new XAttribute("Id", rel.Id)); 137 | root.Add(re); 138 | } 139 | 140 | AddFile("_rels/.rels", new XDocument(root)); 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /src/simplexcel_oss.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mstum/Simplexcel/a41b291104d14c85d9f497695d85a79f477e6196/src/simplexcel_oss.snk -------------------------------------------------------------------------------- /src/simplexcel_oss.txt: -------------------------------------------------------------------------------- 1 | Public key (hash algorithm: sha1): 2 | 00240000048000009400000006020000002400005253413100040000010001004131a505299be2e17e97ea1d04b7383b88ff53e68698071513c8698718a3804409c1051da9447b2c96da55d46d89bb7c587f8fbbba151aac898f13a7327992e8a3a306702db2e702ec84d7d0ae6c69526f01f1eeeaeedb3e2a98a4c151e1978945b5a76e0ab2e149151ef6f27da20ca71b3b857a5216660f83c8f8521ca0e3ac 3 | 4 | Public key token is 65e777c740a5d92a --------------------------------------------------------------------------------