Open XML WordprocessingDocument с MemoryStream составляет 0 КБ - PullRequest
1 голос
/ 13 апреля 2020

Я пытаюсь узнать, как использовать Microsoft Open XML SDK. Я следовал их инструкциям по созданию документа Word, используя FileStream, и он работал отлично. Теперь я хочу создать документ Word, но только в памяти, и ждать, пока пользователь укажет, хотели бы они сохранить файл.

В этом документе Microsoft указано, как Для работы с документами в памяти используется MemoryStream, однако документ сначала загружается из существующего файла и «сбрасывается» в 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);
        }
    }
}

Но в результате получается файл размером 0 КБ, который не может быть прочитан Word. Сначала я подумал, что это из-за размера MemoryStream, поэтому я дал ему начальный размер 1024, но результаты были такими же. С другой стороны, если я поменяю MemoryStream на FileStream, он прекрасно работает.

Мой вопрос: возможно ли то, что я хочу сделать, и если да, то как? Я предполагаю, что это возможно, но не так, как я это делаю. Если это невозможно, какая у меня альтернатива?

Ответы [ 2 ]

1 голос
/ 13 апреля 2020

Вы передаете mem в WordprocessingDocument.Create(), который создает документ из (теперь пустого) MemoryStream, однако я не думаю, что это связывает MemoryStream как резервное хранилище документ. То есть mem - это только ввод документа, но не вывод . Поэтому, когда вы вызываете mem.WriteTo(file);, mem все еще пуст (отладчик подтвердит это).

С другой стороны, связанный документ действительно говорит: «Вы должны предоставить поток памяти с изменяемым размером для [Open()» ] ", что означает, что поток будет записан в, поэтому, возможно, mem становится резервным хранилищем, но в него еще ничего не записано, потому что свойство AutoSave ( для которого вы указали true в Create()) еще не было возможности вступить в силу (выделено мое) ...

Получает флаг, который указывает, следует ли сохранять детали при утилизации.

Я вижу, что WordprocessingDocument имеет SaveAs() метод и заменяет его на 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.Length и mem.Position оба по-прежнему 0, что предполагает, что mem используется только для инициализации.

Еще одна вещь, которую я хотел бы отметить, это то, что пример кода вызывает Open(), тогда как вы звоните Create(). Документация довольно скудная, поскольку эти два метода различаются, но я бы посоветовал вам попробовать создать документ с помощью Open() вместо ...

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

... однако когда я это делаю, Open() выдает исключение, вероятно, потому что mem не имеет данных. Таким образом, кажется, что названия несколько очевидны, так как Create() инициализирует новые данные документа, тогда как Open() ожидает существующие данные. Я обнаружил, что если я кормлю Create() a 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.

0 голосов
/ 15 апреля 2020

Здесь происходит несколько вещей:

Во-первых, в отличие от примера Microsoft, я вложил код блока using, который записывает файл на диск внутри блока, который создает и изменяет файл. WordprocessingDocument сохраняется в потоке до его удаления или при вызове метода Save(). WordprocessingDocument автоматически удаляется при достижении конца блока using. Если бы я не вложил третий оператор using, достигнув конца второго оператора using, прежде чем пытаться сохранить файл, я бы разрешил записать документ в MemoryStream - вместо этого я писал все еще пустой поток на диск (отсюда файл 0KB).

I предположим, что вызов Save() мог бы помочь, но это не поддерживается ядром. Net (это то, что я с помощью). Вы можете проверить, поддерживается ли Save() в вашей системе, проверив CanSave.

/// <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; }

Таким образом, код оказался практически идентичным коду Microsoft, за исключением того, что я заранее не читаю никаких файлов, а просто начинаю с пустого 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 и вы также получите файл размером 0 КБ.

...