|
| 1 | +using Syncfusion.DocIO; |
| 2 | +using Syncfusion.DocIO.DLS; |
| 3 | +using System.Dynamic; |
| 4 | +using System.Xml; |
| 5 | + |
| 6 | + |
| 7 | +// Dictionary to temporarily hold merge field HTML values and their positions. |
| 8 | +Dictionary<WParagraph, Dictionary<int, string>> paraToInsertHTML = new Dictionary<WParagraph, Dictionary<int, string>>(); |
| 9 | + |
| 10 | +// Load the Word document template |
| 11 | +using (WordDocument document = new WordDocument(Path.GetFullPath(@"Data/Template.docx"))) |
| 12 | +{ |
| 13 | + // Load the XML file that contains data to be merged |
| 14 | + Stream xmlStream = System.IO.File.OpenRead(Path.GetFullPath(@"Data/Data.xml")); |
| 15 | + XmlDocument xmlDocument = new XmlDocument(); |
| 16 | + xmlDocument.Load(xmlStream); |
| 17 | + xmlStream.Dispose(); // Close and release the file stream |
| 18 | + |
| 19 | + // Convert the XML data into a dynamic ExpandoObject structure |
| 20 | + ExpandoObject allDataObject = new ExpandoObject(); |
| 21 | + GetDataAsExpandoObject(xmlDocument.LastChild, ref allDataObject); |
| 22 | + |
| 23 | + // Traverse the dynamic data to extract and map mail merge records |
| 24 | + if (allDataObject is IDictionary<string, object> allObjects && |
| 25 | + allObjects.ContainsKey("Root")) |
| 26 | + { |
| 27 | + var rootObjects = (allObjects["Root"] as List<ExpandoObject>)[0] as IDictionary<string, object>; |
| 28 | + if (rootObjects.ContainsKey("Validator")) |
| 29 | + { |
| 30 | + var validatorObjects = (rootObjects["Validator"] as List<ExpandoObject>)[0] as IDictionary<string, object>; |
| 31 | + |
| 32 | + // Iterate over each component inside Validator (e.g., Login, Dashboard) |
| 33 | + foreach (var validatorObject in validatorObjects) |
| 34 | + { |
| 35 | + var componentMainTag = validatorObject.Key; // Typically "Component" |
| 36 | + var componentData = validatorObject.Value as List<ExpandoObject>; |
| 37 | + |
| 38 | + if (componentData != null) |
| 39 | + { |
| 40 | + // Create a mail merge data table for each component |
| 41 | + MailMergeDataTable componentDataTable = new MailMergeDataTable(componentMainTag, componentData); |
| 42 | + |
| 43 | + // Attach event handler to handle HTML merge fields |
| 44 | + document.MailMerge.MergeField += new MergeFieldEventHandler(MergeFieldEvent); |
| 45 | + |
| 46 | + // Execute group mail merge using nested data table |
| 47 | + document.MailMerge.ExecuteGroup(componentDataTable); |
| 48 | + } |
| 49 | + } |
| 50 | + } |
| 51 | + } |
| 52 | + |
| 53 | + // After mail merge is complete, insert HTML into placeholders |
| 54 | + InsertHtml(); |
| 55 | + |
| 56 | + // Detach mail merge event handlers |
| 57 | + document.MailMerge.MergeField -= new MergeFieldEventHandler(MergeFieldEvent); |
| 58 | + |
| 59 | + // Save the updated document |
| 60 | + document.Save(Path.GetFullPath(@"Output/Result.docx"), FormatType.Docx); |
| 61 | +} |
| 62 | + |
| 63 | +/// <summary> |
| 64 | +/// Event handler that intercepts the mail merge process to handle HTML content. |
| 65 | +/// </summary> |
| 66 | +void MergeFieldEvent(object sender, MergeFieldEventArgs args) |
| 67 | +{ |
| 68 | + if (args.FieldName.Equals("Description")) |
| 69 | + { |
| 70 | + // Get the paragraph containing the current merge field |
| 71 | + WParagraph paragraph = args.CurrentMergeField.OwnerParagraph; |
| 72 | + |
| 73 | + // Get the index of the merge field inside the paragraph |
| 74 | + int mergeFieldIndex = paragraph.ChildEntities.IndexOf(args.CurrentMergeField); |
| 75 | + |
| 76 | + // Store the HTML field value with its position |
| 77 | + Dictionary<int, string> fieldValues = new Dictionary<int, string>(); |
| 78 | + fieldValues.Add(mergeFieldIndex, args.FieldValue.ToString()); |
| 79 | + |
| 80 | + // Associate the paragraph with its corresponding HTML values |
| 81 | + paraToInsertHTML.Add(paragraph, fieldValues); |
| 82 | + |
| 83 | + // Remove the default text replacement; HTML will be inserted later |
| 84 | + args.Text = string.Empty; |
| 85 | + } |
| 86 | +} |
| 87 | + |
| 88 | +/// <summary> |
| 89 | +/// Recursively parses XML nodes into ExpandoObject structure for easy access. |
| 90 | +/// Supports handling of HTML content inside text nodes. |
| 91 | +/// </summary> |
| 92 | +void GetDataAsExpandoObject(XmlNode node, ref ExpandoObject dynamicObject) |
| 93 | +{ |
| 94 | + try |
| 95 | + { |
| 96 | + // Check if node is simple text (possibly with HTML tags) |
| 97 | + if (node.InnerText == node.InnerXml || (node.ChildNodes.Count == 1 && node.FirstChild.NodeType == XmlNodeType.Text)) |
| 98 | + { |
| 99 | + if (!(dynamicObject as IDictionary<string, object>).ContainsKey(node.LocalName)) |
| 100 | + (dynamicObject as IDictionary<string, object>).Add(node.LocalName, node.InnerText); |
| 101 | + } |
| 102 | + else |
| 103 | + { |
| 104 | + List<ExpandoObject> childObjects; |
| 105 | + |
| 106 | + // Reuse existing list if it already exists for this node |
| 107 | + if ((dynamicObject as IDictionary<string, object>).ContainsKey(node.LocalName)) |
| 108 | + childObjects = (dynamicObject as IDictionary<string, object>)[node.LocalName] as List<ExpandoObject>; |
| 109 | + else |
| 110 | + { |
| 111 | + childObjects = new List<ExpandoObject>(); |
| 112 | + (dynamicObject as IDictionary<string, object>).Add(node.LocalName, childObjects); |
| 113 | + } |
| 114 | + |
| 115 | + ExpandoObject childObject = new ExpandoObject(); |
| 116 | + |
| 117 | + // Recursively parse each child node |
| 118 | + foreach (XmlNode childNode in node.ChildNodes) |
| 119 | + { |
| 120 | + GetDataAsExpandoObject(childNode, ref childObject); |
| 121 | + } |
| 122 | + |
| 123 | + childObjects.Add(childObject); |
| 124 | + } |
| 125 | + } |
| 126 | + catch (Exception e) |
| 127 | + { |
| 128 | + Console.WriteLine("Error in XML reading: " + e.ToString()); |
| 129 | + } |
| 130 | +} |
| 131 | + |
| 132 | +/// <summary> |
| 133 | +/// Replaces merge fields with actual HTML content using collected placeholders. |
| 134 | +/// </summary> |
| 135 | +void InsertHtml() |
| 136 | +{ |
| 137 | + // Iterate each paragraph and its associated HTML values |
| 138 | + foreach (KeyValuePair<WParagraph, Dictionary<int, string>> dictionaryItems in paraToInsertHTML) |
| 139 | + { |
| 140 | + WParagraph paragraph = dictionaryItems.Key; |
| 141 | + Dictionary<int, string> values = dictionaryItems.Value; |
| 142 | + |
| 143 | + foreach (KeyValuePair<int, string> valuePair in values) |
| 144 | + { |
| 145 | + int index = valuePair.Key; |
| 146 | + string fieldValue = valuePair.Value; |
| 147 | + |
| 148 | + // Insert HTML at the specified location in the paragraph |
| 149 | + paragraph.OwnerTextBody.InsertXHTML(fieldValue, paragraph.OwnerTextBody.ChildEntities.IndexOf(paragraph), index); |
| 150 | + } |
| 151 | + } |
| 152 | + |
| 153 | + // Clear dictionary after inserting all HTML |
| 154 | + paraToInsertHTML.Clear(); |
| 155 | +} |
0 commit comments