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
相关产品推荐
相关产品推荐

