├── .config └── dotnet-tools.json ├── .editorconfig ├── appveyor.yml ├── TODO.md ├── LICENSE ├── src └── Serialize.OpenXml.CodeGen │ ├── Serialize.OpenXml.CodeGen.csproj │ ├── IOpenXmlHandler.cs │ ├── TypeMonitorCollection.cs │ ├── NamespaceAliasOrder.cs │ ├── Extensions │ ├── CodeStatementCollectionExtensions.cs │ ├── StringExtensions.cs │ └── TypeExtensions.cs │ ├── UriEqualityComparer.cs │ ├── OpenXmlPartBluePrintCollection.cs │ ├── ISerializeSettings.cs │ ├── IOpenXmlElementHandler.cs │ ├── DefaultSerializeSettings.cs │ ├── IOpenXmlPartHandler.cs │ ├── OpenXmlPartBluePrint.cs │ ├── NamespaceAliasOptions.cs │ ├── CodeNamespaceImportComparer.cs │ ├── TypeMonitor.cs │ ├── OpenXmlPartContainerExtensions.cs │ ├── OpenXmlPackageExtensions.cs │ └── OpenXmlPartExtensions.cs ├── Serialize.OpenXml.CodeGen.sln ├── README.md ├── CHANGELOG.md └── .gitignore /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "cake.tool": { 6 | "version": "1.3.0", 7 | "commands": [ 8 | "dotnet-cake" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | trim_trailing_whitespace = true 6 | indent_style = space 7 | 8 | [*.ps1] 9 | indent_size = 2 10 | 11 | [*.{cs,cake,csproj,sh}] 12 | indent_size = 4 13 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 0.5.0.{build} 2 | os: Visual Studio 2022 3 | 4 | # Only focus on the master branch 5 | branches: 6 | only: 7 | - master 8 | - main # If/when the master branch becomes 'main' 9 | 10 | install: 11 | 12 | build_script: 13 | - cmd: dotnet --info 14 | - cmd: dotnet tool restore 15 | - cmd: dotnet cake 16 | 17 | test: off 18 | 19 | artifacts: 20 | - path: artifacts/zip/*.zip 21 | - path: artifacts/nuget/*.nupkg -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | TODO 2 | ==== 3 | 4 | This is a simple list of current planned items to work on 5 | 6 | ## Version 1.0 7 | 8 | * Setup automated build process 9 | * ~~Cake~~ 10 | * ~~Appveyor~~ 11 | * ~~TravisCI (maybe)~~ 12 | * ~~Create nuget package~~ 13 | * ~~Create async versions of the CreateSourceCode extension methods~~ 14 | * Add hooks system to allow users to create custom code generation by OpenXml type _(in progress)_ 15 | * Setup Wiki 16 | * Add examples 17 | * Add test cases 18 | * Simplify code generation, if possible 19 | 20 | ## Version 2.0 21 | 22 | * Investigate using rosyln to generate source code 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/Serialize.OpenXml.CodeGen.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)/../../CHANGELOG.md 5 | 6 | 7 | netstandard2.0 8 | 0.5.0-beta 9 | Ryan Boggs 10 | MIT 11 | https://github.com/Docx2Src/Serialize.OpenXml.CodeGen 12 | false 13 | https://github.com/Docx2Src/Serialize.OpenXml.CodeGens 14 | $([System.IO.File]::ReadAllText($(ChangeLogFile))) 15 | git 16 | openxml;codedom;DocumentFormat 17 | .NET assembly class responsible for converting OpenXml based documents into corrisponding dotnet code 18 | ../../artifacts/nuget 19 | true 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/IOpenXmlHandler.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | 25 | namespace Serialize.OpenXml.CodeGen 26 | { 27 | /// 28 | /// Defines objects that provide custom code generation instructions for 29 | /// specific OpenXml SDK objects that the process may encounter. 30 | /// 31 | public interface IOpenXmlHandler 32 | { 33 | #region Properties 34 | 35 | /// 36 | /// The of object that the current object generates 37 | /// source code for. 38 | /// 39 | Type OpenXmlType { get; } 40 | 41 | #endregion 42 | } 43 | } -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/TypeMonitorCollection.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2021 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | 25 | namespace Serialize.OpenXml.CodeGen 26 | { 27 | /// 28 | /// Simple collection to maintain the objects for 29 | /// the code generation of a given . 30 | /// 31 | internal class TypeMonitorCollection 32 | : System.Collections.ObjectModel.KeyedCollection 33 | { 34 | #region Protected Instance Methods 35 | 36 | /// 37 | protected override Type GetKeyForItem(TypeMonitor item) => item.Type; 38 | 39 | #endregion 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Serialize.OpenXml.CodeGen.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.26124.0 5 | MinimumVisualStudioVersion = 15.0.26124.0 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Serialize.OpenXml.CodeGen", "src\Serialize.OpenXml.CodeGen\Serialize.OpenXml.CodeGen.csproj", "{1A80CC41-59B0-48DF-8E32-0E276841F769}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Debug|x64 = Debug|x64 12 | Debug|x86 = Debug|x86 13 | Release|Any CPU = Release|Any CPU 14 | Release|x64 = Release|x64 15 | Release|x86 = Release|x86 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {1A80CC41-59B0-48DF-8E32-0E276841F769}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {1A80CC41-59B0-48DF-8E32-0E276841F769}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {1A80CC41-59B0-48DF-8E32-0E276841F769}.Debug|x64.ActiveCfg = Debug|Any CPU 21 | {1A80CC41-59B0-48DF-8E32-0E276841F769}.Debug|x64.Build.0 = Debug|Any CPU 22 | {1A80CC41-59B0-48DF-8E32-0E276841F769}.Debug|x86.ActiveCfg = Debug|Any CPU 23 | {1A80CC41-59B0-48DF-8E32-0E276841F769}.Debug|x86.Build.0 = Debug|Any CPU 24 | {1A80CC41-59B0-48DF-8E32-0E276841F769}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {1A80CC41-59B0-48DF-8E32-0E276841F769}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {1A80CC41-59B0-48DF-8E32-0E276841F769}.Release|x64.ActiveCfg = Release|Any CPU 27 | {1A80CC41-59B0-48DF-8E32-0E276841F769}.Release|x64.Build.0 = Release|Any CPU 28 | {1A80CC41-59B0-48DF-8E32-0E276841F769}.Release|x86.ActiveCfg = Release|Any CPU 29 | {1A80CC41-59B0-48DF-8E32-0E276841F769}.Release|x86.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {9AB1B35F-7F1E-42E8-81B4-DBCE1CEBF92F} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/NamespaceAliasOrder.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | namespace Serialize.OpenXml.CodeGen 24 | { 25 | /// 26 | /// Identifies which order that the namespace alias assignment 27 | /// is performed for a given .NET language. 28 | /// 29 | public enum NamespaceAliasOrder : byte 30 | { 31 | /// 32 | /// Indicates that a given .NET language does not support 33 | /// namespace aliases. 34 | /// 35 | None = 0, 36 | 37 | /// 38 | /// Indicates that the namespace should be placed before 39 | /// the alias in the import/using statement. 40 | /// 41 | NamespaceFirst, 42 | 43 | /// 44 | /// /// Indicates that the alias should be placed before 45 | /// the namespace in the import/using statement. 46 | /// 47 | AliasFirst 48 | } 49 | } -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/Extensions/CodeStatementCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | using System.CodeDom; 25 | 26 | namespace Serialize.OpenXml.CodeGen.Extentions 27 | { 28 | /// 29 | /// Static class containing extension methods for the class. 30 | /// 31 | public static class CodeStatementCollectionExtensions 32 | { 33 | #region Public Static Methods 34 | 35 | /// 36 | /// Adds a blank/empty code statement to an existing object. 37 | /// 38 | /// 39 | /// The existing object to add the blank line statement to. 40 | /// 41 | public static void AddBlankLine(this CodeStatementCollection statements) => 42 | statements.Add(new CodeSnippetStatement(String.Empty)); 43 | 44 | #endregion 45 | } 46 | } -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/UriEqualityComparer.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using DocumentFormat.OpenXml.Packaging; 26 | 27 | namespace Serialize.OpenXml.CodeGen 28 | { 29 | /// 30 | /// Equality comparer class used to ensure that circular part references are avoided when 31 | /// trying to build source code for objects. 32 | /// 33 | public sealed class UriEqualityComparer : EqualityComparer 34 | { 35 | #region Public Static Fields 36 | 37 | /// 38 | /// A default to use when comparing 2 39 | /// objects. 40 | /// 41 | public static readonly UriEqualityComparer DefaultComparer = new UriEqualityComparer(); 42 | 43 | #endregion 44 | 45 | #region Public Instance Methods 46 | 47 | /// 48 | public override bool Equals(Uri x, Uri y) 49 | { 50 | if (x == null || y == null) return false; 51 | return x == y; 52 | } 53 | 54 | /// 55 | public override int GetHashCode(Uri obj) 56 | { 57 | return obj.ToString().GetHashCode(); 58 | } 59 | 60 | #endregion 61 | } 62 | } -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/OpenXmlPartBluePrintCollection.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | using System.Collections.ObjectModel; 25 | 26 | namespace Serialize.OpenXml.CodeGen 27 | { 28 | /// 29 | /// Collection class used to organize all of the objects 30 | /// for a single request. 31 | /// 32 | public sealed class OpenXmlPartBluePrintCollection : KeyedCollection 33 | { 34 | #region Internal Constructors 35 | 36 | /// 37 | /// Initializes a new instance of the class 38 | /// that is empty. 39 | /// 40 | internal OpenXmlPartBluePrintCollection() : base(new UriEqualityComparer()) { } 41 | 42 | #endregion 43 | 44 | #region Public Instance Methods 45 | 46 | /// 47 | /// Gets the object associated with the specified 48 | /// . 49 | /// 50 | /// 51 | /// The of the object to get. 52 | /// 53 | /// 54 | /// When the method returns, contains the object 55 | /// associated with , if found; otherwise, the default value 56 | /// of is returned (). 57 | /// This parameter is passed uninitialized. 58 | /// 59 | /// 60 | /// if collection contains an 61 | /// object with the specified ; otherwise, . 62 | /// 63 | public bool TryGetValue(Uri uri, out OpenXmlPartBluePrint bp) 64 | { 65 | if (uri == null || !Contains(uri)) 66 | { 67 | bp = null; 68 | return false; 69 | } 70 | bp = this[uri]; 71 | return true; 72 | } 73 | 74 | #endregion 75 | 76 | #region Protected Instance Methods 77 | 78 | /// 79 | protected override Uri GetKeyForItem(OpenXmlPartBluePrint item) => item.Uri; 80 | 81 | #endregion 82 | } 83 | } -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/ISerializeSettings.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Xml; 26 | using DocumentFormat.OpenXml; 27 | 28 | namespace Serialize.OpenXml.CodeGen 29 | { 30 | /// 31 | /// Defines objects containing settings to use when generating 32 | /// source code for OpenXml SDK based files. 33 | /// 34 | public interface ISerializeSettings 35 | { 36 | #region Properties 37 | 38 | /// 39 | /// Gets the lookup collection of objects to use 40 | /// for custom code generations for specific OpenXml SDK objects. 41 | /// 42 | /// 43 | /// If this collection is or if the current OpenXml SDK 44 | /// object type being analyzed does not exist, then the default code generation 45 | /// process will be used. 46 | /// 47 | IReadOnlyDictionary Handlers { get; } 48 | 49 | /// 50 | /// Gets all of the setting values of the 51 | /// objects to ignore. 52 | /// 53 | /// 54 | /// If this collection is or if the 55 | /// value of a object does not exist in this collection, 56 | /// then the object will be processed as a normal 57 | /// object. 58 | /// 59 | IReadOnlyList IgnoreMiscNodeTypes { get; } 60 | 61 | /// 62 | /// Indicates whether or not to ignore 63 | /// objects when generating source code. 64 | /// 65 | bool IgnoreUnknownElements { get; } 66 | 67 | /// 68 | /// Gets the object to use during the code 69 | /// generation process. 70 | /// 71 | NamespaceAliasOptions NamespaceAliasOptions { get; } 72 | 73 | /// 74 | /// Gets the name of the namespace to use for the generated code. 75 | /// 76 | string NamespaceName { get; } 77 | 78 | /// 79 | /// Indicates whether or not to generate unique variable names for each 80 | /// object being serialized. 81 | /// 82 | bool UseUniqueVariableNames { get; } 83 | 84 | #endregion 85 | } 86 | } -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/IOpenXmlElementHandler.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | using System.CodeDom; 25 | using System.Collections.Generic; 26 | using System.Collections.ObjectModel; 27 | using System.Threading; 28 | using DocumentFormat.OpenXml; 29 | 30 | namespace Serialize.OpenXml.CodeGen 31 | { 32 | /// 33 | /// Defines objects that provide custom code generation instructions for 34 | /// derived objects that the process may 35 | /// encounter. 36 | /// 37 | public interface IOpenXmlElementHandler : IOpenXmlHandler 38 | { 39 | #region Methods 40 | 41 | /// 42 | /// Builds custom code objects that would build the contents of 43 | /// . 44 | /// 45 | /// 46 | /// The object to codify. 47 | /// 48 | /// 49 | /// The to use during the code generation 50 | /// process. 51 | /// 52 | /// 53 | /// A lookup containing the 54 | /// available elements to use for variable naming 55 | /// purposes. 56 | /// 57 | /// 58 | /// Collection used to keep track of all 59 | /// openxml namespaces used during the process. 60 | /// 61 | /// 62 | /// Task cancellation token from the parent method. 63 | /// 64 | /// 65 | /// The variable name of the root object that was built 66 | /// from the . 67 | /// 68 | /// 69 | /// A collection of code statements and expressions that could be used to generate 70 | /// a new object from code. 71 | /// 72 | /// 73 | /// If this method returns , the default implementation will 74 | /// be used instead. 75 | /// 76 | CodeStatementCollection BuildCodeStatements( 77 | OpenXmlElement element, 78 | ISerializeSettings settings, 79 | KeyedCollection types, 80 | IDictionary namespaces, 81 | CancellationToken token, 82 | out string elementName); 83 | 84 | #endregion 85 | } 86 | } -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/DefaultSerializeSettings.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.Xml; 26 | using System.Threading.Tasks; 27 | 28 | namespace Serialize.OpenXml.CodeGen 29 | { 30 | /// 31 | /// A default implementation of the interface 32 | /// to use when custom object is not provided. 33 | /// 34 | internal sealed class DefaultSerializeSettings : ISerializeSettings 35 | { 36 | #region Internal Static Fields 37 | 38 | /// 39 | /// object used to call the async methods in 40 | /// a synchronous fashion. 41 | /// 42 | /// 43 | /// This is being used in order to avoid big breaking changes. 44 | /// 45 | internal static readonly TaskFactory TaskIndustry = new TaskFactory( 46 | System.Threading.CancellationToken.None, 47 | TaskCreationOptions.None, 48 | TaskContinuationOptions.None, 49 | TaskScheduler.Default); 50 | 51 | #endregion 52 | 53 | #region Public Constructors 54 | 55 | /// 56 | /// Initializes a new instance of the 57 | /// class that is empty. 58 | /// 59 | public DefaultSerializeSettings() : this(NamespaceAliasOptions.Default) {} 60 | 61 | /// 62 | /// Initializes a new instance of the 63 | /// class with the requested object. 64 | /// 65 | /// 66 | public DefaultSerializeSettings(NamespaceAliasOptions nsAliasOpts) 67 | { 68 | NamespaceAliasOptions = nsAliasOpts 69 | ?? throw new ArgumentNullException(nameof(nsAliasOpts)); 70 | } 71 | 72 | #endregion 73 | 74 | #region Public Instance Properties 75 | 76 | /// 77 | public IReadOnlyDictionary Handlers => null; 78 | 79 | /// 80 | public IReadOnlyList IgnoreMiscNodeTypes => null; 81 | 82 | /// 83 | public bool IgnoreUnknownElements => false; 84 | 85 | /// 86 | public NamespaceAliasOptions NamespaceAliasOptions 87 | { 88 | get; 89 | private set; 90 | } 91 | 92 | /// 93 | public string NamespaceName => "OpenXmlSample"; 94 | 95 | /// 96 | public bool UseUniqueVariableNames => false; 97 | 98 | #endregion 99 | } 100 | } -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/IOpenXmlPartHandler.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | using System.CodeDom; 25 | using System.Collections.Generic; 26 | using System.Threading; 27 | using DocumentFormat.OpenXml.Packaging; 28 | 29 | namespace Serialize.OpenXml.CodeGen 30 | { 31 | /// 32 | /// Defines objects that provide custom code generation instructions for 33 | /// derived objects that the process may encounter. 34 | /// 35 | public interface IOpenXmlPartHandler : IOpenXmlHandler 36 | { 37 | #region Methods 38 | 39 | /// 40 | /// Creates the appropriate code objects needed to create the entry method for the 41 | /// current request. 42 | /// 43 | /// 44 | /// The object and relationship id to build code for. 45 | /// 46 | /// 47 | /// The to use during the code generation 48 | /// process. 49 | /// 50 | /// /// 51 | /// A lookup object containing the 52 | /// number of times a given type was referenced. This is used for variable naming 53 | /// purposes. 54 | /// 55 | /// 56 | /// Collection used to keep track of all 57 | /// openxml namespaces used during the process. 58 | /// 59 | /// 60 | /// The collection of objects that have already been 61 | /// visited. 62 | /// 63 | /// 64 | /// The root variable name and to use when building code 65 | /// statements to create new objects. 66 | /// 67 | /// 68 | /// Task cancellation token from the parent method. 69 | /// 70 | /// 71 | /// A collection of code statements and expressions that could be used to generate 72 | /// a new object from code. 73 | /// 74 | /// 75 | /// If this method returns , the default implementation will 76 | /// be used instead. 77 | /// 78 | CodeStatementCollection BuildEntryMethodCodeStatements( 79 | IdPartPair part, 80 | ISerializeSettings settings, 81 | IDictionary typeCounts, 82 | IDictionary namespaces, 83 | OpenXmlPartBluePrintCollection blueprints, 84 | KeyValuePair rootVar, 85 | CancellationToken token); 86 | 87 | /// 88 | /// Builds the helper method of a given . 89 | /// 90 | /// 91 | /// The object to generate the source code for. 92 | /// 93 | /// 94 | /// The name of the method to use when building the 95 | /// 96 | /// 97 | /// The to use during the code generation 98 | /// process. 99 | /// 100 | /// 101 | /// Collection used to keep track of all 102 | /// openxml namespaces used during the process. 103 | /// 104 | /// 105 | /// Task cancellation token from the parent method. 106 | /// 107 | /// 108 | /// A new object containing the necessary source code 109 | /// to recreate . 110 | /// 111 | /// 112 | /// If this method returns , the default implementation will 113 | /// be used instead. 114 | /// 115 | CodeMemberMethod BuildHelperMethod( 116 | OpenXmlPart part, 117 | string methodName, 118 | ISerializeSettings settings, 119 | IDictionary namespaces, 120 | CancellationToken token); 121 | 122 | #endregion 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/OpenXmlPartBluePrint.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | using System.Globalization; 25 | using DocumentFormat.OpenXml.Packaging; 26 | 27 | namespace Serialize.OpenXml.CodeGen 28 | { 29 | /// 30 | /// Simple organization class used to keep track of all the OpenXmlPart details needed 31 | /// to complete a code generation request. 32 | /// 33 | public sealed class OpenXmlPartBluePrint : IEquatable 34 | { 35 | #region Public Constructrs 36 | 37 | /// 38 | /// Initializes a new instance of the class 39 | /// with the required object and instance variable name. 40 | /// 41 | /// 42 | /// The object. 43 | /// 44 | /// 45 | /// Variable name that was initialized with in the 46 | /// generated code. 47 | /// 48 | /// 49 | /// is or 50 | /// is or blank. 51 | /// 52 | public OpenXmlPartBluePrint(OpenXmlPart part, string varName) 53 | { 54 | Part = part ?? throw new ArgumentNullException(nameof(part)); 55 | VariableName = varName ?? throw new ArgumentNullException(nameof(varName)); 56 | MethodName = this.CreateMethodName(VariableName); 57 | PartType = Part.GetType(); 58 | } 59 | 60 | #endregion 61 | 62 | #region Public Instance Properties 63 | 64 | /// 65 | /// Gets the name to use when generating the code responsible for creating 66 | /// . 67 | /// 68 | public string MethodName { get; private set;} 69 | 70 | /// 71 | /// Gets the object that this instance represents. 72 | /// 73 | public OpenXmlPart Part { get; private set; } 74 | 75 | /// 76 | /// Gets the of that this instance represents. 77 | /// 78 | public Type PartType { get; private set; } 79 | 80 | /// 81 | /// Gets the for this object. 82 | /// 83 | public Uri Uri => Part?.Uri; 84 | 85 | /// 86 | /// Gets the variable name that was initialized with. 87 | /// 88 | public string VariableName { get; private set; } 89 | 90 | #endregion 91 | 92 | #region Public Instance Methods 93 | 94 | /// 95 | public override bool Equals(object obj) 96 | { 97 | if (obj == null) return false; 98 | if (!(obj is OpenXmlPartBluePrint)) return false; 99 | if (Object.ReferenceEquals((object)this, obj)) return true; 100 | return Equals(obj as OpenXmlPartBluePrint); 101 | } 102 | 103 | /// 104 | public bool Equals(OpenXmlPartBluePrint other) 105 | { 106 | if (other == null) return false; 107 | return UriEqualityComparer.DefaultComparer.Equals(Uri, other.Uri); 108 | } 109 | 110 | /// 111 | public override int GetHashCode() 112 | { 113 | if (Uri == null) return 0; 114 | return Uri.GetHashCode(); 115 | } 116 | 117 | #endregion 118 | 119 | #region Private Instance Methods 120 | 121 | /// 122 | /// Create a method name for the current instance. 123 | /// 124 | /// 125 | /// The variable name that this instance was initialized with. 126 | /// 127 | /// 128 | /// A method name. 129 | /// 130 | private string CreateMethodName(string varName) 131 | { 132 | CultureInfo ci = CultureInfo.CurrentCulture; 133 | char[] chars = new char[varName.Length]; 134 | 135 | for (int i = 0; i < varName.Length; i++) 136 | { 137 | if (i == 0) 138 | { 139 | chars[i] = !Char.IsUpper(varName[i]) 140 | ? Char.ToUpper(varName[i], ci) 141 | : varName[i]; 142 | continue; 143 | } 144 | chars[i] = varName[i]; 145 | } 146 | return $"Generate{new string(chars)}"; 147 | } 148 | 149 | #endregion 150 | } 151 | } -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/Extensions/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | using System.CodeDom; 25 | using System.Globalization; 26 | using System.IO; 27 | using System.Linq; 28 | using System.Text; 29 | using System.Text.RegularExpressions; 30 | 31 | namespace Serialize.OpenXml.CodeGen.Extentions 32 | { 33 | /// 34 | /// Static class containing extension methods for the class. 35 | /// 36 | public static class StringExtensions 37 | { 38 | #region Public Static Methods 39 | 40 | /// 41 | /// Strips out the standard header text that is included when a 42 | /// 43 | /// derived class 44 | /// generates dotnet code. 45 | /// 46 | /// 47 | /// The raw code produced by the 48 | /// 49 | /// derived class. 50 | /// 51 | /// 52 | /// A new code value with the default headers removed. 53 | /// 54 | public static string RemoveOutputHeaders(this string raw) 55 | { 56 | var indicator = new string('-', 78); 57 | var sb = new StringBuilder(); 58 | bool inHeader = false; 59 | 60 | using (var sr = new StringReader(raw)) 61 | { 62 | string currentLine = sr.ReadLine(); 63 | while (currentLine != null) 64 | { 65 | if (currentLine.EndsWith(indicator)) 66 | { 67 | if (inHeader) currentLine = sr.ReadLine(); 68 | inHeader = !inHeader; 69 | } 70 | if (Regex.IsMatch(currentLine, "\".*\\\'.*\"")) 71 | { 72 | currentLine = currentLine.Replace("\\'", "'"); 73 | } 74 | if (!inHeader) sb.AppendLine(currentLine); 75 | currentLine = sr.ReadLine(); 76 | } 77 | } 78 | return sb.ToString(); 79 | } 80 | 81 | /// 82 | /// Retrieves only the upper case characters from a given string. 83 | /// 84 | /// The to analyze. 85 | /// 86 | /// The upper case characters from . 87 | /// 88 | public static string RetrieveUpperCaseChars(this string s) 89 | { 90 | var cs = s.Where(c => Char.IsUpper(c)).ToArray(); 91 | return new string(cs); 92 | } 93 | 94 | /// 95 | /// Ensures that the first letter of a given string is lowercase. 96 | /// 97 | /// 98 | /// The to manipulate. 99 | /// 100 | /// 101 | /// The same value as with the first character converted 102 | /// to lowercase. 103 | /// 104 | public static string ToCamelCase(this string s) 105 | { 106 | if (String.IsNullOrWhiteSpace(s)) return s; 107 | var sArray = s.ToCharArray(); 108 | 109 | sArray[0] = Char.ToLowerInvariant(sArray[0]); 110 | return new string(sArray); 111 | } 112 | 113 | /// 114 | /// Ensures that the only the first letter of is 115 | /// capitalized. 116 | /// 117 | /// 118 | /// The to capitalize. 119 | /// 120 | /// 121 | /// A new with the value of that has only 122 | /// the first letter capitalized. 123 | /// 124 | public static string ToTitleCase(this string s) 125 | { 126 | CultureInfo ci = CultureInfo.CurrentCulture; 127 | char[] chars = new char[s.Length]; 128 | 129 | for (int i = 0; i < s.Length; i++) 130 | { 131 | if (i == 0) 132 | { 133 | chars[i] = !Char.IsUpper(s[i]) 134 | ? Char.ToUpper(s[i], ci) 135 | : s[i]; 136 | continue; 137 | } 138 | chars[i] = !Char.IsWhiteSpace(s[i]) && !Char.IsLower(s[i]) 139 | ? Char.ToLower(s[i], ci) 140 | : s[i]; 141 | } 142 | return new string(chars); 143 | } 144 | 145 | #endregion 146 | } 147 | } -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/NamespaceAliasOptions.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | using System.CodeDom; 25 | 26 | namespace Serialize.OpenXml.CodeGen 27 | { 28 | /// 29 | /// Class used to organize namespace import information 30 | /// during the OpenXml document reflection process. 31 | /// 32 | public class NamespaceAliasOptions 33 | { 34 | #region Public Static Fields 35 | 36 | /// 37 | /// Gets a object to use 38 | /// with .NET languages that do not support namespace aliasing. 39 | /// 40 | public static readonly NamespaceAliasOptions Empty 41 | = new NamespaceAliasOptions(); 42 | 43 | /// 44 | /// Gets a object to use 45 | /// with the first class Microsoft .NET languages, C# and 46 | /// VB.NET. 47 | /// 48 | public static readonly NamespaceAliasOptions Default 49 | = new NamespaceAliasOptions() 50 | { 51 | Order = NamespaceAliasOrder.AliasFirst, 52 | AssignmentOperator = "=" 53 | }; 54 | 55 | #endregion 56 | 57 | #region Public Constructors 58 | 59 | /// 60 | /// Initializes a new instance of the 61 | /// class that is empty. 62 | /// 63 | public NamespaceAliasOptions() 64 | { 65 | AssignmentOperator = String.Empty; 66 | Order = NamespaceAliasOrder.None; 67 | } 68 | 69 | #endregion 70 | 71 | #region Public Instance Properties 72 | 73 | /// 74 | /// Gets or sets the text used to assign an alias to a namespace. 75 | /// 76 | public string AssignmentOperator 77 | { 78 | get; 79 | set; 80 | } 81 | 82 | /// 83 | /// Indicates which order the namespace alias assignment is 84 | /// specified. 85 | /// 86 | public NamespaceAliasOrder Order 87 | { 88 | get; 89 | set; 90 | } 91 | 92 | #endregion 93 | 94 | #region Public Instance Methods 95 | 96 | /// 97 | /// Builds a new object based 98 | /// on the settings of the current object. 99 | /// 100 | /// 101 | /// The namespace to build the new 102 | /// object. 103 | /// 104 | /// 105 | /// The alias name to use when building the new 106 | /// object. 107 | /// 108 | /// 109 | /// A new object. 110 | /// 111 | public virtual CodeNamespaceImport BuildNamespaceImport(string ns, string alias) 112 | { 113 | if (String.IsNullOrWhiteSpace(alias) || 114 | Order == NamespaceAliasOrder.None) 115 | return new CodeNamespaceImport(ns); 116 | 117 | const string assignment = "{0} {1} {2}"; 118 | string name = String.Empty; 119 | 120 | switch (Order) 121 | { 122 | case NamespaceAliasOrder.NamespaceFirst: 123 | name = String.Format(assignment, 124 | ns.Trim(), 125 | AssignmentOperator.Trim(), 126 | alias.Trim()); 127 | break; 128 | case NamespaceAliasOrder.AliasFirst: 129 | name = String.Format(assignment, 130 | alias.Trim(), 131 | AssignmentOperator.Trim(), 132 | ns.Trim()); 133 | break; 134 | } 135 | return new CodeNamespaceImport(name); 136 | } 137 | 138 | /// 139 | /// Builds a new object based 140 | /// on the settings of the current object. 141 | /// 142 | /// 143 | /// The object containing the namespace to build 144 | /// the new object. 145 | /// 146 | /// 147 | /// The alias name to use when building the new 148 | /// object. 149 | /// 150 | /// 151 | /// A new object. 152 | /// 153 | public CodeNamespaceImport BuildNamespaceImport(Type ns, string alias) 154 | { 155 | return BuildNamespaceImport(ns.Namespace, alias); 156 | } 157 | 158 | #endregion 159 | } 160 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Open XML Document Code Generator 2 | ================================ 3 | 4 | ![GitHub](https://img.shields.io/github/license/rmboggs/Serialize.OpenXml.CodeGen) 5 | 6 | [![Build status](https://ci.appveyor.com/api/projects/status/rachpeoigx71q1ro/branch/master?svg=true)](https://ci.appveyor.com/project/rmboggs/serialize-openxml-codegen/branch/master) ![Nuget](https://img.shields.io/nuget/v/Serialize.OpenXml.CodeGen) 7 | 8 | The Open XML Document Code Generator is a dotnet standard library that contains processes that convert OpenXML documents (such as .docx, .pptx, and .xlsx) into [CodeCompileUnit](https://docs.microsoft.com/en-us/dotnet/api/system.codedom.codecompileunit?view=netcore-3.1) objects that can be transformed into source code using any class that inherits from the [CodeDomProvider](https://docs.microsoft.com/en-us/dotnet/api/system.codedom.compiler.codedomprovider?view=netcore-3.1) class. This allows source code generation into other .NET languages, such as Visual Basic.net, for greater learning possibilities. 9 | 10 | Please be aware that while this project is producing working code, I would consider this is in beta status and not yet ready for production. More testing should be done before it will be production ready. 11 | 12 | As of version 0.4.0-beta, this project is now generating code that can reproduce entire basic OpenXml documents. More testing is required of advanced document types before this project can move beyond beta status. 13 | 14 | Looking for a front end interface for this project? Check out [DocxToSource](https://github.com/rmboggs/DocxToSource). There is still much work to be done but it is becoming a nice replacement for the old OpenXml SDK Productivity Tool. 15 | 16 | ## Examples 17 | 18 | Generate the [CodeCompileUnit](https://docs.microsoft.com/en-us/dotnet/api/system.codedom.codecompileunit?view=netcore-3.1) to process manually: 19 | 20 | ```cs 21 | using System; 22 | using System.IO; 23 | using System.Linq; 24 | using System.Text; 25 | using Serialize.OpenXml.CodeGen; 26 | using System.CodeDom.Compiler; 27 | using Microsoft.CSharp; 28 | using DocumentFormat.OpenXml; 29 | using DocumentFormat.OpenXml.Packaging; 30 | 31 | namespace CodeGenSample 32 | { 33 | class Program 34 | { 35 | private static readonly CodeGeneratorOptions Cgo = new CodeGeneratorOptions() 36 | { 37 | BracingStyle = "C" 38 | }; 39 | 40 | static void Main(string[] args) 41 | { 42 | var sourceFile = new FileInfo(@"C:\Temp\Sample1.xlsx"); 43 | var targetFile = new FileInfo(@"C:\Temp\Sample1.cs"); 44 | 45 | using (var source = sourceFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) 46 | { 47 | using (var xlsx = SpreadsheetDocument.Open(source, false)) 48 | { 49 | if (xlsx != null) 50 | { 51 | var codeString = new StringBuilder(); 52 | var cs = new CSharpCodeProvider(); 53 | 54 | // This will build the CodeCompileUnit object containing all of 55 | // the commands that would create the source code to rebuild Sample1.xlsx 56 | var code = xlsx.GenerateSourceCode(); 57 | 58 | // This will convert the CodeCompileUnit into C# source code 59 | using (var sw = new StringWriter(codeString)) 60 | { 61 | cs.GenerateCodeFromCompileUnit(code, sw, Cgo); 62 | } 63 | 64 | // Save the source code to the target file 65 | using (var target = targetFile.Open(FileMode.Create, FileAccess.ReadWrite)) 66 | { 67 | using (var tw = new StreamWriter(target)) 68 | { 69 | tw.Write(codeString.ToString().Trim()); 70 | } 71 | target.Close(); 72 | } 73 | } 74 | } 75 | source.Close(); 76 | } 77 | Console.WriteLine("Press any key to quit"); 78 | Console.ReadKey(true); 79 | } 80 | } 81 | } 82 | ``` 83 | 84 | Generate the actual source code as a string value: 85 | 86 | ```cs 87 | using System; 88 | using System.IO; 89 | using System.Linq; 90 | using System.Text; 91 | using Serialize.OpenXml.CodeGen; 92 | using System.CodeDom.Compiler; 93 | using Microsoft.VisualBasic; 94 | using DocumentFormat.OpenXml; 95 | using DocumentFormat.OpenXml.Packaging; 96 | 97 | namespace CodeGenSample 98 | { 99 | class Program 100 | { 101 | static void Main(string[] args) 102 | { 103 | var sourceFile = new FileInfo(@"./Sample1.xlsx"); 104 | var targetFile = new FileInfo(@"./Sample1.vb"); 105 | 106 | using (var source = sourceFile.Open(FileMode.Open, FileAccess.Read, FileShare.Read)) 107 | { 108 | using (var xlsx = SpreadsheetDocument.Open(source, false)) 109 | { 110 | if (xlsx != null) 111 | { 112 | // Generate VB.NET source code 113 | var vb = new VBCodeProvider(); 114 | 115 | // Save the source code to the target file 116 | using (var target = targetFile.Open(FileMode.Create, FileAccess.ReadWrite)) 117 | { 118 | using (var tw = new StreamWriter(target)) 119 | { 120 | // Providing the CodeDomProvider object as a parameter will 121 | // cause the method to return the source code as a string 122 | tw.Write(xlsx.GenerateSourceCode(vb).Trim()); 123 | } 124 | target.Close(); 125 | } 126 | } 127 | } 128 | source.Close(); 129 | } 130 | Console.WriteLine("Press any key to quit"); 131 | Console.ReadKey(true); 132 | } 133 | } 134 | } 135 | ``` 136 | -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/CodeNamespaceImportComparer.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | using System; 24 | using System.Collections.Generic; 25 | using System.CodeDom; 26 | 27 | namespace Serialize.OpenXml.CodeGen 28 | { 29 | /// 30 | /// Comparer based class used to sort objects. 31 | /// 32 | public class CodeNamespaceImportComparer 33 | : Comparer, 34 | IEqualityComparer 35 | { 36 | #region Private Static Fields 37 | 38 | /// 39 | /// Holds the comparer object to use for all string comparison operations. 40 | /// 41 | private static readonly StringComparer _cmpr = StringComparer.Ordinal; 42 | 43 | #endregion 44 | 45 | #region Private Instance Fields 46 | 47 | /// 48 | /// Holds the object to use when comparing. 49 | /// 50 | private readonly NamespaceAliasOptions _opts; 51 | 52 | #endregion 53 | 54 | #region Public Constructors 55 | 56 | /// 57 | /// Initializes a new instance of the 58 | /// class with the object of the current 59 | /// request. 60 | /// 61 | /// 62 | /// The object to use when comparing. 63 | /// 64 | public CodeNamespaceImportComparer(NamespaceAliasOptions options) 65 | { 66 | _opts = options ?? throw new ArgumentNullException(nameof(options)); 67 | } 68 | 69 | #endregion 70 | 71 | #region Public Instance Methods 72 | 73 | /// 74 | public override int Compare(CodeNamespaceImport x, CodeNamespaceImport y) 75 | { 76 | // Check to make sure that either both CodeNamespaceImport objects 77 | // have the assignment operator or both do not have one. 78 | if (!String.IsNullOrWhiteSpace(_opts.AssignmentOperator)) 79 | { 80 | if (x.Namespace.Contains(_opts.AssignmentOperator) && 81 | !y.Namespace.Contains(_opts.AssignmentOperator)) 82 | { 83 | return 1; 84 | } 85 | if (!x.Namespace.Contains(_opts.AssignmentOperator) && 86 | y.Namespace.Contains(_opts.AssignmentOperator)) 87 | { 88 | return -1; 89 | } 90 | } 91 | 92 | // Check the namespace name first. 93 | if (!_cmpr.Equals(x.Namespace, y.Namespace)) 94 | { 95 | return _cmpr.Compare(x.Namespace, y.Namespace); 96 | } 97 | 98 | // See if any of the LinePragma properties are null and return the appropriate code. 99 | if (x.LinePragma is null && y.LinePragma is null) return 0; 100 | if (x.LinePragma is null) return 1; 101 | if (y.LinePragma is null) return -1; 102 | 103 | // Compare linepragma properties if both parameters have them. 104 | if (!_cmpr.Equals(x.LinePragma.FileName, y.LinePragma.FileName)) 105 | { 106 | return _cmpr.Compare(x.LinePragma.FileName, y.LinePragma.FileName); 107 | } 108 | if (!x.LinePragma.LineNumber.Equals(y.LinePragma.LineNumber)) 109 | { 110 | return x.LinePragma.LineNumber.CompareTo(y.LinePragma.LineNumber); 111 | } 112 | return 0; 113 | } 114 | 115 | /// 116 | public bool Equals(CodeNamespaceImport x, CodeNamespaceImport y) 117 | { 118 | // Check the namespace name first. 119 | if (!_cmpr.Equals(x.Namespace, y.Namespace)) return false; 120 | 121 | // Check the linepragma properties next 122 | if (x.LinePragma != null) 123 | { 124 | if (y.LinePragma == null) return false; 125 | 126 | if (!_cmpr.Equals(x.LinePragma.FileName, y.LinePragma.FileName)) return false; 127 | if (!x.LinePragma.LineNumber.Equals(y.LinePragma.LineNumber)) return false; 128 | } 129 | else 130 | { 131 | if (y.LinePragma != null) return false; 132 | } 133 | return true; 134 | } 135 | 136 | /// 137 | public int GetHashCode(CodeNamespaceImport obj) 138 | { 139 | if (obj is null) return 0; 140 | 141 | unchecked 142 | { 143 | int hash = unchecked((int)2166136261); 144 | const int prime = 16777619; 145 | 146 | hash = (hash ^ obj.Namespace.GetHashCode()) * prime; 147 | 148 | if (obj.LinePragma != null) 149 | { 150 | hash = (hash ^ obj.LinePragma.FileName.GetHashCode()) * prime; 151 | hash = (hash ^ obj.LinePragma.LineNumber.GetHashCode()) * prime; 152 | } 153 | 154 | return hash; 155 | } 156 | } 157 | 158 | #endregion 159 | } 160 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) 6 | and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). 7 | 8 | ## [0.5.1-beta] - TBD 9 | 10 | ### Changed 11 | - Cleaned up `CancellationToken.ThrowIfCancellationRequested()` calls. 12 | - Update project url references. 13 | - Update DocumentFormat.OpenXml reference to 2.18.0. 14 | - Update System.CodeDom reference to 7.0.0. 15 | 16 | ## [0.5.0-beta] - 2022-05-29 17 | 18 | ### Added 19 | - Added asynchronous versions of the `GenerateSourceCode(...)` extenson methods for 20 | `OpenXmlElement`, `OpenXmlPart`, and `OpenXmlPackage` objects. 21 | 22 | ### Changed 23 | - *\[Breaking Change\]:* Updated `IOpenXmlElementHandler` and `IOpenXmlPartHandler` 24 | interfaces to add `CancellationToken` parameters to all applicable method 25 | definitions. 26 | - Update DocumentFormat.OpenXml reference to 2.16.0. 27 | - Update System.CodeDom reference to 6.0.0. 28 | 29 | ### Fixed 30 | - When creating and initializing `OpenXmlUnknownElement` variables, use the 31 | `CreateOpenXmlUnknownElement` static method instead of the traditional constructor. 32 | - When the `BuildCodeStatements` method of `OpenXmlElementExtensions` extensions class 33 | encounterd a DateTime property it was throwing a `System.ArgumentException` Invalid 34 | Primitive Type System.DateTime. fixed by @vtgrady2k 35 | 36 | ## [0.4.2-beta] - 2021-11-22 37 | 38 | ### Changed 39 | - Refactored the variable name generation process to reuse existing variable names 40 | when they become available. 41 | - *\[Breaking Change\]:* Added `UseUniqueVariableNames` property to `ISerializeSettings` 42 | interface. This allows to switch between unique and reused variable names. 43 | - *\[Breaking Change\]:* Changed `typeCounts` parameter to `types` in the 44 | `IOpenXmlElementHandler.BuildCodeStatements(...)` method to account for the repurposing 45 | of existing variable name. 46 | - Update DocumentFormat.OpenXml reference to 2.14.0. 47 | 48 | ### Fixed 49 | - Use the correct `CodeExpression` classes for the `XmlNodeType` parameter of the 50 | `OpenXmlMiscNode` constructor. 51 | 52 | ## [0.4.1-beta] - 2021-08-08 53 | 54 | ### Changed 55 | - Refactored the using directive generation logic to better match how the OpenXML SDK 56 | Productivity Tool used to create them. Using directive aliases are now dynamically 57 | generated based on the OpenXml object that the code output is based on. 58 | - *\[Breaking Change\]:* Updated the `namespace` parameter type in the `IOpenXmlElementHandler` 59 | and `IOpenXmlPartHandler` interface methods from `ISet` to `IDictionary` 60 | to account for the new namespace/using directive generation logic. The following 61 | interface methods are impacted: 62 | - `IOpenXmlElementHandler.BuildCodeStatements(...)` 63 | - `IOpenXmlPartHandler.BuildEntryMethodCodeStatements(...)` 64 | - `IOpenXmlPartHandler.BuildHelperMethod(...)` 65 | 66 | ### Fixed 67 | 68 | - Issue related to Hyperlink and external relationship references were not being added properly 69 | in all `OpenXmlPart` code creation scenarios. 70 | - Use the right constructor parameters for `OpenXmlMiscNode` objects. 71 | 72 | ## [0.4.0-beta] - 2021-08-02 73 | 74 | ### Added 75 | 76 | - New `ISerializeSettings` interface to allows greater flexibility in the source code generation. 77 | - New `IOpenXmlHandler`, `IOpenXmlElementHandler`, and `IOpenXmlPartHandler` interfaces that will 78 | allow developers to control how source code is created. 79 | 80 | ### Changed 81 | 82 | - Change visibility of many of the static method helpers so developers can use them in their custom 83 | code generation. 84 | - Update DocumentFormat.OpenXml reference to 2.13.0. 85 | 86 | ### Fixed 87 | 88 | - Make sure that the return type of generated element methods include the namespace alias if 89 | needed. 90 | - Choose between the default method or contentType parameter method for the custom OpenXmlPart.AddNewPart 91 | methods (ex: pkg.AddExtendedFilePropertiesPart() or mainDocumentPart.AddImagePart("image/x-emf")) 92 | 93 | ## [0.3.2-alpha] - 2020-07-30 94 | 95 | ### Changed 96 | 97 | - Updated process to account for more OpenXmlPart classes that may require custom AddNewPart methods 98 | to initialize. 99 | - Changed the `CreatePackage` method to take in a `String` parameter for the full file path of the target file 100 | instead of a `Stream` when generating code for `OpenXmlPackage` objects. This was to avoid using a C# `ref` 101 | parameter that made using the generated code in a C# project more difficult to use. 102 | 103 | ### Fixed 104 | 105 | - TargetInvocationException/FormatException when trying to parse a value that is not valid for 106 | `OpenXmlSimpleType` derived types being evaluated. [See this](https://github.com/OfficeDev/Open-XML-SDK/issues/780) 107 | for more details. 108 | - When encountering OpenXmlUnknownElement objects, make sure to initialize them with the appropriate `ctor` method. 109 | - Correct the initialization parameters for the generated `AddExternalRelationship` method. 110 | - Issue where AddPart methods for OpenXmlPart paths that have already been visited are generated on variables 111 | that do not exist. 112 | 113 | ## [0.3.1-alpha] - 2020-07-25 114 | 115 | ### Fixed 116 | 117 | - TargetInvocationException/FormatException when trying to parse a value that is not valid for 118 | the `EnumValue` type being evaluated. [See this](https://github.com/OfficeDev/Open-XML-SDK/issues/780) 119 | for more details. 120 | 121 | ## [0.3.0-alpha] - 2020-07-20 122 | 123 | ### Changed 124 | 125 | - Update DocumentFormat.OpenXml reference to 2.11.3. 126 | 127 | ### Fixed 128 | 129 | - Ambiguous Match Exception occuring when trying to identify parts that need to use the 130 | `AddImagePart` initialization method. 131 | 132 | ## [0.2.1-alpha] - 2020-07-03 133 | 134 | ### Changed 135 | 136 | - Change the parameters for all of the methods to `ref` parameters. This changes the generated 137 | VB code to create `byref` parameters instead of `byval` ones. 138 | 139 | ## [0.2.0-alpha] - 2020-06-27 140 | 141 | ### Added 142 | 143 | - Added documentation output 144 | 145 | ### Changed 146 | 147 | - Use the alias `AP` for DocumentFormat.OpenXml.ExtendedProperties namespace objects 148 | - Use the `AddImagePart` method for initializing `ImagePart` objects. 149 | - Included the content type parameter for the `AddNewPart` method for `EmbeddedPackagePart` objects. 150 | 151 | ## [0.1.0-alpha] - 2020-06-24 152 | 153 | ### Added 154 | 155 | - Added initial project to convert OpenXml SDK based documents to source code files. 156 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Visual Studio code coverage results 141 | *.coverage 142 | *.coveragexml 143 | 144 | # NCrunch 145 | _NCrunch_* 146 | .*crunch*.local.xml 147 | nCrunchTemp_* 148 | 149 | # MightyMoose 150 | *.mm.* 151 | AutoTest.Net/ 152 | 153 | # Web workbench (sass) 154 | .sass-cache/ 155 | 156 | # Installshield output folder 157 | [Ee]xpress/ 158 | 159 | # DocProject is a documentation generator add-in 160 | DocProject/buildhelp/ 161 | DocProject/Help/*.HxT 162 | DocProject/Help/*.HxC 163 | DocProject/Help/*.hhc 164 | DocProject/Help/*.hhk 165 | DocProject/Help/*.hhp 166 | DocProject/Help/Html2 167 | DocProject/Help/html 168 | 169 | # Click-Once directory 170 | publish/ 171 | 172 | # Publish Web Output 173 | *.[Pp]ublish.xml 174 | *.azurePubxml 175 | # Note: Comment the next line if you want to checkin your web deploy settings, 176 | # but database connection strings (with potential passwords) will be unencrypted 177 | *.pubxml 178 | *.publishproj 179 | 180 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 181 | # checkin your Azure Web App publish settings, but sensitive information contained 182 | # in these scripts will be unencrypted 183 | PublishScripts/ 184 | 185 | # NuGet Packages 186 | *.nupkg 187 | # NuGet Symbol Packages 188 | *.snupkg 189 | # The packages folder can be ignored because of Package Restore 190 | **/[Pp]ackages/* 191 | # except build/, which is used as an MSBuild target. 192 | !**/[Pp]ackages/build/ 193 | # Uncomment if necessary however generally it will be regenerated when needed 194 | #!**/[Pp]ackages/repositories.config 195 | # NuGet v3's project.json files produces more ignorable files 196 | *.nuget.props 197 | *.nuget.targets 198 | 199 | # Microsoft Azure Build Output 200 | csx/ 201 | *.build.csdef 202 | 203 | # Microsoft Azure Emulator 204 | ecf/ 205 | rcf/ 206 | 207 | # Windows Store app package directories and files 208 | AppPackages/ 209 | BundleArtifacts/ 210 | Package.StoreAssociation.xml 211 | _pkginfo.txt 212 | *.appx 213 | *.appxbundle 214 | *.appxupload 215 | 216 | # Visual Studio cache files 217 | # files ending in .cache can be ignored 218 | *.[Cc]ache 219 | # but keep track of directories ending in .cache 220 | !?*.[Cc]ache/ 221 | 222 | # Others 223 | ClientBin/ 224 | ~$* 225 | *~ 226 | *.dbmdl 227 | *.dbproj.schemaview 228 | *.jfm 229 | *.pfx 230 | *.publishsettings 231 | orleans.codegen.cs 232 | 233 | # Including strong name files can present a security risk 234 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 235 | #*.snk 236 | 237 | # Since there are multiple workflows, uncomment next line to ignore bower_components 238 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 239 | #bower_components/ 240 | 241 | # RIA/Silverlight projects 242 | Generated_Code/ 243 | 244 | # Backup & report files from converting an old project file 245 | # to a newer Visual Studio version. Backup files are not needed, 246 | # because we have git ;-) 247 | _UpgradeReport_Files/ 248 | Backup*/ 249 | UpgradeLog*.XML 250 | UpgradeLog*.htm 251 | ServiceFabricBackup/ 252 | *.rptproj.bak 253 | 254 | # SQL Server files 255 | *.mdf 256 | *.ldf 257 | *.ndf 258 | 259 | # Business Intelligence projects 260 | *.rdl.data 261 | *.bim.layout 262 | *.bim_*.settings 263 | *.rptproj.rsuser 264 | *- [Bb]ackup.rdl 265 | *- [Bb]ackup ([0-9]).rdl 266 | *- [Bb]ackup ([0-9][0-9]).rdl 267 | 268 | # Microsoft Fakes 269 | FakesAssemblies/ 270 | 271 | # GhostDoc plugin setting file 272 | *.GhostDoc.xml 273 | 274 | # Node.js Tools for Visual Studio 275 | .ntvs_analysis.dat 276 | node_modules/ 277 | 278 | # Visual Studio 6 build log 279 | *.plg 280 | 281 | # Visual Studio 6 workspace options file 282 | *.opt 283 | 284 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 285 | *.vbw 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # CodeRush personal settings 303 | .cr/personal 304 | 305 | # Python Tools for Visual Studio (PTVS) 306 | __pycache__/ 307 | *.pyc 308 | 309 | # Cake - Uncomment if you are using it 310 | tools/** 311 | !tools/packages.config 312 | 313 | # Tabs Studio 314 | *.tss 315 | 316 | # Telerik's JustMock configuration file 317 | *.jmconfig 318 | 319 | # BizTalk build output 320 | *.btp.cs 321 | *.btm.cs 322 | *.odx.cs 323 | *.xsd.cs 324 | 325 | # OpenCover UI analysis results 326 | OpenCover/ 327 | 328 | # Azure Stream Analytics local run output 329 | ASALocalRun/ 330 | 331 | # MSBuild Binary and Structured Log 332 | *.binlog 333 | 334 | # NVidia Nsight GPU debugger configuration file 335 | *.nvuser 336 | 337 | # MFractors (Xamarin productivity tool) working folder 338 | .mfractor/ 339 | 340 | # Local History for Visual Studio 341 | .localhistory/ 342 | 343 | # BeatPulse healthcheck temp database 344 | healthchecksdb 345 | 346 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 347 | MigrationBackup/ 348 | 349 | # Ionide (cross platform F# VS Code tools) working folder 350 | .ionide/ 351 | -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/TypeMonitor.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2021 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | using Serialize.OpenXml.CodeGen.Extentions; 24 | using System; 25 | using System.Collections; 26 | using System.Collections.Generic; 27 | using System.Linq; 28 | 29 | namespace Serialize.OpenXml.CodeGen 30 | { 31 | /// 32 | /// Class designed to monitor the variable names generated by a single type. 33 | /// 34 | /// 35 | /// This will be used only for code generation of 36 | /// objects. 37 | /// 38 | public class TypeMonitor : IReadOnlyDictionary 39 | { 40 | #region Private Instance Fields 41 | 42 | /// 43 | /// Holds the that the current instance will 44 | /// represent for its lifetime. 45 | /// 46 | private readonly Type _type; 47 | 48 | /// 49 | /// Holds the counter used when unique variable names are generated. 50 | /// 51 | private int _uniqueCount = 0; 52 | 53 | /// 54 | /// The object containing all of the 55 | /// variable names that have already been generated and their 'consumed' 56 | /// indicators. 57 | /// 58 | #pragma warning disable IDE0044 // Add readonly modifier 59 | private Dictionary _values = new Dictionary(); 60 | #pragma warning restore IDE0044 // Add readonly modifier 61 | 62 | #endregion 63 | 64 | #region Public Constructors 65 | 66 | /// 67 | /// Initializes a new instance of the class with 68 | /// the type that it will represent. 69 | /// 70 | /// 71 | /// The that this new object will represent. 72 | /// 73 | public TypeMonitor(Type t) 74 | { 75 | _type = t ?? throw new ArgumentNullException(nameof(t)); 76 | } 77 | 78 | #endregion 79 | 80 | #region Internal Static Properties 81 | 82 | /// 83 | /// Indicates whether or not to use unique/numbered variable names 84 | /// for the current request. 85 | /// 86 | internal static bool UseUniqueVariableNames { get; set; } = false; 87 | 88 | #endregion 89 | 90 | #region Public Instance Properties 91 | 92 | /// 93 | public bool this[string key] 94 | { 95 | get => _values[key]; 96 | set => _values[key] = value; 97 | } 98 | 99 | /// 100 | public IEnumerable Keys => _values.Keys; 101 | 102 | /// 103 | public IEnumerable Values => _values.Values; 104 | 105 | /// 106 | public int Count => UseUniqueVariableNames ? _uniqueCount : _values.Count; 107 | 108 | /// 109 | /// Gets the that the current object represents. 110 | /// 111 | public Type Type { get => _type; } 112 | 113 | #endregion 114 | 115 | #region Protected Instance Properties 116 | 117 | /// 118 | /// Gets the underlying object 119 | /// for the current instance. 120 | /// 121 | protected IDictionary Dictionary { get { return _values; } } 122 | 123 | #endregion 124 | 125 | #region Public Instance Methods 126 | 127 | /// 128 | public bool ContainsKey(string key) => _values.ContainsKey(key); 129 | 130 | /// 131 | /// Retrieves an existing or new variable name, depending on what is 132 | /// available at the time, to generate new code statements with. 133 | /// 134 | /// 135 | /// Collection used to keep 136 | /// track of all openxml namespaces used during the process. 137 | /// 138 | /// 139 | /// The variable name to use. 140 | /// 141 | /// 142 | /// if already exists 143 | /// in existing generated code with constructor statements; otherwise 144 | /// is returned, indicating that no existing 145 | /// generated code yet exists for . 146 | /// 147 | public bool GetVariableName(IDictionary namespaces, out string varName) 148 | { 149 | if (UseUniqueVariableNames) 150 | { 151 | 152 | varName = Type.GenerateVariableName(_uniqueCount++, namespaces); 153 | return false; 154 | } 155 | else 156 | { 157 | return CreateAndTrackVariableName(namespaces, out varName); 158 | } 159 | } 160 | 161 | /// 162 | public bool TryGetValue(string key, out bool value) 163 | { 164 | return _values.TryGetValue(key, out value); 165 | } 166 | 167 | #endregion 168 | 169 | #region Protected Instance Methods 170 | 171 | /// 172 | /// Adds a new key/value pair to this collection. 173 | /// 174 | /// The key of the new pair. 175 | /// The value of the new pair. 176 | protected void Add(string key, bool value) => _values.Add(key, value); 177 | 178 | /// 179 | /// Responible for retrieving an existing or new variable name, 180 | /// depending on what is available at the time, to generate new 181 | /// code statements with. 182 | /// 183 | /// 184 | /// Collection used to keep 185 | /// track of all openxml namespaces used during the process. 186 | /// 187 | /// 188 | /// The variable name to use. 189 | /// 190 | /// 191 | /// if already exists 192 | /// in existing generated code with constructor statements; otherwise 193 | /// is returned, indicating that no existing 194 | /// generated code yet exists for . 195 | /// 196 | protected virtual bool CreateAndTrackVariableName( 197 | IDictionary namespaces, out string varName) 198 | { 199 | if (Count > 0 && this.Values.Any(v => v)) 200 | { 201 | varName = this.LastOrDefault(v => v.Value).Key; 202 | this[varName] = false; 203 | return true; 204 | } 205 | 206 | int tries = _values.Count; 207 | varName = Type.GenerateVariableName(tries, namespaces); 208 | Add(varName, false); 209 | return false; 210 | } 211 | 212 | #endregion 213 | 214 | #region Private Instance Methods; 215 | 216 | /// 217 | IEnumerator> IEnumerable> 218 | .GetEnumerator() 219 | { 220 | return ((IEnumerable>)_values).GetEnumerator(); 221 | } 222 | 223 | /// 224 | IEnumerator IEnumerable.GetEnumerator() 225 | { 226 | return _values.GetEnumerator(); 227 | } 228 | 229 | #endregion 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/OpenXmlPartContainerExtensions.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2021 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | using DocumentFormat.OpenXml.Packaging; 24 | using Serialize.OpenXml.CodeGen.Extentions; 25 | using System; 26 | using System.CodeDom; 27 | using System.Collections.Generic; 28 | using System.Linq; 29 | 30 | namespace Serialize.OpenXml.CodeGen 31 | { 32 | /// 33 | /// Static class that is responsible for generating 34 | /// objects for derived objects. 35 | /// 36 | public static class OpenXmlPartContainerExtensions 37 | { 38 | #region Public Static Methods 39 | 40 | /// 41 | /// Creates a collection of code statements that describe how to add external relationships 42 | /// to a object. 43 | /// 44 | /// 45 | /// The collection of objects to build the code 46 | /// statements for. 47 | /// 48 | /// 49 | /// The name of the object that the external 50 | /// relationship assignments should be for. 51 | /// 52 | /// 53 | /// A collection of code statements that could be used to generate and assign new 54 | /// objects to a 55 | /// object. 56 | /// 57 | public static CodeStatementCollection BuildExternalRelationshipStatements( 58 | this IEnumerable relationships, string parentName) 59 | { 60 | if (String.IsNullOrWhiteSpace(parentName)) 61 | throw new ArgumentNullException(nameof(parentName)); 62 | 63 | return relationships.BuildExternalRelationshipStatements( 64 | new CodeVariableReferenceExpression(parentName)); 65 | } 66 | 67 | /// 68 | /// Creates a collection of code statements that describe how to add external relationships 69 | /// to a object. 70 | /// 71 | /// 72 | /// The collection of objects to build the code 73 | /// statements for. 74 | /// 75 | /// 76 | /// The object that the generated code statements will 77 | /// reference to build the external relationship assignments. 78 | /// 79 | /// 80 | /// A collection of code statements that could be used to generate and assign new 81 | /// objects to a 82 | /// object. 83 | /// 84 | public static CodeStatementCollection BuildExternalRelationshipStatements( 85 | this IEnumerable relationships, CodeExpression parent) 86 | { 87 | if (parent is null) throw new ArgumentNullException(nameof(parent)); 88 | 89 | var result = new CodeStatementCollection(); 90 | 91 | // Return an empty code statement collection if the hyperlinks parameter is empty. 92 | if (relationships.Count() == 0) return result; 93 | 94 | CodeObjectCreateExpression createExpression; 95 | CodeMethodReferenceExpression methodReferenceExpression; 96 | CodeMethodInvokeExpression invokeExpression; 97 | CodePrimitiveExpression param; 98 | CodeTypeReference typeReference; 99 | 100 | foreach (var ex in relationships) 101 | { 102 | // Need special care to create the uri for the current object. 103 | typeReference = new CodeTypeReference(ex.Uri.GetType()); 104 | param = new CodePrimitiveExpression(ex.Uri.ToString()); 105 | createExpression = new CodeObjectCreateExpression(typeReference, param); 106 | 107 | // Create the AddHyperlinkRelationship statement 108 | methodReferenceExpression = new CodeMethodReferenceExpression(parent, 109 | "AddExternalRelationship"); 110 | invokeExpression = new CodeMethodInvokeExpression(methodReferenceExpression, 111 | new CodePrimitiveExpression(ex.RelationshipType), 112 | createExpression, 113 | new CodePrimitiveExpression(ex.Id)); 114 | result.Add(invokeExpression); 115 | } 116 | return result; 117 | } 118 | 119 | /// 120 | /// Creates a collection of code statements that describe how to add hyperlink 121 | /// relationships to a object. 122 | /// 123 | /// 124 | /// The collection of objects to build the code 125 | /// statements for. 126 | /// 127 | /// 128 | /// The name of the object that the hyperlink 129 | /// relationship assignments should be for. 130 | /// 131 | /// 132 | /// A collection of code statements that could be used to generate and assign new 133 | /// objects to a 134 | /// object. 135 | /// 136 | public static CodeStatementCollection BuildHyperlinkRelationshipStatements( 137 | this IEnumerable hyperlinks, string parentName) 138 | { 139 | if (String.IsNullOrWhiteSpace(parentName)) 140 | { 141 | throw new ArgumentNullException(nameof(parentName)); 142 | } 143 | 144 | return hyperlinks.BuildHyperlinkRelationshipStatements( 145 | new CodeVariableReferenceExpression(parentName)); 146 | } 147 | 148 | /// 149 | /// Creates a collection of code statements that describe how to add hyperlink 150 | /// relationships to a object. 151 | /// 152 | /// 153 | /// The collection of objects to build the code 154 | /// statements for. 155 | /// 156 | /// 157 | /// The object that the generated code statements will 158 | /// reference to build the hyperlink relationship assignments. 159 | /// 160 | /// 161 | /// A collection of code statements that could be used to generate and assign new 162 | /// objects to a 163 | /// object. 164 | /// 165 | public static CodeStatementCollection BuildHyperlinkRelationshipStatements( 166 | this IEnumerable hyperlinks, CodeExpression parent) 167 | { 168 | if (parent is null) throw new ArgumentNullException(nameof(parent)); 169 | 170 | var result = new CodeStatementCollection(); 171 | 172 | // Return an empty code statement collection if the hyperlinks parameter is empty. 173 | if (hyperlinks.Count() == 0) return result; 174 | 175 | CodeObjectCreateExpression createExpression; 176 | CodeMethodReferenceExpression methodReferenceExpression; 177 | CodeMethodInvokeExpression invokeExpression; 178 | CodePrimitiveExpression param; 179 | CodeTypeReference typeReference; 180 | 181 | foreach (var hl in hyperlinks) 182 | { 183 | // Need special care to create the uri for the current object. 184 | typeReference = new CodeTypeReference(hl.Uri.GetType()); 185 | param = new CodePrimitiveExpression(hl.Uri.ToString()); 186 | createExpression = new CodeObjectCreateExpression(typeReference, param); 187 | 188 | // Create the AddHyperlinkRelationship statement 189 | methodReferenceExpression = new CodeMethodReferenceExpression(parent, 190 | "AddHyperlinkRelationship"); 191 | invokeExpression = new CodeMethodInvokeExpression(methodReferenceExpression, 192 | createExpression, 193 | new CodePrimitiveExpression(hl.IsExternal), 194 | new CodePrimitiveExpression(hl.Id)); 195 | result.Add(invokeExpression); 196 | } 197 | return result; 198 | } 199 | 200 | /// 201 | /// Locates any relationship elements associated with an 202 | /// object and generates codes statements for them. 203 | /// 204 | /// 205 | /// The 206 | /// 207 | /// 208 | /// The variable name that the generated code will reference. 209 | /// 210 | /// 211 | /// A new containing all of the code statements 212 | /// needed to create the relationships associated with . 213 | /// 214 | /// 215 | /// If doesn't contain any relationships, then an empty 216 | /// is returned. 217 | /// 218 | public static CodeStatementCollection GenerateRelationshipCodeStatements( 219 | this OpenXmlPartContainer part, string varName) 220 | { 221 | if (String.IsNullOrEmpty(varName)) throw new ArgumentNullException(nameof(varName)); 222 | 223 | return part.GenerateRelationshipCodeStatements( 224 | new CodeVariableReferenceExpression(varName)); 225 | } 226 | 227 | /// 228 | /// Locates any relationship elements associated with an 229 | /// object and generates codes statements for them. 230 | /// 231 | /// 232 | /// The 233 | /// 234 | /// 235 | /// The variable that the generated code will reference. 236 | /// 237 | /// 238 | /// A new containing all of the code statements 239 | /// needed to create the relationships associated with . 240 | /// 241 | /// 242 | /// If doesn't contain any relationships, then an empty 243 | /// is returned. 244 | /// 245 | public static CodeStatementCollection GenerateRelationshipCodeStatements( 246 | this OpenXmlPartContainer part, CodeExpression variable) 247 | { 248 | if (variable is null) throw new ArgumentNullException(nameof(variable)); 249 | 250 | var result = new CodeStatementCollection(); 251 | 252 | // Generate statements for the hyperlink relationships if applicable. 253 | if (part.HyperlinkRelationships.Count() > 0) 254 | { 255 | // Add a line break first for easier reading 256 | result.AddBlankLine(); 257 | result.AddRange( 258 | part.HyperlinkRelationships.BuildHyperlinkRelationshipStatements(variable)); 259 | } 260 | 261 | // Generate statement for the non-hyperlink/external relationships if applicable 262 | if (part.ExternalRelationships.Count() > 0) 263 | { 264 | // Add a line break first for easier reading 265 | result.AddBlankLine(); 266 | result.AddRange( 267 | part.ExternalRelationships.BuildExternalRelationshipStatements(variable)); 268 | } 269 | return result; 270 | } 271 | 272 | #endregion 273 | } 274 | } 275 | -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/Extensions/TypeExtensions.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | using DocumentFormat.OpenXml; 24 | using System; 25 | using System.CodeDom; 26 | using System.Collections.Generic; 27 | using System.Linq; 28 | using System.Reflection; 29 | using System.Text; 30 | 31 | namespace Serialize.OpenXml.CodeGen.Extentions 32 | { 33 | /// 34 | /// Collection of extension methods for the class 35 | /// specific to the generating code dom representations of 36 | /// OpenXml objects. 37 | /// 38 | public static class TypeExtensions 39 | { 40 | #region Static Constructors 41 | 42 | /// 43 | /// Static constructor. 44 | /// 45 | static TypeExtensions() 46 | { 47 | // Now setup the simple type collection. 48 | var simpleTypes = new Type[] 49 | { 50 | typeof(StringValue), 51 | typeof(OpenXmlSimpleValue), 52 | typeof(OpenXmlSimpleValue), 53 | typeof(OpenXmlSimpleValue), 54 | typeof(OpenXmlSimpleValue), 55 | typeof(OpenXmlSimpleValue), 56 | typeof(OpenXmlSimpleValue), 57 | typeof(OpenXmlSimpleValue), 58 | typeof(OpenXmlSimpleValue), 59 | typeof(OpenXmlSimpleValue), 60 | typeof(OpenXmlSimpleValue), 61 | typeof(OpenXmlSimpleValue), 62 | typeof(OpenXmlSimpleValue), 63 | typeof(OpenXmlSimpleValue) 64 | }; 65 | 66 | SimpleValueTypes = simpleTypes.ToList(); 67 | } 68 | 69 | #endregion 70 | 71 | #region Public Static Properties 72 | 73 | /// 74 | /// Gets a collection of based types. 75 | /// 76 | /// 77 | /// This is used to identify property types of objects that 78 | /// can be initialized with simple values of their base type counterparts. 79 | /// 80 | public static IReadOnlyList SimpleValueTypes { get; private set; } 81 | 82 | #endregion 83 | 84 | #region Public Static methods 85 | 86 | /// 87 | /// Checks to see if a exists in a different namespace. 88 | /// 89 | /// 90 | /// The with the name to search for. 91 | /// 92 | /// 93 | /// The of collected namespaces 94 | /// to search in. 95 | /// 96 | /// 97 | /// if exists in a different 98 | /// namespace; otherwise, . 99 | /// 100 | public static bool ExistsInDifferentNamespace(this Type t, 101 | IDictionary namespaces) 102 | { 103 | if (namespaces is null) throw new ArgumentNullException(nameof(namespaces)); 104 | if (namespaces.Count == 0) return false; 105 | 106 | Type tmp; 107 | bool foundElsewhere = false; 108 | Type[] classes; 109 | string tmpName; 110 | 111 | bool getTypesWhere(Type c) => 112 | !String.IsNullOrEmpty(c.Namespace) && 113 | c.Namespace.Equals(t.Namespace, StringComparison.Ordinal); 114 | 115 | foreach (var ns in namespaces) 116 | { 117 | // First scan for the actual type name in other existing 118 | // namespaces 119 | tmpName = $"{ns.Key}.{t.Name}"; 120 | tmp = t.Assembly.GetType(tmpName) ?? Type.GetType(tmpName); 121 | 122 | if (tmp != null) 123 | { 124 | foundElsewhere = true; 125 | break; 126 | } 127 | } 128 | 129 | if (!foundElsewhere) 130 | { 131 | // Next, try to scan for other classes in the type's namespace 132 | // in other namespaces 133 | classes = Assembly.GetAssembly(t).GetTypes() 134 | .Where(getTypesWhere) 135 | .ToArray(); 136 | 137 | foreach (var ns in namespaces) 138 | { 139 | foreach (var cl in classes) 140 | { 141 | tmpName = $"{ns.Key}.{cl.Name}"; 142 | tmp = t.Assembly.GetType(tmpName) ?? Type.GetType(tmpName); 143 | 144 | if (tmp != null) 145 | { 146 | foundElsewhere = true; 147 | break; 148 | } 149 | } 150 | if (foundElsewhere) break; 151 | } 152 | } 153 | 154 | return foundElsewhere; 155 | } 156 | 157 | /// 158 | /// Checks to see if a exists in a different namespace. 159 | /// 160 | /// 161 | /// The with the name to search for. 162 | /// 163 | /// 164 | /// The of collected namespaces 165 | /// to search in. 166 | /// 167 | /// 168 | /// The namespace alias to use, if necessary. 169 | /// 170 | /// 171 | /// if exists in a different 172 | /// namespace; otherwise, . 173 | /// 174 | public static bool ExistsInDifferentNamespace(this Type t, 175 | IDictionary namespaces, out string alias) 176 | { 177 | if (namespaces is null) throw new ArgumentNullException(nameof(namespaces)); 178 | if (namespaces.Count == 0) 179 | { 180 | alias = String.Empty; 181 | return false; 182 | } 183 | 184 | Type tmp; 185 | bool foundElsewhere = false; 186 | Type[] classes; 187 | string tmpName; 188 | string al = String.Empty; 189 | 190 | bool getTypesWhere(Type c) => 191 | !String.IsNullOrEmpty(c.Namespace) && 192 | c.Namespace.Equals(t.Namespace, StringComparison.Ordinal); 193 | 194 | // Tries to find an alias to use for the specified type 195 | string findAlias(Type c) 196 | { 197 | foundElsewhere = true; 198 | string result = null; 199 | 200 | // If the current type is a subclass of OpenXmlElement 201 | // try to initialize it with a default constructor to 202 | // get its prefix for the namespace alias 203 | if (c.IsSubclassOf(typeof(OpenXmlElement))) 204 | { 205 | var ctor = c.GetConstructor(Type.EmptyTypes); 206 | 207 | if (ctor != null) 208 | { 209 | var element = c.Assembly.CreateInstance(c.FullName) as OpenXmlElement; 210 | result = element.Prefix.ToUpperInvariant(); 211 | } 212 | } 213 | 214 | // Create a new alias if the element prefix could not be located. 215 | if (String.IsNullOrEmpty(result)) 216 | { 217 | var sb = new StringBuilder(); 218 | var ns = c.Namespace; 219 | 220 | // Use only upper case or numeric characters from the 221 | // type's namespace as the new alias. 222 | for (int i = 0; i < ns.Length; i++) 223 | { 224 | if (Char.IsUpper(ns[i]) || Char.IsDigit(ns[i])) 225 | { 226 | sb.Append(ns[i]); 227 | } 228 | } 229 | result = sb.ToString(); 230 | } 231 | return result; 232 | } 233 | 234 | foreach (var ns in namespaces) 235 | { 236 | // First scan for the actual type name in other existing 237 | // namespaces 238 | tmpName = $"{ns.Key}.{t.Name}"; 239 | tmp = t.Assembly.GetType(tmpName) ?? Type.GetType(tmpName); 240 | 241 | if (tmp != null) 242 | { 243 | al = findAlias(t); 244 | break; 245 | } 246 | } 247 | 248 | if (!foundElsewhere) 249 | { 250 | // Next, try to scan for other classes in the type's namespace 251 | // in other namespaces 252 | classes = Assembly.GetAssembly(t).GetTypes() 253 | .Where(getTypesWhere) 254 | .ToArray(); 255 | 256 | foreach (var ns in namespaces) 257 | { 258 | foreach (var cl in classes) 259 | { 260 | tmpName = $"{ns.Key}.{cl.Name}"; 261 | tmp = t.Assembly.GetType(tmpName) ?? Type.GetType(tmpName); 262 | 263 | 264 | if (tmp != null) 265 | { 266 | al = findAlias(cl); 267 | break; 268 | } 269 | } 270 | if (foundElsewhere) break; 271 | } 272 | } 273 | 274 | alias = al; 275 | return foundElsewhere; 276 | } 277 | 278 | /// 279 | /// Generates a variable name to use when generating the appropriate 280 | /// CodeDom objects for a given . 281 | /// 282 | /// 283 | /// The to generate the variable name for. 284 | /// 285 | /// 286 | /// The number of variables that have been already created for 287 | /// . 288 | /// 289 | /// 290 | /// Collection used to keep 291 | /// track of all openxml namespaces used during the process. 292 | /// 293 | /// 294 | /// A new variable name to use to represent . 295 | /// 296 | public static string GenerateVariableName( 297 | this Type t, 298 | int tries, 299 | IDictionary namespaces) 300 | { 301 | if (namespaces is null) throw new ArgumentNullException(nameof(namespaces)); 302 | 303 | string tmp; // Hold the generated name 304 | string nsPrefix = String.Empty; 305 | 306 | // Include the namespace alias as part of the variable name 307 | if (namespaces.ContainsKey(t.Namespace) && 308 | !String.IsNullOrWhiteSpace(namespaces[t.Namespace])) 309 | { 310 | nsPrefix = namespaces[t.Namespace].ToLowerInvariant(); 311 | } 312 | 313 | // Simply return the generated name if the current 314 | // type is not considered generic. 315 | if (!t.IsGenericType) 316 | { 317 | tmp = String.Concat(nsPrefix, t.Name).ToCamelCase(); 318 | if (tries > 0) 319 | { 320 | return String.Concat(tmp, tries); 321 | } 322 | return tmp; 323 | } 324 | 325 | // Include the generic types as part of the var name. 326 | var sb = new StringBuilder(); 327 | foreach (var item in t.GenericTypeArguments) 328 | { 329 | sb.Append(item.Name.RetrieveUpperCaseChars().ToTitleCase()); 330 | } 331 | tmp = t.Name; 332 | 333 | if (tries > 0) 334 | { 335 | return String.Concat(nsPrefix, 336 | tmp.Substring(0, tmp.IndexOf("`")), 337 | sb.ToString(), 338 | tries).ToCamelCase(); 339 | } 340 | 341 | return String.Concat(nsPrefix, 342 | tmp.Substring(0, tmp.IndexOf("`")), 343 | sb.ToString()).ToCamelCase(); 344 | } 345 | 346 | /// 347 | /// Creates a class name to use when generating 348 | /// source code. 349 | /// 350 | /// 351 | /// The object containing the class name to evaluate. 352 | /// 353 | /// 354 | /// Collection used to keep track of a 355 | /// ll openxml namespaces used during the process. 356 | /// 357 | /// 358 | /// The value to evaluate when building the 359 | /// appropriate class name. 360 | /// 361 | /// 362 | /// The class name to use when building a new 363 | /// object. 364 | /// 365 | public static string GetObjectTypeName(this Type t, 366 | IDictionary namespaces, NamespaceAliasOrder order) 367 | { 368 | if (!namespaces.ContainsKey(t.Namespace) || order == NamespaceAliasOrder.None) 369 | { 370 | return t.FullName; 371 | } 372 | if (!String.IsNullOrWhiteSpace(namespaces[t.Namespace])) 373 | { 374 | return $"{namespaces[t.Namespace]}.{t.Name}"; 375 | } 376 | return t.Name; 377 | } 378 | 379 | /// 380 | /// Gets all of the objects that inherit from 381 | /// the class in . 382 | /// 383 | /// 384 | /// The object to retrieve the 385 | /// objects from. 386 | /// 387 | /// 388 | /// A collection of objects that inherit from 389 | /// the class. 390 | /// 391 | /// 392 | /// All necessary OpenXml object properties inherit from the 393 | /// class. This makes it easier to 394 | /// iterate through. 395 | /// 396 | public static IReadOnlyList GetOpenXmlSimpleTypeProperties(this Type t) 397 | { 398 | return t.GetOpenXmlSimpleTypeProperties(true); 399 | } 400 | 401 | /// 402 | /// Gets all of the objects that inherit from 403 | /// the class in . 404 | /// 405 | /// 406 | /// The object to retrieve the 407 | /// objects from. 408 | /// 409 | /// 410 | /// Include properties with types that inherit from 411 | /// type. 412 | /// 413 | /// 414 | /// A collection of objects that inherit from 415 | /// the class. 416 | /// 417 | /// 418 | /// All necessary OpenXml object properties inherit from the 419 | /// class. This makes it easier to 420 | /// iterate through. 421 | /// 422 | public static IReadOnlyList GetOpenXmlSimpleTypeProperties( 423 | this Type t, bool includeSimpleValueTypes) 424 | { 425 | var props = t.GetProperties(); 426 | var result = new List(); 427 | 428 | // Collect all properties that are of type or subclass of 429 | // OpenXmlSimpleType 430 | foreach (var p in props) 431 | { 432 | if (p.PropertyType.Equals(typeof(OpenXmlSimpleType)) || 433 | p.PropertyType.IsSubclassOf(typeof(OpenXmlSimpleType))) 434 | { 435 | if (includeSimpleValueTypes || !p.PropertyType.IsSimpleValueType()) 436 | { 437 | result.Add(p); 438 | } 439 | } 440 | } 441 | // Return the collected 442 | return result; 443 | } 444 | 445 | /// 446 | /// Gets all of the objects that inherit from 447 | /// the class in . 448 | /// 449 | /// 450 | /// The object to retrieve the 451 | /// objects from. 452 | /// 453 | /// 454 | /// A collection of objects that inherit from 455 | /// the class. 456 | /// 457 | /// 458 | /// All necessary OpenXml object properties inherit from the 459 | /// class. This makes it easier to 460 | /// iterate through. 461 | /// 462 | public static IReadOnlyList GetOpenXmlSimpleValuesProperties(this Type t) 463 | { 464 | var props = t.GetProperties(); 465 | var result = new List(); 466 | 467 | // Collect all properties that are of type or subclass of 468 | // OpenXmlSimpleType 469 | foreach (var p in props) 470 | { 471 | foreach (var item in SimpleValueTypes) 472 | { 473 | if (p.PropertyType.Equals(item) || 474 | p.PropertyType.IsSubclassOf(item)) 475 | { 476 | result.Add(p); 477 | break; 478 | } 479 | } 480 | } 481 | // Return the collected properties 482 | return result; 483 | } 484 | 485 | /// 486 | /// Gets all objects from 487 | /// of type . 488 | /// 489 | /// 490 | /// The to retrieve the objects 491 | /// from. 492 | /// 493 | /// 494 | /// A read only collection of objects with a 495 | /// property type. 496 | /// 497 | public static IReadOnlyList GetStringValueProperties(this Type t) 498 | => t.GetProperties().Where(s => s.PropertyType == typeof(StringValue)).ToList(); 499 | 500 | /// 501 | /// Checks to see if is EnumValue`1. 502 | /// 503 | /// 504 | /// The to evaluate. 505 | /// 506 | /// 507 | /// if the type of 508 | /// is EnumValue`1; otherwise, . 509 | /// 510 | public static bool IsEnumValueType(this Type t) => 511 | t.Name.Equals("EnumValue`1", StringComparison.Ordinal); 512 | 513 | /// 514 | /// Indicates whether or not is considered 515 | /// a type. 516 | /// 517 | /// 518 | /// The to check. 519 | /// 520 | /// 521 | /// if is derived 522 | /// from an OpenXmlSimpleValue type; otherwise, . 523 | /// 524 | public static bool IsSimpleValueType(this Type t) 525 | { 526 | foreach (var item in SimpleValueTypes) 527 | { 528 | if (t.Equals(item) || t.IsSubclassOf(item)) 529 | { 530 | return true; 531 | } 532 | } 533 | return false; 534 | } 535 | 536 | #endregion 537 | } 538 | } -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/OpenXmlPackageExtensions.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | using DocumentFormat.OpenXml.Packaging; 24 | using Serialize.OpenXml.CodeGen.Extentions; 25 | using System; 26 | using System.CodeDom; 27 | using System.CodeDom.Compiler; 28 | using System.Collections.Generic; 29 | using System.IO; 30 | using System.Linq; 31 | using System.Threading; 32 | using System.Threading.Tasks; 33 | 34 | namespace Serialize.OpenXml.CodeGen 35 | { 36 | /// 37 | /// Static class that converts packages 38 | /// into Code DOM objects. 39 | /// 40 | public static class OpenXmlPackageExtensions 41 | { 42 | #region Public Static Methods 43 | 44 | /// 45 | /// Converts an into a CodeDom object that can be used 46 | /// to build code in a given .NET language to build the referenced . 47 | /// 48 | /// 49 | /// The object to generate source code for. 50 | /// 51 | /// 52 | /// A new containing the instructions to build 53 | /// the referenced . 54 | /// 55 | public static CodeCompileUnit GenerateSourceCode(this OpenXmlPackage pkg) 56 | { 57 | return pkg.GenerateSourceCode(new DefaultSerializeSettings()); 58 | } 59 | 60 | /// 61 | /// Converts an into a CodeDom object that can be used 62 | /// to build code in a given .NET language to build the referenced . 63 | /// 64 | /// 65 | /// The object to generate source code for. 66 | /// 67 | /// 68 | /// The to apply to the resulting source code. 69 | /// 70 | /// 71 | /// A new containing the instructions to build 72 | /// the referenced . 73 | /// 74 | public static CodeCompileUnit GenerateSourceCode(this OpenXmlPackage pkg, 75 | NamespaceAliasOptions opts) 76 | { 77 | return pkg.GenerateSourceCode(new DefaultSerializeSettings(opts)); 78 | } 79 | 80 | /// 81 | /// Converts an into a CodeDom object that can be used 82 | /// to build code in a given .NET language to build the referenced . 83 | /// 84 | /// 85 | /// The object to generate source code for. 86 | /// 87 | /// 88 | /// The to use during the code generation 89 | /// process. 90 | /// 91 | /// 92 | /// A new containing the instructions to build 93 | /// the referenced . 94 | /// 95 | public static CodeCompileUnit GenerateSourceCode(this OpenXmlPackage pkg, 96 | ISerializeSettings settings) 97 | { 98 | return DefaultSerializeSettings.TaskIndustry.StartNew( 99 | () => pkg.GenerateSourceCodeAsync(settings, CancellationToken.None)) 100 | .Unwrap() 101 | .GetAwaiter() 102 | .GetResult(); 103 | } 104 | 105 | /// 106 | /// Converts an into a representation 107 | /// of dotnet source code that can be compiled to build . 108 | /// 109 | /// 110 | /// The object to generate source code for. 111 | /// 112 | /// 113 | /// The object to create the resulting source code. 114 | /// 115 | /// 116 | /// A representation of the source code generated by 117 | /// that could create when compiled. 118 | /// 119 | public static string GenerateSourceCode(this OpenXmlPackage pkg, CodeDomProvider provider) 120 | { 121 | return pkg.GenerateSourceCode(new DefaultSerializeSettings(), provider); 122 | } 123 | 124 | /// 125 | /// Converts an into a representation 126 | /// of dotnet source code that can be compiled to build . 127 | /// 128 | /// 129 | /// The object to generate source code for. 130 | /// 131 | /// 132 | /// The to apply to the resulting source code. 133 | /// 134 | /// 135 | /// The object to create the resulting source code. 136 | /// 137 | /// 138 | /// A representation of the source code generated by 139 | /// that could create when compiled. 140 | /// 141 | public static string GenerateSourceCode( 142 | this OpenXmlPackage pkg, NamespaceAliasOptions opts, CodeDomProvider provider) 143 | { 144 | return pkg.GenerateSourceCode(new DefaultSerializeSettings(opts), provider); 145 | } 146 | 147 | /// 148 | /// Converts an into a representation 149 | /// of dotnet source code that can be compiled to build . 150 | /// 151 | /// 152 | /// The object to generate source code for. 153 | /// 154 | /// 155 | /// The to use during the code generation 156 | /// process. 157 | /// 158 | /// 159 | /// The object to create the resulting source code. 160 | /// 161 | /// 162 | /// A representation of the source code generated by 163 | /// that could create when compiled. 164 | /// 165 | public static string GenerateSourceCode(this OpenXmlPackage pkg, 166 | ISerializeSettings settings, CodeDomProvider provider) 167 | { 168 | var codeString = new System.Text.StringBuilder(); 169 | var code = pkg.GenerateSourceCode(settings); 170 | 171 | using (var sw = new StringWriter(codeString)) 172 | { 173 | provider.GenerateCodeFromCompileUnit(code, sw, 174 | new CodeGeneratorOptions() { BracingStyle = "C" }); 175 | } 176 | return codeString.ToString().RemoveOutputHeaders().Trim(); 177 | } 178 | 179 | /// 180 | /// Converts an into a CodeDom object that can be used 181 | /// to build code in a given .NET language to build the referenced . 182 | /// 183 | /// 184 | /// The object to generate source code for. 185 | /// 186 | /// 187 | /// Task cancellation token. 188 | /// 189 | /// 190 | /// A new containing the instructions to build 191 | /// the referenced . 192 | /// 193 | public static async Task GenerateSourceCodeAsync( 194 | this OpenXmlPackage pkg, 195 | CancellationToken token) 196 | { 197 | return await pkg.GenerateSourceCodeAsync( 198 | new DefaultSerializeSettings(), 199 | token); 200 | } 201 | 202 | /// 203 | /// Converts an into a CodeDom object that can be used 204 | /// to build code in a given .NET language to build the referenced . 205 | /// 206 | /// 207 | /// The object to generate source code for. 208 | /// 209 | /// 210 | /// The to apply to the resulting source code. 211 | /// 212 | /// 213 | /// Task cancellation token. 214 | /// 215 | /// 216 | /// A new containing the instructions to build 217 | /// the referenced . 218 | /// 219 | public static async Task GenerateSourceCodeAsync( 220 | this OpenXmlPackage pkg, 221 | NamespaceAliasOptions opts, 222 | CancellationToken token) 223 | { 224 | return await pkg.GenerateSourceCodeAsync(new DefaultSerializeSettings(opts), token); 225 | } 226 | 227 | /// 228 | /// Converts an into a CodeDom object that can be used 229 | /// to build code in a given .NET language to build the referenced . 230 | /// 231 | /// 232 | /// The object to generate source code for. 233 | /// 234 | /// 235 | /// The to use during the code generation 236 | /// process. 237 | /// 238 | /// 239 | /// Task cancellation token. 240 | /// 241 | /// 242 | /// A new containing the instructions to build 243 | /// the referenced . 244 | /// 245 | public static async Task GenerateSourceCodeAsync( 246 | this OpenXmlPackage pkg, 247 | ISerializeSettings settings, 248 | CancellationToken token) 249 | { 250 | const string pkgVarName = "pkg"; 251 | const string paramName = "pathToFile"; 252 | 253 | return await Task.Run(() => 254 | { 255 | var result = new CodeCompileUnit(); 256 | var pkgType = pkg.GetType(); 257 | var pkgTypeName = pkgType.Name; 258 | var partTypeCounts = new Dictionary(); 259 | var namespaces = new Dictionary(); 260 | var mainNamespace = new CodeNamespace(settings.NamespaceName); 261 | var bluePrints = new OpenXmlPartBluePrintCollection(); 262 | CodeConditionStatement conditionStatement; 263 | CodeMemberMethod entryPoint; 264 | CodeMemberMethod createParts; 265 | CodeTypeDeclaration mainClass; 266 | CodeTryCatchFinallyStatement tryAndCatch; 267 | CodeFieldReferenceExpression docTypeVarRef = null; 268 | CodeStatementCollection relCodeStatements; 269 | Type docTypeEnum; 270 | string docTypeEnumVal; 271 | KeyValuePair rootVarType; 272 | 273 | // Set the var uniqueness indicator 274 | TypeMonitor.UseUniqueVariableNames = settings.UseUniqueVariableNames; 275 | 276 | // Add all initial namespace names first 277 | if (!namespaces.ContainsKey("System")) 278 | { 279 | // Adding system first because the entry point for 280 | // packages has a string parameter that does not use 281 | // the 'string' keyword. 282 | namespaces.Add("System", String.Empty); 283 | } 284 | 285 | // The OpenXmlDocument derived parts all contain a property called "DocumentType" 286 | // but the property types differ depending on the derived part. Need to get both 287 | // the enum name of selected value to use as a parameter for the Create statement. 288 | switch (pkg) 289 | { 290 | case PresentationDocument p: 291 | docTypeEnum = p.DocumentType.GetType(); 292 | docTypeEnumVal = p.DocumentType.ToString(); 293 | break; 294 | case SpreadsheetDocument s: 295 | docTypeEnum = s.DocumentType.GetType(); 296 | docTypeEnumVal = s.DocumentType.ToString(); 297 | break; 298 | case WordprocessingDocument w: 299 | docTypeEnum = w.DocumentType.GetType(); 300 | docTypeEnumVal = w.DocumentType.ToString(); 301 | break; 302 | default: 303 | throw new ArgumentException( 304 | "object is not a recognized OpenXmlPackage type.", 305 | nameof(pkg)); 306 | } 307 | 308 | // Check to see if the task has been cancelled. 309 | token.ThrowIfCancellationRequested(); 310 | 311 | // Create the entry method 312 | entryPoint = new CodeMemberMethod() 313 | { 314 | Name = "CreatePackage", 315 | ReturnType = new CodeTypeReference(), 316 | Attributes = MemberAttributes.Public | MemberAttributes.Final 317 | }; 318 | entryPoint.Parameters.Add( 319 | new CodeParameterDeclarationExpression(typeof(string).Name, paramName)); 320 | 321 | // Create package declaration expression first 322 | entryPoint.Statements.Add(new CodeVariableDeclarationStatement( 323 | pkgTypeName, pkgVarName, new CodePrimitiveExpression(null))); 324 | 325 | // Add the required DocumentType parameter here, if available 326 | if (docTypeEnum != null) 327 | { 328 | if (!namespaces.ContainsKey(docTypeEnum.Namespace)) 329 | { 330 | // All OpenXmlPackage objects are in the DocumentFormat.OpenXml.Packaging 331 | // namespace so there shouldn't be any collisions here. 332 | namespaces.Add(docTypeEnum.Namespace, String.Empty); 333 | } 334 | 335 | var simpleFieldRef = new CodeVariableReferenceExpression( 336 | docTypeEnum.GetObjectTypeName(namespaces, 337 | settings.NamespaceAliasOptions.Order)); 338 | docTypeVarRef = new CodeFieldReferenceExpression( 339 | simpleFieldRef, docTypeEnumVal); 340 | } 341 | 342 | // initialize package var 343 | var pkgCreateMethod = new CodeMethodReferenceExpression( 344 | new CodeTypeReferenceExpression(pkgTypeName), 345 | "Create"); 346 | var pkgCreateInvoke = new CodeMethodInvokeExpression(pkgCreateMethod, 347 | new CodeArgumentReferenceExpression(paramName), 348 | docTypeVarRef); 349 | var initializePkg = new CodeAssignStatement( 350 | new CodeVariableReferenceExpression(pkgVarName), 351 | pkgCreateInvoke); 352 | 353 | // Call CreateParts method 354 | var partsCreateMethod = new CodeMethodReferenceExpression( 355 | new CodeThisReferenceExpression(), 356 | "CreateParts"); 357 | var partsCreateInvoke = new CodeMethodInvokeExpression( 358 | partsCreateMethod, 359 | new CodeDirectionExpression(FieldDirection.Ref, 360 | new CodeVariableReferenceExpression(pkgVarName))); 361 | 362 | // Clean up pkg var 363 | var pkgDisposeMethod = new CodeMethodReferenceExpression( 364 | new CodeVariableReferenceExpression(pkgVarName), 365 | "Dispose"); 366 | var pkgDisposeInvoke = new CodeMethodInvokeExpression( 367 | pkgDisposeMethod); 368 | 369 | // Setup the try/catch statement to properly initialize the pkg var 370 | tryAndCatch = new CodeTryCatchFinallyStatement(); 371 | 372 | // Try statements 373 | tryAndCatch.TryStatements.Add(initializePkg); 374 | tryAndCatch.TryStatements.AddBlankLine(); 375 | tryAndCatch.TryStatements.Add(partsCreateInvoke); 376 | 377 | // If statement to ensure pkgVarName is not null before trying to dispose 378 | conditionStatement = new CodeConditionStatement( 379 | new CodeBinaryOperatorExpression( 380 | new CodeVariableReferenceExpression(pkgVarName), 381 | CodeBinaryOperatorType.IdentityInequality, 382 | new CodePrimitiveExpression(null))); 383 | 384 | conditionStatement.TrueStatements.Add(pkgDisposeInvoke); 385 | 386 | // Finally statements 387 | tryAndCatch.FinallyStatements.Add(conditionStatement); 388 | entryPoint.Statements.Add(tryAndCatch); 389 | 390 | // Check to see if the task has been cancelled. 391 | token.ThrowIfCancellationRequested(); 392 | 393 | // Create the CreateParts method 394 | createParts = new CodeMemberMethod() 395 | { 396 | Name = "CreateParts", 397 | ReturnType = new CodeTypeReference(), 398 | Attributes = MemberAttributes.Private | MemberAttributes.Final 399 | }; 400 | createParts.Parameters.Add(new CodeParameterDeclarationExpression( 401 | pkgTypeName, pkgVarName) 402 | { Direction = FieldDirection.Ref }); 403 | 404 | relCodeStatements = pkg.GenerateRelationshipCodeStatements(pkgVarName); 405 | if (relCodeStatements.Count > 0) 406 | { 407 | createParts.Statements.AddRange(relCodeStatements); 408 | } 409 | 410 | // Add all of the child part references here 411 | if (pkg.Parts != null) 412 | { 413 | var customNewPartTypes = new Type[] 414 | { 415 | typeof(PresentationPart), 416 | typeof(WorkbookPart), 417 | typeof(MainDocumentPart) 418 | }; 419 | OpenXmlPartBluePrint bpTemp; 420 | CodeMethodReferenceExpression referenceExpression; 421 | CodeMethodInvokeExpression invokeExpression; 422 | CodeMethodReferenceExpression methodReference; 423 | Type currentPartType; 424 | string varName = null; 425 | string initMethodName = null; 426 | string mainPartTypeName; 427 | 428 | foreach (var pair in pkg.Parts) 429 | { 430 | // Check to see if the task has been cancelled. 431 | token.ThrowIfCancellationRequested(); 432 | 433 | // Need special handling rules for WorkbookPart, MainDocumentPart, and 434 | // PresentationPart objects. They cannot be created using the usual 435 | // "AddNewPart" methods, unfortunately. 436 | currentPartType = pair.OpenXmlPart.GetType(); 437 | if (customNewPartTypes.Contains(currentPartType)) 438 | { 439 | if (!namespaces.ContainsKey(currentPartType.Namespace)) 440 | { 441 | // All OpenXmlPart objects are in the 442 | // DocumentFormat.OpenXml.Packaging namespace so there shouldn't 443 | // be any collisions here. 444 | namespaces.Add(currentPartType.Namespace, String.Empty); 445 | } 446 | mainPartTypeName = currentPartType.Name; 447 | if (pair.OpenXmlPart is PresentationPart) 448 | { 449 | varName = "presentationPart"; 450 | initMethodName = "AddPresentationPart"; 451 | } 452 | else if (pair.OpenXmlPart is WorkbookPart) 453 | { 454 | varName = "workbookPart"; 455 | initMethodName = "AddWorkbookPart"; 456 | } 457 | else if (pair.OpenXmlPart is MainDocumentPart) 458 | { 459 | varName = "mainDocumentPart"; 460 | initMethodName = "AddMainDocumentPart"; 461 | } 462 | rootVarType = new KeyValuePair(varName, currentPartType); 463 | 464 | // Setup the blueprint 465 | bpTemp = new OpenXmlPartBluePrint(pair.OpenXmlPart, varName); 466 | 467 | // Setup the add new part statement for the current OpenXmlPart object 468 | referenceExpression = new CodeMethodReferenceExpression( 469 | new CodeArgumentReferenceExpression(pkgVarName), initMethodName); 470 | 471 | invokeExpression = new CodeMethodInvokeExpression(referenceExpression); 472 | 473 | createParts.Statements.Add(new CodeVariableDeclarationStatement( 474 | mainPartTypeName, varName, invokeExpression)); 475 | 476 | // Add the call to the method to populate the current OpenXmlPart object 477 | methodReference = new CodeMethodReferenceExpression( 478 | new CodeThisReferenceExpression(), bpTemp.MethodName); 479 | createParts.Statements.Add(new CodeMethodInvokeExpression( 480 | methodReference, 481 | new CodeDirectionExpression(FieldDirection.Ref, 482 | new CodeVariableReferenceExpression(varName)))); 483 | 484 | // Add the current main part to the collection of blueprints to ensure 485 | // that the appropriate 'Generate*' method is added to the collection 486 | // of helper methods. 487 | bluePrints.Add(bpTemp); 488 | 489 | relCodeStatements = pair.OpenXmlPart 490 | .GenerateRelationshipCodeStatements(varName); 491 | if (relCodeStatements.Count > 0) 492 | { 493 | createParts.Statements.AddRange(relCodeStatements); 494 | } 495 | 496 | // Add a blank line for clarity 497 | createParts.Statements.AddBlankLine(); 498 | 499 | // now create the child parts for the current one an continue the loop 500 | // to avoid creating an additional invalid 'AddNewPart' method for the 501 | // current main part. 502 | foreach (var child in pair.OpenXmlPart.Parts) 503 | { 504 | // Check to see if the task has been cancelled. 505 | token.ThrowIfCancellationRequested(); 506 | 507 | createParts.Statements.AddRange( 508 | OpenXmlPartExtensions.BuildEntryMethodCodeStatements( 509 | child, settings, partTypeCounts, namespaces, bluePrints, 510 | rootVarType, token)); 511 | } 512 | continue; 513 | } 514 | 515 | // Check to see if the task has been cancelled. 516 | token.ThrowIfCancellationRequested(); 517 | 518 | rootVarType = new KeyValuePair(pkgVarName, pkgType); 519 | createParts.Statements.AddRange( 520 | OpenXmlPartExtensions.BuildEntryMethodCodeStatements( 521 | pair, settings, partTypeCounts, namespaces, bluePrints, 522 | rootVarType, token)); 523 | } 524 | } 525 | 526 | // Setup the main class next 527 | mainClass = new CodeTypeDeclaration($"{pkgTypeName}BuilderClass") 528 | { 529 | IsClass = true, 530 | Attributes = MemberAttributes.Public 531 | }; 532 | 533 | // Check to see if the task has been cancelled. 534 | token.ThrowIfCancellationRequested(); 535 | 536 | // Setup the main class members 537 | mainClass.Members.Add(entryPoint); 538 | mainClass.Members.Add(createParts); 539 | mainClass.Members.AddRange(OpenXmlPartExtensions.BuildHelperMethods 540 | (bluePrints, settings, namespaces, token)); 541 | 542 | // Setup the imports 543 | var codeNameSpaces = new List(namespaces.Count); 544 | foreach (var ns in namespaces) 545 | { 546 | if (!String.IsNullOrWhiteSpace(ns.Value)) 547 | { 548 | codeNameSpaces.Add(settings.NamespaceAliasOptions.BuildNamespaceImport( 549 | ns.Key, ns.Value)); 550 | } 551 | else 552 | { 553 | codeNameSpaces.Add(new CodeNamespaceImport(ns.Key)); 554 | } 555 | } 556 | codeNameSpaces.Sort(new CodeNamespaceImportComparer(settings.NamespaceAliasOptions)); 557 | 558 | mainNamespace.Imports.AddRange(codeNameSpaces.ToArray()); 559 | mainNamespace.Types.Add(mainClass); 560 | 561 | // Finish up 562 | result.Namespaces.Add(mainNamespace); 563 | return result; 564 | }, token); 565 | } 566 | 567 | /// 568 | /// Converts an into a representation 569 | /// of dotnet source code that can be compiled to build . 570 | /// 571 | /// 572 | /// The object to generate source code for. 573 | /// 574 | /// 575 | /// The object to create the resulting source code. 576 | /// 577 | /// 578 | /// Task cancellation token. 579 | /// 580 | /// 581 | /// A representation of the source code generated by 582 | /// that could create when compiled. 583 | /// 584 | public static async Task GenerateSourceCodeAsync( 585 | this OpenXmlPackage pkg, 586 | CodeDomProvider provider, 587 | CancellationToken token) 588 | { 589 | return await pkg.GenerateSourceCodeAsync( 590 | new DefaultSerializeSettings(), provider, token); 591 | } 592 | 593 | /// 594 | /// Converts an into a representation 595 | /// of dotnet source code that can be compiled to build . 596 | /// 597 | /// 598 | /// The object to generate source code for. 599 | /// 600 | /// 601 | /// The to apply to the resulting source code. 602 | /// 603 | /// 604 | /// The object to create the resulting source code. 605 | /// 606 | /// 607 | /// Task cancellation token. 608 | /// 609 | /// 610 | /// A representation of the source code generated by 611 | /// that could create when compiled. 612 | /// 613 | public static async Task GenerateSourceCodeAsync( 614 | this OpenXmlPackage pkg, 615 | NamespaceAliasOptions opts, 616 | CodeDomProvider provider, 617 | CancellationToken token) 618 | { 619 | return await pkg.GenerateSourceCodeAsync( 620 | new DefaultSerializeSettings(opts), provider, token); 621 | } 622 | 623 | /// 624 | /// Converts an into a representation 625 | /// of dotnet source code that can be compiled to build . 626 | /// 627 | /// 628 | /// The object to generate source code for. 629 | /// 630 | /// 631 | /// The to use during the code generation 632 | /// process. 633 | /// 634 | /// 635 | /// The object to create the resulting source code. 636 | /// 637 | /// 638 | /// Task cancellation token. 639 | /// 640 | /// 641 | /// A representation of the source code generated by 642 | /// that could create when compiled. 643 | /// 644 | public static async Task GenerateSourceCodeAsync( 645 | this OpenXmlPackage pkg, 646 | ISerializeSettings settings, 647 | CodeDomProvider provider, 648 | CancellationToken token) 649 | { 650 | var codeString = new System.Text.StringBuilder(); 651 | var code = await pkg.GenerateSourceCodeAsync(settings, token); 652 | 653 | using (var sw = new StringWriter(codeString)) 654 | { 655 | provider.GenerateCodeFromCompileUnit(code, sw, 656 | new CodeGeneratorOptions() { BracingStyle = "C" }); 657 | } 658 | return codeString.ToString().RemoveOutputHeaders().Trim(); 659 | } 660 | 661 | #endregion 662 | } 663 | } -------------------------------------------------------------------------------- /src/Serialize.OpenXml.CodeGen/OpenXmlPartExtensions.cs: -------------------------------------------------------------------------------- 1 | /* MIT License 2 | 3 | Copyright (c) 2020 Ryan Boggs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 6 | software and associated documentation files (the "Software"), to deal in the Software 7 | without restriction, including without limitation the rights to use, copy, modify, 8 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all copies 13 | or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 16 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 17 | PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 18 | FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 | DEALINGS IN THE SOFTWARE. 21 | */ 22 | 23 | using DocumentFormat.OpenXml.Packaging; 24 | using Serialize.OpenXml.CodeGen.Extentions; 25 | using System; 26 | using System.CodeDom; 27 | using System.CodeDom.Compiler; 28 | using System.Collections.Generic; 29 | using System.IO; 30 | using System.Linq; 31 | using System.Reflection; 32 | using System.Threading; 33 | using System.Threading.Tasks; 34 | 35 | namespace Serialize.OpenXml.CodeGen 36 | { 37 | /// 38 | /// Static class that converts objects 39 | /// into Code DOM objects. 40 | /// 41 | public static class OpenXmlPartExtensions 42 | { 43 | #region Private Static Fields 44 | 45 | /// 46 | /// The default parameter name for an object. 47 | /// 48 | private const string methodParamName = "part"; 49 | 50 | #endregion 51 | 52 | #region Public Static Methods 53 | 54 | /// 55 | /// Creates the appropriate code objects needed to create the entry method for the 56 | /// current request. 57 | /// 58 | /// 59 | /// The object and relationship id to build code for. 60 | /// 61 | /// 62 | /// The to use during the code generation 63 | /// process. 64 | /// 65 | /// /// 66 | /// A lookup object containing the 67 | /// number of times a given type was referenced. This is used for variable naming 68 | /// purposes. 69 | /// 70 | /// 71 | /// used to keep track of all openxml 72 | /// namespaces used during the process. 73 | /// 74 | /// 75 | /// The collection of objects that have already been 76 | /// visited. 77 | /// 78 | /// 79 | /// The root variable name and to use when building code 80 | /// statements to create new objects. 81 | /// 82 | /// 83 | /// Task cancellation token from the parent method. 84 | /// 85 | /// 86 | /// A collection of code statements and expressions that could be used to generate 87 | /// a new object from code. 88 | /// 89 | public static CodeStatementCollection BuildEntryMethodCodeStatements( 90 | IdPartPair part, 91 | ISerializeSettings settings, 92 | IDictionary typeCounts, 93 | IDictionary namespaces, 94 | OpenXmlPartBluePrintCollection blueprints, 95 | KeyValuePair rootVar, 96 | CancellationToken token) 97 | { 98 | // Argument validation 99 | if (settings is null) throw new ArgumentNullException(nameof(settings)); 100 | if (blueprints is null) throw new ArgumentNullException(nameof(blueprints)); 101 | 102 | if (String.IsNullOrWhiteSpace(rootVar.Key)) 103 | { 104 | throw new ArgumentNullException(nameof(rootVar.Key)); 105 | } 106 | 107 | bool hasHandlers = settings?.Handlers != null; 108 | 109 | // Check to see if the task has been cancelled. 110 | token.ThrowIfCancellationRequested(); 111 | 112 | // Use the custom handler methods if present and provide actual code 113 | if (hasHandlers && settings.Handlers.TryGetValue(part.OpenXmlPart.GetType(), 114 | out IOpenXmlHandler h)) 115 | { 116 | if (h is IOpenXmlPartHandler partHandler) 117 | { 118 | var customStatements = partHandler.BuildEntryMethodCodeStatements( 119 | part, settings, typeCounts, namespaces, blueprints, rootVar, token); 120 | 121 | if (customStatements != null) return customStatements; 122 | } 123 | } 124 | 125 | var result = new CodeStatementCollection(); 126 | var partType = part.OpenXmlPart.GetType(); 127 | 128 | CodeMethodReferenceExpression referenceExpression; 129 | CodeMethodInvokeExpression invokeExpression; 130 | CodeMethodReferenceExpression methodReference; 131 | 132 | // Check to see if the task has been cancelled. 133 | if (token.IsCancellationRequested) 134 | { 135 | result.Clear(); 136 | throw new OperationCanceledException(token); 137 | } 138 | 139 | // Make sure that the namespace for the current part is captured 140 | if (!namespaces.ContainsKey(partType.Namespace)) 141 | { 142 | // All OpenXmlPart objects are in the DocumentFormat.OpenXml.Packaging 143 | // namespace so there shouldn't be any collisions here. 144 | namespaces.Add(partType.Namespace, String.Empty); 145 | } 146 | 147 | // If the URI of the current part has already been included into 148 | // the blue prints collection, build the AddPart invocation 149 | // code statement and exit current method iteration. 150 | if (blueprints.TryGetValue(part.OpenXmlPart.Uri, out OpenXmlPartBluePrint bpTemp)) 151 | { 152 | // Surround this snippet with blank lines to make it 153 | // stand out in the current section of code. 154 | result.AddBlankLine(); 155 | referenceExpression = new CodeMethodReferenceExpression( 156 | new CodeVariableReferenceExpression(rootVar.Key), "AddPart", 157 | new CodeTypeReference(part.OpenXmlPart.GetType().Name)); 158 | invokeExpression = new CodeMethodInvokeExpression(referenceExpression, 159 | new CodeVariableReferenceExpression(bpTemp.VariableName), 160 | new CodePrimitiveExpression(part.RelationshipId)); 161 | result.Add(invokeExpression); 162 | result.AddBlankLine(); 163 | return result; 164 | } 165 | 166 | var partTypeName = partType.Name; 167 | var partTypeFullName = partType.FullName; 168 | string varName = partType.Name.ToCamelCase(); 169 | 170 | // Assign the appropriate variable name 171 | if (typeCounts.ContainsKey(partTypeFullName)) 172 | { 173 | varName = String.Concat(varName, typeCounts[partTypeFullName]++); 174 | } 175 | else 176 | { 177 | typeCounts.Add(partTypeFullName, 1); 178 | } 179 | 180 | // Setup the blueprint 181 | bpTemp = new OpenXmlPartBluePrint(part.OpenXmlPart, varName); 182 | 183 | // Need to evaluate the current OpenXmlPart type first to make sure the 184 | // correct "Add" statement is used as not all Parts can be initialized 185 | // using the "AddNewPart" method 186 | var addNewPartExpressions = CreateAddNewPartMethod(part, rootVar); 187 | // referenceExpression = addNewPartExpressions.Item1; 188 | invokeExpression = addNewPartExpressions.Item2; 189 | 190 | result.Add(new CodeVariableDeclarationStatement( 191 | partTypeName, varName, invokeExpression)); 192 | 193 | // Because the custom AddNewPart methods don't consistently take in a string relId 194 | // as a parameter, the id needs to be assigned after it is created. 195 | if (addNewPartExpressions.Item3) 196 | { 197 | methodReference = new CodeMethodReferenceExpression( 198 | new CodeVariableReferenceExpression(rootVar.Key), "ChangeIdOfPart"); 199 | result.Add(new CodeMethodInvokeExpression(methodReference, 200 | new CodeVariableReferenceExpression(varName), 201 | new CodePrimitiveExpression(part.RelationshipId))); 202 | } 203 | 204 | // Add the call to the method to populate the current OpenXmlPart object 205 | methodReference = new CodeMethodReferenceExpression(new CodeThisReferenceExpression(), 206 | bpTemp.MethodName); 207 | result.Add(new CodeMethodInvokeExpression(methodReference, 208 | new CodeDirectionExpression(FieldDirection.Ref, 209 | new CodeVariableReferenceExpression(varName)))); 210 | 211 | // Add the relationships, if applicable 212 | var relCodeStatements = part.OpenXmlPart.GenerateRelationshipCodeStatements(varName); 213 | 214 | if (relCodeStatements.Count > 0) 215 | { 216 | result.AddRange(relCodeStatements); 217 | } 218 | 219 | // put a line break before going through the child parts 220 | result.AddBlankLine(); 221 | 222 | // Add the current blueprint to the collection 223 | blueprints.Add(bpTemp); 224 | 225 | // Now check to see if there are any child parts for the current OpenXmlPart object. 226 | if (bpTemp.Part.Parts != null) 227 | { 228 | OpenXmlPartBluePrint childBluePrint; 229 | 230 | foreach (var p in bpTemp.Part.Parts) 231 | { 232 | // Check to see if the task has been cancelled. 233 | if (token.IsCancellationRequested) 234 | { 235 | result.Clear(); 236 | throw new OperationCanceledException(token); 237 | } 238 | 239 | // If the current child object has already been created, simply add a reference 240 | // to said object using the AddPart method. 241 | if (blueprints.Contains(p.OpenXmlPart.Uri)) 242 | { 243 | childBluePrint = blueprints[p.OpenXmlPart.Uri]; 244 | 245 | referenceExpression = new CodeMethodReferenceExpression( 246 | new CodeVariableReferenceExpression(varName), "AddPart", 247 | new CodeTypeReference(p.OpenXmlPart.GetType().Name)); 248 | 249 | invokeExpression = new CodeMethodInvokeExpression(referenceExpression, 250 | new CodeVariableReferenceExpression(childBluePrint.VariableName), 251 | new CodePrimitiveExpression(p.RelationshipId)); 252 | 253 | result.Add(invokeExpression); 254 | continue; 255 | } 256 | 257 | // If this is a new part, call this method with the current part's details 258 | result.AddRange(BuildEntryMethodCodeStatements( 259 | p, settings, typeCounts, namespaces, blueprints, 260 | new KeyValuePair(varName, partType), token)); 261 | } 262 | } 263 | 264 | return result; 265 | } 266 | 267 | /// 268 | /// Creates the appropriate helper methods for all of the objects 269 | /// for the current request. 270 | /// 271 | /// 272 | /// The collection of objects that have already been 273 | /// visited. 274 | /// 275 | /// 276 | /// The to use during the code generation 277 | /// process. 278 | /// 279 | /// 280 | /// collection used to keep track of all openxml 281 | /// namespaces used during the process. 282 | /// 283 | /// 284 | /// Task cancellation token from the parent method. 285 | /// 286 | /// 287 | /// A collection of code helper statements and expressions that could be used to generate a 288 | /// new object from code. 289 | /// 290 | public static CodeTypeMemberCollection BuildHelperMethods( 291 | OpenXmlPartBluePrintCollection bluePrints, 292 | ISerializeSettings settings, 293 | IDictionary namespaces, 294 | CancellationToken token) 295 | { 296 | if (bluePrints == null) throw new ArgumentNullException(nameof(bluePrints)); 297 | var result = new CodeTypeMemberCollection(); 298 | var localTypes = new TypeMonitorCollection(); 299 | Type rootElementType; 300 | CodeMemberMethod method; 301 | bool hasHandlers = settings?.Handlers != null; 302 | 303 | foreach (var bp in bluePrints) 304 | { 305 | // Check to see if the task has been cancelled. 306 | if (token.IsCancellationRequested) 307 | { 308 | result.Clear(); 309 | throw new OperationCanceledException(token); 310 | } 311 | 312 | // Implement the custom helper if present 313 | if (hasHandlers && settings.Handlers.TryGetValue(bp.PartType, out IOpenXmlHandler h)) 314 | { 315 | if (h is IOpenXmlPartHandler partHandler) 316 | { 317 | method = partHandler.BuildHelperMethod( 318 | bp.Part, bp.MethodName, settings, namespaces, token); 319 | 320 | if (method != null) 321 | { 322 | result.Add(method); 323 | continue; 324 | } 325 | } 326 | } 327 | 328 | // Setup the first method 329 | method = new CodeMemberMethod() 330 | { 331 | Name = bp.MethodName, 332 | Attributes = MemberAttributes.Private | MemberAttributes.Final, 333 | ReturnType = new CodeTypeReference() 334 | }; 335 | method.Parameters.Add( 336 | new CodeParameterDeclarationExpression(bp.Part.GetType().Name, methodParamName) 337 | { Direction = FieldDirection.Ref }); 338 | 339 | // Code part elements next 340 | if (bp.Part.RootElement is null) 341 | { 342 | // Put all of the pieces together 343 | method.Statements.AddRange(bp.Part.BuildPartFeedData(namespaces)); 344 | } 345 | else 346 | { 347 | rootElementType = bp.Part.RootElement?.GetType(); 348 | localTypes.Clear(); 349 | 350 | // Build the element details of the requested part for the current method 351 | method.Statements.AddRange( 352 | bp.Part.RootElement.BuildCodeStatements( 353 | settings, localTypes, namespaces, token, out string rootElementVar)); 354 | 355 | // Now finish up the current method by assigning the OpenXmlElement code 356 | // statements back to the appropriate property of the part parameter 357 | if (rootElementType != null && !String.IsNullOrWhiteSpace(rootElementVar)) 358 | { 359 | foreach (var paramProp in bp.Part.GetType().GetProperties()) 360 | { 361 | // Check to see if the task has been cancelled. 362 | if (token.IsCancellationRequested) 363 | { 364 | result.Clear(); 365 | throw new OperationCanceledException(token); 366 | } 367 | 368 | if (paramProp.PropertyType == rootElementType) 369 | { 370 | var varRef = new CodeVariableReferenceExpression(rootElementVar); 371 | var paramRef = new CodeArgumentReferenceExpression(methodParamName); 372 | var propRef = new CodePropertyReferenceExpression( 373 | paramRef, paramProp.Name); 374 | 375 | method.Statements.Add(new CodeAssignStatement(propRef, varRef)); 376 | break; 377 | } 378 | } 379 | } 380 | } 381 | result.Add(method); 382 | } 383 | 384 | return result; 385 | } 386 | 387 | /// 388 | /// Builds code statements that will build using the 389 | /// method. 390 | /// 391 | /// 392 | /// The object to build the source code for. 393 | /// 394 | /// 395 | /// values used to keep 396 | /// track of all openxml namespaces used during the process. 397 | /// 398 | /// 399 | /// A collection of code statements 400 | /// that would regenerate using the 401 | /// method. 402 | /// 403 | public static CodeStatementCollection BuildPartFeedData( 404 | this OpenXmlPart part, 405 | IDictionary namespaces) 406 | { 407 | // Make sure no null values were passed. 408 | if (part == null) throw new ArgumentNullException(nameof(part)); 409 | if (namespaces == null) throw new ArgumentNullException(nameof(namespaces)); 410 | 411 | // If the root element is not present (aka: null) then perform a simple feed 412 | // dump of the part in the current method 413 | const string memName = "mem"; 414 | const string b64Name = "base64"; 415 | 416 | var result = new CodeStatementCollection(); 417 | 418 | // Add the necessary namespaces by hand to the namespace set 419 | if (!namespaces.ContainsKey("System")) 420 | { 421 | namespaces.Add("System", String.Empty); 422 | } 423 | if (!namespaces.ContainsKey("System.IO")) 424 | { 425 | namespaces.Add("System.IO", String.Empty); 426 | } 427 | 428 | using (var partStream = part.GetStream(FileMode.Open, FileAccess.Read)) 429 | { 430 | using (var mem = new MemoryStream()) 431 | { 432 | partStream.CopyTo(mem); 433 | result.Add(new CodeVariableDeclarationStatement(typeof(string), b64Name, 434 | new CodePrimitiveExpression(Convert.ToBase64String(mem.ToArray())))); 435 | } 436 | } 437 | result.AddBlankLine(); 438 | 439 | var fromBase64 = new CodeMethodReferenceExpression( 440 | new CodeVariableReferenceExpression("Convert"), 441 | "FromBase64String"); 442 | var invokeFromBase64 = new CodeMethodInvokeExpression(fromBase64, 443 | new CodeVariableReferenceExpression("base64")); 444 | var createStream = new CodeObjectCreateExpression(new CodeTypeReference("MemoryStream"), 445 | invokeFromBase64, new CodePrimitiveExpression(false)); 446 | var feedData = new CodeMethodReferenceExpression( 447 | new CodeArgumentReferenceExpression(methodParamName), "FeedData"); 448 | var invokeFeedData = new CodeMethodInvokeExpression( 449 | feedData, new CodeVariableReferenceExpression(memName)); 450 | var disposeMem = new CodeMethodReferenceExpression( 451 | new CodeVariableReferenceExpression(memName), "Dispose"); 452 | var invokeDisposeMem = new CodeMethodInvokeExpression(disposeMem); 453 | 454 | // Setup the try statement 455 | var tryAndCatch = new CodeTryCatchFinallyStatement(); 456 | tryAndCatch.TryStatements.Add(invokeFeedData); 457 | tryAndCatch.FinallyStatements.Add(invokeDisposeMem); 458 | 459 | // Put all of the pieces together 460 | result.Add(new CodeVariableDeclarationStatement("Stream", memName, createStream)); 461 | result.Add(tryAndCatch); 462 | 463 | return result; 464 | } 465 | 466 | /// 467 | /// Converts an into a CodeDom object that can be used 468 | /// to build code in a given .NET language to build the referenced . 469 | /// 470 | /// 471 | /// The object to generate source code for. 472 | /// 473 | /// 474 | /// A new containing the instructions to build 475 | /// the referenced . 476 | /// 477 | public static CodeCompileUnit GenerateSourceCode(this OpenXmlPart part) 478 | { 479 | return part.GenerateSourceCode(new DefaultSerializeSettings()); 480 | } 481 | 482 | /// 483 | /// Converts an into a CodeDom object that can be used 484 | /// to build code in a given .NET language to build the referenced . 485 | /// 486 | /// 487 | /// The object to generate source code for. 488 | /// 489 | /// 490 | /// The to apply to the resulting source code. 491 | /// 492 | /// 493 | /// A new containing the instructions to build 494 | /// the referenced . 495 | /// 496 | public static CodeCompileUnit GenerateSourceCode(this OpenXmlPart part, NamespaceAliasOptions opts) 497 | { 498 | return part.GenerateSourceCode(new DefaultSerializeSettings(opts)); 499 | } 500 | 501 | /// 502 | /// Converts an into a CodeDom object that can be used 503 | /// to build code in a given .NET language to build the referenced . 504 | /// 505 | /// 506 | /// The object to generate source code for. 507 | /// 508 | /// 509 | /// The to use during the code generation 510 | /// process. 511 | /// 512 | /// 513 | /// A new containing the instructions to build 514 | /// the referenced . 515 | /// 516 | public static CodeCompileUnit GenerateSourceCode( 517 | this OpenXmlPart part, ISerializeSettings settings) 518 | { 519 | return DefaultSerializeSettings.TaskIndustry.StartNew( 520 | () => part.GenerateSourceCodeAsync(settings, CancellationToken.None)) 521 | .Unwrap() 522 | .GetAwaiter() 523 | .GetResult(); 524 | } 525 | 526 | /// 527 | /// Converts an into a representation 528 | /// of dotnet source code that can be compiled to build . 529 | /// 530 | /// 531 | /// The object to generate source code for. 532 | /// 533 | /// 534 | /// The object to create the resulting source code. 535 | /// 536 | /// 537 | /// A representation of the source code generated by 538 | /// that could create when compiled. 539 | /// 540 | public static string GenerateSourceCode(this OpenXmlPart part, CodeDomProvider provider) 541 | { 542 | return part.GenerateSourceCode(new DefaultSerializeSettings(), provider); 543 | } 544 | 545 | /// 546 | /// Converts an into a representation 547 | /// of dotnet source code that can be compiled to build . 548 | /// 549 | /// 550 | /// The object to generate source code for. 551 | /// 552 | /// 553 | /// The to apply to the resulting source code. 554 | /// 555 | /// 556 | /// The object to create the resulting source code. 557 | /// 558 | /// 559 | /// A representation of the source code generated by 560 | /// that could create when compiled. 561 | /// 562 | public static string GenerateSourceCode( 563 | this OpenXmlPart part, NamespaceAliasOptions opts, CodeDomProvider provider) 564 | { 565 | return part.GenerateSourceCode(new DefaultSerializeSettings(opts), provider); 566 | } 567 | 568 | /// 569 | /// Converts an into a representation 570 | /// of dotnet source code that can be compiled to build . 571 | /// 572 | /// 573 | /// The object to generate source code for. 574 | /// 575 | /// 576 | /// The to use during the code generation 577 | /// process. 578 | /// 579 | /// 580 | /// The object to create the resulting source code. 581 | /// 582 | /// 583 | /// A representation of the source code generated by 584 | /// that could create when compiled. 585 | /// 586 | public static string GenerateSourceCode( 587 | this OpenXmlPart part, ISerializeSettings settings, CodeDomProvider provider) 588 | { 589 | var codeString = new System.Text.StringBuilder(); 590 | var code = part.GenerateSourceCode(settings); 591 | 592 | using (var sw = new StringWriter(codeString)) 593 | { 594 | provider.GenerateCodeFromCompileUnit(code, sw, 595 | new CodeGeneratorOptions() { BracingStyle = "C" }); 596 | } 597 | return codeString.ToString().RemoveOutputHeaders().Trim(); 598 | } 599 | 600 | /// 601 | /// Converts an into a CodeDom object that can be used 602 | /// to build code in a given .NET language to build the referenced . 603 | /// 604 | /// 605 | /// The object to generate source code for. 606 | /// 607 | /// 608 | /// Task cancellation token. 609 | /// 610 | /// 611 | /// A new containing the instructions to build 612 | /// the referenced . 613 | /// 614 | public static async Task GenerateSourceCodeAsync( 615 | this OpenXmlPart part, 616 | CancellationToken token) 617 | { 618 | return await part.GenerateSourceCodeAsync(new DefaultSerializeSettings(), token); 619 | } 620 | 621 | /// 622 | /// Converts an into a CodeDom object that can be used 623 | /// to build code in a given .NET language to build the referenced . 624 | /// 625 | /// 626 | /// The object to generate source code for. 627 | /// 628 | /// 629 | /// The to apply to the resulting source code. 630 | /// 631 | /// 632 | /// Task cancellation token. 633 | /// 634 | /// 635 | /// A new containing the instructions to build 636 | /// the referenced . 637 | /// 638 | public static async Task GenerateSourceCodeAsync( 639 | this OpenXmlPart part, 640 | NamespaceAliasOptions opts, 641 | CancellationToken token) 642 | { 643 | return await part.GenerateSourceCodeAsync(new DefaultSerializeSettings(opts), token); 644 | } 645 | 646 | /// 647 | /// Converts an into a CodeDom object that can be used 648 | /// to build code in a given .NET language to build the referenced . 649 | /// 650 | /// 651 | /// The object to generate source code for. 652 | /// 653 | /// 654 | /// The to use during the code generation 655 | /// process. 656 | /// 657 | /// 658 | /// Task cancellation token. 659 | /// 660 | /// 661 | /// A new containing the instructions to build 662 | /// the referenced . 663 | /// 664 | public static async Task GenerateSourceCodeAsync( 665 | this OpenXmlPart part, 666 | ISerializeSettings settings, 667 | CancellationToken token) 668 | { 669 | return await Task.Run(() => 670 | { 671 | CodeMethodReferenceExpression methodRef; 672 | OpenXmlPartBluePrint mainBluePrint; 673 | var result = new CodeCompileUnit(); 674 | var eType = part.GetType(); 675 | var partTypeName = eType.Name; 676 | var partTypeFullName = eType.FullName; 677 | var varName = eType.Name.ToCamelCase(); 678 | var partTypeCounts = new Dictionary(); 679 | var namespaces = new Dictionary(); 680 | var mainNamespace = new CodeNamespace(settings.NamespaceName); 681 | var bluePrints = new OpenXmlPartBluePrintCollection(); 682 | 683 | // Set the var uniqueness indicator 684 | TypeMonitor.UseUniqueVariableNames = settings.UseUniqueVariableNames; 685 | 686 | // Assign the appropriate variable name 687 | if (partTypeCounts.ContainsKey(partTypeFullName)) 688 | { 689 | varName = String.Concat(varName, partTypeCounts[partTypeFullName]++); 690 | } 691 | else 692 | { 693 | partTypeCounts.Add(partTypeFullName, 1); 694 | } 695 | 696 | // Generate a new blue print for the current part to help create the main 697 | // method reference then add it to the blue print collection 698 | mainBluePrint = new OpenXmlPartBluePrint(part, varName); 699 | bluePrints.Add(mainBluePrint); 700 | methodRef = new CodeMethodReferenceExpression( 701 | new CodeThisReferenceExpression(), mainBluePrint.MethodName); 702 | 703 | // Build the entry method 704 | var entryMethod = new CodeMemberMethod() 705 | { 706 | Name = $"Create{partTypeName}", 707 | ReturnType = new CodeTypeReference(), 708 | Attributes = MemberAttributes.Public | MemberAttributes.Final 709 | }; 710 | entryMethod.Parameters.Add( 711 | new CodeParameterDeclarationExpression(partTypeName, methodParamName) 712 | { Direction = FieldDirection.Ref }); 713 | 714 | // Check to see if the task has been cancelled. 715 | token.ThrowIfCancellationRequested(); 716 | 717 | var relCodeStatements = part.GenerateRelationshipCodeStatements( 718 | new CodeThisReferenceExpression()); 719 | if (relCodeStatements.Count > 0) 720 | { 721 | entryMethod.Statements.AddRange(relCodeStatements); 722 | } 723 | 724 | // Add all of the child part references here 725 | if (part.Parts != null) 726 | { 727 | var rootPartPair = new KeyValuePair(methodParamName, eType); 728 | foreach (var pair in part.Parts) 729 | { 730 | // Check to see if the task has been cancelled. 731 | token.ThrowIfCancellationRequested(); 732 | 733 | entryMethod.Statements.AddRange(BuildEntryMethodCodeStatements( 734 | pair, 735 | settings, 736 | partTypeCounts, 737 | namespaces, 738 | bluePrints, 739 | rootPartPair, 740 | token)); 741 | } 742 | } 743 | 744 | entryMethod.Statements.Add(new CodeMethodInvokeExpression(methodRef, 745 | new CodeArgumentReferenceExpression(methodParamName))); 746 | 747 | // Setup the main class next 748 | var mainClass = new CodeTypeDeclaration($"{eType.Name}BuilderClass") 749 | { 750 | IsClass = true, 751 | Attributes = MemberAttributes.Public 752 | }; 753 | mainClass.Members.Add(entryMethod); 754 | mainClass.Members.AddRange(BuildHelperMethods( 755 | bluePrints, settings, namespaces, token)); 756 | 757 | // Setup the imports 758 | var codeNameSpaces = new List(namespaces.Count); 759 | foreach (var ns in namespaces) 760 | { 761 | // Check to see if the task has been cancelled. 762 | token.ThrowIfCancellationRequested(); 763 | 764 | if (!String.IsNullOrWhiteSpace(ns.Value)) 765 | { 766 | codeNameSpaces.Add(settings.NamespaceAliasOptions.BuildNamespaceImport( 767 | ns.Key, ns.Value)); 768 | } 769 | else 770 | { 771 | codeNameSpaces.Add(new CodeNamespaceImport(ns.Key)); 772 | } 773 | } 774 | codeNameSpaces.Sort(new CodeNamespaceImportComparer(settings.NamespaceAliasOptions)); 775 | 776 | mainNamespace.Imports.AddRange(codeNameSpaces.ToArray()); 777 | mainNamespace.Types.Add(mainClass); 778 | 779 | // Finish up 780 | result.Namespaces.Add(mainNamespace); 781 | return result; 782 | }, token); 783 | } 784 | 785 | /// 786 | /// Converts an into a representation 787 | /// of dotnet source code that can be compiled to build . 788 | /// 789 | /// 790 | /// The object to generate source code for. 791 | /// 792 | /// 793 | /// The object to create the resulting source code. 794 | /// 795 | /// 796 | /// Task cancellation token. 797 | /// 798 | /// 799 | /// A representation of the source code generated by 800 | /// that could create when compiled. 801 | /// 802 | public static async Task GenerateSourceCodeAsync( 803 | this OpenXmlPart part, 804 | CodeDomProvider provider, 805 | CancellationToken token) 806 | { 807 | return await part.GenerateSourceCodeAsync( 808 | new DefaultSerializeSettings(), provider, token); 809 | } 810 | 811 | /// 812 | /// Converts an into a representation 813 | /// of dotnet source code that can be compiled to build . 814 | /// 815 | /// 816 | /// The object to generate source code for. 817 | /// 818 | /// 819 | /// The to apply to the resulting source code. 820 | /// 821 | /// 822 | /// The object to create the resulting source code. 823 | /// 824 | /// 825 | /// Task cancellation token. 826 | /// 827 | /// 828 | /// A representation of the source code generated by 829 | /// that could create when compiled. 830 | /// 831 | public static async Task GenerateSourceCodeAsync( 832 | this OpenXmlPart part, 833 | NamespaceAliasOptions opts, 834 | CodeDomProvider provider, 835 | CancellationToken token) 836 | { 837 | return await part.GenerateSourceCodeAsync( 838 | new DefaultSerializeSettings(opts), provider, token); 839 | } 840 | 841 | /// 842 | /// Converts an into a representation 843 | /// of dotnet source code that can be compiled to build . 844 | /// 845 | /// 846 | /// The object to generate source code for. 847 | /// 848 | /// 849 | /// The to use during the code generation 850 | /// process. 851 | /// 852 | /// 853 | /// The object to create the resulting source code. 854 | /// 855 | /// 856 | /// Task cancellation token. 857 | /// 858 | /// 859 | /// A representation of the source code generated by 860 | /// that could create when compiled. 861 | /// 862 | public static async Task GenerateSourceCodeAsync( 863 | this OpenXmlPart part, 864 | ISerializeSettings settings, 865 | CodeDomProvider provider, 866 | CancellationToken token) 867 | { 868 | var codeString = new System.Text.StringBuilder(); 869 | var code = await part.GenerateSourceCodeAsync(settings, token); 870 | 871 | using (var sw = new StringWriter(codeString)) 872 | { 873 | provider.GenerateCodeFromCompileUnit(code, sw, 874 | new CodeGeneratorOptions() { BracingStyle = "C" }); 875 | } 876 | return codeString.ToString().RemoveOutputHeaders().Trim(); 877 | } 878 | 879 | #endregion 880 | 881 | #region Private Static Methods 882 | 883 | /// 884 | /// Constructs the appropriate and 885 | /// objects for adding new parts 886 | /// to an existing object. 887 | /// 888 | /// 889 | /// The object and relationship id to build 890 | /// the code expressions for. 891 | /// 892 | /// 893 | /// The root variable name and to use when building code 894 | /// statements to create new objects. 895 | /// 896 | /// 897 | /// A tuple containing the AddNewPart code expressions. 898 | /// 899 | private static (CodeMethodReferenceExpression, CodeMethodInvokeExpression, bool) 900 | CreateAddNewPartMethod( 901 | IdPartPair part, KeyValuePair rootVar) 902 | { 903 | CodeMethodReferenceExpression item1 = null; 904 | CodeMethodInvokeExpression item2 = null; 905 | var pType = part.OpenXmlPart.GetType(); 906 | var checkName = $"Add{pType.Name}"; 907 | bool check(MethodInfo m) => m.Name.Equals(checkName, StringComparison.OrdinalIgnoreCase); 908 | var methods = rootVar.Value.GetMethods().Where(check).ToArray(); 909 | 910 | // If no custom AddNewPart methods exist for the requested part, revert back to 911 | // the default AddNewPart method. 912 | if (methods.Length == 0) 913 | { 914 | item1 = new CodeMethodReferenceExpression( 915 | new CodeVariableReferenceExpression(rootVar.Key), "AddNewPart", 916 | new CodeTypeReference(pType.Name)); 917 | item2 = new CodeMethodInvokeExpression(item1); 918 | item2.Parameters.Add(new CodePrimitiveExpression(part.RelationshipId)); 919 | 920 | return (item1, item2, false); 921 | } 922 | 923 | // Initialize the custom AddNewPart method reference and invoke expresstions 924 | // before examining the method parameters 925 | item1 = new CodeMethodReferenceExpression( 926 | new CodeVariableReferenceExpression(rootVar.Key), checkName); 927 | item2 = new CodeMethodInvokeExpression(item1); 928 | 929 | // Now figure out what parameters are needed for this method 930 | ParameterInfo[] parameters; 931 | foreach (var m in methods) 932 | { 933 | // Please Note: To my knowledge, the custom AddNewPart methods have 2 main 934 | // types of method parameters common among all custom methods: 935 | // * Default (no parameters) 936 | // * string contentType 937 | // This loop should be looking for the second type primarily and reverting 938 | // to the first type if not found. 939 | parameters = m.GetParameters(); 940 | 941 | // Methods with 1 string parameter should be the contentType parameter method 942 | if (parameters != null && 943 | parameters.Length == 1 && 944 | parameters[0].ParameterType == typeof(string)) 945 | { 946 | item2.Parameters.Add( 947 | new CodePrimitiveExpression(part.OpenXmlPart.ContentType)); 948 | break; 949 | } 950 | } 951 | return (item1, item2, true); 952 | } 953 | 954 | #endregion 955 | } 956 | } --------------------------------------------------------------------------------