/// <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> /// 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 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> /// 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); }