/// <summary>
        /// Converts an <see cref="OpenXmlPackage"/> into a <see cref="string"/> representation
        /// of dotnet source code that can be compiled to build <paramref name="pkg"/>.
        /// </summary>
        /// <param name="pkg">
        /// The <see cref="OpenXmlPackage"/> object to generate source code for.
        /// </param>
        /// <param name="settings">
        /// The <see cref="ISerializeSettings"/> to use during the code generation
        /// process.
        /// </param>
        /// <param name="provider">
        /// The <see cref="CodeDomProvider"/> object to create the resulting source code.
        /// </param>
        /// <returns>
        /// A <see cref="string"/> representation of the source code generated by <paramref name="provider"/>
        /// that could create <paramref name="pkg"/> when compiled.
        /// </returns>
        public static string GenerateSourceCode(this OpenXmlPackage pkg, ISerializeSettings settings, CodeDomProvider provider)
        {
            var codeString = new System.Text.StringBuilder();
            var code = pkg.GenerateSourceCode(settings);

            using (var sw = new StringWriter(codeString))
            {
                provider.GenerateCodeFromCompileUnit(code, sw,
                    new CodeGeneratorOptions() { BracingStyle = "C" });
            }
            return codeString.ToString().RemoveOutputHeaders().Trim();
        }
Пример #2
0
        /// <summary>
        /// Builds the appropriate code objects that would build the contents of
        /// <paramref name="e"/>.
        /// </summary>
        /// <param name="e">
        /// The <see cref="OpenXmlElement"/> object to codify.
        /// </param>
        /// <param name="settings">
        /// The <see cref="ISerializeSettings"/> to use during the code generation
        /// process.
        /// </param>
        /// <param name="typeCounts">
        /// A lookup <see cref="IDictionary{TKey, TValue}"/> object containing the
        /// number of times a given type was referenced.  This is used for variable naming
        /// purposes.
        /// </param>
        /// <param name="namespaces">
        /// Collection <see cref="ISet{T}"/> used to keep track of all openxml namespaces
        /// used during the process.
        /// </param>
        /// <param name="elementName">
        /// The variable name of the root <see cref="OpenXmlElement"/> object that was built
        /// from the <paramref name="e"/>.
        /// </param>
        /// <returns>
        /// A collection of code statements and expressions that could be used to generate
        /// a new <paramref name="e"/> object from code.
        /// </returns>
        public static CodeStatementCollection BuildCodeStatements(
            this OpenXmlElement e,
            ISerializeSettings settings,
            IDictionary <Type, int> typeCounts,
            ISet <string> namespaces,
            out string elementName)
        {
            // argument validation
            if (e is null)
            {
                throw new ArgumentNullException(nameof(e));
            }
            if (settings is null)
            {
                throw new ArgumentNullException(nameof(settings));
            }
            if (typeCounts is null)
            {
                throw new ArgumentNullException(nameof(typeCounts));
            }
            if (namespaces is null)
            {
                throw new ArgumentNullException(nameof(namespaces));
            }

            // method vars
            var result      = new CodeStatementCollection();
            var elementType = e.GetType();

            // If current element is OpenXmlUnknownElement and IgnoreUnknownElements
            // setting is enabled, return an empty CodeStatementCollection and
            // proceed no further.
            if (settings.IgnoreUnknownElements && e is OpenXmlUnknownElement)
            {
                elementName = String.Empty;
                return(result);
            }

            // If current element is OpenXmlMiscNode and its XmlNodeType is found in
            // the IgnoreMiscNoteTypes setting, return an empty CodeStatementCollection
            // and proceed no futher.
            if (e is OpenXmlMiscNode eMisc)
            {
                if (settings.IgnoreMiscNodeTypes != null && settings.IgnoreMiscNodeTypes.Contains(eMisc.XmlNodeType))
                {
                    elementName = String.Empty;
                    return(result);
                }
            }

            // If there is any custom code available for the current element, use the
            // custom code instead
            if (settings?.Handlers != null && settings.Handlers.TryGetValue(elementType, out IOpenXmlHandler customHandler))
            {
                // Make sure that the current handler implements IOpenXmlElementHandler.
                // If so, return the custom code statement collection.
                if (customHandler is IOpenXmlElementHandler cHandler)
                {
                    // Only return the custom code statements if the hanlder
                    // implementation doesn't return null
                    var customCodeStatements = cHandler.BuildCodeStatements(
                        e, settings, typeCounts, namespaces, out elementName);

                    if (customCodeStatements != null)
                    {
                        return(customCodeStatements);
                    }
                }
            }

            // Build the initializer for the current element
            elementName = elementType.GenerateVariableName(typeCounts);

            CodeStatement statement;
            CodeObjectCreateExpression    createExpression;
            CodeMethodReferenceExpression methodReferenceExpression;
            CodeMethodInvokeExpression    invokeExpression;
            CodeTypeReferenceCollection   typeReferenceCollection;
            CodeTypeReference             typeReference;

            // Used for dealing with thrown FormatExceptions
            Func <string, CodeStatement> handleFmtException;

            // Dictionary used to map complex objects to element properties
            var simpleTypePropReferences = new Dictionary <string, string>();

            Type              tmpType    = null;
            string            simpleName = null;
            string            junk       = null;
            object            val        = null;
            object            propVal    = null;
            PropertyInfo      pi         = null;
            OpenXmlSimpleType tmpSimpleType;

            // Start pulling out the properties of the current element.
            var sProperties = elementType.GetOpenXmlSimpleValuesProperties();
            var cProps      = elementType.GetOpenXmlSimpleTypeProperties(false);
            IReadOnlyList <PropertyInfo> cProperties = cProps
                                                       .Where(m => !m.PropertyType.IsEnumValueType())
                                                       .ToList();
            IReadOnlyList <PropertyInfo> enumProperties = cProps
                                                          .Where(m => m.PropertyType.IsEnumValueType())
                                                          .ToList();

            // Create a variable reference statement
            CodeAssignStatement primitivePropertyAssignment(
                string objName,
                string varName,
                string rVal,
                bool varIsRef = false)
            {
                var rValExp = (varIsRef
                    ? new CodeVariableReferenceExpression(rVal)
                    : new CodePrimitiveExpression(rVal) as CodeExpression);

                return(new CodeAssignStatement(
                           new CodePropertyReferenceExpression(
                               new CodeVariableReferenceExpression(objName), varName),
                           rValExp));
            }

            // Add the current element type namespace to the set object
            namespaces.Add(elementType.Namespace);

            // Need to build the non enumvalue complex objects first before assigning
            // them as properties of the current element
            foreach (var complex in cProperties)
            {
                // Get the value of the current property
                val = complex.GetValue(e);

                // Skip properties that are null
                if (val is null)
                {
                    continue;
                }

                // Add the complex property namespace to the set
                namespaces.Add(complex.PropertyType.Namespace);

                // Use the junk var to store the property type name
                junk = complex.PropertyType.Name;

                // Build the variable name
                simpleName = complex.PropertyType.GenerateVariableName(typeCounts);

                // Need to handle the generic properties special when trying
                // to build a variable name.
                if (complex.PropertyType.IsGenericType)
                {
                    // Setup necessary CodeDom objects
                    typeReferenceCollection = new CodeTypeReferenceCollection();

                    foreach (var gen in complex.PropertyType.GenericTypeArguments)
                    {
                        typeReferenceCollection.Add(gen.Name);
                    }

                    typeReference = new CodeTypeReference(junk);
                    typeReference.TypeArguments.AddRange(typeReferenceCollection);
                    createExpression = new CodeObjectCreateExpression(typeReference);
                    statement        = new CodeVariableDeclarationStatement(typeReference, simpleName, createExpression);
                }
                else
                {
                    createExpression = new CodeObjectCreateExpression(junk);
                    statement        = new CodeVariableDeclarationStatement(junk, simpleName, createExpression);
                }
                result.Add(statement);

                // Finish the variable assignment statement
                result.Add(primitivePropertyAssignment(simpleName, "InnerText", val.ToString()));
                result.AddBlankLine();

                // Keep track of the objects to assign to the current element
                // complex properties
                simpleTypePropReferences.Add(complex.Name, simpleName);
            }

            // Initialize the mc attribute information, if available
            if (e.MCAttributes != null)
            {
                tmpType = e.MCAttributes.GetType();

                simpleName = tmpType.GenerateVariableName(typeCounts);

                createExpression = new CodeObjectCreateExpression(tmpType.Name);
                statement        = new CodeVariableDeclarationStatement(tmpType.Name, simpleName, createExpression);
                result.Add(statement);

                foreach (var m in tmpType.GetStringValueProperties())
                {
                    val = m.GetValue(e.MCAttributes);
                    if (val != null)
                    {
                        statement = new CodeAssignStatement(
                            new CodePropertyReferenceExpression(
                                new CodeVariableReferenceExpression(simpleName), m.Name),
                            new CodePrimitiveExpression(val.ToString()));
                        result.Add(statement);
                    }
                }
                result.AddBlankLine();
                simpleTypePropReferences.Add("MCAttributes", simpleName);
            }

            // Include the alias prefix if the current element belongs to a class
            // within the namespaces identified to needing an alias
            junk             = elementType.GetObjectTypeName(settings.NamespaceAliasOptions.Order);
            createExpression = new CodeObjectCreateExpression(junk);

            // OpenXmlUknownElement objects require the calling of custom constructors
            if (e is OpenXmlUnknownElement)
            {
                createExpression.Parameters.AddRange(new CodeExpression[]
                {
                    new CodePrimitiveExpression(e.Prefix),
                    new CodePrimitiveExpression(e.LocalName),
                    new CodePrimitiveExpression(e.NamespaceUri)
                });
            }
            // OpenXmlLeafTextElement classes have constructors that take in
            // one StringValue object as a parameter to populate the new
            // object's Text property.  This takes advantange of that knowledge.
            else if (elementType.IsSubclassOf(typeof(OpenXmlLeafTextElement)))
            {
                var leafText = elementType.GetProperty("Text").GetValue(e);
                var param    = new CodePrimitiveExpression(leafText);
                createExpression.Parameters.Add(param);
            }
            statement = new CodeVariableDeclarationStatement(junk, elementName, createExpression);
            result.Add(statement);

            // Don't forget to add any additional namespaces to the element
            if (e.NamespaceDeclarations != null && e.NamespaceDeclarations.Count() > 0)
            {
                result.AddBlankLine();
                foreach (var ns in e.NamespaceDeclarations)
                {
                    methodReferenceExpression = new CodeMethodReferenceExpression(
                        new CodeVariableReferenceExpression(elementName),
                        "AddNamespaceDeclaration");
                    invokeExpression = new CodeMethodInvokeExpression(methodReferenceExpression,
                                                                      new CodePrimitiveExpression(ns.Key),
                                                                      new CodePrimitiveExpression(ns.Value));
                    result.Add(invokeExpression);
                }

                // Add a line break if namespace declarations were present and if the current
                // element has additional properties that need to be filled out.
                if ((cProperties.Count > 0 || simpleTypePropReferences.Count > 0) ||
                    (sProperties.Count > 0 && sProperties.Count(sp => sp.GetValue(e) != null) > 0))
                {
                    result.AddBlankLine();
                }
            }

            // Now set the properties of the current variable
            foreach (var p in sProperties)
            {
                val = p.GetValue(e);
                if (val == null)
                {
                    continue;
                }

                // Add the simple property type namespace to the set
                namespaces.Add(p.PropertyType.Namespace);

                tmpSimpleType = val as OpenXmlSimpleType;

                if (!tmpSimpleType.HasValue)
                {
                    statement = new CodeCommentStatement(
                        $"'{val}' is not a valid value for the {p.Name} property");
                }
                else
                {
                    propVal = val.GetType().GetProperty("Value").GetValue(val);

                    statement = new CodeAssignStatement(
                        new CodePropertyReferenceExpression(
                            new CodeVariableReferenceExpression(elementName), p.Name),
                        new CodePrimitiveExpression(propVal));
                }
                result.Add(statement);
            }

            if (simpleTypePropReferences.Count > 0)
            {
                foreach (var sProp in simpleTypePropReferences)
                {
                    statement = new CodeAssignStatement(
                        new CodePropertyReferenceExpression(
                            new CodeVariableReferenceExpression(elementName), sProp.Key),
                        new CodeVariableReferenceExpression(sProp.Value));
                    result.Add(statement);
                }
            }

            // Go through the list of complex properties again but include
            // EnumValue`1 type properties in the search.
            foreach (var cp in enumProperties)
            {
                val        = cp.GetValue(e);
                simpleName = null;

                if (val is null)
                {
                    continue;
                }

                pi         = cp.PropertyType.GetProperty("Value");
                simpleName = pi.PropertyType.GetObjectTypeName(settings.NamespaceAliasOptions.Order);

                // Add the simple property type namespace to the set
                namespaces.Add(pi.PropertyType.Namespace);

                handleFmtException = (eName) =>
                                     new CodeCommentStatement(
                    $"Could not parse value of '{cp.Name}' property for variable " +
                    $"`{eName}` - {simpleName} enum does not contain '{val}' field");

                // This code may run into issues if, for some unfortunate reason, the xml schema used to help
                // create the current OpenXml SDK library is not set correctly.  If that happens, the
                // catch statements will print out the current line of code as a comment as a stop gap until
                // the issue is reported and fix.
                try
                {
                    statement = new CodeAssignStatement(
                        new CodePropertyReferenceExpression(
                            new CodeVariableReferenceExpression(elementName), cp.Name),
                        new CodeFieldReferenceExpression(
                            new CodeVariableReferenceExpression(simpleName),
                            pi.GetValue(val).ToString()));
                }
                catch (TargetInvocationException tie)
                    when(tie.InnerException != null && tie.InnerException is FormatException)
                    {
                        // This is used if the value retrieved from an element property
                        // doesn't match any of the enum values of the expected property
                        // type
                        statement = handleFmtException(elementName);
                    }
                catch (FormatException)
                {
                    statement = handleFmtException(elementName);
                }
                catch
                {
                    throw;
                }
                result.Add(statement);
            }
            // Insert an empty line
            result.AddBlankLine();

            // See if the current element has children and retrieve that information
            if (e.HasChildren)
            {
                foreach (var child in e)
                {
                    // Ignore OpenXmlUnknownElement objects if specified
                    if (settings.IgnoreUnknownElements && child is OpenXmlUnknownElement)
                    {
                        continue;
                    }

                    // use recursion to generate source code for the child elements
                    result.AddRange(
                        child.BuildCodeStatements(settings, typeCounts, namespaces, out string appendName));

                    methodReferenceExpression = new CodeMethodReferenceExpression(
                        new CodeVariableReferenceExpression(elementName),
                        "Append");
                    invokeExpression = new CodeMethodInvokeExpression(methodReferenceExpression,
                                                                      new CodeVariableReferenceExpression(appendName));
                    result.Add(invokeExpression);
                    result.AddBlankLine();
                }
            }

            // Return all of the collected expressions and statements
            return(result);
        }
Пример #3
0
        /// <summary>
        /// Converts an <see cref="OpenXmlElement"/> into a <see cref="CodeCompileUnit"/>
        /// object that can be used to build code in a given .NET language that would
        /// build the referenced <see cref="OpenXmlElement"/>.
        /// </summary>
        /// <param name="element">
        /// The <see cref="OpenXmlElement"/> object to generate source code for.
        /// </param>
        /// <param name="settings">
        /// The <see cref="ISerializeSettings"/> to use during the code generation
        /// process.
        /// </param>
        /// <returns>
        /// A new <see cref="CodeCompileUnit"/> containing the instructions to build
        /// the referenced <see cref="OpenXmlElement"/>.
        /// </returns>
        public static CodeCompileUnit GenerateSourceCode(this OpenXmlElement element, ISerializeSettings settings)
        {
            var result        = new CodeCompileUnit();
            var eType         = element.GetType();
            var typeCounts    = new Dictionary <Type, int>();
            var namespaces    = new SortedSet <string>();
            var mainNamespace = new CodeNamespace(settings.NamespaceName);

            // Setup the main method
            var mainMethod = new CodeMemberMethod()
            {
                Name       = $"Build{eType.Name}",
                ReturnType = new CodeTypeReference(
                    eType.GetObjectTypeName(settings.NamespaceAliasOptions.Order)),
                Attributes = MemberAttributes.Public | MemberAttributes.Final
            };

            mainMethod.Statements.AddRange(element.BuildCodeStatements(settings, typeCounts, namespaces, out string tmpName));
            mainMethod.Statements.Add(new CodeMethodReturnStatement(new CodeVariableReferenceExpression(tmpName)));

            // Setup the main class next
            var mainClass = new CodeTypeDeclaration($"{eType.Name}BuilderClass")
            {
                IsClass    = true,
                Attributes = MemberAttributes.Public
            };

            mainClass.Members.Add(mainMethod);

            // Setup the imports
            var codeNameSpaces = new List <CodeNamespaceImport>(namespaces.Count);

            foreach (var ns in namespaces)
            {
                codeNameSpaces.Add(ns.GetCodeNamespaceImport(settings.NamespaceAliasOptions));
            }
            codeNameSpaces.Sort(new CodeNamespaceImportComparer());

            mainNamespace.Imports.AddRange(codeNameSpaces.ToArray());
            mainNamespace.Types.Add(mainClass);

            // Finish up
            result.Namespaces.Add(mainNamespace);
            return(result);
        }
        /// <summary>
        /// Converts an <see cref="OpenXmlPackage"/> into a CodeDom object that can be used
        /// to build code in a given .NET language to build the referenced <paramref name="pkg"/>.
        /// </summary>
        /// <param name="pkg">
        /// The <see cref="OpenXmlPackage"/> object to generate source code for.
        /// </param>
        /// <param name="settings">
        /// The <see cref="ISerializeSettings"/> to use during the code generation
        /// process.
        /// </param>
        /// <returns>
        /// A new <see cref="CodeCompileUnit"/> containing the instructions to build
        /// the referenced <see cref="OpenXmlPackage"/>.
        /// </returns>
        public static CodeCompileUnit GenerateSourceCode(this OpenXmlPackage pkg, ISerializeSettings settings)
        {
            const string pkgVarName = "pkg";
            const string paramName = "pathToFile";
            var result = new CodeCompileUnit();
            var pkgType = pkg.GetType();
            var pkgTypeName = pkgType.Name;
            var partTypeCounts = new Dictionary<string, int>();
            var namespaces = new SortedSet<string>();
            var mainNamespace = new CodeNamespace(settings.NamespaceName);
            var bluePrints = new OpenXmlPartBluePrintCollection();
            CodeConditionStatement conditionStatement;
            CodeMemberMethod entryPoint;
            CodeMemberMethod createParts;
            CodeTypeDeclaration mainClass;
            CodeTryCatchFinallyStatement tryAndCatch;
            CodeFieldReferenceExpression docTypeVarRef = null;
            Type docTypeEnum;
            string docTypeEnumVal;
            KeyValuePair<string, Type> rootVarType;

            // Add all initial namespace names first
            namespaces.Add("System");

            // The OpenXmlDocument derived parts all contain a property called "DocumentType"
            // but the property types differ depending on the derived part.  Need to get both
            // the enum name of selected value to use as a parameter for the Create statement.
            switch (pkg)
            {
                case PresentationDocument p:
                    docTypeEnum = p.DocumentType.GetType();
                    docTypeEnumVal = p.DocumentType.ToString();
                    break;
                case SpreadsheetDocument s:
                    docTypeEnum = s.DocumentType.GetType();
                    docTypeEnumVal = s.DocumentType.ToString();
                    break;
                case WordprocessingDocument w:
                    docTypeEnum = w.DocumentType.GetType();
                    docTypeEnumVal = w.DocumentType.ToString();
                    break;
                default:
                    throw new ArgumentException("object is not a recognized OpenXmlPackage type.", nameof(pkg));
            }

            // Create the entry method
            entryPoint = new CodeMemberMethod()
            {
                Name = "CreatePackage",
                ReturnType = new CodeTypeReference(),
                Attributes = MemberAttributes.Public | MemberAttributes.Final
            };
            entryPoint.Parameters.Add(
                new CodeParameterDeclarationExpression(typeof(string).Name, paramName));

            // Create package declaration expression first
            entryPoint.Statements.Add(new CodeVariableDeclarationStatement(pkgTypeName, pkgVarName,
                new CodePrimitiveExpression(null)));

            // Add the required DocumentType parameter here, if available
            if (docTypeEnum != null)
            {
                namespaces.Add(docTypeEnum.Namespace);

                var simpleFieldRef = new CodeVariableReferenceExpression(
                    docTypeEnum.GetObjectTypeName(settings.NamespaceAliasOptions.Order));
                docTypeVarRef = new CodeFieldReferenceExpression(simpleFieldRef, docTypeEnumVal);
            }

            // initialize package var
            var pkgCreateMethod = new CodeMethodReferenceExpression(
                new CodeTypeReferenceExpression(pkgTypeName),
                "Create");
            var pkgCreateInvoke = new CodeMethodInvokeExpression(pkgCreateMethod,
                new CodeArgumentReferenceExpression(paramName),
                docTypeVarRef);
            var initializePkg = new CodeAssignStatement(
                new CodeVariableReferenceExpression(pkgVarName),
                pkgCreateInvoke);

            // Call CreateParts method
            var partsCreateMethod = new CodeMethodReferenceExpression(
                new CodeThisReferenceExpression(),
                "CreateParts");
            var partsCreateInvoke = new CodeMethodInvokeExpression(
                partsCreateMethod,
                new CodeDirectionExpression(FieldDirection.Ref,
                    new CodeVariableReferenceExpression(pkgVarName)));

            // Clean up pkg var
            var pkgDisposeMethod = new CodeMethodReferenceExpression(
                new CodeVariableReferenceExpression(pkgVarName),
                "Dispose");
            var pkgDisposeInvoke = new CodeMethodInvokeExpression(
                pkgDisposeMethod);

            // Setup the try/catch statement to properly initialize the pkg var
            tryAndCatch = new CodeTryCatchFinallyStatement();

            // Try statements
            tryAndCatch.TryStatements.Add(initializePkg);
            tryAndCatch.TryStatements.AddBlankLine();
            tryAndCatch.TryStatements.Add(partsCreateInvoke);

            // If statement to ensure pkgVarName is not null before trying to dispose
            conditionStatement = new CodeConditionStatement(
                new CodeBinaryOperatorExpression(
                    new CodeVariableReferenceExpression(pkgVarName),
                    CodeBinaryOperatorType.IdentityInequality,
                    new CodePrimitiveExpression(null)));

            conditionStatement.TrueStatements.Add(pkgDisposeInvoke);

            // Finally statements
            tryAndCatch.FinallyStatements.Add(conditionStatement);
            entryPoint.Statements.Add(tryAndCatch);

            // Create the CreateParts method
            createParts = new CodeMemberMethod()
            {
                Name = "CreateParts",
                ReturnType = new CodeTypeReference(),
                Attributes = MemberAttributes.Private | MemberAttributes.Final
            };
            createParts.Parameters.Add(new CodeParameterDeclarationExpression(pkgTypeName, pkgVarName)
                { Direction = FieldDirection.Ref });

            // Add all of the child part references here
            if (pkg.Parts != null)
            {
                var customNewPartTypes = new Type[] { typeof(PresentationPart), typeof(WorkbookPart), typeof(MainDocumentPart) };
                OpenXmlPartBluePrint bpTemp;
                CodeMethodReferenceExpression referenceExpression;
                CodeMethodInvokeExpression invokeExpression;
                CodeMethodReferenceExpression methodReference;
                Type currentPartType;
                string varName = null;
                string initMethodName = null;
                string mainPartTypeName;

                foreach (var pair in pkg.Parts)
                {
                    // Need special handling rules for WorkbookPart, MainDocumentPart, and PresentationPart
                    // objects.  They cannot be created using the usual "AddNewPart" methods, unfortunately.
                    currentPartType = pair.OpenXmlPart.GetType();
                    if (customNewPartTypes.Contains(currentPartType))
                    {
                        namespaces.Add(currentPartType.Namespace);
                        mainPartTypeName = currentPartType.Name;
                        if (pair.OpenXmlPart is PresentationPart)
                        {
                            varName = "presentationPart";
                            initMethodName = "AddPresentationPart";
                        }
                        else if (pair.OpenXmlPart is WorkbookPart)
                        {
                            varName = "workbookPart";
                            initMethodName = "AddWorkbookPart";
                        }
                        else if (pair.OpenXmlPart is MainDocumentPart)
                        {
                            varName = "mainDocumentPart";
                            initMethodName = "AddMainDocumentPart";
                        }
                        rootVarType = new KeyValuePair<string, Type>(varName, currentPartType);

                        // Setup the blueprint
                        bpTemp = new OpenXmlPartBluePrint(pair.OpenXmlPart, varName);

                        // Setup the add new part statement for the current OpenXmlPart object
                        referenceExpression = new CodeMethodReferenceExpression(
                            new CodeArgumentReferenceExpression(pkgVarName), initMethodName);

                        invokeExpression = new CodeMethodInvokeExpression(referenceExpression);

                        createParts.Statements.Add(new CodeVariableDeclarationStatement(mainPartTypeName, varName, invokeExpression));

                        // Add the call to the method to populate the current OpenXmlPart object
                        methodReference = new CodeMethodReferenceExpression(new CodeThisReferenceExpression(), bpTemp.MethodName);
                        createParts.Statements.Add(new CodeMethodInvokeExpression(methodReference,
                            new CodeDirectionExpression(FieldDirection.Ref, new CodeVariableReferenceExpression(varName))));

                        // Add the current main part to the collection of blueprints to ensure that the appropriate 'Generate*'
                        // method is added to the collection of helper methods.
                        bluePrints.Add(bpTemp);

                        // Add a blank line for clarity
                        createParts.Statements.AddBlankLine();

                        // now create the child parts for the current one an continue the loop to avoid creating
                        // an additional invalid 'AddNewPart' method for the current main part.
                        foreach (var child in pair.OpenXmlPart.Parts)
                        {
                            createParts.Statements.AddRange(
                                OpenXmlPartExtensions.BuildEntryMethodCodeStatements(
                                    child, settings, partTypeCounts, namespaces, bluePrints, rootVarType));
                        }
                        continue;
                    }

                    rootVarType = new KeyValuePair<string, Type>(pkgVarName, pkgType);
                    createParts.Statements.AddRange(
                        OpenXmlPartExtensions.BuildEntryMethodCodeStatements(
                            pair, settings, partTypeCounts, namespaces, bluePrints, rootVarType));
                }
            }

            // Setup the main class next
            mainClass = new CodeTypeDeclaration($"{pkgTypeName}BuilderClass")
            {
                IsClass = true,
                Attributes = MemberAttributes.Public
            };

            // Setup the main class members
            mainClass.Members.Add(entryPoint);
            mainClass.Members.Add(createParts);
            mainClass.Members.AddRange(OpenXmlPartExtensions.BuildHelperMethods
                (bluePrints, settings, namespaces));

            // Setup the imports
            var codeNameSpaces = new List<CodeNamespaceImport>(namespaces.Count);
            foreach (var ns in namespaces)
            {
                codeNameSpaces.Add(ns.GetCodeNamespaceImport(settings.NamespaceAliasOptions));
            }
            codeNameSpaces.Sort(new CodeNamespaceImportComparer());

            mainNamespace.Imports.AddRange(codeNameSpaces.ToArray());
            mainNamespace.Types.Add(mainClass);

            // Finish up
            result.Namespaces.Add(mainNamespace);
            return result;
        }
        /// <summary>
        /// Creates the appropriate code objects needed to create the entry method for the
        /// current request.
        /// </summary>
        /// <param name="part">
        /// The <see cref="OpenXmlPart"/> object and relationship id to build code for.
        /// </param>
        /// <param name="settings">
        /// The <see cref="ISerializeSettings"/> to use during the code generation
        /// process.
        /// </param>
        /// /// <param name="typeCounts">
        /// A lookup <see cref="IDictionary{TKey, TValue}"/> object containing the
        /// number of times a given type was referenced.  This is used for variable naming
        /// purposes.
        /// </param>
        /// <param name="namespaces">
        /// Collection <see cref="ISet{T}"/> used to keep track of all openxml namespaces
        /// used during the process.
        /// </param>
        /// <param name="blueprints">
        /// The collection of <see cref="OpenXmlPartBluePrint"/> objects that have already been
        /// visited.
        /// </param>
        /// <param name="rootVar">
        /// The root variable name and <see cref="Type"/> to use when building code
        /// statements to create new <see cref="OpenXmlPart"/> objects.
        /// </param>
        /// <returns>
        /// A collection of code statements and expressions that could be used to generate
        /// a new <paramref name="part"/> object from code.
        /// </returns>
        public static CodeStatementCollection BuildEntryMethodCodeStatements(
            IdPartPair part,
            ISerializeSettings settings,
            IDictionary <string, int> typeCounts,
            ISet <string> namespaces,
            OpenXmlPartBluePrintCollection blueprints,
            KeyValuePair <string, Type> rootVar)
        {
            // Argument validation
            if (part is null)
            {
                throw new ArgumentNullException(nameof(part));
            }
            if (settings is null)
            {
                throw new ArgumentNullException(nameof(settings));
            }
            if (blueprints is null)
            {
                throw new ArgumentNullException(nameof(blueprints));
            }
            if (String.IsNullOrWhiteSpace(rootVar.Key))
            {
                throw new ArgumentNullException(nameof(rootVar.Key));
            }
            bool hasHandlers = settings?.Handlers != null;

            // Use the custom handler methods if present and provide actual code
            if (hasHandlers && settings.Handlers.TryGetValue(part.OpenXmlPart.GetType(), out IOpenXmlHandler h))
            {
                if (h is IOpenXmlPartHandler partHandler)
                {
                    var customStatements = partHandler.BuildEntryMethodCodeStatements(
                        part, settings, typeCounts, namespaces, blueprints, rootVar);

                    if (customStatements != null)
                    {
                        return(customStatements);
                    }
                }
            }

            var    result                   = new CodeStatementCollection();
            var    partType                 = part.OpenXmlPart.GetType();
            var    partTypeName             = partType.Name;
            var    partTypeFullName         = partType.FullName;
            string varName                  = partType.Name.ToCamelCase();
            bool   customAddNewPartRequired = CheckForCustomAddNewPartMethod(partType, rootVar.Value, out string addNewPartName);

#pragma warning disable IDE0018 // Inline variable declaration
            OpenXmlPartBluePrint bpTemp;
#pragma warning restore IDE0018 // Inline variable declaration

            CodeMethodReferenceExpression referenceExpression;
            CodeMethodInvokeExpression    invokeExpression;
            CodeMethodReferenceExpression methodReference;

            // Make sure that the namespace for the current part is captured
            namespaces.Add(partType.Namespace);

            // If the URI of the current part has already been included into
            // the blue prints collection, build the AddPart invocation
            // code statement and exit current method iteration.
            if (blueprints.TryGetValue(part.OpenXmlPart.Uri, out bpTemp))
            {
                // Surround this snippet with blank lines to make it
                // stand out in the current section of code.
                result.AddBlankLine();
                referenceExpression = new CodeMethodReferenceExpression(
                    new CodeVariableReferenceExpression(rootVar.Key), "AddPart",
                    new CodeTypeReference(part.OpenXmlPart.GetType().Name));
                invokeExpression = new CodeMethodInvokeExpression(referenceExpression,
                                                                  new CodeVariableReferenceExpression(bpTemp.VariableName),
                                                                  new CodePrimitiveExpression(part.RelationshipId));
                result.Add(invokeExpression);
                result.AddBlankLine();
                return(result);
            }

            // Assign the appropriate variable name
            if (typeCounts.ContainsKey(partTypeFullName))
            {
                varName = String.Concat(varName, typeCounts[partTypeFullName]++);
            }
            else
            {
                typeCounts.Add(partTypeFullName, 1);
            }

            // Setup the blueprint
            bpTemp = new OpenXmlPartBluePrint(part.OpenXmlPart, varName);

            // Need to evaluate the current OpenXmlPart type first to make sure the
            // correct "Add" statement is used as not all Parts can be initialized
            // using the "AddNewPart" method

            // Check for image part methods
            if (customAddNewPartRequired)
            {
                referenceExpression = new CodeMethodReferenceExpression(
                    new CodeVariableReferenceExpression(rootVar.Key), addNewPartName);
            }
            else
            {
                // Setup the add new part statement for the current OpenXmlPart object
                referenceExpression = new CodeMethodReferenceExpression(
                    new CodeVariableReferenceExpression(rootVar.Key), "AddNewPart",
                    new CodeTypeReference(partTypeName));
            }

            // Create the invoke expression
            invokeExpression = new CodeMethodInvokeExpression(referenceExpression);

            // Add content type to invoke method for embeddedpackage and image parts.
            if (part.OpenXmlPart is EmbeddedPackagePart || customAddNewPartRequired)
            {
                invokeExpression.Parameters.Add(
                    new CodePrimitiveExpression(part.OpenXmlPart.ContentType));
            }
            else if (!customAddNewPartRequired)
            {
                invokeExpression.Parameters.Add(
                    new CodePrimitiveExpression(part.RelationshipId));
            }

            result.Add(new CodeVariableDeclarationStatement(partTypeName, varName, invokeExpression));

            // Because the custom AddNewPart methods don't consistently take in a string relId
            // as a parameter, the id needs to be assigned after it is created.
            if (customAddNewPartRequired)
            {
                methodReference = new CodeMethodReferenceExpression(
                    new CodeVariableReferenceExpression(rootVar.Key), "ChangeIdOfPart");
                result.Add(new CodeMethodInvokeExpression(methodReference,
                                                          new CodeVariableReferenceExpression(varName),
                                                          new CodePrimitiveExpression(part.RelationshipId)));
            }

            // Add the call to the method to populate the current OpenXmlPart object
            methodReference = new CodeMethodReferenceExpression(new CodeThisReferenceExpression(), bpTemp.MethodName);
            result.Add(new CodeMethodInvokeExpression(methodReference,
                                                      new CodeDirectionExpression(FieldDirection.Ref,
                                                                                  new CodeVariableReferenceExpression(varName))));

            // Add the appropriate code statements if the current part
            // contains any hyperlink relationships
            if (part.OpenXmlPart.HyperlinkRelationships.Count() > 0)
            {
                // Add a line break first for easier reading
                result.AddBlankLine();
                result.AddRange(
                    part.OpenXmlPart.HyperlinkRelationships.BuildHyperlinkRelationshipStatements(varName));
            }

            // Add the appropriate code statements if the current part
            // contains any non-hyperlink external relationships
            if (part.OpenXmlPart.ExternalRelationships.Count() > 0)
            {
                // Add a line break first for easier reading
                result.AddBlankLine();
                result.AddRange(
                    part.OpenXmlPart.ExternalRelationships.BuildExternalRelationshipStatements(varName));
            }

            // put a line break before going through the child parts
            result.AddBlankLine();

            // Add the current blueprint to the collection
            blueprints.Add(bpTemp);

            // Now check to see if there are any child parts for the current OpenXmlPart object.
            if (bpTemp.Part.Parts != null)
            {
                OpenXmlPartBluePrint childBluePrint;

                foreach (var p in bpTemp.Part.Parts)
                {
                    // If the current child object has already been created, simply add a reference to
                    // said object using the AddPart method.
                    if (blueprints.Contains(p.OpenXmlPart.Uri))
                    {
                        childBluePrint = blueprints[p.OpenXmlPart.Uri];

                        referenceExpression = new CodeMethodReferenceExpression(
                            new CodeVariableReferenceExpression(varName), "AddPart",
                            new CodeTypeReference(p.OpenXmlPart.GetType().Name));

                        invokeExpression = new CodeMethodInvokeExpression(referenceExpression,
                                                                          new CodeVariableReferenceExpression(childBluePrint.VariableName),
                                                                          new CodePrimitiveExpression(p.RelationshipId));

                        result.Add(invokeExpression);
                        continue;
                    }

                    // If this is a new part, call this method with the current part's details
                    result.AddRange(BuildEntryMethodCodeStatements(p, settings, typeCounts, namespaces, blueprints,
                                                                   new KeyValuePair <string, Type>(varName, partType)));
                }
            }

            return(result);
        }
        /// <summary>
        /// Converts an <see cref="OpenXmlPart"/> into a CodeDom object that can be used
        /// to build code in a given .NET language to build the referenced <see cref="OpenXmlPart"/>.
        /// </summary>
        /// <param name="part">
        /// The <see cref="OpenXmlPart"/> object to generate source code for.
        /// </param>
        /// <param name="settings">
        /// The <see cref="ISerializeSettings"/> to use during the code generation
        /// process.
        /// </param>
        /// <returns>
        /// A new <see cref="CodeCompileUnit"/> containing the instructions to build
        /// the referenced <see cref="OpenXmlPart"/>.
        /// </returns>
        public static CodeCompileUnit GenerateSourceCode(this OpenXmlPart part, ISerializeSettings settings)
        {
            CodeMethodReferenceExpression methodRef;
            OpenXmlPartBluePrint          mainBluePrint;
            var result           = new CodeCompileUnit();
            var eType            = part.GetType();
            var partTypeName     = eType.Name;
            var partTypeFullName = eType.FullName;
            var varName          = eType.Name.ToCamelCase();
            var partTypeCounts   = new Dictionary <string, int>();
            var namespaces       = new SortedSet <string>();
            var mainNamespace    = new CodeNamespace(settings.NamespaceName);
            var bluePrints       = new OpenXmlPartBluePrintCollection();

            // Assign the appropriate variable name
            if (partTypeCounts.ContainsKey(partTypeFullName))
            {
                varName = String.Concat(varName, partTypeCounts[partTypeFullName]++);
            }
            else
            {
                partTypeCounts.Add(partTypeFullName, 1);
            }

            // Generate a new blue print for the current part to help create the main
            // method reference then add it to the blue print collection
            mainBluePrint = new OpenXmlPartBluePrint(part, varName);
            bluePrints.Add(mainBluePrint);
            methodRef = new CodeMethodReferenceExpression(new CodeThisReferenceExpression(), mainBluePrint.MethodName);

            // Build the entry method
            var entryMethod = new CodeMemberMethod()
            {
                Name       = $"Create{partTypeName}",
                ReturnType = new CodeTypeReference(),
                Attributes = MemberAttributes.Public | MemberAttributes.Final
            };

            entryMethod.Parameters.Add(
                new CodeParameterDeclarationExpression(partTypeName, methodParamName)
            {
                Direction = FieldDirection.Ref
            });

            // Add all of the child part references here
            if (part.Parts != null)
            {
                var rootPartPair = new KeyValuePair <string, Type>(methodParamName, eType);
                foreach (var pair in part.Parts)
                {
                    entryMethod.Statements.AddRange(BuildEntryMethodCodeStatements(
                                                        pair, settings, partTypeCounts, namespaces, bluePrints, rootPartPair));
                }
            }

            entryMethod.Statements.Add(new CodeMethodInvokeExpression(methodRef,
                                                                      new CodeArgumentReferenceExpression(methodParamName)));

            // Setup the main class next
            var mainClass = new CodeTypeDeclaration($"{eType.Name}BuilderClass")
            {
                IsClass    = true,
                Attributes = MemberAttributes.Public
            };

            mainClass.Members.Add(entryMethod);
            mainClass.Members.AddRange(BuildHelperMethods(bluePrints, settings, namespaces));

            // Setup the imports
            var codeNameSpaces = new List <CodeNamespaceImport>(namespaces.Count);

            foreach (var ns in namespaces)
            {
                codeNameSpaces.Add(ns.GetCodeNamespaceImport(settings.NamespaceAliasOptions));
            }
            codeNameSpaces.Sort(new CodeNamespaceImportComparer());

            mainNamespace.Imports.AddRange(codeNameSpaces.ToArray());
            mainNamespace.Types.Add(mainClass);

            // Finish up
            result.Namespaces.Add(mainNamespace);
            return(result);
        }
        /// <summary>
        /// Creates the appropriate helper methods for all of the <see cref="OpenXmlPart"/> objects
        /// for the current request.
        /// </summary>
        /// <param name="bluePrints">
        /// The collection of <see cref="OpenXmlPartBluePrint"/> objects that have already been
        /// visited.
        /// </param>
        /// <param name="settings">
        /// The <see cref="ISerializeSettings"/> to use during the code generation
        /// process.
        /// </param>
        /// <param name="namespaces">
        /// <see cref="ISet{T}"/> collection used to keep track of all openxml namespaces
        /// used during the process.
        /// </param>
        /// <returns>
        /// A collection of code helper statements and expressions that could be used to generate a new
        /// <see cref="OpenXmlPart"/> object from code.
        /// </returns>
        public static CodeTypeMemberCollection BuildHelperMethods(
            OpenXmlPartBluePrintCollection bluePrints,
            ISerializeSettings settings,
            ISet <string> namespaces)
        {
            if (bluePrints == null)
            {
                throw new ArgumentNullException(nameof(bluePrints));
            }
            var              result          = new CodeTypeMemberCollection();
            var              localTypeCounts = new Dictionary <Type, int>();
            Type             rootElementType;
            CodeMemberMethod method;
            bool             hasHandlers = settings?.Handlers != null;

            foreach (var bp in bluePrints)
            {
                // Implement the custom helper if present
                if (hasHandlers && settings.Handlers.TryGetValue(bp.PartType, out IOpenXmlHandler h))
                {
                    if (h is IOpenXmlPartHandler partHandler)
                    {
                        method = partHandler.BuildHelperMethod(
                            bp.Part, bp.MethodName, settings, namespaces);

                        if (method != null)
                        {
                            result.Add(method);
                            continue;
                        }
                    }
                }

                // Setup the first method
                method = new CodeMemberMethod()
                {
                    Name       = bp.MethodName,
                    Attributes = MemberAttributes.Private | MemberAttributes.Final,
                    ReturnType = new CodeTypeReference()
                };
                method.Parameters.Add(
                    new CodeParameterDeclarationExpression(bp.Part.GetType().Name, methodParamName)
                {
                    Direction = FieldDirection.Ref
                });

                // Code part elements next
                if (bp.Part.RootElement is null)
                {
                    // Put all of the pieces together
                    method.Statements.AddRange(bp.Part.BuildPartFeedData(namespaces));
                }
                else
                {
                    rootElementType = bp.Part.RootElement?.GetType();

                    // Build the element details of the requested part for the current method
                    method.Statements.AddRange(
                        bp.Part.RootElement.BuildCodeStatements(
                            settings, localTypeCounts, namespaces, out string rootElementVar));

                    // Now finish up the current method by assigning the OpenXmlElement code statements
                    // back to the appropriate property of the part parameter
                    if (rootElementType != null && !String.IsNullOrWhiteSpace(rootElementVar))
                    {
                        foreach (var paramProp in bp.Part.GetType().GetProperties())
                        {
                            if (paramProp.PropertyType == rootElementType)
                            {
                                var varRef   = new CodeVariableReferenceExpression(rootElementVar);
                                var paramRef = new CodeArgumentReferenceExpression(methodParamName);
                                var propRef  = new CodePropertyReferenceExpression(paramRef, paramProp.Name);
                                method.Statements.Add(new CodeAssignStatement(propRef, varRef));
                                break;
                            }
                        }
                    }
                }
                result.Add(method);
            }

            return(result);
        }