├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── codeql-analysis.yml │ └── dotnet.yml ├── .gitignore ├── LICENSE ├── MiniWord.sln ├── README.md ├── README.zh-CN.md ├── README.zh-Hant.md ├── release-note ├── README.md ├── README.zh-CN.md └── README.zh-Hant.md ├── samples └── docx │ ├── DemoExpenseMeeting01.png │ ├── DemoExpenseMeeting02.png │ ├── DemoLogo.png │ ├── TestBasicFill.docx │ ├── TestBasicFill │ ├── [Content_Types].xml │ ├── _rels │ │ └── .rels │ ├── docProps │ │ ├── app.xml │ │ └── core.xml │ └── word │ │ ├── _rels │ │ └── document.xml.rels │ │ ├── document.xml │ │ ├── fontTable.xml │ │ ├── settings.xml │ │ ├── styles.xml │ │ ├── theme │ │ └── theme1.xml │ │ └── webSettings.xml │ ├── TestBasicImage.docx │ ├── TestBasicImage.png │ ├── TestDemo01.docx │ ├── TestDemo02.docx │ ├── TestDemo03.docx │ ├── TestDemo04.docx │ ├── TestExpenseDemo.docx │ ├── TestForeachInTablesDemo.docx │ ├── TestForeachInTablesWithIfStatementDemo.docx │ ├── TestIfStatement.docx │ ├── TestIssue11.docx │ ├── TestIssue17.docx │ ├── TestIssue18.docx │ ├── TestIssue37.docx │ ├── TestIssue43.docx │ ├── TestIssue47.docx │ └── demo01.png ├── src └── MiniWord │ ├── Common │ └── Enums │ │ └── Extension.cs │ ├── Extensions │ ├── ObjectExtension.cs │ ├── OpenXmlExtension.cs │ └── StringExtension.cs │ ├── MiniWord.Implment.cs │ ├── MiniWord.cs │ ├── MiniWord.csproj │ ├── MiniWordColorText.cs │ ├── MiniWordForeach.cs │ ├── MiniWordHyperLink.cs │ ├── MiniWordPicture.cs │ ├── Utility │ ├── Helpers.cs │ └── TargetFrameType.cs │ └── icon.png └── tests ├── AspNetCoreDemo ├── AspNetCoreDemo.sln └── AspNetCoreDemo │ ├── AspNetCoreDemo.csproj │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── TestTemplateComplex.docx │ ├── appsettings.Development.json │ └── appsettings.json ├── MiniWordTests ├── Helpers.cs ├── IssueTests.cs ├── IssueTestsAsync.cs ├── MiniWordTestAsync.cs ├── MiniWordTests.cs ├── MiniWordTests.csproj └── PathHelper.cs └── linqpads ├── basic_fill.linq ├── miniword_draft.linq └── object to N level Dictionary.linq /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | *.doc diff=astextplain 4 | *.DOC diff=astextplain 5 | *.docx diff=astextplain 6 | *.DOCX diff=astextplain 7 | *.dot diff=astextplain 8 | *.DOT diff=astextplain 9 | *.pdf diff=astextplain 10 | *.PDF diff=astextplain 11 | *.rtf diff=astextplain 12 | *.RTF diff=astextplain 13 | 14 | *.jpg binary 15 | *.png binary 16 | *.gif binary 17 | 18 | *.cs -text diff=csharp 19 | *.vb -text 20 | *.c -text 21 | *.cpp -text 22 | *.cxx -text 23 | *.h -text 24 | *.hxx -text 25 | *.py -text 26 | *.rb -text 27 | *.java -text 28 | *.html -text 29 | *.htm -text 30 | *.css -text 31 | *.scss -text 32 | *.sass -text 33 | *.less -text 34 | *.js -text 35 | *.lisp -text 36 | *.clj -text 37 | *.sql -text 38 | *.php -text 39 | *.lua -text 40 | *.m -text 41 | *.asm -text 42 | *.erl -text 43 | *.fs -text 44 | *.fsx -text 45 | *.hs -text 46 | 47 | *.csproj -text merge=union 48 | *.vbproj -text merge=union 49 | *.fsproj -text merge=union 50 | *.dbproj -text merge=union 51 | *.sln -text merge=union 52 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | #github: [shps951023] 4 | #patreon: # Replace with a single Patreon username 5 | #open_collective: MiniExcel # Replace with a single Open Collective username 6 | #ko_fi: # Replace with a single Ko-fi username 7 | #tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | #community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | #liberapay: # Replace with a single Liberapay username 10 | #issuehunt: # Replace with a single IssueHunt username 11 | #otechie: # Replace with a single Otechie username 12 | #lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry 13 | custom: ['https://miniexcel.github.io'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 14 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '18 0 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'csharp' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 54 | # If this step fails, then you should remove it and run the build manually (see below) 55 | - name: Autobuild 56 | uses: github/codeql-action/autobuild@v1 57 | 58 | # ℹ️ Command-line programs to run using the OS shell. 59 | # 📚 https://git.io/JvXDl 60 | 61 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 62 | # and modify them (or add more) to build your code if your project 63 | # uses a compiled language 64 | 65 | #- run: | 66 | # make bootstrap 67 | # make release 68 | 69 | - name: Perform CodeQL Analysis 70 | uses: github/codeql-action/analyze@v1 71 | -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: .NET 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Setup .NET 17 | uses: actions/setup-dotnet@v1 18 | with: 19 | dotnet-version: 6.0.x 20 | - name: Restore dependencies 21 | run: dotnet restore 22 | - name: Build 23 | run: dotnet build --no-restore 24 | - name: Test 25 | run: dotnet test --no-build --verbosity normal 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userosscache 8 | *.sln.docstates 9 | 10 | # User-specific files (MonoDevelop/Xamarin Studio) 11 | *.userprefs 12 | 13 | # Build results 14 | [Dd]ebug/ 15 | [Dd]ebugPublic/ 16 | [Rr]elease/ 17 | [Rr]eleases/ 18 | x64/ 19 | x86/ 20 | bld/ 21 | [Bb]in/ 22 | [Oo]bj/ 23 | [Ll]og/ 24 | 25 | # Visual Studio 2015 cache/options directory 26 | .vs/ 27 | # Uncomment if you have tasks that create the project's static files in wwwroot 28 | #wwwroot/ 29 | 30 | # MSTest test Results 31 | [Tt]est[Rr]esult*/ 32 | [Bb]uild[Ll]og.* 33 | 34 | # NUNIT 35 | *.VisualState.xml 36 | TestResult.xml 37 | 38 | # Build Results of an ATL Project 39 | [Dd]ebugPS/ 40 | [Rr]eleasePS/ 41 | dlldata.c 42 | 43 | # DNX 44 | project.lock.json 45 | project.fragment.lock.json 46 | artifacts/ 47 | 48 | *_i.c 49 | *_p.c 50 | *_i.h 51 | *.ilk 52 | *.meta 53 | *.obj 54 | *.pch 55 | *.pdb 56 | *.pgc 57 | *.pgd 58 | *.rsp 59 | *.sbr 60 | *.tlb 61 | *.tli 62 | *.tlh 63 | *.tmp 64 | *.tmp_proj 65 | *.log 66 | *.vspscc 67 | *.vssscc 68 | .builds 69 | *.pidb 70 | *.svclog 71 | *.scc 72 | 73 | # Chutzpah Test files 74 | _Chutzpah* 75 | 76 | # Visual C++ cache files 77 | ipch/ 78 | *.aps 79 | *.ncb 80 | *.opendb 81 | *.opensdf 82 | *.sdf 83 | *.cachefile 84 | *.VC.db 85 | *.VC.VC.opendb 86 | 87 | # Visual Studio profiler 88 | *.psess 89 | *.vsp 90 | *.vspx 91 | *.sap 92 | 93 | # TFS 2012 Local Workspace 94 | $tf/ 95 | 96 | # Guidance Automation Toolkit 97 | *.gpState 98 | 99 | # ReSharper is a .NET coding add-in 100 | _ReSharper*/ 101 | *.[Rr]e[Ss]harper 102 | *.DotSettings.user 103 | 104 | # JustCode is a .NET coding add-in 105 | .JustCode 106 | 107 | # TeamCity is a build add-in 108 | _TeamCity* 109 | 110 | # DotCover is a Code Coverage Tool 111 | *.dotCover 112 | 113 | # NCrunch 114 | _NCrunch_* 115 | .*crunch*.local.xml 116 | nCrunchTemp_* 117 | 118 | # MightyMoose 119 | *.mm.* 120 | AutoTest.Net/ 121 | 122 | # Web workbench (sass) 123 | .sass-cache/ 124 | 125 | # Installshield output folder 126 | [Ee]xpress/ 127 | 128 | # DocProject is a documentation generator add-in 129 | DocProject/buildhelp/ 130 | DocProject/Help/*.HxT 131 | DocProject/Help/*.HxC 132 | DocProject/Help/*.hhc 133 | DocProject/Help/*.hhk 134 | DocProject/Help/*.hhp 135 | DocProject/Help/Html2 136 | DocProject/Help/html 137 | 138 | # Click-Once directory 139 | publish/ 140 | 141 | # Publish Web Output 142 | *.[Pp]ublish.xml 143 | *.azurePubxml 144 | # TODO: Comment the next line if you want to checkin your web deploy settings 145 | # but database connection strings (with potential passwords) will be unencrypted 146 | #*.pubxml 147 | *.publishproj 148 | 149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 150 | # checkin your Azure Web App publish settings, but sensitive information contained 151 | # in these scripts will be unencrypted 152 | PublishScripts/ 153 | 154 | # NuGet Packages 155 | *.nupkg 156 | # The packages folder can be ignored because of Package Restore 157 | **/packages/* 158 | # except build/, which is used as an MSBuild target. 159 | !**/packages/build/ 160 | # Uncomment if necessary however generally it will be regenerated when needed 161 | #!**/packages/repositories.config 162 | # NuGet v3's project.json files produces more ignoreable files 163 | *.nuget.props 164 | *.nuget.targets 165 | 166 | # Microsoft Azure Build Output 167 | csx/ 168 | *.build.csdef 169 | 170 | # Microsoft Azure Emulator 171 | ecf/ 172 | rcf/ 173 | 174 | # Windows Store app package directories and files 175 | AppPackages/ 176 | BundleArtifacts/ 177 | Package.StoreAssociation.xml 178 | _pkginfo.txt 179 | 180 | # Visual Studio cache files 181 | # files ending in .cache can be ignored 182 | *.[Cc]ache 183 | # but keep track of directories ending in .cache 184 | !*.[Cc]ache/ 185 | 186 | # Others 187 | ClientBin/ 188 | ~$* 189 | *~ 190 | *.dbmdl 191 | *.dbproj.schemaview 192 | *.jfm 193 | *.pfx 194 | *.publishsettings 195 | node_modules/ 196 | orleans.codegen.cs 197 | 198 | # Since there are multiple workflows, uncomment next line to ignore bower_components 199 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 200 | #bower_components/ 201 | 202 | # RIA/Silverlight projects 203 | Generated_Code/ 204 | 205 | # Backup & report files from converting an old project file 206 | # to a newer Visual Studio version. Backup files are not needed, 207 | # because we have git ;-) 208 | _UpgradeReport_Files/ 209 | Backup*/ 210 | UpgradeLog*.XML 211 | UpgradeLog*.htm 212 | 213 | # SQL Server files 214 | *.mdf 215 | *.ldf 216 | 217 | # Business Intelligence projects 218 | *.rdl.data 219 | *.bim.layout 220 | *.bim_*.settings 221 | 222 | # Microsoft Fakes 223 | FakesAssemblies/ 224 | 225 | # GhostDoc plugin setting file 226 | *.GhostDoc.xml 227 | 228 | # Node.js Tools for Visual Studio 229 | .ntvs_analysis.dat 230 | 231 | # Visual Studio 6 build log 232 | *.plg 233 | 234 | # Visual Studio 6 workspace options file 235 | *.opt 236 | 237 | # Visual Studio LightSwitch build output 238 | **/*.HTMLClient/GeneratedArtifacts 239 | **/*.DesktopClient/GeneratedArtifacts 240 | **/*.DesktopClient/ModelManifest.xml 241 | **/*.Server/GeneratedArtifacts 242 | **/*.Server/ModelManifest.xml 243 | _Pvt_Extensions 244 | 245 | # Paket dependency manager 246 | .paket/paket.exe 247 | paket-files/ 248 | 249 | # FAKE - F# Make 250 | .fake/ 251 | 252 | # JetBrains Rider 253 | .idea/ 254 | *.sln.iml 255 | 256 | # CodeRush 257 | .cr/ 258 | 259 | # Python Tools for Visual Studio (PTVS) 260 | __pycache__/ 261 | *.pyc 262 | 263 | /BenchmarkDotNet.Artifacts 264 | /tests/MiniExcel.Tests.AspNetMvc/packages 265 | /TestTemplate 266 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /MiniWord.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32328.378 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniWord", "src\MiniWord\MiniWord.csproj", "{6DDDB819-3988-41C7-8275-DD8F3291BDB1}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{4EAD1BCD-0F7F-4463-8890-C07ACA8164B7}" 9 | ProjectSection(SolutionItems) = preProject 10 | README.md = README.md 11 | README.zh-CN.md = README.zh-CN.md 12 | README.zh-Hant.md = README.zh-Hant.md 13 | EndProjectSection 14 | EndProject 15 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniWordTests", "tests\MiniWordTests\MiniWordTests.csproj", "{7CB52636-4C20-4FE8-8C69-F794ABBB86A6}" 16 | EndProject 17 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "release-note", "release-note", "{BEA78871-F09C-47A6-98D0-1ED6E0E8476D}" 18 | ProjectSection(SolutionItems) = preProject 19 | release-note\README.md = release-note\README.md 20 | release-note\README.zh-CN.md = release-note\README.zh-CN.md 21 | release-note\README.zh-Hant.md = release-note\README.zh-Hant.md 22 | EndProjectSection 23 | EndProject 24 | Global 25 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 26 | Debug|Any CPU = Debug|Any CPU 27 | Release|Any CPU = Release|Any CPU 28 | EndGlobalSection 29 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 30 | {6DDDB819-3988-41C7-8275-DD8F3291BDB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {6DDDB819-3988-41C7-8275-DD8F3291BDB1}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {6DDDB819-3988-41C7-8275-DD8F3291BDB1}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {6DDDB819-3988-41C7-8275-DD8F3291BDB1}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {7CB52636-4C20-4FE8-8C69-F794ABBB86A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {7CB52636-4C20-4FE8-8C69-F794ABBB86A6}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {7CB52636-4C20-4FE8-8C69-F794ABBB86A6}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {7CB52636-4C20-4FE8-8C69-F794ABBB86A6}.Release|Any CPU.Build.0 = Release|Any CPU 38 | EndGlobalSection 39 | GlobalSection(SolutionProperties) = preSolution 40 | HideSolutionNode = FALSE 41 | EndGlobalSection 42 | GlobalSection(ExtensibilityGlobals) = postSolution 43 | SolutionGuid = {C39CDB06-1E12-454D-95FA-8524A9C7D823} 44 | EndGlobalSection 45 | EndGlobal 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

NuGet 3 | GitHub stars 4 | version 5 | Ask DeepWiki 6 |

7 |
8 | 9 | --- 10 | 11 |
12 |

English | 简体中文 | 繁體中文

13 |
14 | 15 | --- 16 | 17 |
18 | Your Star and dotnate can make MiniWord better 19 |
20 | 21 | --- 22 | 23 | ## Introduction 24 | 25 | MiniWord is an easy and effective .NET Word Template library. 26 | 27 | ![image](https://user-images.githubusercontent.com/12729184/190835307-6cd87982-b5f3-4a79-9682-bdd1cc02a4ea.png) 28 | 29 | 30 | 31 | ## Getting Started 32 | 33 | ### Installation 34 | 35 | - nuget link : https://www.nuget.org/packages/MiniWord 36 | 37 | ### Quick Start 38 | 39 | Template follow "WHAT you see is what you get" design,and the template tag styles are completely preserved. 40 | 41 | ```csharp 42 | var value = new Dictionary(){["title"] = "Hello MiniWord"}; 43 | MiniSoftware.MiniWord.SaveAsByTemplate(outputPath, templatePath, value); 44 | ``` 45 | 46 | ![image](https://user-images.githubusercontent.com/12729184/190875707-6c5639ab-9518-4dc1-85d8-81e20af465e8.png) 47 | 48 | ### Input, Output 49 | 50 | - Input support file path, byte[] 51 | - Output support file path, byte[], stream 52 | 53 | ```csharp 54 | SaveAsByTemplate(string path, string templatePath, Dictionary value) 55 | SaveAsByTemplate(string path, byte[] templateBytes, Dictionary value) 56 | SaveAsByTemplate(this Stream stream, string templatePath, Dictionary value) 57 | SaveAsByTemplate(this Stream stream, byte[] templateBytes, Dictionary value) 58 | ``` 59 | 60 | 61 | 62 | ## Tags 63 | 64 | MiniWord template format string like Vue, React `{{tag}}`,users only need to make sure tag and value parameter key same then system will replace them automatically. 65 | 66 | ### Text 67 | 68 | ```csharp 69 | {{tag}} 70 | ``` 71 | 72 | ##### Example 73 | 74 | ```csharp 75 | var value = new Dictionary() 76 | { 77 | ["Name"] = "Jack", 78 | ["Department"] = "IT Department", 79 | ["Purpose"] = "Shanghai site needs a new system to control HR system.", 80 | ["StartDate"] = DateTime.Parse("2022-09-07 08:30:00"), 81 | ["EndDate"] = DateTime.Parse("2022-09-15 15:30:00"), 82 | ["Approved"] = true, 83 | ["Total_Amount"] = 123456, 84 | }; 85 | MiniWord.SaveAsByTemplate(path, templatePath, value); 86 | ``` 87 | 88 | ##### Template 89 | 90 | ![image](https://user-images.githubusercontent.com/12729184/190834360-39b4b799-d523-4b7e-9331-047a61fd5eb9.png) 91 | 92 | ##### Result 93 | 94 | ![image](https://user-images.githubusercontent.com/12729184/190834455-ba065211-0f9d-41d1-9b7a-5d9e96ac2eff.png) 95 | 96 | ### Image 97 | 98 | Value type is `MiniWordPicture` 99 | 100 | ##### Example 101 | 102 | ```csharp 103 | var value = new Dictionary() 104 | { 105 | ["Logo"] = new MiniWordPicture() { Path= PathHelper.GetFile("DemoLogo.png"), Width= 180, Height= 180 } 106 | }; 107 | MiniWord.SaveAsByTemplate(path, templatePath, value); 108 | ``` 109 | 110 | 111 | 112 | ##### Template 113 | 114 | ![image](https://user-images.githubusercontent.com/12729184/190647953-6f9da393-e666-4658-a56d-b3a7f13c0ea1.png) 115 | 116 | ##### Result 117 | 118 | ![image](https://user-images.githubusercontent.com/12729184/190648179-30258d82-723d-4266-b711-43f132d1842d.png) 119 | 120 | ### List 121 | 122 | tag value is `string[]` or `IList` type 123 | 124 | ##### Example 125 | 126 | ```csharp 127 | var value = new Dictionary() 128 | { 129 | ["managers"] = new[] { "Jack" ,"Alan"}, 130 | ["employees"] = new[] { "Mike" ,"Henry"}, 131 | }; 132 | MiniWord.SaveAsByTemplate(path, templatePath, value); 133 | ``` 134 | 135 | Template 136 | 137 | ![image](https://user-images.githubusercontent.com/12729184/190645513-230c54f3-d38f-47af-b844-0c8c1eff2f52.png) 138 | 139 | ##### Result 140 | 141 | ![image](https://user-images.githubusercontent.com/12729184/190645704-1f6405e9-71e3-45b9-aa99-2ba52e5e1519.png) 142 | 143 | ### Table 144 | 145 | Tag value is `IEmerable>` type 146 | 147 | ##### Example 148 | 149 | ```csharp 150 | var value = new Dictionary() 151 | { 152 | ["TripHs"] = new List> 153 | { 154 | new Dictionary 155 | { 156 | { "sDate",DateTime.Parse("2022-09-08 08:30:00")}, 157 | { "eDate",DateTime.Parse("2022-09-08 15:00:00")}, 158 | { "How","Discussion requirement part1"}, 159 | { "Photo",new MiniWordPicture() { Path = PathHelper.GetFile("DemoExpenseMeeting02.png"), Width = 160, Height = 90 }}, 160 | }, 161 | new Dictionary 162 | { 163 | { "sDate",DateTime.Parse("2022-09-09 08:30:00")}, 164 | { "eDate",DateTime.Parse("2022-09-09 17:00:00")}, 165 | { "How","Discussion requirement part2 and development"}, 166 | { "Photo",new MiniWordPicture() { Path = PathHelper.GetFile("DemoExpenseMeeting01.png"), Width = 160, Height = 90 }}, 167 | }, 168 | } 169 | }; 170 | MiniWord.SaveAsByTemplate(path, templatePath, value); 171 | ``` 172 | 173 | ##### Template 174 | 175 | ![image](https://user-images.githubusercontent.com/12729184/190843632-05bb6459-f1c1-4bdc-a79b-54889afdfeea.png) 176 | 177 | 178 | ##### Result 179 | 180 | ![image](https://user-images.githubusercontent.com/12729184/190843663-c00baf16-21f2-4579-9d08-996a2c8c549b.png) 181 | 182 | ### List inside list 183 | 184 | Tag value is `IEnumerable` type. Adding `{{foreach` and `endforeach}}` tags to template is required. 185 | 186 | ##### Example 187 | 188 | ```csharp 189 | var value = new Dictionary() 190 | { 191 | ["TripHs"] = new List> 192 | { 193 | new Dictionary 194 | { 195 | { "sDate", DateTime.Parse("2022-09-08 08:30:00") }, 196 | { "eDate", DateTime.Parse("2022-09-08 15:00:00") }, 197 | { "How", "Discussion requirement part1" }, 198 | { 199 | "Details", new List() 200 | { 201 | new MiniWordForeach() 202 | { 203 | Value = new Dictionary() 204 | { 205 | {"Text", "Air"}, 206 | {"Value", "Airplane"} 207 | }, 208 | Separator = " | " 209 | }, 210 | new MiniWordForeach() 211 | { 212 | Value = new Dictionary() 213 | { 214 | {"Text", "Parking"}, 215 | {"Value", "Car"} 216 | }, 217 | Separator = " / " 218 | } 219 | } 220 | } 221 | } 222 | } 223 | }; 224 | MiniWord.SaveAsByTemplate(path, templatePath, value); 225 | ``` 226 | 227 | ##### Template 228 | 229 | ![before_foreach](https://user-images.githubusercontent.com/38832863/220123955-063c9345-3998-4fd7-982c-8d1e3b48bbf8.PNG) 230 | 231 | Screenshot 2023-08-08 at 17 59 37 232 | 233 | ##### Result 234 | 235 | ![after_foreach](https://user-images.githubusercontent.com/38832863/220123960-913a7140-2fa2-415e-bb3e-456e04167382.PNG) 236 | 237 | Screenshot 2023-08-08 at 18 00 15 238 | 239 | ### If statement inside template 240 | 241 | For multip paragraph, use @if and @endif tags. 242 | For single paragraph and inside foreach, use `{{if` and `endif}}` tags to template is required. 243 | 244 | ##### Example 245 | 246 | ```csharp 247 | var value = new Dictionary() 248 | { 249 | ["Name"] = new List(){ 250 | new MiniWordHyperLink(){ 251 | Url = "https://google.com", 252 | Text = "測試連結22!!" 253 | }, 254 | new MiniWordHyperLink(){ 255 | Url = "https://google1.com", 256 | Text = "測試連結11!!" 257 | } 258 | }, 259 | ["Company_Name"] = "MiniSofteware", 260 | ["CreateDate"] = new DateTime(2021, 01, 01), 261 | ["VIP"] = true, 262 | ["Points"] = 123, 263 | ["APP"] = "Demo APP", 264 | }; 265 | MiniWord.SaveAsByTemplate(path, templatePath, value); 266 | ``` 267 | 268 | ##### Template For Multi Paragraph 269 | 270 | ![before_if](https://user-images.githubusercontent.com/38832863/220125429-7dd6ce94-35c6-478e-8903-064f9cf9361a.PNG) 271 | 272 | ##### Result Of Multi Paragraph 273 | 274 | ![after_if](https://user-images.githubusercontent.com/38832863/220125435-72ea24b4-2412-45de-961a-ad4b2134417b.PNG) 275 | 276 | ##### Template For Single Paragraph 277 | 278 | Screenshot 2023-08-08 at 17 55 46 279 | 280 | ##### Result Of Single Paragraph 281 | 282 | Screenshot 2023-08-08 at 17 56 47 283 | 284 | ### ColorText 285 | 286 | ##### Example 287 | 288 | ```csharp 289 | var value = new 290 | { 291 | Company_Name = new MiniWordColorText { Text = "MiniSofteware", FontColor = "#eb70AB", }, 292 | Name = new[] { 293 | new MiniWordColorText { Text = "Ja", HighlightColor = "#eb70AB" }, 294 | new MiniWordColorText { Text = "ck", HighlightColor = "#a56abe" } 295 | }, 296 | CreateDate = new MiniWordColorText 297 | { 298 | Text = new DateTime(2021, 01, 01).ToString(), 299 | HighlightColor = "#eb70AB", 300 | FontColor = "#ffffff", 301 | }, 302 | VIP = true, 303 | Points = 123, 304 | APP = "Demo APP", 305 | }; 306 | MiniWord.SaveAsByTemplate(path, templatePath, value); 307 | ``` 308 | 309 | 310 | ## Other 311 | 312 | ### POCO or dynamic parameter 313 | 314 | v0.5.0 support POCO or dynamic parameter 315 | 316 | ```csharp 317 | var value = new { title = "Hello MiniWord" }; 318 | MiniWord.SaveAsByTemplate(outputPath, templatePath, value); 319 | ``` 320 | 321 | ### FontColor and HighlightColor 322 | ```csharp 323 | var value = new 324 | { 325 | Company_Name = new MiniWordColorText { Text = "MiniSofteware", FontColor = "#eb70AB" }, 326 | Name = new MiniWordColorText { Text = "Jack", HighlightColor = "#eb70AB" }, 327 | CreateDate = new MiniWordColorText { Text = new DateTime(2021, 01, 01).ToString(), HighlightColor = "#eb70AB", FontColor = "#ffffff" }, 328 | VIP = true, 329 | Points = 123, 330 | APP = "Demo APP", 331 | }; 332 | ``` 333 | 334 | ### HyperLink 335 | 336 | If value type is `MiniWordHyperLink` system will replace template string by hyperlink. 337 | 338 | * Url: HyperLink URI target path 339 | * Text:Description 340 | 341 | ```csharp 342 | var value = new 343 | { 344 | ["Name"] = new MiniWordHyperLink(){ 345 | Url = "https://google.com", 346 | Text = "Test Link!!" 347 | }, 348 | ["Company_Name"] = "MiniSofteware", 349 | ["CreateDate"] = new DateTime(2021, 01, 01), 350 | ["VIP"] = true, 351 | ["Points"] = 123, 352 | ["APP"] = "Demo APP", 353 | }; 354 | MiniWord.SaveAsByTemplate(path, templatePath, value); 355 | ``` 356 | 357 | 358 | 359 | ## Examples 360 | 361 | 362 | 363 | #### ASP.NET Core 3.1 API Export 364 | 365 | ```cs 366 | using Microsoft.AspNetCore.Builder; 367 | using Microsoft.AspNetCore.Hosting; 368 | using Microsoft.AspNetCore.Mvc; 369 | using Microsoft.Extensions.DependencyInjection; 370 | using Microsoft.Extensions.Hosting; 371 | using System; 372 | using System.Collections.Generic; 373 | using System.IO; 374 | using System.Net; 375 | using MiniSoftware; 376 | 377 | public class Program 378 | { 379 | public static void Main(string[] args) => CreateHostBuilder(args).Build().Run(); 380 | 381 | public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); 382 | } 383 | 384 | public class Startup 385 | { 386 | public void ConfigureServices(IServiceCollection services) => services.AddMvc(); 387 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 388 | { 389 | app.UseStaticFiles(); 390 | app.UseRouting(); 391 | app.UseEndpoints(endpoints => 392 | { 393 | endpoints.MapControllerRoute( 394 | name: "default", 395 | pattern: "{controller=api}/{action=Index}/{id?}"); 396 | }); 397 | } 398 | } 399 | 400 | public class ApiController : Controller 401 | { 402 | public IActionResult Index() 403 | { 404 | return new ContentResult 405 | { 406 | ContentType = "text/html", 407 | StatusCode = (int)HttpStatusCode.OK, 408 | Content = @" 409 | DownloadWordFromTemplatePath
410 | DownloadWordFromTemplateBytes
411 | " 412 | }; 413 | } 414 | 415 | static Dictionary defaultValue = new Dictionary() 416 | { 417 | ["title"] = "FooCompany", 418 | ["managers"] = new List> { 419 | new Dictionary{{"name","Jack"},{ "department", "HR" } }, 420 | new Dictionary {{ "name", "Loan"},{ "department", "IT" } } 421 | }, 422 | ["employees"] = new List> { 423 | new Dictionary{{ "name", "Wade" },{ "department", "HR" } }, 424 | new Dictionary {{ "name", "Felix" },{ "department", "HR" } }, 425 | new Dictionary{{ "name", "Eric" },{ "department", "IT" } }, 426 | new Dictionary {{ "name", "Keaton" },{ "department", "IT" } } 427 | } 428 | }; 429 | 430 | public IActionResult DownloadWordFromTemplatePath() 431 | { 432 | string templatePath = "TestTemplateComplex.docx"; 433 | 434 | Dictionary value = defaultValue; 435 | 436 | MemoryStream memoryStream = new MemoryStream(); 437 | MiniWord.SaveAsByTemplate(memoryStream, templatePath, value); 438 | memoryStream.Seek(0, SeekOrigin.Begin); 439 | return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.wordprocessingml.document") 440 | { 441 | FileDownloadName = "demo.docx" 442 | }; 443 | } 444 | 445 | private static Dictionary TemplateBytesCache = new Dictionary(); 446 | 447 | static ApiController() 448 | { 449 | string templatePath = "TestTemplateComplex.docx"; 450 | byte[] bytes = System.IO.File.ReadAllBytes(templatePath); 451 | TemplateBytesCache.Add(templatePath, bytes); 452 | } 453 | 454 | public IActionResult DownloadWordFromTemplateBytes() 455 | { 456 | byte[] bytes = TemplateBytesCache["TestTemplateComplex.docx"]; 457 | 458 | Dictionary value = defaultValue; 459 | 460 | MemoryStream memoryStream = new MemoryStream(); 461 | MiniWord.SaveAsByTemplate(memoryStream, bytes, value); 462 | memoryStream.Seek(0, SeekOrigin.Begin); 463 | return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.wordprocessingml.document") 464 | { 465 | FileDownloadName = "demo.docx" 466 | }; 467 | } 468 | } 469 | ``` 470 | 471 | 472 | 473 | 474 | 475 | 476 | ## Support : [Donate Link](https://miniexcel.github.io/) -------------------------------------------------------------------------------- /README.zh-CN.md: -------------------------------------------------------------------------------- 1 |
2 |

NuGet 3 | GitHub stars 4 | version 5 | Ask DeepWiki 6 |

7 |
8 | 9 | --- 10 | 11 | 14 | 15 | --- 16 | 17 |
18 | 您的 Star赞助 可以让 MiniWord 走更远 19 |
20 | 21 | --- 22 | 23 | ## QQ群(1群) : [813100564](https://qm.qq.com/cgi-bin/qm/qr?k=3OkxuL14sXhJsUimWK8wx_Hf28Wl49QE&jump_from=webapi) / QQ群(2群) : [579033769](https://jq.qq.com/?_wv=1027&k=UxTdB8pR) 24 | 25 | ---- 26 | 27 | ## 介绍 28 | 29 | MiniWord .NET Word模板引擎,藉由Word模板和数据简单、快速生成文件。 30 | 31 | ![image](https://user-images.githubusercontent.com/12729184/190835307-6cd87982-b5f3-4a79-9682-bdd1cc02a4ea.png) 32 | 33 | 34 | 35 | ## Getting Started 36 | 37 | ### 安装 38 | 39 | - nuget link : https://www.nuget.org/packages/MiniWord 40 | 41 | ### 快速入门 42 | 43 | 模板遵循“所见即所得”的设计,模板和标签的样式会被完全保留 44 | 45 | ```csharp 46 | var value = new Dictionary(){["title"] = "Hello MiniWord"}; 47 | MiniSoftware.MiniWord.SaveAsByTemplate(outputPath, templatePath, value); 48 | ``` 49 | 50 | ![image](https://user-images.githubusercontent.com/12729184/190875707-6c5639ab-9518-4dc1-85d8-81e20af465e8.png) 51 | 52 | ### 输入、输出 53 | 54 | - 输入系统支持模版路径或是Byte[] 55 | - 输出支持文件路径、Byte[]、Stream 56 | 57 | ```csharp 58 | SaveAsByTemplate(string path, string templatePath, Dictionary value) 59 | SaveAsByTemplate(string path, byte[] templateBytes, Dictionary value) 60 | SaveAsByTemplate(this Stream stream, string templatePath, Dictionary value) 61 | SaveAsByTemplate(this Stream stream, byte[] templateBytes, Dictionary value) 62 | ``` 63 | 64 | 65 | 66 | ## 标签 67 | 68 | MiniWord 使用类似 Vue, React 的模版字串 `{{tag}}`,只需要确保 tag 与 value 参数的 key 一样`(大小写敏感)`,系统会自动替换字串。 69 | 70 | ### 文本 71 | 72 | ```csharp 73 | {{tag}} 74 | ``` 75 | 76 | 77 | 78 | ##### 代码例子 79 | 80 | ```csharp 81 | var value = new Dictionary() 82 | { 83 | ["Name"] = "Jack", 84 | ["Department"] = "IT Department", 85 | ["Purpose"] = "Shanghai site needs a new system to control HR system.", 86 | ["StartDate"] = DateTime.Parse("2022-09-07 08:30:00"), 87 | ["EndDate"] = DateTime.Parse("2022-09-15 15:30:00"), 88 | ["Approved"] = true, 89 | ["Total_Amount"] = 123456, 90 | }; 91 | MiniWord.SaveAsByTemplate(path, templatePath, value); 92 | ``` 93 | 94 | ##### 模版 95 | 96 | ![image](https://user-images.githubusercontent.com/12729184/190834360-39b4b799-d523-4b7e-9331-047a61fd5eb9.png) 97 | 98 | ##### 导出 99 | 100 | ![image](https://user-images.githubusercontent.com/12729184/190834455-ba065211-0f9d-41d1-9b7a-5d9e96ac2eff.png) 101 | 102 | ### 图片 103 | 104 | 标签值为 `MiniWordPicture` 类别 105 | 106 | ##### 代码例子 107 | 108 | ```csharp 109 | var value = new Dictionary() 110 | { 111 | ["Logo"] = new MiniWordPicture() { Path= PathHelper.GetFile("DemoLogo.png"), Width= 180, Height= 180 } 112 | }; 113 | MiniWord.SaveAsByTemplate(path, templatePath, value); 114 | ``` 115 | 116 | 117 | 118 | ##### 模版 119 | 120 | ![image](https://user-images.githubusercontent.com/12729184/190647953-6f9da393-e666-4658-a56d-b3a7f13c0ea1.png) 121 | 122 | ##### 导出 123 | 124 | ![image](https://user-images.githubusercontent.com/12729184/190648179-30258d82-723d-4266-b711-43f132d1842d.png) 125 | 126 | ### 列表 127 | 128 | 标签值为 `string[]` 或是 `IList`类别 129 | 130 | ##### 代码例子 131 | 132 | ```csharp 133 | var value = new Dictionary() 134 | { 135 | ["managers"] = new[] { "Jack" ,"Alan"}, 136 | ["employees"] = new[] { "Mike" ,"Henry"}, 137 | }; 138 | MiniWord.SaveAsByTemplate(path, templatePath, value); 139 | ``` 140 | 141 | ##### 模版 142 | 143 | ![image](https://user-images.githubusercontent.com/12729184/190645513-230c54f3-d38f-47af-b844-0c8c1eff2f52.png) 144 | 145 | ##### 导出 146 | 147 | ![image](https://user-images.githubusercontent.com/12729184/190645704-1f6405e9-71e3-45b9-aa99-2ba52e5e1519.png) 148 | 149 | ### 表格 150 | 151 | 标签值为 `IEmerable>`类别 152 | 153 | ##### 代码例子 154 | 155 | ```csharp 156 | var value = new Dictionary() 157 | { 158 | ["TripHs"] = new List> 159 | { 160 | new Dictionary 161 | { 162 | { "sDate",DateTime.Parse("2022-09-08 08:30:00")}, 163 | { "eDate",DateTime.Parse("2022-09-08 15:00:00")}, 164 | { "How","Discussion requirement part1"}, 165 | { "Photo",new MiniWordPicture() { Path = PathHelper.GetFile("DemoExpenseMeeting02.png"), Width = 160, Height = 90 }}, 166 | }, 167 | new Dictionary 168 | { 169 | { "sDate",DateTime.Parse("2022-09-09 08:30:00")}, 170 | { "eDate",DateTime.Parse("2022-09-09 17:00:00")}, 171 | { "How","Discussion requirement part2 and development"}, 172 | { "Photo",new MiniWordPicture() { Path = PathHelper.GetFile("DemoExpenseMeeting01.png"), Width = 160, Height = 90 }}, 173 | }, 174 | } 175 | }; 176 | MiniWord.SaveAsByTemplate(path, templatePath, value); 177 | ``` 178 | 179 | ##### 模版 180 | 181 | ![image](https://user-images.githubusercontent.com/12729184/190843632-05bb6459-f1c1-4bdc-a79b-54889afdfeea.png) 182 | 183 | 184 | ##### 导出 185 | 186 | ![image](https://user-images.githubusercontent.com/12729184/190843663-c00baf16-21f2-4579-9d08-996a2c8c549b.png) 187 | 188 | ### 二级列表 189 | 190 | Tag 是 `IEnumerable` 类别. 使用方式`{{foreach` 和 `endforeach}}`. 191 | 192 | ##### Example 193 | 194 | ```csharp 195 | var value = new Dictionary() 196 | { 197 | ["TripHs"] = new List> 198 | { 199 | new Dictionary 200 | { 201 | { "sDate", DateTime.Parse("2022-09-08 08:30:00") }, 202 | { "eDate", DateTime.Parse("2022-09-08 15:00:00") }, 203 | { "How", "Discussion requirement part1" }, 204 | { 205 | "Details", new List() 206 | { 207 | new MiniWordForeach() 208 | { 209 | Value = new Dictionary() 210 | { 211 | {"Text", "Air"}, 212 | {"Value", "Airplane"} 213 | }, 214 | Separator = " | " 215 | }, 216 | new MiniWordForeach() 217 | { 218 | Value = new Dictionary() 219 | { 220 | {"Text", "Parking"}, 221 | {"Value", "Car"} 222 | }, 223 | Separator = " / " 224 | } 225 | } 226 | } 227 | } 228 | } 229 | }; 230 | MiniWord.SaveAsByTemplate(path, templatePath, value); 231 | ``` 232 | 233 | ##### Template 234 | 235 | ![before_foreach](https://user-images.githubusercontent.com/38832863/220123955-063c9345-3998-4fd7-982c-8d1e3b48bbf8.PNG) 236 | 237 | ##### Result 238 | 239 | ![after_foreach](https://user-images.githubusercontent.com/38832863/220123960-913a7140-2fa2-415e-bb3e-456e04167382.PNG) 240 | 241 | ### 条件判断 242 | 243 | `@if` 和 `@endif` tags . 244 | 245 | ##### Example 246 | 247 | ```csharp 248 | var value = new Dictionary() 249 | { 250 | ["Name"] = new List(){ 251 | new MiniWordHyperLink(){ 252 | Url = "https://google.com", 253 | Text = "測試連結22!!" 254 | }, 255 | new MiniWordHyperLink(){ 256 | Url = "https://google1.com", 257 | Text = "測試連結11!!" 258 | } 259 | }, 260 | ["Company_Name"] = "MiniSofteware", 261 | ["CreateDate"] = new DateTime(2021, 01, 01), 262 | ["VIP"] = true, 263 | ["Points"] = 123, 264 | ["APP"] = "Demo APP", 265 | }; 266 | MiniWord.SaveAsByTemplate(path, templatePath, value); 267 | ``` 268 | 269 | ##### Template 270 | 271 | ![before_if](https://user-images.githubusercontent.com/38832863/220125429-7dd6ce94-35c6-478e-8903-064f9cf9361a.PNG) 272 | 273 | ##### Result 274 | 275 | ![after_if](https://user-images.githubusercontent.com/38832863/220125435-72ea24b4-2412-45de-961a-ad4b2134417b.PNG) 276 | 277 | ### 循环 278 | 279 | `@foreach` 和 `@endforeach` tags . 280 | 281 | ##### Example 282 | 283 | ```csharp 284 | var value = new 285 | { 286 | LoopData = new List() 287 | { 288 | new { 289 | Type="类型A", 290 | Items = new List() {new {Name = "A-1"}, new {Name = "A-2"},} 291 | }, 292 | new 293 | { 294 | Type="类型B", 295 | Items = new List() {new {Name = "B-1"}, new {Name = "B-2"}, new {Name = "B-3"},} 296 | }, 297 | } 298 | }; 299 | MiniWord.SaveAsByTemplate(path, templatePath, value); 300 | ``` 301 | 302 | ##### Template 303 | 304 | ![1](https://github.com/user-attachments/assets/5d32241d-3977-46e7-b3de-cae130e5a653) 305 | 306 | ##### Result 307 | 308 | ![2](https://github.com/user-attachments/assets/69daa15e-4864-483e-b132-d8e867b6d1d1) 309 | 310 | ### 多彩字体 311 | 312 | ##### 代码例子 313 | 314 | ```csharp 315 | var value = new 316 | { 317 | Company_Name = new MiniWordColorText { Text = "MiniSofteware", FontColor = "#eb70AB", }, 318 | Name = new[] { 319 | new MiniWordColorText { Text = "Ja", HighlightColor = "#eb70AB" }, 320 | new MiniWordColorText { Text = "ck", HighlightColor = "#a56abe" } 321 | }, 322 | CreateDate = new MiniWordColorText 323 | { 324 | Text = new DateTime(2021, 01, 01).ToString(), 325 | HighlightColor = "#eb70AB", 326 | FontColor = "#ffffff", 327 | }, 328 | VIP = true, 329 | Points = 123, 330 | APP = "Demo APP", 331 | }; 332 | MiniWord.SaveAsByTemplate(path, templatePath, value); 333 | ``` 334 | 335 | 336 | 337 | 338 | 339 | ## 其他 340 | 341 | ### POCO or dynamic 参数 342 | 343 | v0.5.0 支持 POCO 或 dynamic parameter 344 | 345 | ```csharp 346 | var value = new { title = "Hello MiniWord" }; 347 | MiniWord.SaveAsByTemplate(outputPath, templatePath, value); 348 | ``` 349 | 350 | ### 字体FontColor和HighlightColor 351 | ```csharp 352 | var value = new 353 | { 354 | Company_Name = new MiniWordColorText { Text = "MiniSofteware", FontColor = "#eb70AB" }, 355 | Name = new MiniWordColorText { Text = "Jack", HighlightColor = "#eb70AB" }, 356 | CreateDate = new MiniWordColorText { Text = new DateTime(2021, 01, 01).ToString(), HighlightColor = "#eb70AB", FontColor = "#ffffff" }, 357 | VIP = true, 358 | Points = 123, 359 | APP = "Demo APP", 360 | }; 361 | ``` 362 | 363 | ### HyperLink 364 | 365 | 我们可以尝试使用 `MiniWodrHyperLink` 类,用模板测试替换为超链接。 366 | 367 | `MiniWordHyperLink` 提供了两个主要参数。 368 | 369 | * Url: HyperLink URI 目标路径 370 | * 文字:超链接文字 371 | 372 | ```csharp 373 | var value = new 374 | { 375 | ["Name"] = new MiniWordHyperLink(){ 376 | Url = "https://google.com", 377 | Text = "測試連結!!" 378 | }, 379 | ["Company_Name"] = "MiniSofteware", 380 | ["CreateDate"] = new DateTime(2021, 01, 01), 381 | ["VIP"] = true, 382 | ["Points"] = 123, 383 | ["APP"] = "Demo APP", 384 | }; 385 | MiniWord.SaveAsByTemplate(path, templatePath, value); 386 | ``` 387 | 388 | ### 浮动图像 389 | 390 | 可以通过MiniWordPicture扩展参数配置图片悬浮环绕在文字上或文字下 391 | `MiniWordPicture` 扩展参数。 392 | * WrappingType: MiniWordPictureWrappingType.Anchor 浮动图像 393 | * HorizontalPositionOffset: 设置图片相对于锚点的水平偏移量(以像素为单位) 394 | * VerticalPositionOffset:设置图片相对于锚点的垂直偏移量(以像素为单位) 395 | * BehindDoc: 控制图片是否显示在文档文字的后方 396 | * AllowOverlap: 控制图片是否允许与其他图片或对象重叠 397 | 398 | 399 | ## 例子 400 | 401 | 402 | 403 | #### ASP.NET Core 3.1 API Export 404 | 405 | ```cs 406 | using Microsoft.AspNetCore.Builder; 407 | using Microsoft.AspNetCore.Hosting; 408 | using Microsoft.AspNetCore.Mvc; 409 | using Microsoft.Extensions.DependencyInjection; 410 | using Microsoft.Extensions.Hosting; 411 | using System; 412 | using System.Collections.Generic; 413 | using System.IO; 414 | using System.Net; 415 | using MiniSoftware; 416 | 417 | public class Program 418 | { 419 | public static void Main(string[] args) => CreateHostBuilder(args).Build().Run(); 420 | 421 | public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); 422 | } 423 | 424 | public class Startup 425 | { 426 | public void ConfigureServices(IServiceCollection services) => services.AddMvc(); 427 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 428 | { 429 | app.UseStaticFiles(); 430 | app.UseRouting(); 431 | app.UseEndpoints(endpoints => 432 | { 433 | endpoints.MapControllerRoute( 434 | name: "default", 435 | pattern: "{controller=api}/{action=Index}/{id?}"); 436 | }); 437 | } 438 | } 439 | 440 | public class ApiController : Controller 441 | { 442 | public IActionResult Index() 443 | { 444 | return new ContentResult 445 | { 446 | ContentType = "text/html", 447 | StatusCode = (int)HttpStatusCode.OK, 448 | Content = @" 449 | DownloadWordFromTemplatePath
450 | DownloadWordFromTemplateBytes
451 | " 452 | }; 453 | } 454 | 455 | static Dictionary defaultValue = new Dictionary() 456 | { 457 | ["title"] = "FooCompany", 458 | ["managers"] = new List> { 459 | new Dictionary{{"name","Jack"},{ "department", "HR" } }, 460 | new Dictionary {{ "name", "Loan"},{ "department", "IT" } } 461 | }, 462 | ["employees"] = new List> { 463 | new Dictionary{{ "name", "Wade" },{ "department", "HR" } }, 464 | new Dictionary {{ "name", "Felix" },{ "department", "HR" } }, 465 | new Dictionary{{ "name", "Eric" },{ "department", "IT" } }, 466 | new Dictionary {{ "name", "Keaton" },{ "department", "IT" } } 467 | } 468 | }; 469 | 470 | public IActionResult DownloadWordFromTemplatePath() 471 | { 472 | string templatePath = "TestTemplateComplex.docx"; 473 | 474 | Dictionary value = defaultValue; 475 | 476 | MemoryStream memoryStream = new MemoryStream(); 477 | MiniWord.SaveAsByTemplate(memoryStream, templatePath, value); 478 | memoryStream.Seek(0, SeekOrigin.Begin); 479 | return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.wordprocessingml.document") 480 | { 481 | FileDownloadName = "demo.docx" 482 | }; 483 | } 484 | 485 | private static Dictionary TemplateBytesCache = new Dictionary(); 486 | 487 | static ApiController() 488 | { 489 | string templatePath = "TestTemplateComplex.docx"; 490 | byte[] bytes = System.IO.File.ReadAllBytes(templatePath); 491 | TemplateBytesCache.Add(templatePath, bytes); 492 | } 493 | 494 | public IActionResult DownloadWordFromTemplateBytes() 495 | { 496 | byte[] bytes = TemplateBytesCache["TestTemplateComplex.docx"]; 497 | 498 | Dictionary value = defaultValue; 499 | 500 | MemoryStream memoryStream = new MemoryStream(); 501 | MiniWord.SaveAsByTemplate(memoryStream, bytes, value); 502 | memoryStream.Seek(0, SeekOrigin.Begin); 503 | return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.wordprocessingml.document") 504 | { 505 | FileDownloadName = "demo.docx" 506 | }; 507 | } 508 | } 509 | ``` 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | ## 支持 : [Donate Link](https://miniexcel.github.io/) 518 | 519 | wechat 520 | 521 | 522 | alipay 523 | 524 | 525 | 526 | -------------------------------------------------------------------------------- /README.zh-Hant.md: -------------------------------------------------------------------------------- 1 |
2 |

NuGet 3 | GitHub stars 4 | version 5 | Ask DeepWiki 6 |

7 |
8 | 9 | 10 | --- 11 | 12 | 15 | 16 | --- 17 | 18 |
19 | 您的 Star贊助 可以讓 MiniWord 走更遠 20 |
21 | 22 | --- 23 | 24 | 25 | ## 介紹 26 | 27 | MiniWord .NET Word模板引擎,藉由Word模板和數據簡單、快速生成文件。 28 | 29 | ![image](https://user-images.githubusercontent.com/12729184/190835307-6cd87982-b5f3-4a79-9682-bdd1cc02a4ea.png) 30 | 31 | 32 | 33 | ## Getting Started 34 | 35 | ### 安裝 36 | 37 | - nuget link : https://www.nuget.org/packages/MiniWord 38 | 39 | ### 快速入門 40 | 41 | 模板遵循“所見即所得”的設計,模板和標籤的樣式會被完全保留 42 | 43 | ```csharp 44 | var value = new Dictionary(){["title"] = "Hello MiniWord"}; 45 | MiniSoftware.MiniWord.SaveAsByTemplate(outputPath, templatePath, value); 46 | ``` 47 | 48 | ![image](https://user-images.githubusercontent.com/12729184/190875707-6c5639ab-9518-4dc1-85d8-81e20af465e8.png) 49 | 50 | ### 輸入、輸出 51 | 52 | - 輸入系統支持模版路徑或是Byte[] 53 | - 輸出支持文件路徑、Byte[]、Stream 54 | 55 | ```csharp 56 | SaveAsByTemplate(string path, string templatePath, Dictionary value) 57 | SaveAsByTemplate(string path, byte[] templateBytes, Dictionary value) 58 | SaveAsByTemplate(this Stream stream, string templatePath, Dictionary value) 59 | SaveAsByTemplate(this Stream stream, byte[] templateBytes, Dictionary value) 60 | ``` 61 | 62 | 63 | 64 | ## 標籤 65 | 66 | MiniWord 使用類似 Vue, React 的模版字串 `{{tag}}`,只需要確保 tag 與 value 參數的 key 一樣`(大小寫敏感)`,系統會自動替換字串。 67 | 68 | ### 文本 69 | 70 | ```csharp 71 | {{tag}} 72 | ``` 73 | 74 | ##### 代碼例子 75 | 76 | ```csharp 77 | var value = new Dictionary() 78 | { 79 | ["Name"] = "Jack", 80 | ["Department"] = "IT Department", 81 | ["Purpose"] = "Shanghai site needs a new system to control HR system.", 82 | ["StartDate"] = DateTime.Parse("2022-09-07 08:30:00"), 83 | ["EndDate"] = DateTime.Parse("2022-09-15 15:30:00"), 84 | ["Approved"] = true, 85 | ["Total_Amount"] = 123456, 86 | }; 87 | MiniWord.SaveAsByTemplate(path, templatePath, value); 88 | ``` 89 | 90 | ##### 模版 91 | 92 | ![image](https://user-images.githubusercontent.com/12729184/190834360-39b4b799-d523-4b7e-9331-047a61fd5eb9.png) 93 | 94 | ##### 導出 95 | 96 | ![image](https://user-images.githubusercontent.com/12729184/190834455-ba065211-0f9d-41d1-9b7a-5d9e96ac2eff.png) 97 | 98 | 99 | 100 | ### 圖片 101 | 102 | 標籤值為 `MiniWordPicture` 類別 103 | 104 | ##### 代碼例子 105 | 106 | ```csharp 107 | var value = new Dictionary() 108 | { 109 | ["Logo"] = new MiniWordPicture() { Path= PathHelper.GetFile("DemoLogo.png"), Width= 180, Height= 180 } 110 | }; 111 | MiniWord.SaveAsByTemplate(path, templatePath, value); 112 | ``` 113 | 114 | 115 | 116 | ##### 模版 117 | 118 | ![image](https://user-images.githubusercontent.com/12729184/190647953-6f9da393-e666-4658-a56d-b3a7f13c0ea1.png) 119 | 120 | ##### 導出 121 | 122 | ![image](https://user-images.githubusercontent.com/12729184/190648179-30258d82-723d-4266-b711-43f132d1842d.png) 123 | 124 | ### 列表 125 | 126 | 標籤值為 `string[]` 或是 `IList`類別 127 | 128 | ##### 代碼例子 129 | 130 | ```csharp 131 | var value = new Dictionary() 132 | { 133 | ["managers"] = new[] { "Jack" ,"Alan"}, 134 | ["employees"] = new[] { "Mike" ,"Henry"}, 135 | }; 136 | MiniWord.SaveAsByTemplate(path, templatePath, value); 137 | ``` 138 | 139 | ##### 模版 140 | 141 | ![image](https://user-images.githubusercontent.com/12729184/190645513-230c54f3-d38f-47af-b844-0c8c1eff2f52.png) 142 | 143 | ##### 導出 144 | 145 | ![image](https://user-images.githubusercontent.com/12729184/190645704-1f6405e9-71e3-45b9-aa99-2ba52e5e1519.png) 146 | 147 | ### 表格 148 | 149 | 標籤值為 `IEmerable>`類別 150 | 151 | ##### 代碼例子 152 | 153 | ```csharp 154 | var value = new Dictionary() 155 | { 156 | ["TripHs"] = new List> 157 | { 158 | new Dictionary 159 | { 160 | { "sDate",DateTime.Parse("2022-09-08 08:30:00")}, 161 | { "eDate",DateTime.Parse("2022-09-08 15:00:00")}, 162 | { "How","Discussion requirement part1"}, 163 | { "Photo",new MiniWordPicture() { Path = PathHelper.GetFile("DemoExpenseMeeting02.png"), Width = 160, Height = 90 }}, 164 | }, 165 | new Dictionary 166 | { 167 | { "sDate",DateTime.Parse("2022-09-09 08:30:00")}, 168 | { "eDate",DateTime.Parse("2022-09-09 17:00:00")}, 169 | { "How","Discussion requirement part2 and development"}, 170 | { "Photo",new MiniWordPicture() { Path = PathHelper.GetFile("DemoExpenseMeeting01.png"), Width = 160, Height = 90 }}, 171 | }, 172 | } 173 | }; 174 | MiniWord.SaveAsByTemplate(path, templatePath, value); 175 | ``` 176 | 177 | ##### 模版 178 | 179 | ![image](https://user-images.githubusercontent.com/12729184/190843632-05bb6459-f1c1-4bdc-a79b-54889afdfeea.png) 180 | 181 | 182 | ##### 導出 183 | 184 | ![image](https://user-images.githubusercontent.com/12729184/190843663-c00baf16-21f2-4579-9d08-996a2c8c549b.png) 185 | 186 | 187 | 188 | ### 二级列表 189 | 190 | Tag 是 `IEnumerable` 类别. 使用方式`{{foreach` 和 `endforeach}}`. 191 | 192 | ##### Example 193 | 194 | ```csharp 195 | var value = new Dictionary() 196 | { 197 | ["TripHs"] = new List> 198 | { 199 | new Dictionary 200 | { 201 | { "sDate", DateTime.Parse("2022-09-08 08:30:00") }, 202 | { "eDate", DateTime.Parse("2022-09-08 15:00:00") }, 203 | { "How", "Discussion requirement part1" }, 204 | { 205 | "Details", new List() 206 | { 207 | new MiniWordForeach() 208 | { 209 | Value = new Dictionary() 210 | { 211 | {"Text", "Air"}, 212 | {"Value", "Airplane"} 213 | }, 214 | Separator = " | " 215 | }, 216 | new MiniWordForeach() 217 | { 218 | Value = new Dictionary() 219 | { 220 | {"Text", "Parking"}, 221 | {"Value", "Car"} 222 | }, 223 | Separator = " / " 224 | } 225 | } 226 | } 227 | } 228 | } 229 | }; 230 | MiniWord.SaveAsByTemplate(path, templatePath, value); 231 | ``` 232 | 233 | ##### Template 234 | 235 | ![before_foreach](https://user-images.githubusercontent.com/38832863/220123955-063c9345-3998-4fd7-982c-8d1e3b48bbf8.PNG) 236 | 237 | ##### Result 238 | 239 | ![after_foreach](https://user-images.githubusercontent.com/38832863/220123960-913a7140-2fa2-415e-bb3e-456e04167382.PNG) 240 | 241 | ### 条件判断 242 | 243 | `@if` 和 `@endif` tags . 244 | 245 | ##### Example 246 | 247 | ```csharp 248 | var value = new Dictionary() 249 | { 250 | ["Name"] = new List(){ 251 | new MiniWordHyperLink(){ 252 | Url = "https://google.com", 253 | Text = "測試連結22!!" 254 | }, 255 | new MiniWordHyperLink(){ 256 | Url = "https://google1.com", 257 | Text = "測試連結11!!" 258 | } 259 | }, 260 | ["Company_Name"] = "MiniSofteware", 261 | ["CreateDate"] = new DateTime(2021, 01, 01), 262 | ["VIP"] = true, 263 | ["Points"] = 123, 264 | ["APP"] = "Demo APP", 265 | }; 266 | MiniWord.SaveAsByTemplate(path, templatePath, value); 267 | ``` 268 | 269 | ##### Template 270 | 271 | ![before_if](https://user-images.githubusercontent.com/38832863/220125429-7dd6ce94-35c6-478e-8903-064f9cf9361a.PNG) 272 | 273 | ##### Result 274 | 275 | ![after_if](https://user-images.githubusercontent.com/38832863/220125435-72ea24b4-2412-45de-961a-ad4b2134417b.PNG) 276 | 277 | ### 多彩字體 278 | 279 | ##### 例子 280 | 281 | ```csharp 282 | var value = new 283 | { 284 | Company_Name = new MiniWordColorText { Text = "MiniSofteware", FontColor = "#eb70AB", }, 285 | Name = new[] { 286 | new MiniWordColorText { Text = "Ja", HighlightColor = "#eb70AB" }, 287 | new MiniWordColorText { Text = "ck", HighlightColor = "#a56abe" } 288 | }, 289 | CreateDate = new MiniWordColorText 290 | { 291 | Text = new DateTime(2021, 01, 01).ToString(), 292 | HighlightColor = "#eb70AB", 293 | FontColor = "#ffffff", 294 | }, 295 | VIP = true, 296 | Points = 123, 297 | APP = "Demo APP", 298 | }; 299 | MiniWord.SaveAsByTemplate(path, templatePath, value); 300 | ``` 301 | 302 | 303 | 304 | ## 其他 305 | 306 | ### POCO or dynamic 參數 307 | 308 | v0.5.0 支持 POCO 或 dynamic parameter 309 | 310 | ```csharp 311 | var value = new { title = "Hello MiniWord" }; 312 | MiniWord.SaveAsByTemplate(outputPath, templatePath, value); 313 | ``` 314 | 315 | ### 字体FontColor和HighlightColor 316 | ```csharp 317 | var value = new 318 | { 319 | Company_Name = new MiniWordColorText { Text = "MiniSofteware", FontColor = "#eb70AB" }, 320 | Name = new MiniWordColorText { Text = "Jack", HighlightColor = "#eb70AB" }, 321 | CreateDate = new MiniWordColorText { Text = new DateTime(2021, 01, 01).ToString(), HighlightColor = "#eb70AB", FontColor = "#ffffff" }, 322 | VIP = true, 323 | Points = 123, 324 | APP = "Demo APP", 325 | }; 326 | ``` 327 | 328 | ### HyperLink 329 | 330 | 我們可以嘗試使用 `MiniWordHyperLink` 類,它將模板文本替換為超鏈接。 331 | 332 | `MiniWordHyperLink` 提供了兩個主要參數。 333 | 334 | * Url: HyperLink URI 目標路徑 335 | * 文字:超鏈接文字 336 | 337 | ```csharp 338 | var value = new 339 | { 340 | ["Name"] = new MiniWordHyperLink(){ 341 | Url = "https://google.com", 342 | Text = "測試連結!!" 343 | }, 344 | ["Company_Name"] = "MiniSofteware", 345 | ["CreateDate"] = new DateTime(2021, 01, 01), 346 | ["VIP"] = true, 347 | ["Points"] = 123, 348 | ["APP"] = "Demo APP", 349 | }; 350 | MiniWord.SaveAsByTemplate(path, templatePath, value); 351 | ``` 352 | 353 | ## 例子 354 | 355 | 356 | 357 | #### ASP.NET Core 3.1 API Export 358 | 359 | ``` 360 | using Microsoft.AspNetCore.Builder; 361 | using Microsoft.AspNetCore.Hosting; 362 | using Microsoft.AspNetCore.Mvc; 363 | using Microsoft.Extensions.DependencyInjection; 364 | using Microsoft.Extensions.Hosting; 365 | using System; 366 | using System.Collections.Generic; 367 | using System.IO; 368 | using System.Net; 369 | using MiniSoftware; 370 | 371 | public class Program 372 | { 373 | public static void Main(string[] args) => CreateHostBuilder(args).Build().Run(); 374 | 375 | public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); 376 | } 377 | 378 | public class Startup 379 | { 380 | public void ConfigureServices(IServiceCollection services) => services.AddMvc(); 381 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 382 | { 383 | app.UseStaticFiles(); 384 | app.UseRouting(); 385 | app.UseEndpoints(endpoints => 386 | { 387 | endpoints.MapControllerRoute( 388 | name: "default", 389 | pattern: "{controller=api}/{action=Index}/{id?}"); 390 | }); 391 | } 392 | } 393 | 394 | public class ApiController : Controller 395 | { 396 | public IActionResult Index() 397 | { 398 | return new ContentResult 399 | { 400 | ContentType = "text/html", 401 | StatusCode = (int)HttpStatusCode.OK, 402 | Content = @" 403 | DownloadWordFromTemplatePath
404 | DownloadWordFromTemplateBytes
405 | " 406 | }; 407 | } 408 | 409 | static Dictionary defaultValue = new Dictionary() 410 | { 411 | ["title"] = "FooCompany", 412 | ["managers"] = new List> { 413 | new Dictionary{{"name","Jack"},{ "department", "HR" } }, 414 | new Dictionary {{ "name", "Loan"},{ "department", "IT" } } 415 | }, 416 | ["employees"] = new List> { 417 | new Dictionary{{ "name", "Wade" },{ "department", "HR" } }, 418 | new Dictionary {{ "name", "Felix" },{ "department", "HR" } }, 419 | new Dictionary{{ "name", "Eric" },{ "department", "IT" } }, 420 | new Dictionary {{ "name", "Keaton" },{ "department", "IT" } } 421 | } 422 | }; 423 | 424 | public IActionResult DownloadWordFromTemplatePath() 425 | { 426 | string templatePath = "TestTemplateComplex.docx"; 427 | 428 | Dictionary value = defaultValue; 429 | 430 | MemoryStream memoryStream = new MemoryStream(); 431 | MiniWord.SaveAsByTemplate(memoryStream, templatePath, value); 432 | memoryStream.Seek(0, SeekOrigin.Begin); 433 | return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.wordprocessingml.document") 434 | { 435 | FileDownloadName = "demo.docx" 436 | }; 437 | } 438 | 439 | private static Dictionary TemplateBytesCache = new Dictionary(); 440 | 441 | static ApiController() 442 | { 443 | string templatePath = "TestTemplateComplex.docx"; 444 | byte[] bytes = System.IO.File.ReadAllBytes(templatePath); 445 | TemplateBytesCache.Add(templatePath, bytes); 446 | } 447 | 448 | public IActionResult DownloadWordFromTemplateBytes() 449 | { 450 | byte[] bytes = TemplateBytesCache["TestTemplateComplex.docx"]; 451 | 452 | Dictionary value = defaultValue; 453 | 454 | MemoryStream memoryStream = new MemoryStream(); 455 | MiniWord.SaveAsByTemplate(memoryStream, bytes, value); 456 | memoryStream.Seek(0, SeekOrigin.Begin); 457 | return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.wordprocessingml.document") 458 | { 459 | FileDownloadName = "demo.docx" 460 | }; 461 | } 462 | } 463 | ``` 464 | 465 | 466 | 467 | 468 | 469 | ## 支持 : [Donate Link](https://miniexcel.github.io/) 470 | 471 | wechat 472 | 473 | 474 | alipay 475 | 476 | 477 | 478 | -------------------------------------------------------------------------------- /release-note/README.md: -------------------------------------------------------------------------------- 1 | ## Release Notes 2 | 3 |
4 |

NuGet 5 | GitHub stars 6 | version 7 |

8 |
9 | 10 | 11 | --- 12 | 13 | 16 | 17 | --- 18 | 19 |
20 | Your Star and Donate can make MiniWord better 21 |
22 | 23 | --- 24 | 25 | ### 0.9.1-0.9.2 26 | - [Bug] Fix async (via @isdaniel) 27 | - [Bug] Fix openxml version 28 | 29 | ### 0.9.0 30 | - [New] Support async (@isdaniel) 31 | - [New] Support new OpenXml to solve the problem of line wrapping to multiple lines #68 (via @ping9719) 32 | - [New] Add support to conditional check for undefined tags #75 (via @bprucha) 33 | - [New] Fix when a Split Tag Text element Inner Text start with " {" instead of "{" (via @hieplenet) 34 | - [New] Extension: floating image, you can configure the image to float on the text (via @dessli) 35 | - [New] Extension: Table supports Obj.objA.List.Prop1 rendering (via wangx036) 36 | - [New] Extension: Common types support multi-level attribute rendering, such as {{Obj.A.B.C}} (via wangx036) 37 | - [New] @foreach (via wangx036) 38 | - [Bug] Fixed the problem that fonts with color, underline and other styles are normal in office, but not visible after opening with WPS (via @haozekang) 39 | - [Bug] Remove all elements between @if~@endif, not paragraph (via wangx036) 40 | - [Bug] Fix build, Fix tests, Update openxml (via @masterworgen) 41 | 42 | ### 0.8.0 43 | 44 | - [New] Support new OpenXml to solve the problem of line wrapping to multiple lines #68 (via @ping9719) 45 | 46 | - [New] Support if statement inside foreach statement inside templates. Please refer to samples. (via @eynarhaji) 47 | - [New] Change tags for if statements for single paragraph if statement {{if and endif}} inside templates. Please refer to samples. (via @eynarhaji) 48 | - [Bug] The table should be inserted at the template tag position instead of the last row #47 (via @itldg) 49 | 50 | ### 0.7.0 51 | - [New] Add support to List inside List via `IEnumerable` and `{{foreach`/`endforeach}}` tags (via @eynarhaji) 52 | - [New] Add support to @if statements inside templates (via @eynarhaji) 53 | - [New] Support multiple color word by word (via @andy505050) 54 | 55 | ### 0.6.1 56 | - [Bug] Fixed system does not support `IEnumerable` (#39 via @isdaniel) 57 | 58 | ### 0.6.0 59 | - [New] Support hyperLink (#33 via @isdaniel) 60 | - [New] Support custom font color and highlight color (#35 via impPDX) 61 | - [New] Support 2 level object parameter (#32 via @ping9719 , @shps951023) 62 | - [Bug] Fix Multiple tags format error (#37 via @shps951023) 63 | 64 | ### 0.5.0 65 | - [New] support object & dynamic parameter (#19 via @isdaniel ) 66 | 67 | 68 | ### 0.4.0 69 | - [New] support HeaderParts, FooterParts template 70 | - [Bug] fixed multiple table generate problem #18 71 | 72 | ### 0.3.0 73 | - [New] Support table generate #13 74 | - [New] datetime format -> yyyy-MM-dd HH:mm:ss 75 | - [Bug] fixed spliting template string like `{{Tag}}` problem #17 76 | 77 | 78 | ### 0.2.1 79 | 80 | - [Bug] fixed mutiple tag System.InvalidOperationException: 'The parent of this element is null.' #13 81 | 82 | 83 | 84 | ### 0.2.0 85 | 86 | - [Feature] support array list string to generate multiple row #11 87 | - [Feature] support image #10 #3 88 | - [Feature] image support to custom width and height #8 89 | - [Feature] support multiple breakline 90 | - [Optimize] Remove xmlns declaration #7 91 | 92 | ### 0.1.1 93 | 94 | - [Bug] Fix Fuzzy Regex replace similar key 95 | 96 | ### 0.1.0 97 | - [Feature] basic template export 98 | 99 | ### 0.0.0 100 | - Init -------------------------------------------------------------------------------- /release-note/README.zh-CN.md: -------------------------------------------------------------------------------- 1 | ## Release Notes 2 | 3 |
4 |

NuGet 5 | GitHub stars 6 | version 7 |

8 |
9 | 10 | 11 | --- 12 | 13 | 16 | 17 | --- 18 | 19 |
20 | Your Star and Donate can make MiniWord better 21 |
22 | 23 | --- 24 | 25 | 26 | 27 | ### 0.9.1-0.9.2 28 | - [Bug] Fix async (via @isdaniel) 29 | - [Bug] Fix openxml version 30 | 31 | ### 0.9.0 32 | - [New] Support async (@isdaniel) 33 | - [New] Support new OpenXml to solve the problem of line wrapping to multiple lines #68 (via @ping9719) 34 | - [New] Add support to conditional check for undefined tags #75 (via @bprucha) 35 | - [New] Fix when a Split Tag Text element Inner Text start with " {" instead of "{" (via @hieplenet) 36 | - [New] Extension: floating image, you can configure the image to float on the text (via @dessli) 37 | - [New] Extension: Table supports Obj.objA.List.Prop1 rendering (via wangx036) 38 | - [New] Extension: Common types support multi-level attribute rendering, such as {{Obj.A.B.C}} (via wangx036) 39 | - [New] @foreach (via wangx036) 40 | - [Bug] Fixed the problem that fonts with color, underline and other styles are normal in office, but not visible after opening with WPS (via @haozekang) 41 | - [Bug] Remove all elements between @if~@endif, not paragraph (via wangx036) 42 | - [Bug] Fix build, Fix tests, Update openxml (via @masterworgen) 43 | 44 | ### 0.8.0 45 | 46 | - [New] 支持 new OpenXml to solve the problem of line wrapping to multiple lines #68 (via @ping9719) 47 | - [New] 支持 to if statement inside foreach statement inside templates. Please refer to samples. (via @eynarhaji) 48 | - [New] 变更 tags for if statements for single paragraph if statement {{if and endif}} inside templates. Please refer to samples. (via @eynarhaji) 49 | - [Bug] The table should be inserted at the template tag position instead of the last row #47 (via @itldg) 50 | 51 | ### 0.7.0 52 | - [New] 支持 List inside List via `IEnumerable` and `{{foreach`/`endforeach}}` tags (via @eynarhaji) 53 | - [New] 支持 @if statements inside templates (via @eynarhaji) 54 | - [New] 支持 multiple color word by word (via @andy505050) 55 | 56 | ### 0.6.1 57 | - [Bug] 修正系统不支持 `IEnumerable` (#39 via @isdaniel) 58 | 59 | ### 0.6.0 60 | - [New] 支持 hyperLink (#33 via @isdaniel) 61 | - [New] 支持 custom font color and highlight color (#35 via impPDX) 62 | - [New] 支持 2 level object parameter (#32 via @ping9719 , @shps951023) 63 | - [Bug] 修正 Multiple tags format error (#37 via @shps951023) 64 | 65 | ### 0.5.0 66 | 67 | - [New] 支持 object & dynamic parameter (#19 via @isdaniel ) 68 | 69 | ### 0.4.0 70 | - [New] 支持HeaderParts, FooterParts template 71 | - [Bug] 修正multiple table generate problem #18 72 | 73 | ### 0.3.0 74 | - [New] 支持 table 标签 #13 75 | - [New] datetime format -> yyyy-MM-dd HH:mm:ss 76 | - [Bug] fixed spliting template string like `{{Tag}}` problem #17 77 | 78 | ### 0.2.1 79 | 80 | - [Bug] fixed mutiple tag System.InvalidOperationException: 'The parent of this element is null.' #13 81 | 82 | ### 0.2.0 83 | 84 | - [Feature] 支持 array list string 生成多行 #11 85 | - [Feature] 支持图片 #10 #3 86 | - [Feature] 图片支持自定义 width 和 height #8 87 | - [Feature] 支持多 breakline 88 | - [Optimize] 删除 xmlns declaration #7 89 | 90 | ### 0.1.1 91 | - [Bug] 修正 Fuzzy Regex replace similar key 92 | 93 | 94 | ### 0.1.0 95 | - 基本 template 导出 96 | 97 | ### 0.0.0 98 | - Init -------------------------------------------------------------------------------- /release-note/README.zh-Hant.md: -------------------------------------------------------------------------------- 1 | ## Release Notes 2 | 3 |
4 |

NuGet 5 | GitHub stars 6 | version 7 |

8 |
9 | 10 | 11 | --- 12 | 13 | 16 | 17 | --- 18 | 19 |
20 | Your Star and Donate can make MiniWord better 21 |
22 | 23 | --- 24 | 25 | ### 0.9.1-0.9.2 26 | - [Bug] Fix async (via @isdaniel) 27 | - [Bug] Fix openxml version 28 | 29 | ### 0.9.0 30 | - [New] Support async (@isdaniel) 31 | - [New] Support new OpenXml to solve the problem of line wrapping to multiple lines #68 (via @ping9719) 32 | - [New] Add support to conditional check for undefined tags #75 (via @bprucha) 33 | - [New] Fix when a Split Tag Text element Inner Text start with " {" instead of "{" (via @hieplenet) 34 | - [New] Extension: floating image, you can configure the image to float on the text (via @dessli) 35 | - [New] Extension: Table supports Obj.objA.List.Prop1 rendering (via wangx036) 36 | - [New] Extension: Common types support multi-level attribute rendering, such as {{Obj.A.B.C}} (via wangx036) 37 | - [New] @foreach (via wangx036) 38 | - [Bug] Fixed the problem that fonts with color, underline and other styles are normal in office, but not visible after opening with WPS (via @haozekang) 39 | - [Bug] Remove all elements between @if~@endif, not paragraph (via wangx036) 40 | - [Bug] Fix build, Fix tests, Update openxml (via @masterworgen) 41 | 42 | 43 | ### 0.8.0 44 | 45 | - [New] 支持 new OpenXml to solve the problem of line wrapping to multiple lines #68 (via @ping9719) 46 | - [New] 支持 to if statement inside foreach statement inside templates. Please refer to samples. (via @eynarhaji) 47 | - [New] 变更 tags for if statements for single paragraph if statement {{if and endif}} inside templates. Please refer to samples. (via @eynarhaji) 48 | - [Bug] The table should be inserted at the template tag position instead of the last row #47 (via @itldg) 49 | 50 | ### 0.7.0 51 | - [New] 支持 List inside List via `IEnumerable` and `{{foreach`/`endforeach}}` tags (via @eynarhaji) 52 | - [New] 支持 @if statements inside templates (via @eynarhaji) 53 | - [New] 支持 multiple color word by word (via @andy505050) 54 | 55 | ### 0.6.1 56 | - [Bug] 修正系統不支持 `IEnumerable` (#39 via @isdaniel) 57 | 58 | ### 0.6.0 59 | - [New] 支持 hyperLink (#33 via @isdaniel) 60 | - [New] 支持 custom font color and highlight color (#35 via impPDX) 61 | - [New] 支持 2 level object parameter (#32 via @ping9719 , @shps951023) 62 | - [Bug] 修正 Multiple tags format error (#37 via @shps951023) 63 | 64 | 65 | ### 0.5.0 66 | 67 | - [New] 支持 object & dynamic parameter (#19 via @isdaniel ) 68 | 69 | ### 0.4.0 70 | 71 | - [New] 支持HeaderParts, FooterParts template 72 | - [Bug] 修正multiple table generate problem #18 73 | 74 | ### 0.3.0 75 | 76 | - [New] 支持 table 标签 #13 77 | 78 | - [New] datetime format -> yyyy-MM-dd HH:mm:ss 79 | - [Bug] fixed spliting template string like `{{Tag}}` problem #17 80 | 81 | ### 0.2.1 82 | 83 | - [Bug] fixed mutiple tag System.InvalidOperationException: 'The parent of this element is null.' #13 84 | 85 | ### 0.2.0 86 | - [Feature] 支持 array list string 生成多行 #11 87 | - [Feature] 支持圖片 #10 #3 88 | - [Feature] 圖片支持自定義 width 和 height #8 89 | - [Feature] 支持多 breakline 90 | - [Optimize] 刪除 xmlns declaration #7 91 | 92 | ### 0.1.1 93 | - [Bug] 修正 Fuzzy Regex replace similar key 94 | 95 | 96 | ### 0.1.0 97 | - 基本 template 導出 98 | 99 | ### 0.0.0 100 | - Init -------------------------------------------------------------------------------- /samples/docx/DemoExpenseMeeting01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/DemoExpenseMeeting01.png -------------------------------------------------------------------------------- /samples/docx/DemoExpenseMeeting02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/DemoExpenseMeeting02.png -------------------------------------------------------------------------------- /samples/docx/DemoLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/DemoLogo.png -------------------------------------------------------------------------------- /samples/docx/TestBasicFill.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/TestBasicFill.docx -------------------------------------------------------------------------------- /samples/docx/TestBasicFill/[Content_Types].xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /samples/docx/TestBasicFill/_rels/.rels: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /samples/docx/TestBasicFill/docProps/app.xml: -------------------------------------------------------------------------------- 1 | 2 | 799122129Microsoft Office Word011falsefalse150falsefalse16.0000 -------------------------------------------------------------------------------- /samples/docx/TestBasicFill/docProps/core.xml: -------------------------------------------------------------------------------- 1 | 2 | Lin WeiHanLin WeiHan32022-09-11T15:14:00Z2022-09-12T08:45:00Z -------------------------------------------------------------------------------- /samples/docx/TestBasicFill/word/_rels/document.xml.rels: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /samples/docx/TestBasicFill/word/document.xml: -------------------------------------------------------------------------------- 1 | 2 | {{Company_Name}} {{APP}} Account DataNameCreateDateVIPPointsRemark{{Name}}{{CreateDate}}{{VIP}}{{Points}}{{Name}} has {{Points}} points -------------------------------------------------------------------------------- /samples/docx/TestBasicFill/word/fontTable.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /samples/docx/TestBasicFill/word/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /samples/docx/TestBasicFill/word/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /samples/docx/TestBasicFill/word/theme/theme1.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /samples/docx/TestBasicFill/word/webSettings.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /samples/docx/TestBasicImage.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/TestBasicImage.docx -------------------------------------------------------------------------------- /samples/docx/TestBasicImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/TestBasicImage.png -------------------------------------------------------------------------------- /samples/docx/TestDemo01.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/TestDemo01.docx -------------------------------------------------------------------------------- /samples/docx/TestDemo02.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/TestDemo02.docx -------------------------------------------------------------------------------- /samples/docx/TestDemo03.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/TestDemo03.docx -------------------------------------------------------------------------------- /samples/docx/TestDemo04.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/TestDemo04.docx -------------------------------------------------------------------------------- /samples/docx/TestExpenseDemo.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/TestExpenseDemo.docx -------------------------------------------------------------------------------- /samples/docx/TestForeachInTablesDemo.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/TestForeachInTablesDemo.docx -------------------------------------------------------------------------------- /samples/docx/TestForeachInTablesWithIfStatementDemo.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/TestForeachInTablesWithIfStatementDemo.docx -------------------------------------------------------------------------------- /samples/docx/TestIfStatement.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/TestIfStatement.docx -------------------------------------------------------------------------------- /samples/docx/TestIssue11.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/TestIssue11.docx -------------------------------------------------------------------------------- /samples/docx/TestIssue17.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/TestIssue17.docx -------------------------------------------------------------------------------- /samples/docx/TestIssue18.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/TestIssue18.docx -------------------------------------------------------------------------------- /samples/docx/TestIssue37.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/TestIssue37.docx -------------------------------------------------------------------------------- /samples/docx/TestIssue43.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/TestIssue43.docx -------------------------------------------------------------------------------- /samples/docx/TestIssue47.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/TestIssue47.docx -------------------------------------------------------------------------------- /samples/docx/demo01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/samples/docx/demo01.png -------------------------------------------------------------------------------- /src/MiniWord/Common/Enums/Extension.cs: -------------------------------------------------------------------------------- 1 | namespace MiniSoftware.Common.Enums 2 | { 3 | public enum Extension 4 | { 5 | Bmp, 6 | Emf, 7 | Icon, 8 | Jpeg, 9 | Pcx, 10 | Png, 11 | Svg, 12 | Tiff, 13 | Wmf 14 | } 15 | } -------------------------------------------------------------------------------- /src/MiniWord/Extensions/ObjectExtension.cs: -------------------------------------------------------------------------------- 1 | namespace MiniSoftware.Extensions 2 | { 3 | using System; 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.ComponentModel; 7 | using System.Dynamic; 8 | 9 | internal static class ObjectExtension 10 | { 11 | internal static Dictionary ToDictionary(this object value) 12 | { 13 | if (value == null) 14 | return new Dictionary(); 15 | else if (value is Dictionary dicStr) 16 | return dicStr; 17 | else if (value is ExpandoObject) 18 | return new Dictionary(value as ExpandoObject); 19 | 20 | if (IsStrongTypeEnumerable(value)) 21 | throw new Exception("The parameter cannot be a collection type"); 22 | else 23 | { 24 | Dictionary reuslt = new Dictionary(); 25 | PropertyDescriptorCollection props = TypeDescriptor.GetProperties(value); 26 | foreach (PropertyDescriptor prop in props) 27 | { 28 | object val1 = prop.GetValue(value); 29 | 30 | if (IsStrongTypeEnumerable(val1)) 31 | { 32 | var isValueOrStringType = false; 33 | List> sx = new List>(); 34 | foreach (object val1item in (IEnumerable)val1) 35 | { 36 | if (val1item == null) 37 | { 38 | sx.Add(new Dictionary()); 39 | continue; 40 | } 41 | // not custom type 42 | if (val1item is string || val1item is DateTime || value.GetType().IsValueType) 43 | { 44 | isValueOrStringType = true; 45 | reuslt.Add(prop.Name, val1); 46 | break; 47 | } 48 | if (val1item is Dictionary dicStr) 49 | { 50 | sx.Add(dicStr); 51 | continue; 52 | } 53 | else if (val1item is ExpandoObject) 54 | { 55 | sx.Add(new Dictionary(value as ExpandoObject)); 56 | continue; 57 | } 58 | 59 | PropertyDescriptorCollection props2 = TypeDescriptor.GetProperties(val1item); 60 | Dictionary reuslt2 = new Dictionary(); 61 | foreach (PropertyDescriptor prop2 in props2) 62 | { 63 | object val2 = prop2.GetValue(val1item); 64 | reuslt2.Add(prop2.Name, val2); 65 | } 66 | sx.Add(reuslt2); 67 | } 68 | if (!isValueOrStringType) 69 | reuslt.Add(prop.Name, sx); 70 | } 71 | else 72 | { 73 | reuslt.Add(prop.Name, val1); 74 | } 75 | } 76 | return reuslt; 77 | } 78 | } 79 | internal static bool IsStrongTypeEnumerable(this object obj) 80 | { 81 | return obj is IEnumerable && !(obj is string) && !(obj is char[]) && !(obj is string[]) && !(obj is MiniWordColorText[]); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/MiniWord/Extensions/OpenXmlExtension.cs: -------------------------------------------------------------------------------- 1 | namespace MiniSoftware.Extensions 2 | { 3 | using DocumentFormat.OpenXml.Wordprocessing; 4 | using System; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | using System.ComponentModel; 8 | using System.Dynamic; 9 | using System.Linq; 10 | using System.Text; 11 | 12 | internal static class OpenXmlExtension 13 | { 14 | /// 15 | /// 高级搜索:得到段落里面的连续字符串 16 | /// 17 | /// 段落 18 | /// Item1:连续文本;Item2:块;Item3:块文本 19 | internal static List, List>> GetContinuousString(this Paragraph paragraph) 20 | { 21 | List, List>> tuples = new List, List>>(); 22 | if (paragraph == null) 23 | return tuples; 24 | 25 | var sb = new StringBuilder(); 26 | var runs = new List(); 27 | var texts = new List(); 28 | 29 | //段落:所有子级 30 | foreach (var pChildElement in paragraph.ChildElements) 31 | { 32 | //块 33 | if (pChildElement is Run run) 34 | { 35 | //文本块 36 | if (run.IsText()) 37 | { 38 | var text = run.GetFirstChild(); 39 | runs.Add(run); 40 | texts.Add(text); 41 | sb.Append(text.Text); 42 | } 43 | else 44 | { 45 | if (runs.Any()) 46 | tuples.Add(new Tuple, List>(sb.ToString(), runs, texts)); 47 | 48 | sb = new StringBuilder(); 49 | runs = new List(); 50 | texts = new List(); 51 | } 52 | } 53 | //公式,书签... 54 | else 55 | { 56 | //跳过的类型 57 | if (pChildElement is BookmarkStart || pChildElement is BookmarkEnd) 58 | { 59 | 60 | } 61 | else 62 | { 63 | if (runs.Any()) 64 | tuples.Add(new Tuple, List>(sb.ToString(), runs, texts)); 65 | 66 | sb = new StringBuilder(); 67 | runs = new List(); 68 | texts = new List(); 69 | } 70 | } 71 | } 72 | 73 | if (runs.Any()) 74 | tuples.Add(new Tuple, List>(sb.ToString(), runs, texts)); 75 | 76 | sb = null; 77 | runs = null; 78 | texts = null; 79 | 80 | return tuples; 81 | } 82 | 83 | /// 84 | /// 整理字符串到连续字符串块中 85 | /// 86 | /// 连续字符串块 87 | /// 待整理字符串 88 | internal static void TrimStringToInContinuousString(this IEnumerable texts, string text) 89 | { 90 | /* 91 | //假如块为:[A][BC][DE][FG][H] 92 | //假如替换:[AB][E][GH] 93 | //优化块为:[AB][C][DE][FGH][] 94 | */ 95 | 96 | var allTxtx = string.Concat(texts.SelectMany(o => o.Text)); 97 | var indexState = allTxtx.IndexOf(text); 98 | if (indexState == -1) 99 | return; 100 | 101 | int indexEnd = indexState + text.Length - 1; 102 | List> yl = new List>(allTxtx.Length); 103 | int iRun = 0; 104 | int iIndex = 0; 105 | int iRunOf = -1; 106 | foreach (var item in texts) 107 | { 108 | foreach (var item2 in item.Text) 109 | { 110 | if (indexState <= iIndex && iIndex <= indexEnd) 111 | { 112 | if (iRunOf == -1) 113 | iRunOf = iRun; 114 | 115 | yl.Add(new Tuple(iRunOf, item2)); 116 | } 117 | else 118 | { 119 | yl.Add(new Tuple(iRun, item2)); 120 | } 121 | 122 | iIndex++; 123 | } 124 | iRun++; 125 | } 126 | 127 | int i = 0; 128 | foreach (var item in texts) 129 | { 130 | item.Text = string.Concat(yl.Where(o => o.Item1 == i).Select(o => o.Item2)); 131 | i++; 132 | } 133 | 134 | } 135 | 136 | 137 | internal static bool IsText(this Run run) 138 | { 139 | return run.Elements().All(o => o is Text || o is RunProperties); 140 | } 141 | } 142 | } -------------------------------------------------------------------------------- /src/MiniWord/Extensions/StringExtension.cs: -------------------------------------------------------------------------------- 1 | namespace MiniSoftware.Extensions 2 | { 3 | public static class StringExtension 4 | { 5 | public static string FirstCharacterToLower(this string str) 6 | { 7 | return string.IsNullOrWhiteSpace(str) ? str : char.ToLowerInvariant(str[0]) + str.Substring(1); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /src/MiniWord/MiniWord.cs: -------------------------------------------------------------------------------- 1 | namespace MiniSoftware 2 | { 3 | using DocumentFormat.OpenXml.Office2013.Excel; 4 | using MiniSoftware.Extensions; 5 | using MiniSoftware.Utility; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.ComponentModel; 9 | using System.Dynamic; 10 | using System.IO; 11 | using System.Linq.Expressions; 12 | using System.Threading; 13 | using System.Threading.Tasks; 14 | 15 | public static partial class MiniWord 16 | { 17 | public static void SaveAsByTemplate(string path, string templatePath, object value) 18 | { 19 | using (var stream = File.Create(path)) 20 | SaveAsByTemplate(stream, templatePath, value); 21 | } 22 | 23 | public static void SaveAsByTemplate(string path, byte[] templateBytes, object value) 24 | { 25 | using (var stream = File.Create(path)) 26 | SaveAsByTemplate(stream, templateBytes, value); 27 | } 28 | 29 | public static void SaveAsByTemplate(this Stream stream, string templatePath, object value) 30 | { 31 | SaveAsByTemplateImpl(stream, GetBytes(templatePath), value.ToDictionary()); 32 | } 33 | 34 | public static void SaveAsByTemplate(this Stream stream, byte[] templateBytes, object value) 35 | { 36 | SaveAsByTemplateImpl(stream, templateBytes, value.ToDictionary()); 37 | } 38 | 39 | public static async Task SaveAsByTemplateAsync(this Stream stream, byte[] templateBytes, object value,CancellationToken token = default(CancellationToken)) 40 | { 41 | await SaveAsByTemplateImplAsync(stream, templateBytes, value.ToDictionary(),token).ConfigureAwait(false); 42 | } 43 | 44 | public static async Task SaveAsByTemplateAsync(this Stream stream, string templatePath, object value,CancellationToken token = default(CancellationToken)) 45 | { 46 | await SaveAsByTemplateImplAsync(stream, await GetByteAsync(templatePath), value.ToDictionary(),token).ConfigureAwait(false); 47 | } 48 | 49 | public static async Task SaveAsByTemplateAsync(string path, string templatePath, object value,CancellationToken token = default(CancellationToken)) 50 | { 51 | using (var stream = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, true)) 52 | await SaveAsByTemplateImplAsync(stream, await GetByteAsync(templatePath), value.ToDictionary(),token); 53 | } 54 | 55 | public static async Task SaveAsByTemplateAsync(string path, byte[] templateBytes, object value,CancellationToken token = default(CancellationToken)) 56 | { 57 | using (var stream = new FileStream(path, FileMode.Create, FileAccess.ReadWrite, FileShare.None, 4096, true)) 58 | await SaveAsByTemplateImplAsync(stream, templateBytes, value.ToDictionary(),token); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/MiniWord/MiniWord.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net45;netstandard2.0; 4 | 0.9.1 5 | 6 | 7 | MiniWord 8 | MiniWord 9 | MiniWord 10 | word;template;micro-helper;mini;openxml;helper; 11 | .NET Word(docx) exporting template engine without COM+ and interop (support Linux and Mac) 12 | 13 | mini-software, Wei Lin, eynarhaji 14 | MiniWord 15 | mini-software, Wei Lin, 2022 onwards 16 | en 17 | https://raw.githubusercontent.com/mini-software/MiniWord/main/LICENSE 18 | MiniSoftware 19 | Apache-2.0 20 | https://github.com/mini-software/MiniWord 21 | https://github.com/mini-software/MiniWord 22 | icon.png 23 | Please Check [Release Notes](https://github.com/mini-software/MiniWord/tree/main/release-note) 24 | Github 25 | 26 | 27 | 28 | PreserveNewest 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /src/MiniWord/MiniWordColorText.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MiniSoftware 8 | { 9 | public class MiniWordColorText 10 | { 11 | public string FontColor { get; set; } 12 | public string Text { get; set; } 13 | public string HighlightColor { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/MiniWord/MiniWordForeach.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MiniSoftware 8 | { 9 | public class MiniWordForeach 10 | { 11 | public Dictionary Value { get; set; } 12 | public string Separator { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/MiniWord/MiniWordHyperLink.cs: -------------------------------------------------------------------------------- 1 | namespace MiniSoftware 2 | { 3 | using DocumentFormat.OpenXml.Wordprocessing; 4 | using MiniSoftware.Utility; 5 | 6 | public class MiniWordHyperLink 7 | { 8 | public string Url { get; set; } 9 | 10 | public string Text { get; set; } 11 | 12 | public UnderlineValues UnderLineValue { get; set; } = UnderlineValues.Single; 13 | 14 | public TargetFrameType TargetFrame { get; set; } = TargetFrameType.Blank; 15 | 16 | internal string GetTargetFrame() 17 | { 18 | 19 | switch (TargetFrame) 20 | { 21 | case TargetFrameType.Blank: 22 | return "_blank"; 23 | case TargetFrameType.Top: 24 | return "_top"; 25 | case TargetFrameType.Self: 26 | return "_self"; 27 | case TargetFrameType.Parent: 28 | return "_parent"; 29 | } 30 | 31 | return "_blank"; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/MiniWord/MiniWordPicture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using DocumentFormat.OpenXml; 3 | using DocumentFormat.OpenXml.Packaging; 4 | using MiniSoftware.Common.Enums; 5 | using MiniSoftware.Extensions; 6 | 7 | namespace MiniSoftware 8 | { 9 | public enum MiniWordPictureWrappingType 10 | { 11 | Inline, 12 | Anchor 13 | } 14 | 15 | public class MiniWordPicture 16 | { 17 | public MiniWordPicture(string path, long width = 400, long height = 400) 18 | { 19 | Path = path; 20 | Width = width; 21 | Height= height; 22 | } 23 | 24 | public MiniWordPicture(byte[] bytes, Extension extension, long width = 400, long height = 400) 25 | { 26 | Bytes = bytes; 27 | Extension = extension.ToString().FirstCharacterToLower(); 28 | 29 | Width = width; 30 | Height = height; 31 | } 32 | 33 | private string _extension; 34 | 35 | public MiniWordPictureWrappingType WrappingType { get; set; } = MiniWordPictureWrappingType.Inline; 36 | 37 | public bool BehindDoc { get; set; } = false; 38 | public bool AllowOverlap { get; set; } = false; 39 | public long HorizontalPositionOffset { get; set; } = 0; 40 | public long VerticalPositionOffset { get; set; } = 0; 41 | 42 | public string Path { get; set; } 43 | 44 | public string Extension 45 | { 46 | get 47 | { 48 | if (!string.IsNullOrWhiteSpace(_extension)) 49 | return _extension; 50 | 51 | if (Path != null) 52 | return System.IO.Path.GetExtension(Path).ToUpperInvariant().Replace(".", ""); 53 | 54 | return string.Empty; 55 | } 56 | set => _extension = value; 57 | } 58 | 59 | internal PartTypeInfo GetImagePartType 60 | { 61 | get 62 | { 63 | switch (Extension.ToLower()) 64 | { 65 | case "bmp": return ImagePartType.Bmp; 66 | case "emf": return ImagePartType.Emf; 67 | case "ico": return ImagePartType.Icon; 68 | case "jpg": 69 | case "jpeg": 70 | return ImagePartType.Jpeg; 71 | case "pcx": return ImagePartType.Pcx; 72 | case "png": return ImagePartType.Png; 73 | case "svg": return ImagePartType.Svg; 74 | case "tiff": return ImagePartType.Tiff; 75 | case "wmf": return ImagePartType.Wmf; 76 | default: 77 | throw new NotSupportedException($"{_extension} is not supported"); 78 | } 79 | } 80 | } 81 | 82 | public byte[] Bytes { get; set; } 83 | 84 | /// 85 | /// Unit is Pixel 86 | /// 87 | public Int64Value Width { get; set; } 88 | 89 | internal Int64Value Cx => Width * 9525; 90 | 91 | /// 92 | /// Unit is Pixel 93 | /// 94 | public Int64Value Height { get; set; } 95 | 96 | //format resource from http://openxmltrix.blogspot.com/2011/04/updating-images-in-image-placeholde-and.html 97 | internal Int64Value Cy => Height * 9525; 98 | } 99 | } -------------------------------------------------------------------------------- /src/MiniWord/Utility/Helpers.cs: -------------------------------------------------------------------------------- 1 | namespace MiniSoftware.Utility 2 | { 3 | using System.Collections.Generic; 4 | using System.ComponentModel; 5 | using System.Dynamic; 6 | using System.IO; 7 | using System.Threading.Tasks; 8 | 9 | internal static partial class Helpers 10 | { 11 | public static FileStream OpenSharedRead(string path) => File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); 12 | } 13 | } -------------------------------------------------------------------------------- /src/MiniWord/Utility/TargetFrameType.cs: -------------------------------------------------------------------------------- 1 | namespace MiniSoftware.Utility 2 | { 3 | public enum TargetFrameType 4 | { 5 | Blank, 6 | Self, 7 | Parent, 8 | Top 9 | } 10 | } -------------------------------------------------------------------------------- /src/MiniWord/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/src/MiniWord/icon.png -------------------------------------------------------------------------------- /tests/AspNetCoreDemo/AspNetCoreDemo.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32328.378 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetCoreDemo", "AspNetCoreDemo\AspNetCoreDemo.csproj", "{81AA0AC7-2A3D-4F12-8CFA-811DEA75FBEF}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniWord", "..\..\src\MiniWord\MiniWord.csproj", "{B64788A9-F3C6-46D0-A6E4-53D94538C264}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {81AA0AC7-2A3D-4F12-8CFA-811DEA75FBEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {81AA0AC7-2A3D-4F12-8CFA-811DEA75FBEF}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {81AA0AC7-2A3D-4F12-8CFA-811DEA75FBEF}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {81AA0AC7-2A3D-4F12-8CFA-811DEA75FBEF}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {B64788A9-F3C6-46D0-A6E4-53D94538C264}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {B64788A9-F3C6-46D0-A6E4-53D94538C264}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {B64788A9-F3C6-46D0-A6E4-53D94538C264}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {B64788A9-F3C6-46D0-A6E4-53D94538C264}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {A843B90B-631D-4D7E-9646-6797EB549F68} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /tests/AspNetCoreDemo/AspNetCoreDemo/AspNetCoreDemo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | PreserveNewest 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /tests/AspNetCoreDemo/AspNetCoreDemo/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.IO; 9 | using System.Net; 10 | using MiniSoftware; 11 | 12 | public class Program 13 | { 14 | public static void Main(string[] args) => CreateHostBuilder(args).Build().Run(); 15 | 16 | public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup()); 17 | } 18 | 19 | public class Startup 20 | { 21 | public void ConfigureServices(IServiceCollection services) => services.AddMvc(); 22 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 23 | { 24 | app.UseStaticFiles(); 25 | app.UseRouting(); 26 | app.UseEndpoints(endpoints => 27 | { 28 | endpoints.MapControllerRoute( 29 | name: "default", 30 | pattern: "{controller=api}/{action=Index}/{id?}"); 31 | }); 32 | } 33 | } 34 | 35 | public class ApiController : Controller 36 | { 37 | public IActionResult Index() 38 | { 39 | return new ContentResult 40 | { 41 | ContentType = "text/html", 42 | StatusCode = (int)HttpStatusCode.OK, 43 | Content = @" 44 | DownloadWordFromTemplatePath
45 | DownloadWordFromTemplateBytes
46 | DownloadWordFromTemplatePathByObjectType
47 | DownloadWordFromTemplatePathWithHyperLink
48 | " 49 | }; 50 | } 51 | 52 | static Dictionary defaultValue = new Dictionary() 53 | { 54 | ["title"] = "FooCompany", 55 | ["managers"] = new List> { 56 | new Dictionary{{"name","Jack"},{ "department", "HR" } }, 57 | new Dictionary {{ "name", "Loan"},{ "department", "IT" } } 58 | }, 59 | ["employees"] = new List> { 60 | new Dictionary{{ "name", "Wade" },{ "department", "HR" } }, 61 | new Dictionary {{ "name", "Felix" },{ "department", "HR" } }, 62 | new Dictionary{{ "name", "Eric" },{ "department", "IT" } }, 63 | new Dictionary {{ "name", "Keaton" },{ "department", "IT" } } 64 | } 65 | }; 66 | 67 | static object objectValue = new 68 | { 69 | title = "FooCompany", 70 | managers = new List> { 71 | new Dictionary{{"name","Jack"},{ "department", "HR" } }, 72 | new Dictionary {{ "name", "Loan"},{ "department", "IT" } } 73 | }, 74 | employees = new List> { 75 | new Dictionary{{ "name", "Wade" },{ "department", "HR" } }, 76 | new Dictionary {{ "name", "Felix" },{ "department", "HR" } }, 77 | new Dictionary{{ "name", "Eric" },{ "department", "IT" } }, 78 | new Dictionary {{ "name", "Keaton" },{ "department", "IT" } } 79 | } 80 | }; 81 | 82 | static object hyperLinktValue = new 83 | { 84 | title = new MiniWorHyperLink() { 85 | TargetFrame = TargetFrameType.Self, 86 | Text = "This is a HyperLink!!", 87 | Url = "https://google.com" 88 | }, 89 | managers = new List> { 90 | new Dictionary{{"name","Jack"},{ "department", "HR" } }, 91 | new Dictionary {{ "name", "Loan"},{ "department", "IT" } } 92 | }, 93 | employees = new List> { 94 | new Dictionary{{ "name", "Wade" },{ "department", "HR" } }, 95 | new Dictionary {{ "name", "Felix" },{ "department", "HR" } }, 96 | new Dictionary{{ "name", "Eric" },{ "department", "IT" } }, 97 | new Dictionary {{ "name", "Keaton" },{ "department", "IT" } } 98 | } 99 | }; 100 | 101 | public IActionResult DownloadWordFromTemplatePath() 102 | { 103 | string templatePath = "TestTemplateComplex.docx"; 104 | 105 | Dictionary value = defaultValue; 106 | 107 | MemoryStream memoryStream = new MemoryStream(); 108 | MiniWord.SaveAsByTemplate(memoryStream, templatePath, value); 109 | memoryStream.Seek(0, SeekOrigin.Begin); 110 | return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.wordprocessingml.document") 111 | { 112 | FileDownloadName = "demo.docx" 113 | }; 114 | } 115 | 116 | public IActionResult DownloadWordFromTemplatePathByObjectType() 117 | { 118 | string templatePath = "TestTemplateComplex.docx"; 119 | 120 | MemoryStream memoryStream = new MemoryStream(); 121 | MiniWord.SaveAsByTemplate(memoryStream, templatePath, objectValue); 122 | memoryStream.Seek(0, SeekOrigin.Begin); 123 | return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.wordprocessingml.document") 124 | { 125 | FileDownloadName = "demo.docx" 126 | }; 127 | } 128 | 129 | public IActionResult DownloadWordFromTemplatePathWithHyperLink() 130 | { 131 | string templatePath = "TestTemplateComplex.docx"; 132 | 133 | MemoryStream memoryStream = new MemoryStream(); 134 | MiniWord.SaveAsByTemplate(memoryStream, templatePath, hyperLinktValue); 135 | memoryStream.Seek(0, SeekOrigin.Begin); 136 | return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.wordprocessingml.document") 137 | { 138 | FileDownloadName = "demo.docx" 139 | }; 140 | } 141 | 142 | private static Dictionary TemplateBytesCache = new Dictionary(); 143 | 144 | static ApiController() 145 | { 146 | string templatePath = "TestTemplateComplex.docx"; 147 | byte[] bytes = System.IO.File.ReadAllBytes(templatePath); 148 | TemplateBytesCache.Add(templatePath, bytes); 149 | } 150 | 151 | public IActionResult DownloadWordFromTemplateBytes() 152 | { 153 | byte[] bytes = TemplateBytesCache["TestTemplateComplex.docx"]; 154 | 155 | Dictionary value = defaultValue; 156 | 157 | MemoryStream memoryStream = new MemoryStream(); 158 | MiniWord.SaveAsByTemplate(memoryStream, bytes, value); 159 | memoryStream.Seek(0, SeekOrigin.Begin); 160 | return new FileStreamResult(memoryStream, "application/vnd.openxmlformats-officedocument.wordprocessingml.document") 161 | { 162 | FileDownloadName = "demo.docx" 163 | }; 164 | } 165 | 166 | } -------------------------------------------------------------------------------- /tests/AspNetCoreDemo/AspNetCoreDemo/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:29745", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "AspNetCoreDemo": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "applicationUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/AspNetCoreDemo/AspNetCoreDemo/TestTemplateComplex.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mini-software/MiniWord/0f47ffba9b19ec488f1c16092a1ea073871714e4/tests/AspNetCoreDemo/AspNetCoreDemo/TestTemplateComplex.docx -------------------------------------------------------------------------------- /tests/AspNetCoreDemo/AspNetCoreDemo/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/AspNetCoreDemo/AspNetCoreDemo/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /tests/MiniWordTests/Helpers.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using System.IO.Compression; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Xml; 6 | using System.Xml.Linq; 7 | 8 | namespace MiniWordTests 9 | { 10 | internal static class Helpers 11 | { 12 | internal static string GetZipFileContent(string zipPath, string filePath) 13 | { 14 | var ns = new XmlNamespaceManager(new NameTable()); 15 | ns.AddNamespace("x", "http://schemas.openxmlformats.org/spreadsheetml/2006/main"); 16 | using (var stream = File.OpenRead(zipPath)) 17 | using (ZipArchive archive = new ZipArchive(stream, ZipArchiveMode.Read, false, Encoding.UTF8)) 18 | { 19 | var entry = archive.Entries.Single(w => w.FullName == filePath); 20 | using (var sheetStream = entry.Open()) 21 | { 22 | var doc = XDocument.Load(sheetStream); 23 | return doc.ToString(); 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/MiniWordTests/MiniWordTestAsync.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using DocumentFormat.OpenXml.Office2010.ExcelAc; 5 | using MiniSoftware; 6 | using Xunit; 7 | 8 | namespace MiniWordTests 9 | { 10 | public class MiniWordTestAsync 11 | { 12 | [Fact] 13 | public async void TestForeachLoopInTablesAsync() 14 | { 15 | var path = PathHelper.GetTempFilePath(); 16 | var templatePath = PathHelper.GetFile("TestForeachInTablesDemo.docx"); 17 | var value = new Dictionary() 18 | { 19 | ["TripHs"] = new List> 20 | { 21 | new Dictionary 22 | { 23 | { "sDate", DateTime.Parse("2022-09-08 08:30:00") }, 24 | { "eDate", DateTime.Parse("2022-09-08 15:00:00") }, 25 | { "How", "Discussion requirement part1" }, 26 | { 27 | "Details", new List() 28 | { 29 | new MiniWordForeach() 30 | { 31 | Value = new Dictionary() 32 | { 33 | {"Text", "Air"}, 34 | {"Value", "Airplane"} 35 | }, 36 | Separator = " | " 37 | }, 38 | new MiniWordForeach() 39 | { 40 | Value = new Dictionary() 41 | { 42 | {"Text", "Parking"}, 43 | {"Value", "Car"} 44 | }, 45 | Separator = " / " 46 | }, 47 | new MiniWordForeach() 48 | { 49 | Value = new Dictionary() 50 | { 51 | {"Text", "Hotel"}, 52 | {"Value", "Room"} 53 | }, 54 | Separator = ", " 55 | }, 56 | new MiniWordForeach() 57 | { 58 | Value = new Dictionary() 59 | { 60 | {"Text", "Food"}, 61 | {"Value", "Plate"} 62 | }, 63 | Separator = "" 64 | } 65 | } 66 | } 67 | }, 68 | new Dictionary 69 | { 70 | { "sDate", DateTime.Parse("2022-09-09 08:30:00") }, 71 | { "eDate", DateTime.Parse("2022-09-09 17:00:00") }, 72 | { "How", "Discussion requirement part2 and development" }, 73 | { 74 | "Details", new List() 75 | { 76 | new MiniWordForeach() 77 | { 78 | Value = new Dictionary() 79 | { 80 | {"Text", "Air"}, 81 | {"Value", "Airplane"} 82 | }, 83 | Separator = " | " 84 | }, 85 | new MiniWordForeach() 86 | { 87 | Value = new Dictionary() 88 | { 89 | {"Text", "Parking"}, 90 | {"Value", "Car"} 91 | }, 92 | Separator = " / " 93 | }, 94 | new MiniWordForeach() 95 | { 96 | Value = new Dictionary() 97 | { 98 | {"Text", "Hotel"}, 99 | {"Value", "Room"} 100 | }, 101 | Separator = ", " 102 | }, 103 | new MiniWordForeach() 104 | { 105 | Value = new Dictionary() 106 | { 107 | {"Text", "Food"}, 108 | {"Value", "Plate"} 109 | }, 110 | Separator = "" 111 | } 112 | } 113 | } 114 | } 115 | } 116 | }; 117 | await MiniWord.SaveAsByTemplateAsync(path, templatePath, value); 118 | //System.Diagnostics.Process.Start("explorer.exe", path); 119 | var xml = Helpers.GetZipFileContent(path, "word/document.xml"); 120 | Assert.Contains(@"Discussion requirement part2 and development", xml); 121 | Assert.Contains(@"Discussion requirement part1", xml); 122 | Assert.Contains( 123 | "Air way to the Airplane | Parking way to the Car / Hotel way to the Room, Food way to the Plate", xml); 124 | } 125 | 126 | [Fact] 127 | public async void MiniWordIfStatement_FirstIfAsync() 128 | { 129 | var path = PathHelper.GetTempFilePath(); 130 | var templatePath = PathHelper.GetFile("TestIfStatement.docx"); 131 | var value = new Dictionary() 132 | { 133 | ["Name"] = new List(){ 134 | new MiniWordHyperLink(){ 135 | Url = "https://google.com", 136 | Text = "測試連結22!!" 137 | }, 138 | new MiniWordHyperLink(){ 139 | Url = "https://google1.com", 140 | Text = "測試連結11!!" 141 | } 142 | }, 143 | ["Company_Name"] = "MiniSofteware", 144 | ["CreateDate"] = new DateTime(2021, 01, 01), 145 | ["VIP"] = true, 146 | ["Points"] = 123, 147 | ["APP"] = "Demo APP", 148 | }; 149 | await MiniWord.SaveAsByTemplateAsync(path, templatePath, value); 150 | //Console.WriteLine(path); 151 | var docXml = Helpers.GetZipFileContent(path, "word/document.xml"); 152 | Assert.Contains("First if chosen: MiniSofteware", docXml); 153 | Assert.DoesNotContain("Second if chosen: MaxiSoftware", docXml); 154 | Assert.Contains("Points are greater than 100", docXml); 155 | Assert.Contains("CreateDate is not less than 2021", docXml); 156 | Assert.DoesNotContain("CreateDate is not greater than 2021", docXml); 157 | } 158 | 159 | [Fact] 160 | public async void TestForeachLoopInTablesWithIfStatementAsync() 161 | { 162 | var path = PathHelper.GetTempFilePath(); 163 | var templatePath = PathHelper.GetFile("TestForeachInTablesWithIfStatementDemo.docx"); 164 | var value = new Dictionary() 165 | { 166 | ["TripHs"] = new List> 167 | { 168 | new Dictionary 169 | { 170 | { "sDate", DateTime.Parse("2022-09-08 08:30:00") }, 171 | { "eDate", DateTime.Parse("2022-09-08 15:00:00") }, 172 | { "How", "Discussion requirement part1" }, 173 | { 174 | "Details", new List() 175 | { 176 | new MiniWordForeach() 177 | { 178 | Value = new Dictionary() 179 | { 180 | {"Text", "Air"}, 181 | {"Value", "Airplane"} 182 | }, 183 | Separator = " | " 184 | }, 185 | new MiniWordForeach() 186 | { 187 | Value = new Dictionary() 188 | { 189 | {"Text", "Parking"}, 190 | {"Value", "Car"} 191 | }, 192 | Separator = " / " 193 | }, 194 | new MiniWordForeach() 195 | { 196 | Value = new Dictionary() 197 | { 198 | {"Text", "Hotel"}, 199 | {"Value", "Room"} 200 | }, 201 | Separator = ", " 202 | }, 203 | new MiniWordForeach() 204 | { 205 | Value = new Dictionary() 206 | { 207 | {"Text", "Food"}, 208 | {"Value", "Plate"} 209 | }, 210 | Separator = "" 211 | } 212 | } 213 | } 214 | }, 215 | new Dictionary 216 | { 217 | { "sDate", DateTime.Parse("2022-09-09 08:30:00") }, 218 | { "eDate", DateTime.Parse("2022-09-09 17:00:00") }, 219 | { "How", "Discussion requirement part2 and development" }, 220 | { 221 | "Details", new List() 222 | { 223 | new MiniWordForeach() 224 | { 225 | Value = new Dictionary() 226 | { 227 | {"Text", "Air"}, 228 | {"Value", "Airplane"} 229 | }, 230 | Separator = " | " 231 | }, 232 | new MiniWordForeach() 233 | { 234 | Value = new Dictionary() 235 | { 236 | {"Text", "Parking"}, 237 | {"Value", "Car"} 238 | }, 239 | Separator = " / " 240 | }, 241 | new MiniWordForeach() 242 | { 243 | Value = new Dictionary() 244 | { 245 | {"Text", "Hotel"}, 246 | {"Value", "Room"} 247 | }, 248 | Separator = ", " 249 | }, 250 | new MiniWordForeach() 251 | { 252 | Value = new Dictionary() 253 | { 254 | {"Text", "Food"}, 255 | {"Value", "Plate"} 256 | }, 257 | Separator = "" 258 | } 259 | } 260 | } 261 | } 262 | } 263 | }; 264 | await MiniWord.SaveAsByTemplateAsync(path, templatePath, value); 265 | //System.Diagnostics.Process.Start("explorer.exe", path); 266 | var xml = Helpers.GetZipFileContent(path, "word/document.xml"); 267 | Assert.Contains(@"Discussion requirement part2 and development", xml); 268 | Assert.Contains(@"Discussion requirement part1", xml); 269 | Assert.Contains("Air way to the Airplane | Hotel way to the Room", xml); 270 | } 271 | } 272 | } -------------------------------------------------------------------------------- /tests/MiniWordTests/MiniWordTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using DocumentFormat.OpenXml.Office2010.ExcelAc; 4 | using MiniSoftware; 5 | using Xunit; 6 | 7 | namespace MiniWordTests 8 | { 9 | public class MiniWordTests 10 | { 11 | [Fact] 12 | public void TestForeachLoopInTables() 13 | { 14 | var path = PathHelper.GetTempFilePath(); 15 | var templatePath = PathHelper.GetFile("TestForeachInTablesDemo.docx"); 16 | var value = new Dictionary() 17 | { 18 | ["TripHs"] = new List> 19 | { 20 | new Dictionary 21 | { 22 | { "sDate", DateTime.Parse("2022-09-08 08:30:00") }, 23 | { "eDate", DateTime.Parse("2022-09-08 15:00:00") }, 24 | { "How", "Discussion requirement part1" }, 25 | { 26 | "Details", new List() 27 | { 28 | new MiniWordForeach() 29 | { 30 | Value = new Dictionary() 31 | { 32 | {"Text", "Air"}, 33 | {"Value", "Airplane"} 34 | }, 35 | Separator = " | " 36 | }, 37 | new MiniWordForeach() 38 | { 39 | Value = new Dictionary() 40 | { 41 | {"Text", "Parking"}, 42 | {"Value", "Car"} 43 | }, 44 | Separator = " / " 45 | }, 46 | new MiniWordForeach() 47 | { 48 | Value = new Dictionary() 49 | { 50 | {"Text", "Hotel"}, 51 | {"Value", "Room"} 52 | }, 53 | Separator = ", " 54 | }, 55 | new MiniWordForeach() 56 | { 57 | Value = new Dictionary() 58 | { 59 | {"Text", "Food"}, 60 | {"Value", "Plate"} 61 | }, 62 | Separator = "" 63 | } 64 | } 65 | } 66 | }, 67 | new Dictionary 68 | { 69 | { "sDate", DateTime.Parse("2022-09-09 08:30:00") }, 70 | { "eDate", DateTime.Parse("2022-09-09 17:00:00") }, 71 | { "How", "Discussion requirement part2 and development" }, 72 | { 73 | "Details", new List() 74 | { 75 | new MiniWordForeach() 76 | { 77 | Value = new Dictionary() 78 | { 79 | {"Text", "Air"}, 80 | {"Value", "Airplane"} 81 | }, 82 | Separator = " | " 83 | }, 84 | new MiniWordForeach() 85 | { 86 | Value = new Dictionary() 87 | { 88 | {"Text", "Parking"}, 89 | {"Value", "Car"} 90 | }, 91 | Separator = " / " 92 | }, 93 | new MiniWordForeach() 94 | { 95 | Value = new Dictionary() 96 | { 97 | {"Text", "Hotel"}, 98 | {"Value", "Room"} 99 | }, 100 | Separator = ", " 101 | }, 102 | new MiniWordForeach() 103 | { 104 | Value = new Dictionary() 105 | { 106 | {"Text", "Food"}, 107 | {"Value", "Plate"} 108 | }, 109 | Separator = "" 110 | } 111 | } 112 | } 113 | } 114 | } 115 | }; 116 | MiniWord.SaveAsByTemplate(path, templatePath, value); 117 | //System.Diagnostics.Process.Start("explorer.exe", path); 118 | var xml = Helpers.GetZipFileContent(path, "word/document.xml"); 119 | Assert.Contains(@"Discussion requirement part2 and development", xml); 120 | Assert.Contains(@"Discussion requirement part1", xml); 121 | Assert.Contains( 122 | "Air way to the Airplane | Parking way to the Car / Hotel way to the Room, Food way to the Plate", xml); 123 | } 124 | 125 | [Fact] 126 | public void MiniWordIfStatement_FirstIf() 127 | { 128 | var path = PathHelper.GetTempFilePath(); 129 | var templatePath = PathHelper.GetFile("TestIfStatement.docx"); 130 | var value = new Dictionary() 131 | { 132 | ["Name"] = new List(){ 133 | new MiniWordHyperLink(){ 134 | Url = "https://google.com", 135 | Text = "測試連結22!!" 136 | }, 137 | new MiniWordHyperLink(){ 138 | Url = "https://google1.com", 139 | Text = "測試連結11!!" 140 | } 141 | }, 142 | ["Company_Name"] = "MiniSofteware", 143 | ["CreateDate"] = new DateTime(2021, 01, 01), 144 | ["VIP"] = true, 145 | ["Points"] = 123, 146 | ["APP"] = "Demo APP", 147 | }; 148 | MiniWord.SaveAsByTemplate(path, templatePath, value); 149 | //Console.WriteLine(path); 150 | var docXml = Helpers.GetZipFileContent(path, "word/document.xml"); 151 | Assert.Contains("First if chosen: MiniSofteware", docXml); 152 | Assert.DoesNotContain("Second if chosen: MaxiSoftware", docXml); 153 | Assert.Contains("Points are greater than 100", docXml); 154 | Assert.Contains("CreateDate is not less than 2021", docXml); 155 | Assert.DoesNotContain("CreateDate is not greater than 2021", docXml); 156 | } 157 | 158 | [Fact] 159 | public void TestForeachLoopInTablesWithIfStatement() 160 | { 161 | var path = PathHelper.GetTempFilePath(); 162 | var templatePath = PathHelper.GetFile("TestForeachInTablesWithIfStatementDemo.docx"); 163 | var value = new Dictionary() 164 | { 165 | ["TripHs"] = new List> 166 | { 167 | new Dictionary 168 | { 169 | { "sDate", DateTime.Parse("2022-09-08 08:30:00") }, 170 | { "eDate", DateTime.Parse("2022-09-08 15:00:00") }, 171 | { "How", "Discussion requirement part1" }, 172 | { 173 | "Details", new List() 174 | { 175 | new MiniWordForeach() 176 | { 177 | Value = new Dictionary() 178 | { 179 | {"Text", "Air"}, 180 | {"Value", "Airplane"} 181 | }, 182 | Separator = " | " 183 | }, 184 | new MiniWordForeach() 185 | { 186 | Value = new Dictionary() 187 | { 188 | {"Text", "Parking"}, 189 | {"Value", "Car"} 190 | }, 191 | Separator = " / " 192 | }, 193 | new MiniWordForeach() 194 | { 195 | Value = new Dictionary() 196 | { 197 | {"Text", "Hotel"}, 198 | {"Value", "Room"} 199 | }, 200 | Separator = ", " 201 | }, 202 | new MiniWordForeach() 203 | { 204 | Value = new Dictionary() 205 | { 206 | {"Text", "Food"}, 207 | {"Value", "Plate"} 208 | }, 209 | Separator = "" 210 | } 211 | } 212 | } 213 | }, 214 | new Dictionary 215 | { 216 | { "sDate", DateTime.Parse("2022-09-09 08:30:00") }, 217 | { "eDate", DateTime.Parse("2022-09-09 17:00:00") }, 218 | { "How", "Discussion requirement part2 and development" }, 219 | { 220 | "Details", new List() 221 | { 222 | new MiniWordForeach() 223 | { 224 | Value = new Dictionary() 225 | { 226 | {"Text", "Air"}, 227 | {"Value", "Airplane"} 228 | }, 229 | Separator = " | " 230 | }, 231 | new MiniWordForeach() 232 | { 233 | Value = new Dictionary() 234 | { 235 | {"Text", "Parking"}, 236 | {"Value", "Car"} 237 | }, 238 | Separator = " / " 239 | }, 240 | new MiniWordForeach() 241 | { 242 | Value = new Dictionary() 243 | { 244 | {"Text", "Hotel"}, 245 | {"Value", "Room"} 246 | }, 247 | Separator = ", " 248 | }, 249 | new MiniWordForeach() 250 | { 251 | Value = new Dictionary() 252 | { 253 | {"Text", "Food"}, 254 | {"Value", "Plate"} 255 | }, 256 | Separator = "" 257 | } 258 | } 259 | } 260 | } 261 | } 262 | }; 263 | MiniWord.SaveAsByTemplate(path, templatePath, value); 264 | //System.Diagnostics.Process.Start("explorer.exe", path); 265 | var xml = Helpers.GetZipFileContent(path, "word/document.xml"); 266 | Assert.Contains(@"Discussion requirement part2 and development", xml); 267 | Assert.Contains(@"Discussion requirement part1", xml); 268 | Assert.Contains("Air way to the Airplane | Hotel way to the Room", xml); 269 | } 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /tests/MiniWordTests/MiniWordTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0; 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | all 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /tests/MiniWordTests/PathHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | 4 | namespace MiniWordTests 5 | { 6 | internal static class PathHelper 7 | { 8 | public static string GetFile(string fileName, string folderName = "docx") 9 | { 10 | return $@"../../../../../samples/{folderName}/{fileName}"; 11 | } 12 | 13 | public static string GetTempPath(string extension = "docx") 14 | { 15 | var method = (new System.Diagnostics.StackTrace()).GetFrame(1).GetMethod(); 16 | 17 | var path = Path.Combine(Path.GetTempPath(), $"{method.DeclaringType.Name}_{method.Name}.{extension}").Replace("<", string.Empty).Replace(">", string.Empty); 18 | if (File.Exists(path)) 19 | File.Delete(path); 20 | return path; 21 | } 22 | 23 | public static string GetTempFilePath(string extension = "docx") 24 | { 25 | return Path.GetTempPath() + Guid.NewGuid().ToString() + "." + extension; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /tests/linqpads/basic_fill.linq: -------------------------------------------------------------------------------- 1 | 2 | DocumentFormat.OpenXml 3 | FreeSpire.Doc 4 | iTextSharp 5 | Microsoft.Office.Interop.Word 6 | MiniExcel 7 | Newtonsoft.Json 8 | NPOI 9 | MiniExcelLibs 10 | Newtonsoft.Json 11 | NPOI.XWPF.UserModel 12 | 5.0 13 | 14 | 15 | void Main() 16 | { 17 | var path = Path.GetTempPath() + Guid.NewGuid() + ".docx"; 18 | var templatePath = @"D:\git\MiniWord\tests\linqpads\TestBasicFill.docx"; 19 | var value = new Dictionary() 20 | { 21 | ["Name"] = "Jack", 22 | ["CreateDate"] = new DateTime(2021, 01, 01), 23 | ["VIP"] = true, 24 | ["Points"] = 123, 25 | ["Company_Name"] = "MiniSofteware", 26 | ["APP"] = "Demo APP", 27 | }; 28 | MiniSoftware.MiniWord.SaveAsByTemplate(path, templatePath, value); 29 | Console.WriteLine(path); 30 | } 31 | 32 | namespace MiniSoftware 33 | { 34 | using DocumentFormat.OpenXml; 35 | using DocumentFormat.OpenXml.Packaging; 36 | using DocumentFormat.OpenXml.Wordprocessing; 37 | 38 | public static class MiniWord 39 | { 40 | static void ReplaceTag(this OpenXmlElement xmlElement, WordprocessingDocument docx, Dictionary tags) 41 | { 42 | var paragraphs = xmlElement.Descendants().ToArray(); 43 | foreach (var p in paragraphs) 44 | { 45 | var innerXmlSb = p.InnerXml; 46 | foreach (var tag in tags) 47 | innerXmlSb = Regex.Replace(innerXmlSb, @"\{\{(?:(?!\{\{|}}).)*" + tag.Key + ".*?}}", tags[tag.Key]?.ToString(), RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace | RegexOptions.CultureInvariant); 48 | p.InnerXml = innerXmlSb; 49 | } 50 | } 51 | public static void SaveAsByTemplate(string path, string templatePath, object value) 52 | { 53 | using (var stream = File.Create(path)) 54 | SaveAsByTemplate(stream, templatePath, value); 55 | } 56 | 57 | public static void SaveAsByTemplate(string path, byte[] templateBytes, object value) 58 | { 59 | using (var stream = File.Create(path)) 60 | SaveAsByTemplate(stream, templateBytes, value); 61 | } 62 | 63 | public static void SaveAsByTemplate(this Stream stream, string templatePath, object value) 64 | { 65 | SaveAsByTemplateImpl(stream,GetBytes(templatePath),value); 66 | } 67 | 68 | public static void SaveAsByTemplate(this Stream stream, byte[] templateBytes, object value) 69 | { 70 | SaveAsByTemplateImpl(stream,templateBytes,value); 71 | } 72 | private static byte[] GetBytes(string path){ 73 | using (var st = FileHelper.OpenSharedRead(path)) 74 | using (var ms = new MemoryStream()){ 75 | st.CopyTo(ms); 76 | return ms.ToArray(); 77 | } 78 | } 79 | private static void SaveAsByTemplateImpl(Stream stream,byte[] template, object data) 80 | { 81 | var value = data as Dictionary; //TODO: 82 | byte[] bytes = null; 83 | using (var ms = new MemoryStream(template)){ 84 | ms.Position = 0; 85 | using (var docx = WordprocessingDocument.Open(ms, true)) 86 | { 87 | docx.MainDocumentPart.HeaderParts.ToList().ForEach(hdr => 88 | { 89 | hdr.Header.ReplaceTag(docx, value); 90 | }); 91 | docx.MainDocumentPart.FooterParts.ToList().ForEach(ftr => 92 | { 93 | ftr.Footer.ReplaceTag(docx, value); 94 | }); 95 | docx.MainDocumentPart.Document.Body.ReplaceTag(docx, value); 96 | docx.Save(); 97 | bytes = ms.ToArray(); 98 | } 99 | } 100 | 101 | stream.Write(bytes); 102 | } 103 | } 104 | 105 | internal static partial class FileHelper 106 | { 107 | public static FileStream OpenSharedRead(string path) => File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); 108 | } 109 | } -------------------------------------------------------------------------------- /tests/linqpads/miniword_draft.linq: -------------------------------------------------------------------------------- 1 | 2 | DocumentFormat.OpenXml 3 | FreeSpire.Doc 4 | iTextSharp 5 | Microsoft.Office.Interop.Word 6 | MiniExcel 7 | Newtonsoft.Json 8 | NPOI 9 | MiniExcelLibs 10 | Newtonsoft.Json 11 | NPOI.XWPF.UserModel 12 | 5.0 13 | 14 | 15 | void Main() 16 | { 17 | var path = Path.GetTempPath() + Guid.NewGuid() + ".docx"; 18 | var templatePath = @"D:\git\MiniWord\tests\linqpads\TestBasicFill.docx"; 19 | var value = new Dictionary() 20 | { 21 | ["Name"] = "Jack", 22 | ["CreateDate"] = new DateTime(2021, 01, 01), 23 | ["VIP"] = true, 24 | ["Points"] = 123, 25 | ["Company_Name"] = "MiniSofteware", 26 | ["APP"] = "Demo APP", 27 | }; 28 | MiniSoftware.MiniWord.SaveAsByTemplate(path, templatePath, value); 29 | Console.WriteLine(path); 30 | } 31 | 32 | namespace MiniSoftware 33 | { 34 | using DocumentFormat.OpenXml; 35 | using DocumentFormat.OpenXml.Packaging; 36 | using DocumentFormat.OpenXml.Wordprocessing; 37 | 38 | public static class MiniWord 39 | { 40 | static void ReplaceTag(this OpenXmlElement xmlElement, WordprocessingDocument docx, Dictionary tags) 41 | { 42 | var paragraphs = xmlElement.Descendants().ToArray(); 43 | foreach (var p in paragraphs) 44 | { 45 | var innerXmlSb = p.InnerXml; 46 | foreach (var tag in tags) 47 | innerXmlSb = Regex.Replace(innerXmlSb, @"\{\{(?:(?!\{\{|}}).)*" + tag.Key + ".*?}}", tags[tag.Key]?.ToString(), RegexOptions.Singleline | RegexOptions.IgnorePatternWhitespace | RegexOptions.CultureInvariant); 48 | p.InnerXml = innerXmlSb; 49 | Console.WriteLine(p.InnerXml); 50 | } 51 | } 52 | public static void SaveAsByTemplate(string path, string templatePath, object value) 53 | { 54 | using (var stream = File.Create(path)) 55 | SaveAsByTemplate(stream, templatePath, value); 56 | } 57 | 58 | public static void SaveAsByTemplate(string path, byte[] templateBytes, object value) 59 | { 60 | using (var stream = File.Create(path)) 61 | SaveAsByTemplate(stream, templateBytes, value); 62 | } 63 | 64 | public static void SaveAsByTemplate(this Stream stream, string templatePath, object value) 65 | { 66 | SaveAsByTemplateImpl(stream,GetBytes(templatePath),value); 67 | } 68 | 69 | public static void SaveAsByTemplate(this Stream stream, byte[] templateBytes, object value) 70 | { 71 | SaveAsByTemplateImpl(stream,templateBytes,value); 72 | } 73 | private static byte[] GetBytes(string path){ 74 | using (var st = FileHelper.OpenSharedRead(path)) 75 | using (var ms = new MemoryStream()){ 76 | st.CopyTo(ms); 77 | return ms.ToArray(); 78 | } 79 | } 80 | private static void SaveAsByTemplateImpl(Stream stream,byte[] template, object data) 81 | { 82 | var value = data as Dictionary; //TODO: 83 | byte[] bytes = null; 84 | using (var ms = new MemoryStream()){ 85 | ms.Write(template); 86 | ms.Position = 0; 87 | using (var docx = WordprocessingDocument.Open(ms, true)) 88 | { 89 | foreach (var hdr in docx.MainDocumentPart.HeaderParts) 90 | hdr.Header.ReplaceTag(docx, value); 91 | foreach (var ftr in docx.MainDocumentPart.FooterParts) 92 | ftr.Footer.ReplaceTag(docx, value); 93 | docx.MainDocumentPart.Document.Body.ReplaceTag(docx, value); 94 | docx.Save(); 95 | } 96 | bytes = ms.ToArray(); 97 | } 98 | stream.Write(bytes); 99 | } 100 | } 101 | 102 | internal static partial class FileHelper 103 | { 104 | public static FileStream OpenSharedRead(string path) => File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); 105 | } 106 | } -------------------------------------------------------------------------------- /tests/linqpads/object to N level Dictionary.linq: -------------------------------------------------------------------------------- 1 | 2 | DocumentFormat.OpenXml 3 | MiniExcel 4 | Newtonsoft.Json 5 | DocumentFormat.OpenXml 6 | DocumentFormat.OpenXml.Packaging 7 | MiniExcelLibs 8 | Newtonsoft.Json 9 | System.ComponentModel 10 | System.Dynamic 11 | 12 | 13 | void Main() 14 | { 15 | { 16 | var value = new 17 | { 18 | TripHs = new List> 19 | { 20 | new Dictionary 21 | { 22 | { "sDate",DateTime.Parse("2022-09-08 08:30:00")}, 23 | { "eDate",DateTime.Parse("2022-09-08 15:00:00")}, 24 | { "How","Discussion requirement part1"}, 25 | { "Photo",new MiniWordPicture() { Path = "DemoExpenseMeeting02.png", Width = 160, Height = 90 }}, 26 | }, 27 | new Dictionary 28 | { 29 | { "sDate",DateTime.Parse("2022-09-09 08:30:00")}, 30 | { "eDate",DateTime.Parse("2022-09-09 17:00:00")}, 31 | { "How","Discussion requirement part2 and development"}, 32 | { "Photo",new MiniWordPicture() { Path = "DemoExpenseMeeting02.png", Width = 160, Height = 90 }}, 33 | }, 34 | } 35 | }; 36 | var data = value.ToDictionary(); 37 | Console.WriteLine(data); 38 | } 39 | { 40 | var value = new 41 | { 42 | managers = new List { "Jack", "Alan" }, 43 | employees = new List { null, new DateTime(2011,2,24) }, 44 | }; 45 | var data = value.ToDictionary(); 46 | Console.WriteLine(data); 47 | } 48 | 49 | // How 2 level object convert to 2 level Dictionary this object can be Enumerable> or object 50 | var department = new 51 | { 52 | ID = "S001", 53 | Name = "HR", 54 | Users = new[]{ 55 | new {ID="E001",Name="Jack"}, 56 | new {ID="E002",Name="Terry"}, 57 | new {ID="E003",Name="Jassie"}, 58 | }, 59 | ChildDepartments = new[] {"D004","D005","D006"}, 60 | Times = new[] {new DateTime(2022,10,15),new DateTime(2022,11,08),new DateTime(2022,12,25)}, 61 | }; 62 | //var json = JsonConvert.SerializeObject(department); 63 | //Console.WriteLine(json); //{"ID":"S001","Name":"HR","Users":[{"ID":"E001","Name":"Jack"},{"ID":"E002","Name":"Terry"},{"ID":"E003","Name":"Jassie"}]} 64 | 65 | 66 | var result = ObjectExtension.ToDictionary(department); 67 | //Console.WriteLine(result); 68 | 69 | var expectedResult = new Dictionary 70 | { 71 | ["ID"] = "S001", 72 | ["Name"] = "HR", 73 | ["Users"] = new List>{ 74 | new Dictionary{["ID"]="E001",["Name"]="Jack"}, 75 | new Dictionary{["ID"]="E002",["Name"]="Terry"}, 76 | new Dictionary{["ID"]="E003",["Name"]="Jassie"}, 77 | }, 78 | ["ChildDepartments"] = new[] {"D004","D005","D006"}, 79 | ["Times"] = new[] {new DateTime(2022,10,15),new DateTime(2022,11,08),new DateTime(2022,12,25)}, 80 | }; 81 | //Console.WriteLine(expectedResult); 82 | 83 | 84 | //var data = JsonConvert.DeserializeObject>>(json); 85 | //Console.WriteLine(data); 86 | 87 | } 88 | 89 | // You can define other methods, fields, classes and namespaces here 90 | internal static class ObjectExtension 91 | { 92 | internal static Dictionary ToDictionary(this object value) 93 | { 94 | if (value == null) 95 | return new Dictionary(); 96 | else if (value is Dictionary dicStr) 97 | return dicStr; 98 | else if (value is ExpandoObject) 99 | return new Dictionary(value as ExpandoObject); 100 | 101 | if (IsStrongTypeEnumerable(value)) 102 | throw new Exception("The parameter cannot be a collection type"); 103 | else 104 | { 105 | Dictionary reuslt = new Dictionary(); 106 | PropertyDescriptorCollection props = TypeDescriptor.GetProperties(value); 107 | foreach (PropertyDescriptor prop in props) 108 | { 109 | object val1 = prop.GetValue(value); 110 | 111 | if (IsStrongTypeEnumerable(val1)) 112 | { 113 | var isValueOrStringType = false;; 114 | List> sx = new List>(); 115 | foreach (object val1item in (IEnumerable)val1) 116 | { 117 | if (val1item == null) 118 | { 119 | sx.Add(new Dictionary()); 120 | continue; 121 | } 122 | // not custom type 123 | if (val1item is string || val1item is DateTime || value.GetType().IsValueType) 124 | { 125 | isValueOrStringType=true; 126 | reuslt.Add(prop.Name, val1); 127 | break; 128 | } 129 | if (val1item is Dictionary dicStr) 130 | { 131 | sx.Add(dicStr); 132 | continue; 133 | } 134 | else if (val1item is ExpandoObject) 135 | { 136 | sx.Add(new Dictionary(value as ExpandoObject)); 137 | continue; 138 | } 139 | 140 | PropertyDescriptorCollection props2 = TypeDescriptor.GetProperties(val1item); 141 | Dictionary reuslt2 = new Dictionary(); 142 | foreach (PropertyDescriptor prop2 in props2) 143 | { 144 | object val2 = prop2.GetValue(val1item); 145 | reuslt2.Add(prop2.Name, val2); 146 | } 147 | sx.Add(reuslt2); 148 | } 149 | if(!isValueOrStringType) 150 | reuslt.Add(prop.Name, sx); 151 | } 152 | else 153 | { 154 | reuslt.Add(prop.Name, val1); 155 | } 156 | } 157 | return reuslt; 158 | } 159 | } 160 | internal static bool IsStrongTypeEnumerable(object obj) 161 | { 162 | return obj is IEnumerable && !(obj is string) && !(obj is char[]) && !(obj is string[]); 163 | } 164 | } 165 | 166 | 167 | 168 | public class MiniWordPicture 169 | { 170 | 171 | 172 | public string Path { get; set; } 173 | private string _extension; 174 | public string Extension 175 | { 176 | get 177 | { 178 | if (Path != null) 179 | return System.IO.Path.GetExtension(Path).ToUpperInvariant().Replace(".", ""); 180 | else 181 | { 182 | return _extension.ToUpper(); 183 | } 184 | } 185 | set { _extension = value; } 186 | } 187 | internal ImagePartType GetImagePartType 188 | { 189 | get 190 | { 191 | switch (Extension.ToLower()) 192 | { 193 | case "bmp": return ImagePartType.Bmp; 194 | case "emf": return ImagePartType.Emf; 195 | case "ico": return ImagePartType.Icon; 196 | case "jpg": return ImagePartType.Jpeg; 197 | case "jpeg": return ImagePartType.Jpeg; 198 | case "pcx": return ImagePartType.Pcx; 199 | case "png": return ImagePartType.Png; 200 | case "svg": return ImagePartType.Svg; 201 | case "tiff": return ImagePartType.Tiff; 202 | case "wmf": return ImagePartType.Wmf; 203 | default: 204 | throw new NotSupportedException($"{_extension} is not supported"); 205 | } 206 | } 207 | } 208 | 209 | public byte[] Bytes { get; set; } 210 | /// 211 | /// Unit is Pixel 212 | /// 213 | public Int64Value Width { get; set; } = 400; 214 | internal Int64Value Cx { get { return Width * 9525; } } 215 | /// 216 | /// Unit is Pixel 217 | /// 218 | public Int64Value Height { get; set; } = 400; 219 | //format resource from http://openxmltrix.blogspot.com/2011/04/updating-images-in-image-placeholde-and.html 220 | internal Int64Value Cy { get { return Height * 9525; } } 221 | } --------------------------------------------------------------------------------