You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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!");
}

问题分析

原代码的核心问题:

  1. 文本提取不完整:仅获取第一个Text元素,丢失多段文本或带格式的内容
  2. 替换逻辑错误:尝试替换单个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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 04:53:18