/// <summary> /// Builds code statements that will build <paramref name="part"/> using the /// <see cref="OpenXmlPart.FeedData(Stream)"/> method. /// </summary> /// <param name="part"> /// The <see cref="OpenXmlPart"/> object to build the source code for. /// </param> /// <param name="namespaces"> /// <see cref="ISet{T}">Set</see> of <see cref="String"/> values used to keep /// track of all openxml namespaces used during the process. /// </param> /// <returns> /// A <see cref="CodeStatementCollection">collection of code statements</see> /// that would regenerate <paramref name="part"/> using the /// <see cref="OpenXmlPart.FeedData(Stream)"/> method. /// </returns> public static CodeStatementCollection BuildPartFeedData(this OpenXmlPart part, ISet <string> namespaces) { // Make sure no null values were passed. if (part == null) { throw new ArgumentNullException(nameof(part)); } if (namespaces == null) { throw new ArgumentNullException(nameof(namespaces)); } // If the root element is not present (aka: null) then perform a simple feed // dump of the part in the current method const string memName = "mem"; const string b64Name = "base64"; var result = new CodeStatementCollection(); // Add the necessary namespaces by hand to the namespace set namespaces.Add("System"); namespaces.Add("System.IO"); using (var partStream = part.GetStream(FileMode.Open, FileAccess.Read)) { using (var mem = new MemoryStream()) { partStream.CopyTo(mem); result.Add(new CodeVariableDeclarationStatement(typeof(string), b64Name, new CodePrimitiveExpression(Convert.ToBase64String(mem.ToArray())))); } } result.AddBlankLine(); var fromBase64 = new CodeMethodReferenceExpression(new CodeVariableReferenceExpression("Convert"), "FromBase64String"); var invokeFromBase64 = new CodeMethodInvokeExpression(fromBase64, new CodeVariableReferenceExpression("base64")); var createStream = new CodeObjectCreateExpression(new CodeTypeReference("MemoryStream"), invokeFromBase64, new CodePrimitiveExpression(false)); var feedData = new CodeMethodReferenceExpression(new CodeArgumentReferenceExpression(methodParamName), "FeedData"); var invokeFeedData = new CodeMethodInvokeExpression(feedData, new CodeVariableReferenceExpression(memName)); var disposeMem = new CodeMethodReferenceExpression(new CodeVariableReferenceExpression(memName), "Dispose"); var invokeDisposeMem = new CodeMethodInvokeExpression(disposeMem); // Setup the try statement var tryAndCatch = new CodeTryCatchFinallyStatement(); tryAndCatch.TryStatements.Add(invokeFeedData); tryAndCatch.FinallyStatements.Add(invokeDisposeMem); // Put all of the pieces together result.Add(new CodeVariableDeclarationStatement("Stream", memName, createStream)); result.Add(tryAndCatch); return(result); }
/// <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); }
/// <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); }