├── output.docx ├── template1.docx ├── utilities └── NuGet.exe ├── template-malformed-xml.docx ├── docxtemplateenginetest.bat ├── swxben.docxtemplateengine.0.1.0.nupkg ├── swxben.docxtemplateengine.0.1.1.nupkg ├── swxben.docxtemplateengine.0.1.2.nupkg ├── swxben.docxtemplateengine.0.1.3.nupkg ├── swxben.docxtemplateengine.0.1.4.nupkg ├── swxben.docxtemplateengine.0.1.5.nupkg ├── .nuget └── packages.config ├── src ├── swxben.docxtemplateengine │ ├── packages.config │ ├── IDocXTemplateEngine.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── swxben.docxtemplateengine.csproj │ └── DocXTemplateEngine.cs ├── docxtemplateenginetest │ ├── packages.config │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Program.cs │ └── docxtemplateenginetest.csproj └── Tests │ ├── packages.config │ ├── TestHelpers.cs │ ├── when_replacing_template_field.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── when_reading_to_and_from_a_stream.cs │ ├── Tests.csproj │ └── when_parsing_simple_template.cs ├── tests.bat ├── swxben.docxtemplateengine.nuspec ├── .gitignore ├── DocXTemplateEngine.sln └── README.md /output.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swxben/docx-template-engine/HEAD/output.docx -------------------------------------------------------------------------------- /template1.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swxben/docx-template-engine/HEAD/template1.docx -------------------------------------------------------------------------------- /utilities/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swxben/docx-template-engine/HEAD/utilities/NuGet.exe -------------------------------------------------------------------------------- /template-malformed-xml.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swxben/docx-template-engine/HEAD/template-malformed-xml.docx -------------------------------------------------------------------------------- /docxtemplateenginetest.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | src\docxtemplateenginetest\bin\Debug\docxtemplateenginetest.exe %* 3 | 4 | -------------------------------------------------------------------------------- /swxben.docxtemplateengine.0.1.0.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swxben/docx-template-engine/HEAD/swxben.docxtemplateengine.0.1.0.nupkg -------------------------------------------------------------------------------- /swxben.docxtemplateengine.0.1.1.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swxben/docx-template-engine/HEAD/swxben.docxtemplateengine.0.1.1.nupkg -------------------------------------------------------------------------------- /swxben.docxtemplateengine.0.1.2.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swxben/docx-template-engine/HEAD/swxben.docxtemplateengine.0.1.2.nupkg -------------------------------------------------------------------------------- /swxben.docxtemplateengine.0.1.3.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swxben/docx-template-engine/HEAD/swxben.docxtemplateengine.0.1.3.nupkg -------------------------------------------------------------------------------- /swxben.docxtemplateengine.0.1.4.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swxben/docx-template-engine/HEAD/swxben.docxtemplateengine.0.1.4.nupkg -------------------------------------------------------------------------------- /swxben.docxtemplateengine.0.1.5.nupkg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/swxben/docx-template-engine/HEAD/swxben.docxtemplateengine.0.1.5.nupkg -------------------------------------------------------------------------------- /.nuget/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/swxben.docxtemplateengine/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/docxtemplateenginetest/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/Tests/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | if /i "%1" == "gui" goto :gui 3 | 4 | :console 5 | packages\NUnit.Runners.2.6.2\tools\nunit-console.exe src\Tests\bin\Debug\Tests.dll 6 | goto :done 7 | 8 | :gui 9 | packages\NUnit.Runners.2.6.2\tools\nunit.exe src\Tests\bin\Debug\Tests.dll 10 | goto :done 11 | 12 | :done 13 | del /Q TestResult.xml -------------------------------------------------------------------------------- /src/swxben.docxtemplateengine/IDocXTemplateEngine.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | 3 | namespace swxben.docxtemplateengine 4 | { 5 | public interface IDocXTemplateEngine 6 | { 7 | void Process(string source, string destination, object data); 8 | void Process(string source, Stream destination, object data); 9 | void Process(Stream source, Stream destination, object data); 10 | } 11 | } -------------------------------------------------------------------------------- /src/Tests/TestHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | namespace Tests 8 | { 9 | public static class TestHelpers 10 | { 11 | public static string GetRootPath() 12 | { 13 | var currentDirectory = Directory.GetCurrentDirectory(); 14 | var rootDir = currentDirectory.Substring(0, currentDirectory.IndexOf(@"\src\Tests\bin\", StringComparison.Ordinal)); 15 | return rootDir; 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Tests/when_replacing_template_field.cs: -------------------------------------------------------------------------------- 1 | using NUnit.Framework; 2 | using Shouldly; 3 | using swxben.docxtemplateengine; 4 | 5 | namespace Tests 6 | { 7 | [TestFixture] 8 | class when_replacing_template_field 9 | { 10 | [Test] 11 | public void simple_value_is_replaced() 12 | { 13 | var template = "this is an " + DocXTemplateEngine.TOKEN_START + "tmpl" + DocXTemplateEngine.TOKEN_END + " template"; 14 | 15 | var result = DocXTemplateEngine.ReplaceTemplateField(template, "tmpl", "example"); 16 | 17 | result.ShouldBe("this is an example template"); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /swxben.docxtemplateengine.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | swxben.docxtemplateengine 5 | 0.1.5 6 | DocXTemplateEngine 7 | Ben Scott, contributors 8 | Ben Scott 9 | http://creativecommons.org/licenses/by-sa/3.0/ 10 | https://github.com/swxben/docx-template-engine 11 | false 12 | A template engine for the .NET platform which takes a DOCX file and applies a data object to generate reports, do mail merges, etc. No dependency on MS Word. 13 | A template engine for the .NET platform which takes a DOCX file and applies a data object to generate reports, do mail merges, etc. No dependency on MS Word. 14 | Safe handling of null values in the source data @MarkKGreenway 15 | word msword template docx 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("462c4948-5bb8-488e-849d-bce51a2fe3ea")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/docxtemplateenginetest/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("docxtemplateenginetest")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("docxtemplateenginetest")] 13 | [assembly: AssemblyCopyright("Copyright © 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("a4ea6626-2598-472a-8bcd-71ab229d0f94")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/swxben.docxtemplateengine/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("swxben.docxtemplateengine")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Software by Ben Pty Ltd")] 12 | [assembly: AssemblyProduct("swxben.docxtemplateengine")] 13 | [assembly: AssemblyCopyright("CC BY-SA 3.0")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("57016d23-cc1f-4a41-b56f-8358cd3438b7")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("0.1.3.0")] 36 | [assembly: AssemblyFileVersion("0.1.3.0")] 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | x64/ 20 | *_i.c 21 | *_p.c 22 | *.ilk 23 | *.meta 24 | *.obj 25 | *.pch 26 | *.pdb 27 | *.pgc 28 | *.pgd 29 | *.rsp 30 | *.sbr 31 | *.tlb 32 | *.tli 33 | *.tlh 34 | *.tmp 35 | *.log 36 | *.vspscc 37 | *.vssscc 38 | .builds 39 | 40 | # Visual C++ cache files 41 | ipch/ 42 | *.aps 43 | *.ncb 44 | *.opensdf 45 | *.sdf 46 | 47 | # Visual Studio profiler 48 | *.psess 49 | *.vsp 50 | *.vspx 51 | 52 | # Guidance Automation Toolkit 53 | *.gpState 54 | 55 | # ReSharper is a .NET coding add-in 56 | _ReSharper* 57 | 58 | # NCrunch 59 | *.ncrunch* 60 | .*crunch*.local.xml 61 | 62 | # Installshield output folder 63 | [Ee]xpress 64 | 65 | # DocProject is a documentation generator add-in 66 | DocProject/buildhelp/ 67 | DocProject/Help/*.HxT 68 | DocProject/Help/*.HxC 69 | DocProject/Help/*.hhc 70 | DocProject/Help/*.hhk 71 | DocProject/Help/*.hhp 72 | DocProject/Help/Html2 73 | DocProject/Help/html 74 | 75 | # Click-Once directory 76 | publish 77 | 78 | # Publish Web Output 79 | *.Publish.xml 80 | 81 | # NuGet Packages Directory 82 | packages 83 | 84 | # Windows Azure Build Output 85 | csx 86 | *.build.csdef 87 | 88 | # Windows Store app package directory 89 | AppPackages/ 90 | 91 | # Others 92 | [Bb]in 93 | [Oo]bj 94 | sql 95 | TestResults 96 | [Tt]est[Rr]esult* 97 | *.Cache 98 | ClientBin 99 | [Ss]tyle[Cc]op.* 100 | ~$* 101 | *.dbmdl 102 | Generated_Code #added for RIA/Silverlight projects 103 | 104 | # Backup & report files from converting an old project file to a newer 105 | # Visual Studio version. Backup files are not needed, because we have git ;-) 106 | _UpgradeReport_Files/ 107 | Backup*/ 108 | UpgradeLog*.XML 109 | -------------------------------------------------------------------------------- /src/Tests/when_reading_to_and_from_a_stream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using NUnit.Framework; 7 | using Shouldly; 8 | using swxben.docxtemplateengine; 9 | 10 | namespace Tests 11 | { 12 | public class when_reading_to_and_from_a_stream 13 | { 14 | readonly IDocXTemplateEngine _templateEngine = new DocXTemplateEngine(); 15 | private readonly string _pathToTemplateDoc = Path.Combine(TestHelpers.GetRootPath(), "template1.docx"); 16 | 17 | [Test] 18 | public void the_output_stream_is_the_same_length_as_the_output_when_reading_from_a_file() 19 | { 20 | byte[] bufferFromFile, bufferFromStream; 21 | GetTestBuffers(out bufferFromFile, out bufferFromStream); 22 | 23 | bufferFromStream.Length.ShouldBe(bufferFromFile.Length); 24 | } 25 | 26 | [Test] 27 | public void the_output_stream_matches_the_output_when_reading_from_a_file() 28 | { 29 | byte[] bufferFromFile, bufferFromStream; 30 | GetTestBuffers(out bufferFromFile, out bufferFromStream); 31 | 32 | for (var i = 0; i < bufferFromFile.Length; i++) 33 | { 34 | bufferFromStream[i].ShouldBe(bufferFromFile[i]); 35 | } 36 | } 37 | 38 | private void GetTestBuffers(out byte[] bufferFromFile, out byte[] bufferFromStream) 39 | { 40 | var data = new {Name = "Bar"}; 41 | using (var streamFromFile = new MemoryStream()) 42 | using (var streamFromStream = new MemoryStream()) 43 | { 44 | _templateEngine.Process(_pathToTemplateDoc, streamFromFile, data); 45 | streamFromFile.Close(); 46 | 47 | _templateEngine.Process(File.OpenRead(_pathToTemplateDoc), streamFromStream, data); 48 | streamFromStream.Close(); 49 | 50 | bufferFromFile = streamFromFile.GetBuffer(); 51 | bufferFromStream = streamFromStream.GetBuffer(); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/docxtemplateenginetest/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Newtonsoft.Json; 4 | using swxben.docxtemplateengine; 5 | using Newtonsoft.Json.Linq; 6 | using System.Dynamic; 7 | using System.Collections.Generic; 8 | 9 | namespace docxtemplateenginetest 10 | { 11 | class Program 12 | { 13 | static void Main(string[] args) 14 | { 15 | Console.WriteLine("docxtemplateenginetest, CC BY-SA 3.0, swxben.com"); 16 | 17 | if (args.Count() != 3) 18 | { 19 | Console.WriteLine("Usage: docxtemplateenginetest \"\""); 20 | Console.WriteLine("Eg: docxtemplateenginetest input.docx output.docx { name: 'Software by Ben' }"); 21 | return; 22 | } 23 | 24 | var source = args[0]; 25 | var destination = args[1]; 26 | var json = args[2]; 27 | 28 | var data = JsonToDynamic((JToken)JsonConvert.DeserializeObject(json)); 29 | 30 | var templateEngine = new DocXTemplateEngine(); 31 | 32 | Console.WriteLine("Processing..."); 33 | 34 | templateEngine.Process(source, destination, data); 35 | 36 | Console.WriteLine("Complete"); 37 | Console.WriteLine(); 38 | } 39 | 40 | static dynamic JsonToDynamic(JToken token) 41 | { 42 | if (token is JValue) return ((JValue)token).Value; 43 | 44 | if (token is JObject) 45 | { 46 | var expando = new ExpandoObject(); 47 | foreach (var childToken in token.OfType()) 48 | { 49 | ((IDictionary)expando).Add(childToken.Name, JsonToDynamic(childToken.Value)); 50 | } 51 | return expando; 52 | } 53 | 54 | if (token is JArray) 55 | { 56 | var items = new List(); 57 | foreach (var arrayItem in ((JArray)token)) 58 | { 59 | items.Add(JsonToDynamic(arrayItem)); 60 | } 61 | return items; 62 | } 63 | 64 | throw new ArgumentException(string.Format("Unknown token type {0}", token.GetType()), "token"); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/swxben.docxtemplateengine/swxben.docxtemplateengine.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {F639F573-6CB2-4500-A5BD-C0C31E0A279D} 9 | Library 10 | Properties 11 | swxben.docxtemplateengine 12 | swxben.docxtemplateengine 13 | v4.0 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | ..\..\packages\SharpZipLib.0.86.0\lib\20\ICSharpCode.SharpZipLib.dll 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 61 | -------------------------------------------------------------------------------- /src/docxtemplateenginetest/docxtemplateenginetest.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | x86 6 | 8.0.30703 7 | 2.0 8 | {6CC9B1B0-FF5E-48F3-B4E8-B0BDA46868CE} 9 | Exe 10 | Properties 11 | docxtemplateenginetest 12 | docxtemplateenginetest 13 | v4.0 14 | Client 15 | 512 16 | 17 | 18 | x86 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | x86 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | ..\..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | {F639F573-6CB2-4500-A5BD-C0C31E0A279D} 58 | swxben.docxtemplateengine 59 | 60 | 61 | 62 | 69 | -------------------------------------------------------------------------------- /src/Tests/Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | 8.0.30703 7 | 2.0 8 | {55CA6250-4469-43F9-BA9E-C4E42F8959F5} 9 | Library 10 | Properties 11 | Tests 12 | Tests 13 | v4.0 14 | 512 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | ..\..\packages\Newtonsoft.Json.4.5.11\lib\net40\Newtonsoft.Json.dll 36 | 37 | 38 | ..\..\packages\NUnit.2.6.2\lib\nunit.framework.dll 39 | 40 | 41 | ..\..\packages\Shouldly.1.1.1.1\lib\35\Shouldly.dll 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | {F639F573-6CB2-4500-A5BD-C0C31E0A279D} 64 | swxben.docxtemplateengine 65 | 66 | 67 | 68 | 69 | 70 | 71 | 78 | -------------------------------------------------------------------------------- /DocXTemplateEngine.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 11.00 3 | # Visual Studio 2010 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "swxben.docxtemplateengine", "src\swxben.docxtemplateengine\swxben.docxtemplateengine.csproj", "{F639F573-6CB2-4500-A5BD-C0C31E0A279D}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tests", "src\Tests\Tests.csproj", "{55CA6250-4469-43F9-BA9E-C4E42F8959F5}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{A47A9431-0FEE-41EF-B3EA-FC5989AC7CBA}" 9 | ProjectSection(SolutionItems) = preProject 10 | .nuget\packages.config = .nuget\packages.config 11 | EndProjectSection 12 | EndProject 13 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "docxtemplateenginetest", "src\docxtemplateenginetest\docxtemplateenginetest.csproj", "{6CC9B1B0-FF5E-48F3-B4E8-B0BDA46868CE}" 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|Any CPU = Debug|Any CPU 18 | Debug|Mixed Platforms = Debug|Mixed Platforms 19 | Debug|x86 = Debug|x86 20 | Release|Any CPU = Release|Any CPU 21 | Release|Mixed Platforms = Release|Mixed Platforms 22 | Release|x86 = Release|x86 23 | EndGlobalSection 24 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 25 | {F639F573-6CB2-4500-A5BD-C0C31E0A279D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 26 | {F639F573-6CB2-4500-A5BD-C0C31E0A279D}.Debug|Any CPU.Build.0 = Debug|Any CPU 27 | {F639F573-6CB2-4500-A5BD-C0C31E0A279D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 28 | {F639F573-6CB2-4500-A5BD-C0C31E0A279D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 29 | {F639F573-6CB2-4500-A5BD-C0C31E0A279D}.Debug|x86.ActiveCfg = Debug|Any CPU 30 | {F639F573-6CB2-4500-A5BD-C0C31E0A279D}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {F639F573-6CB2-4500-A5BD-C0C31E0A279D}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {F639F573-6CB2-4500-A5BD-C0C31E0A279D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 33 | {F639F573-6CB2-4500-A5BD-C0C31E0A279D}.Release|Mixed Platforms.Build.0 = Release|Any CPU 34 | {F639F573-6CB2-4500-A5BD-C0C31E0A279D}.Release|x86.ActiveCfg = Release|Any CPU 35 | {55CA6250-4469-43F9-BA9E-C4E42F8959F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {55CA6250-4469-43F9-BA9E-C4E42F8959F5}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {55CA6250-4469-43F9-BA9E-C4E42F8959F5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU 38 | {55CA6250-4469-43F9-BA9E-C4E42F8959F5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU 39 | {55CA6250-4469-43F9-BA9E-C4E42F8959F5}.Debug|x86.ActiveCfg = Debug|Any CPU 40 | {55CA6250-4469-43F9-BA9E-C4E42F8959F5}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {55CA6250-4469-43F9-BA9E-C4E42F8959F5}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {55CA6250-4469-43F9-BA9E-C4E42F8959F5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU 43 | {55CA6250-4469-43F9-BA9E-C4E42F8959F5}.Release|Mixed Platforms.Build.0 = Release|Any CPU 44 | {55CA6250-4469-43F9-BA9E-C4E42F8959F5}.Release|x86.ActiveCfg = Release|Any CPU 45 | {6CC9B1B0-FF5E-48F3-B4E8-B0BDA46868CE}.Debug|Any CPU.ActiveCfg = Debug|x86 46 | {6CC9B1B0-FF5E-48F3-B4E8-B0BDA46868CE}.Debug|Mixed Platforms.ActiveCfg = Debug|x86 47 | {6CC9B1B0-FF5E-48F3-B4E8-B0BDA46868CE}.Debug|Mixed Platforms.Build.0 = Debug|x86 48 | {6CC9B1B0-FF5E-48F3-B4E8-B0BDA46868CE}.Debug|x86.ActiveCfg = Debug|x86 49 | {6CC9B1B0-FF5E-48F3-B4E8-B0BDA46868CE}.Debug|x86.Build.0 = Debug|x86 50 | {6CC9B1B0-FF5E-48F3-B4E8-B0BDA46868CE}.Release|Any CPU.ActiveCfg = Release|x86 51 | {6CC9B1B0-FF5E-48F3-B4E8-B0BDA46868CE}.Release|Mixed Platforms.ActiveCfg = Release|x86 52 | {6CC9B1B0-FF5E-48F3-B4E8-B0BDA46868CE}.Release|Mixed Platforms.Build.0 = Release|x86 53 | {6CC9B1B0-FF5E-48F3-B4E8-B0BDA46868CE}.Release|x86.ActiveCfg = Release|x86 54 | {6CC9B1B0-FF5E-48F3-B4E8-B0BDA46868CE}.Release|x86.Build.0 = Release|x86 55 | EndGlobalSection 56 | GlobalSection(SolutionProperties) = preSolution 57 | HideSolutionNode = FALSE 58 | EndGlobalSection 59 | EndGlobal 60 | -------------------------------------------------------------------------------- /src/swxben.docxtemplateengine/DocXTemplateEngine.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Security.Cryptography; 6 | using System.Text; 7 | using ICSharpCode.SharpZipLib.Zip; 8 | 9 | namespace swxben.docxtemplateengine 10 | { 11 | public class DocXTemplateEngine : IDocXTemplateEngine 12 | { 13 | const string DocumentXmlPath = @"word/document.xml"; 14 | public const string TOKEN_START = "«"; 15 | public const string TOKEN_END = "»"; 16 | 17 | public void Process(string source, Stream destination, object data) 18 | { 19 | var sourceData = File.ReadAllBytes(source); 20 | destination.Write(sourceData, 0, sourceData.Length); 21 | destination.Seek(0, SeekOrigin.Begin); 22 | using (var zipFile = new ZipFile(destination)) 23 | { 24 | ProcessZip(data, zipFile); 25 | } 26 | } 27 | 28 | public void Process(string source, string destination, object data) 29 | { 30 | if (File.Exists(destination)) File.Delete(destination); 31 | 32 | File.Copy(source, destination); 33 | 34 | using (var zipFile = new ZipFile(destination)) 35 | { 36 | ProcessZip(data, zipFile); 37 | } 38 | } 39 | 40 | public void Process(Stream sourceStream, Stream destinationStream, object data) 41 | { 42 | sourceStream.CopyTo(destinationStream); 43 | destinationStream.Seek(0, SeekOrigin.Begin); 44 | 45 | using (var zipFile = new ZipFile(destinationStream)) 46 | { 47 | ProcessZip(data, zipFile); 48 | } 49 | } 50 | 51 | private static void ProcessZip(object data, ZipFile zipFile) 52 | { 53 | zipFile.BeginUpdate(); 54 | 55 | var document = ""; 56 | var entry = zipFile.GetEntry(DocumentXmlPath); 57 | if (entry == null) 58 | { 59 | throw new Exception(string.Format("Can't find {0} in template zip", DocumentXmlPath)); 60 | } 61 | 62 | using (var s = zipFile.GetInputStream(entry)) 63 | using (var reader = new StreamReader(s, Encoding.UTF8)) 64 | { 65 | document = reader.ReadToEnd(); 66 | } 67 | 68 | var newDocument = ParseTemplate(document, data); 69 | 70 | zipFile.Add(new StringStaticDataSource(newDocument), DocumentXmlPath, CompressionMethod.Deflated, true); 71 | 72 | zipFile.CommitUpdate(); 73 | 74 | } 75 | 76 | class StringStaticDataSource : IStaticDataSource 77 | { 78 | readonly string _source; 79 | 80 | public StringStaticDataSource(string source) 81 | { 82 | _source = source; 83 | } 84 | 85 | public Stream GetSource() 86 | { 87 | return new MemoryStream(Encoding.UTF8.GetBytes(_source)); 88 | } 89 | } 90 | 91 | public static string ParseTemplate(string document, object data) 92 | { 93 | document = data.GetType().GetFields().Aggregate(document, 94 | (current, field) => ReplaceTemplateField(current, field.Name, field.GetValue(data))); 95 | document = data.GetType().GetProperties().Aggregate(document, 96 | (current, property) => ReplaceTemplateField(current, property.Name, property.GetValue(data, null))); 97 | 98 | var dictionary = data as IDictionary; 99 | 100 | if (dictionary != null) 101 | { 102 | document = dictionary.Keys.Aggregate(document, (current, key) => ReplaceTemplateField(current, key, dictionary[key])); 103 | } 104 | 105 | return document; 106 | } 107 | 108 | public static string ReplaceTemplateField(string document, string fieldName, object fieldValue) 109 | { 110 | return document.Replace(TOKEN_START + fieldName + TOKEN_END, fieldValue == null ? string.Empty : fieldValue.ToString()); 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DocX Template Engine 2 | ==================== 3 | 4 | A template engine for the .NET platform which takes a DOCX file and applies a data object to generate reports, do mail merges, etc. All without introducing a dependency on MS Word. 5 | 6 | 7 | ## Installation 8 | 9 | Install the engine via [NuGet](http://nuget.org/packages/swxben.docxtemplateengine), either in Visual Studio (right-click project, Manage NuGet Packages, search for docxtemplateengine) or via the package manager console using `Install-Package DocXTemplateEngine`. 10 | 11 | 12 | ## Usage 13 | 14 | In `template.docx` - use a field (Insert / Quick Parts / Field), _Field type_ is `MergeField` and enter the field or property name in _Field_: 15 | 16 | Brought to you by «Name»! 17 | 18 | In code: 19 | 20 | var templateEngine = new swxben.docxtemplateengine.DocXTemplateEngine(); 21 | templateEngine.Process( 22 | source = "template.docx", 23 | destination = "dest.docx", 24 | data = new { 25 | Name = "SWXBEN" 26 | }); 27 | 28 | `data` can be just about any data type. Fields and properties are iterated, and if it can be cast to `IDictionary` (such as a dynamic `ExpandoObject`) the dictionary entries are iterated. 29 | 30 | `dest.docx` is created: 31 | 32 | Brought to you by SWXBEN! 33 | 34 | A very simple use case is given in the `docxtemplateenginetest` project, which is a command line application that takes an input filename, 35 | an output filename, and a JSON string. Eg: 36 | 37 | docxtemplateenginetest.exe template.docx output.docx "{ Name: 'Ben', Website: 'swxben.com' }" 38 | 39 | 40 | ## Null handling and not found properties 41 | 42 | If the data contains a null value for a given property, the template field will be replaced with an empty value (""). If the data doesn't contain a template field, the field won't be replaced at all. 43 | 44 | 45 | ## Limitations 46 | 47 | The templating language in the first milestone is a simple search and replace using the value `.ToString()`. No escaping of the 48 | template sequence, loops, formatting in document etc. The template _probably_ won't be recognised if there is any formatting within 49 | the template string such as an errant bold character like {{th**i**s}}. There are also issues with UTF8 encoding and characters such as em dash which are currently solved just by replacing the errant characters which isn't ideal. 50 | 51 | 52 | ## Versions 53 | 54 | ### 0.1.5 55 | 56 | - Safe handling of null values in the source data ([@MarkKGreenway](https://github.com/MarkKGreenway)) 57 | 58 | ### 0.1.4 59 | 60 | - Read from stream ([@MarkKGreenway](https://github.com/MarkKGreenway)) 61 | 62 | 63 | ## Roadmap 64 | 65 | ### First milestone - completed 20121204 66 | 67 | - Simple search and replace as given in the first example. 68 | - Originally I wanted plain text templates such as `[[Name]]` or `{{Name}}` but Word likes to insert runs for things like grammar warnings, so 69 | sticking to merge fields for now 70 | 71 | ### Second milestone - completed 20121205 72 | 73 | - NuGet package 74 | 75 | ### Third milestone 76 | 77 | - Nested objects, eg `{{Address.Line1}} {{Address.Line2}}` 78 | - Formatting in the template, eg `{{DateOfBirth.ToString("dd-MM-yyyy")}}` 79 | 80 | ### Fourth milestone 81 | 82 | - Flow control in the template, eg `{{foreach (var name in Names)}} {{name}}, {{endforeach}}` 83 | 84 | 85 | ## Contribute 86 | 87 | If you want to contribute to this project, start by forking the repo: . Create an issue if applicable, create a branch in your fork, and create a pull request when it's ready. Thanks! 88 | 89 | Note that the template engine implements the `IDocXTemplateEngine` interface, so any additions to the engine need to be reflected in the interface. 90 | 91 | **Please don't include any new versions of the NuGet package in your PR!** I'll upload a new package as soon as I'm happy with the changes in the new version. But thanks for your enthusisasm ;-) 92 | 93 | 94 | ## Building, running tests and the NuGet package 95 | 96 | THe VS2013 solution is in the root folder. Unit tests (src\Tests\bin\Debug\Tests.dll) can be run in a console using `tests.bat`. The NuGet package can be built by running `build-nuget-package.cmd`. The NuGet build script assumes VS 2013 (VS110). 97 | 98 | 99 | ## Licenses 100 | 101 | All files [CC BY-SA 3.0](http://creativecommons.org/licenses/by-sa/3.0/) unless otherwise specified. 102 | 103 | ### Third party licenses 104 | 105 | Third party libraries or resources have been included in this project under their respective licenses. 106 | 107 | - The engine uses SharpZipLib [1](http://www.icsharpcode.net/opensource/sharpziplib/) [2](https://github.com/icsharpcode/SharpZipLib) which is GPL with an exception meaning it can be used in commercial closed-source applications. 108 | - The example project uses [JSON.NET](http://json.codeplex.com/) which uses the [MIT License and is © James Newton-King](http://json.codeplex.com/license). 109 | 110 | 111 | ## Contributors & Maintainers 112 | 113 | - [@bendetat]() (maintainer) 114 | - [@deltasem](https://github.com/deltasem) 115 | - [@MarkKGreenway](https://github.com/MarkKGreenway) 116 | -------------------------------------------------------------------------------- /src/Tests/when_parsing_simple_template.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text; 6 | using NUnit.Framework; 7 | using Shouldly; 8 | using swxben.docxtemplateengine; 9 | using Newtonsoft.Json; 10 | using System.Dynamic; 11 | 12 | namespace Tests 13 | { 14 | [TestFixture] 15 | class when_parsing_simple_template 16 | { 17 | [Test] 18 | public void single_object_property_is_replaced() 19 | { 20 | var template = "Name: " + DocXTemplateEngine.TOKEN_START + "Name" + DocXTemplateEngine.TOKEN_END; 21 | var data = new { Name = "John" }; 22 | 23 | var result = DocXTemplateEngine.ParseTemplate(template, data); 24 | 25 | result.ShouldBe("Name: John"); 26 | } 27 | 28 | [Test] 29 | public void multiple_object_properties_are_replaced() 30 | { 31 | var template = "Name: " + DocXTemplateEngine.TOKEN_START + "Name" + DocXTemplateEngine.TOKEN_END + ", Age: " + DocXTemplateEngine.TOKEN_START + "Age" + DocXTemplateEngine.TOKEN_END; 32 | var data = new 33 | { 34 | Name = "Ben", 35 | Age = 32 36 | }; 37 | 38 | var result = DocXTemplateEngine.ParseTemplate(template, data); 39 | 40 | result.ShouldBe("Name: Ben, Age: 32"); 41 | } 42 | 43 | class PersonWithFields 44 | { 45 | public string Name; 46 | public int Age; 47 | } 48 | [Test] 49 | public void data_fields_are_used() 50 | { 51 | var template = "Name: " + DocXTemplateEngine.TOKEN_START + "Name" + DocXTemplateEngine.TOKEN_END + ", Age: " + DocXTemplateEngine.TOKEN_START + "Age" + DocXTemplateEngine.TOKEN_END; 52 | var data = new PersonWithFields 53 | { 54 | Name = "Sam", 55 | Age = 29 56 | }; 57 | 58 | var result = DocXTemplateEngine.ParseTemplate(template, data); 59 | 60 | result.ShouldBe("Name: Sam, Age: 29"); 61 | } 62 | 63 | class PersonWithProperties 64 | { 65 | public string Name { get; set; } 66 | public int Age { get; set; } 67 | } 68 | [Test] 69 | public void data_properties_are_used() 70 | { 71 | var template = "Name: " + DocXTemplateEngine.TOKEN_START + "Name" + DocXTemplateEngine.TOKEN_END + ", Age: " + DocXTemplateEngine.TOKEN_START + "Age" + DocXTemplateEngine.TOKEN_END; 72 | var data = new PersonWithProperties 73 | { 74 | Name = "Sam", 75 | Age = 29 76 | }; 77 | 78 | var result = DocXTemplateEngine.ParseTemplate(template, data); 79 | 80 | result.ShouldBe("Name: Sam, Age: 29"); 81 | } 82 | 83 | [Test] 84 | public void typed_field_object_from_json_is_used() 85 | { 86 | var template = "Name: " + DocXTemplateEngine.TOKEN_START + "Name" + DocXTemplateEngine.TOKEN_END + ", Age: " + DocXTemplateEngine.TOKEN_START + "Age" + DocXTemplateEngine.TOKEN_END; 87 | var data = JsonConvert.DeserializeObject("{ Name: 'Bob', Age: 26 }"); 88 | 89 | var result = DocXTemplateEngine.ParseTemplate(template, data); 90 | 91 | result.ShouldBe("Name: Bob, Age: 26"); 92 | } 93 | 94 | [Test] 95 | public void typed_property_object_from_json_is_used() 96 | { 97 | var template = "Name: " + DocXTemplateEngine.TOKEN_START + "Name" + DocXTemplateEngine.TOKEN_END + ", Age: " + DocXTemplateEngine.TOKEN_START + "Age" + DocXTemplateEngine.TOKEN_END; 98 | var data = JsonConvert.DeserializeObject("{ Name: 'Bob', Age: 26 }"); 99 | 100 | var result = DocXTemplateEngine.ParseTemplate(template, data); 101 | 102 | result.ShouldBe("Name: Bob, Age: 26"); 103 | } 104 | [Test] 105 | public void typed_property_object_is_used_with_null_property() 106 | { 107 | var template = "Name: " + DocXTemplateEngine.TOKEN_START + "Name" + DocXTemplateEngine.TOKEN_END + ", Age: " + DocXTemplateEngine.TOKEN_START + "Age" + DocXTemplateEngine.TOKEN_END; 108 | var data = JsonConvert.DeserializeObject("{ Name: 'Bob', Age: 26 }"); 109 | data.Name = null; 110 | 111 | var result = DocXTemplateEngine.ParseTemplate(template, data); 112 | 113 | result.ShouldBe("Name: , Age: 26"); 114 | } 115 | 116 | [Test] 117 | public void typed_field_object_is_used_with_null_field() 118 | { 119 | var template = "Name: " + DocXTemplateEngine.TOKEN_START + "Name" + DocXTemplateEngine.TOKEN_END + ", Age: " + DocXTemplateEngine.TOKEN_START + "Age" + DocXTemplateEngine.TOKEN_END; 120 | var data = JsonConvert.DeserializeObject("{ Name: 'Bob', Age: 26 }"); 121 | data.Name = null; 122 | 123 | var result = DocXTemplateEngine.ParseTemplate(template, data); 124 | 125 | result.ShouldBe("Name: , Age: 26"); 126 | } 127 | [Test] 128 | public void dynamic_object_is_used() 129 | { 130 | var template = "Name: " + DocXTemplateEngine.TOKEN_START + "Name" + DocXTemplateEngine.TOKEN_END + ", Age: " + DocXTemplateEngine.TOKEN_START + "Age" + DocXTemplateEngine.TOKEN_END; 131 | dynamic data = new ExpandoObject(); 132 | data.Name = "Maxi"; 133 | data.Age = 25; 134 | 135 | var result = (string)DocXTemplateEngine.ParseTemplate(template, data); 136 | 137 | result.ShouldBe("Name: Maxi, Age: 25"); 138 | } 139 | 140 | [Test] 141 | public void dynamic_object_is_used_with_null_item() 142 | { 143 | var template = "Name: " + DocXTemplateEngine.TOKEN_START + "Name" + DocXTemplateEngine.TOKEN_END + ", Age: " + DocXTemplateEngine.TOKEN_START + "Age" + DocXTemplateEngine.TOKEN_END; 144 | dynamic data = new ExpandoObject(); 145 | data.Name = "Maxi"; 146 | data.Age = null; 147 | 148 | var result = (string)DocXTemplateEngine.ParseTemplate(template, data); 149 | 150 | result.ShouldBe("Name: Maxi, Age: "); 151 | } 152 | 153 | [Test] 154 | public void property_not_found_is_not_replaced() 155 | { 156 | var template = "Name: " + DocXTemplateEngine.TOKEN_START + "Name" + DocXTemplateEngine.TOKEN_END + ", Age: " + DocXTemplateEngine.TOKEN_START + "Age" + DocXTemplateEngine.TOKEN_END; 157 | dynamic data = new ExpandoObject(); 158 | data.Name = "Sam"; 159 | 160 | var result = (string)DocXTemplateEngine.ParseTemplate(template, data); 161 | 162 | result.ShouldBe(string.Format("Name: Sam, Age: {0}Age{1}", DocXTemplateEngine.TOKEN_START, DocXTemplateEngine.TOKEN_END)); 163 | } 164 | } 165 | } 166 | --------------------------------------------------------------------------------