打开 XML 带有 MemoryStream 的 WordprocessingDocument 是 0KB

Open XML WordprocessingDocument with MemoryStream is 0KB

我正在尝试学习如何使用 Microsoft 的 Open XML SDK。我按照他们关于如何使用 FileStream 创建 Word 文档的步骤进行操作,并且效果很好。现在我想创建一个 Word 文档,但只在内存中,并等待用户指定是否要保存文件。

This document by Microsoft 说明如何使用 MemoryStream 处理内存中的文档,但是,文档首先从现有文件中加载,然后 "dumped" 进入 MemorySteam .我想要的是完全在内存中创建一个文档(而不是基于驱动器中的文件)。我认为可以实现的是这段代码:

// This is almost the same as Microsoft's code except I don't
// dump any files into the MemoryStream
using (var mem = new MemoryStream())
{
    using (var doc = WordprocessingDocument.Create(mem, WordprocessingDocumentType.Document, true))
    {
        doc.AddMainDocumentPart().Document = new Document();
        var body = doc.MainDocumentPart.Document.AppendChild(new Body());
        var paragraph = body.AppendChild(new Paragraph());
        var run = paragraph.AppendChild(new Run());
        run.AppendChild(new Text("Hello docx"));

        using (var file = new FileStream(destination, FileMode.CreateNew))
        {
            mem.WriteTo(file);
        }
    }
}

但结果是一个 0KB 的文件,Word 无法读取该文件。起初我以为是因为 MemoryStream 的大小,所以我给它提供了 1024 的初始大小,但结果是一样的。另一方面,如果我将 MemoryStream 更改为 FileStream,它会完美运行。

我的问题是我想做的事情是否可行,如果可行,如何实现?我想这一定是可能的,只是不是我的做法。如果不可能,我有什么选择?

您正在将 mem 传递给 WordprocessingDocument.Create(),这是从(现在为空)MemoryStream 创建文档,但是,我不认为这是关联MemoryStream 作为文档的后备存储。也就是说,mem 只是文档的 input,而不是 output。因此,当您调用 mem.WriteTo(file); 时,mem 仍然是空的(调试器会确认这一点)。

然后,链接文档确实说 "you must supply a resizable memory stream to [Open()]",这意味着将写入流,因此可能 mem 成为支持存储,但尚未写入任何内容,因为 AutoSave property(您在 Create() 中为其指定了 true)尚未有机会生效(强调我的)...

Gets a flag that indicates whether the parts should be saved when disposed.

我看到 WordprocessingDocument 有一个 SaveAs() method,并用它代替原始代码中的 FileStream...

using (var mem = new MemoryStream())
using (var doc = WordprocessingDocument.Create(mem, WordprocessingDocumentType.Document, true))
{
    doc.AddMainDocumentPart().Document = new Document();
    var body = doc.MainDocumentPart.Document.AppendChild(new Body());
    var paragraph = body.AppendChild(new Paragraph());
    var run = paragraph.AppendChild(new Run());
    run.AppendChild(new Text("Hello docx"));

    // Explicitly close the OpenXmlPackage returned by SaveAs() so destination doesn't stay locked
    doc.SaveAs(destination).Close();
}

...为我生成预期的文件。有趣的是,在调用 doc.SaveAs() 之后,即使我插入对 doc.Save() 的调用,mem.Lengthmem.Position 仍然是 0,这确实表明mem仅用于初始化。

我要注意的另一件事是示例代码正在调用 Open(), whereas you are calling Create()。就这两种方法的不同之处而言,文档非常稀疏,但我建议您尝试使用 Open() 创建文档...

using (MemoryStream mem = new MemoryStream())
using (WordprocessingDocument doc = WordprocessingDocument.Open(mem, true))
{
    // ...
}

... 但是 当我这样做时 Open() 抛出异常,大概是因为 mem 没有数据。因此,这些名称似乎有些不言自明,因为 Create() 初始化新文档数据,而 Open() 期望现有数据。我确实发现如果我喂 Create() 一个装满随机垃圾的 MemoryStream...

using (var mem = new MemoryStream())
{
    // Fill mem with garbage
    byte[] buffer = new byte[1024];
    new Random().NextBytes(buffer);
    mem.Write(buffer, 0, buffer.Length);
    mem.Position = 0;

    using (var doc = WordprocessingDocument.Create(mem, WordprocessingDocumentType.Document, true))
    {
        // ...
    }
}

...它仍然生成 与上面的第一个代码片段完全相同的文档 XML,这让我想知道为什么 Create() 甚至需要一个完全输入 Stream

这里发生了几件事:

首先,与 Microsoft 的示例不同,我将 using 将文件写入磁盘的块代码嵌套在创建和修改文件的块内。 WordprocessingDocument 被保存到流中,直到它被释放或调用 Save() 方法。 WordprocessingDocument 在到达其 using 块的末尾时会自动处理。如果我没有嵌套第三个 using 语句,从而在尝试保存文件之前到达第二个 using 语句的末尾,我会允许将文档写入 MemoryStream- 而我是将仍然为空的流写入磁盘(因此是 0KB 文件)。

假设 调用 Save() 可能有所帮助,但它不受 .Net 核心(我正在使用的)支持。您可以通过检查 CanSave.

来检查您的系统是否支持 Save()
/// <summary>
/// Gets a value indicating whether saving the package is supported by calling <see cref="Save"/>. Some platforms (such as .NET Core), have limited support for saving.
/// If <c>false</c>, in order to save, the document and/or package needs to be fully closed and disposed and then reopened.
/// </summary>
public static bool CanSave { get; }


所以代码最终与微软的代码几乎相同,除了我事先没有读取任何文件,而是我只是从一个空的 MemoryStream:

开始
using (var mem = new MemoryStream())
{
    using (var doc = WordprocessingDocument.Create(mem, WordprocessingDocumentType.Document, true))
    {
        doc.AddMainDocumentPart().Document = new Document();
        var body = doc.MainDocumentPart.Document.AppendChild(new Body());
        var paragraph = body.AppendChild(new Paragraph());
        var run = paragraph.AppendChild(new Run());
        run.AppendChild(new Text("Hello docx"));
    }

    using (var file = new FileStream(destination, FileMode.CreateNew))
    {
        mem.WriteTo(file);
    }
}

此外,您不需要在保存之前重新打开文档,但如果您确实记得使用 Open() 而不是 Create(),因为 Create() 会清空 MemoryStream 并且您还将以 0KB 文件结尾。