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

Open XML SDK复制Word页眉调用FeedData遇内存流不可扩展错误

问题:复制Word页眉时抛出“MEMORY STREAM IS NOT EXPANDABLE”错误

我编写了AddExtraHeader方法,目标是从传入的.docx文件字节数组中复制现有页眉,编辑复制后的页眉,并将原页眉设为首页页眉、复制后的页眉设为偶数页页眉。代码如下:

public byte[] AddExtraHeader(byte[] report)
{
    using (WordprocessingDocument wdDoc = WordprocessingDocument.Open(new MemoryStream(report), true))
    {
        MainDocumentPart mainPart = wdDoc.MainDocumentPart;
        var settingsPart = wdDoc.MainDocumentPart.DocumentSettingsPart;
        var element = new Settings(new TitlePage());
        element.Save(settingsPart);

        // 获取现有页眉
        DocumentFormat.OpenXml.Packaging.HeaderPart oldHeader = mainPart.HeaderParts.FirstOrDefault();

        // 创建新页眉部件
        DocumentFormat.OpenXml.Packaging.HeaderPart newHeader = mainPart.AddNewPart<HeaderPart>();

        // 获取页眉部件的ID
        string rIdOld = mainPart.GetIdOfPart(oldHeader);
        string rIdNew = mainPart.GetIdOfPart(newHeader);

        if (oldHeader != null)
        {
            newHeader.FeedData(oldHeader.GetStream()); // 此处报错
        }

        // 修改新页眉中的文本
        foreach (var currentText in newHeader.RootElement.Descendants<DocumentFormat.OpenXml.Wordprocessing.Text>())
        {
            currentText.Text = currentText.Text.Replace("trecio", "");
        }

        // 获取节属性并替换页眉引用
        IEnumerable<DocumentFormat.OpenXml.Wordprocessing.SectionProperties> sectPrs =
        mainPart.Document.Body.Elements<SectionProperties>();
        foreach (var sectPr in sectPrs)
        {
            // 删除现有页眉引用
            sectPr.RemoveAllChildren<HeaderReference>();

            // 添加新的页眉引用
            sectPr.PrependChild<HeaderReference>(new HeaderReference() { Type = HeaderFooterValues.First, Id = rIdOld});
            sectPr.PrependChild<HeaderReference>(new HeaderReference() { Type = HeaderFooterValues.Even, Id = rIdNew });
        }
    }
    return report;
}

执行到newHeader.FeedData(oldHeader.GetStream())时,抛出以下错误:

MEMORY STREAM IS NOT EXPANDABLE

我参考了微软的官方示例代码(该示例可正常运行),示例代码如下:

public static void AddHeaderFromTo(string filepathFrom, string filepathTo)
{
    // 用源文档的页眉替换目标文档的页眉
    using (WordprocessingDocument wdDoc = WordprocessingDocument.Open(filepathTo, true))
    {
         MainDocumentPart mainPart = wdDoc.MainDocumentPart;
            
         // 删除现有页眉部件
         mainPart.DeleteParts(mainPart.HeaderParts);

         // 创建新页眉部件
         DocumentFormat.OpenXml.Packaging.HeaderPart headerPart = mainPart.AddNewPart<HeaderPart>();
            
         // 获取页眉部件ID
         string rId = mainPart.GetIdOfPart(headerPart);
            
         // 将源文档的页眉数据写入目标页眉部件
         using (WordprocessingDocument wdDocSource = WordprocessingDocument.Open(filepathFrom, true))
         {
                DocumentFormat.OpenXml.Packaging.HeaderPart firstHeader =
                wdDocSource.MainDocumentPart.HeaderParts.FirstOrDefault();

                if (firstHeader != null)
                {
                    headerPart.FeedData(firstHeader.GetStream()); // 此处无报错
                }
         }

         // 获取节属性并替换页眉引用
         IEnumerable<DocumentFormat.OpenXml.Wordprocessing.SectionProperties> sectPrs = 
         mainPart.Document.Body.Elements<SectionProperties>();
         foreach (var sectPr in sectPrs)
         {
             // 删除现有页眉引用
             sectPr.RemoveAllChildren<HeaderReference>();

             // 添加新的页眉引用
             sectPr.PrependChild<HeaderReference>(new HeaderReference() { Id = rId });
         }    
    }
}

我找不到两段代码中FeedData调用的差异,请问我遗漏了什么?


问题原因与解决方案

核心差异:流的可扩展性

你的代码和微软示例的关键区别在于流的底层实现:

  • 微软示例中,源文档从文件打开,firstHeader.GetStream()返回的是可读写、可扩展的文件流;
  • 你的代码中,源文档是通过new MemoryStream(report)创建的——这个流是基于固定长度的字节数组构建的,默认是只读且不可扩展的。当FeedData尝试向新的HeaderPart写入数据时,原流的限制导致抛出“不可扩展”的错误。

修正方案1:复制流到可写的MemoryStream

在调用FeedData前,先将原页眉的流内容复制到一个可写的MemoryStream中,再传入FeedData:

if (oldHeader != null)
{
    using (var tempStream = new MemoryStream())
    {
        oldHeader.GetStream().CopyTo(tempStream);
        tempStream.Position = 0; // 重置流指针到起始位置
        newHeader.FeedData(tempStream);
    }
}

修正方案2:初始化可写的MemoryStream

打开WordprocessingDocument时,不要直接用传入的byte[]创建流,而是先写入一个新的可写MemoryStream:

public byte[] AddExtraHeader(byte[] report)
{
    using (var ms = new MemoryStream())
    {
        // 将原字节数组写入可写的内存流
        ms.Write(report, 0, report.Length);
        using (WordprocessingDocument wdDoc = WordprocessingDocument.Open(ms, true))
        {
            // 原方法内的逻辑保持不变
            MainDocumentPart mainPart = wdDoc.MainDocumentPart;
            
            // 修正文档设置的写法
            var settingsPart = mainPart.DocumentSettingsPart ?? mainPart.AddNewPart<DocumentSettingsPart>();
            if (settingsPart.Settings == null)
            {
                settingsPart.Settings = new Settings();
            }
            settingsPart.Settings.RemoveAllChildren<TitlePage>();
            settingsPart.Settings.PrependChild(new TitlePage());
            settingsPart.Settings.Save();

            DocumentFormat.OpenXml.Packaging.HeaderPart oldHeader = mainPart.HeaderParts.FirstOrDefault();
            DocumentFormat.OpenXml.Packaging.HeaderPart newHeader = mainPart.AddNewPart<HeaderPart>();

            string rIdOld = mainPart.GetIdOfPart(oldHeader);
            string rIdNew = mainPart.GetIdOfPart(newHeader);

            if (oldHeader != null)
            {
                newHeader.FeedData(oldHeader.GetStream());
            }

            foreach (var currentText in newHeader.RootElement.Descendants<DocumentFormat.OpenXml.Wordprocessing.Text>())
            {
                currentText.Text = currentText.Text.Replace("trecio", "");
            }

            IEnumerable<DocumentFormat.OpenXml.Wordprocessing.SectionProperties> sectPrs =
            mainPart.Document.Body.Elements<SectionProperties>();
            foreach (var sectPr in sectPrs)
            {
                sectPr.RemoveAllChildren<HeaderReference>();
                sectPr.PrependChild<HeaderReference>(new HeaderReference() { Type = HeaderFooterValues.First, Id = rIdOld});
                sectPr.PrependChild<HeaderReference>(new HeaderReference() { Type = HeaderFooterValues.Even, Id = rIdNew });
            }
        }
        // 返回修改后的字节数组(原代码直接返回report是错误的)
        return ms.ToArray();
    }
}

注意:原代码最后直接返回传入的report是错误的,所有修改都在内存流中,必须返回内存流的ToArray()结果。

额外修正:文档设置的正确写法

原代码中设置TitlePage的方式存在问题,可能导致设置不生效。正确的写法应该先检查DocumentSettingsPart是否存在,再创建或修改设置。


内容的提问来源于stack exchange,提问作者Tomas

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 04:15:39