使用Open XML SDK替换Word内容控件为文本失败求助
解决Open XML SDK移除内容控件并保留文本的问题
问题背景
使用C#和Open XML SDK通过XML映射填充Word文档内容控件后,需要移除控件以避免后续解析报错,但现有循环仅删除控件未保留文本。尝试Word自带的“填充后移除内容控件”功能无效(流式XML不被识别为填充),更换循环方式仍丢失数据。
原问题代码
问题循环代码
foreach (var control in mainPart.RootElement.Descendants<SdtElement>().ToList()) { var text = control.Descendants<Text>().FirstOrDefault()?.Text; if (!string.IsNullOrEmpty(text)) { var run = control.Descendants<Run>().FirstOrDefault(); var newRun = new Run(new Text(text)); if (run != null) { // Replace the content control with the new run containing the text run.Parent.ReplaceChild(newRun, run); } else { // Insert the new run after the content control var paragraph = control.Ancestors<Paragraph>().FirstOrDefault(); if (paragraph != null) { paragraph.InsertAfter(newRun, control); } } } // Remove the content control control.Remove(); }
完整生成函数
public static void CreateWordDocument(string xmlData) { Console.WriteLine("Setting up paths..."); // Set up output file name and path int index = 1; string outputFileName; string outputDocumentPath; do { outputFileName = $"GeneratedCV_{index}.docx"; outputDocumentPath = Path.Combine(Path.GetDirectoryName(outputDirectoryPath), outputFileName); index++; } while (File.Exists(outputDocumentPath)); // Copy template document to output path and replace custom XML parts with new XML data using var sourceDoc = WordprocessingDocument.Open(templateDocumentPath, false); File.Copy(templateDocumentPath, outputDocumentPath); using var newDoc = WordprocessingDocument.Open(outputDocumentPath, true); newDoc.MainDocumentPart.DocumentSettingsPart.Settings = (DocumentFormat.OpenXml.Wordprocessing.Settings)sourceDoc.MainDocumentPart.DocumentSettingsPart.Settings.Clone(); newDoc.MainDocumentPart.StyleDefinitionsPart.Styles = (DocumentFormat.OpenXml.Wordprocessing.Styles)sourceDoc.MainDocumentPart.StyleDefinitionsPart.Styles.Clone(); newDoc.MainDocumentPart.StyleDefinitionsPart.Styles.Save(); var mainPart = newDoc.MainDocumentPart; mainPart.DeleteParts<CustomXmlPart>(mainPart.CustomXmlParts); // Write the XML data to a file string xmlFilePath = Path.Combine(Path.GetDirectoryName(outputDocumentPath), "CustomXml.xml"); File.WriteAllText(xmlFilePath, xmlData); var customXmlPart = mainPart.AddCustomXmlPart(CustomXmlPartType.CustomXml, "rId1"); using (var stream = new FileStream(xmlFilePath, FileMode.Open)) { customXmlPart.FeedData(stream); // 原问题循环代码位置 foreach (var control in mainPart.RootElement.Descendants<SdtElement>().ToList()) { var text = control.Descendants<Text>().FirstOrDefault()?.Text; if (!string.IsNullOrEmpty(text)) { var run = control.Descendants<Run>().FirstOrDefault(); var newRun = new Run(new Text(text)); if (run != null) { run.Parent.ReplaceChild(newRun, run); } else { var paragraph = control.Ancestors<Paragraph>().FirstOrDefault(); if (paragraph != null) { paragraph.InsertAfter(newRun, control); } } } control.Remove(); } } newDoc.Save(); newDoc.Close(); Console.WriteLine("CV generated!"); }
问题分析
原代码的核心问题:
- 文本提取不完整:仅获取第一个
Text元素,丢失多段文本或带格式的内容 - 替换逻辑错误:尝试替换单个
Run或插入文本到控件后方,未正确将控件内容与控件本身替换
解决方案
修改循环逻辑,直接操作内容控件的内部内容容器SdtContent,将其下所有节点完整迁移到控件父节点,再删除控件:
修改后的循环代码
foreach (var control in mainPart.RootElement.Descendants<SdtElement>().ToList()) { // 获取内容控件的内部内容容器(存储实际文本和格式) var sdtContent = control.Elements<SdtContent>().FirstOrDefault(); if (sdtContent == null) { control.Remove(); continue; } var parentNode = control.Parent; if (parentNode == null) { control.Remove(); continue; } // 将内容控件内部的所有节点(包含完整格式和文本)移动到父节点中,替换控件位置 foreach (var childElement in sdtContent.Elements().ToList()) { parentNode.InsertBefore(childElement, control); } // 移除空的内容控件 control.Remove(); }
代码说明
- 保留完整内容:直接读取
SdtContent节点下的所有子元素,包括带格式的Run、Text等,避免丢失文本和格式 - 正确替换逻辑:将控件内容迁移到控件的父节点中,放在控件的位置前方,再删除控件,实现“用内容替换控件”的效果
- 兼容性强:适用于段落内、表格内等各种位置的内容控件
内容的提问来源于stack exchange,提问作者Hamza Wahbi
相关产品推荐
相关产品推荐

