克隆 Office Open XML 文档的最有效方法是什么?

2023-11-24

使用 Office Open XML 文档(例如,自 Office 2007 发布以来由 Word、Excel 或 PowerPoint 创建的文档)时,您经常需要克隆或复制现有文档,然后对该克隆进行更改,从而创建新的文档文档。

在这种情况下,已经提出并回答了几个问题(有时是错误的或至少不是最佳的),这表明用户确实面临着问题。例如:

  • 使用 OpenXml 和 C# 复制 Word 文档
  • Word OpenXml Word 发现不可读的内容
  • Open XML SDK:打开 Word 模板并保存到不同的文件名
  • 通过 OpenXML C# 复制时 docx 文档损坏

所以,问题是:

  1. 正确克隆或复制这些文档的可能方法有哪些?
  2. 哪种方式最有效?

以下示例类显示了正确复制几乎任何文件并将副本返回到MemoryStream or FileStream然后您可以从中打开一个WordprocessingDocument(单词),SpreadsheetDocument(Excel),或PresentationDocument(PowerPoint) 并使用开放 XML SDK以及可选的Open-XML-PowerTools.

using System.IO;

namespace CodeSnippets.IO
{
    /// <summary>
    /// This class demonstrates multiple ways to clone files stored in the file system.
    /// In all cases, the source file is stored in the file system. Where the return type
    /// is a <see cref="MemoryStream"/>, the destination file will be stored only on that
    /// <see cref="MemoryStream"/>. Where the return type is a <see cref="FileStream"/>,
    /// the destination file will be stored in the file system and opened on that
    /// <see cref="FileStream"/>.
    /// </summary>
    /// <remarks>
    /// The contents of the <see cref="MemoryStream"/> instances returned by the sample
    /// methods can be written to a file as follows:
    ///
    ///     var stream = ReadAllBytesToMemoryStream(sourcePath);
    ///     File.WriteAllBytes(destPath, stream.GetBuffer());
    ///
    /// You can use <see cref="MemoryStream.GetBuffer"/> in cases where the MemoryStream
    /// was created using <see cref="MemoryStream()"/> or <see cref="MemoryStream(int)"/>.
    /// In other cases, you can use the <see cref="MemoryStream.ToArray"/> method, which
    /// copies the internal buffer to a new byte array. Thus, GetBuffer() should be a tad
    /// faster.
    /// </remarks>
    public static class FileCloner
    {
        public static MemoryStream ReadAllBytesToMemoryStream(string path)
        {
            byte[] buffer = File.ReadAllBytes(path);
            var destStream = new MemoryStream(buffer.Length);
            destStream.Write(buffer, 0, buffer.Length);
            destStream.Seek(0, SeekOrigin.Begin);
            return destStream;
        }

        public static MemoryStream CopyFileStreamToMemoryStream(string path)
        {
            using FileStream sourceStream = File.OpenRead(path);
            var destStream = new MemoryStream((int) sourceStream.Length);
            sourceStream.CopyTo(destStream);
            destStream.Seek(0, SeekOrigin.Begin);
            return destStream;
        }

        public static FileStream CopyFileStreamToFileStream(string sourcePath, string destPath)
        {
            using FileStream sourceStream = File.OpenRead(sourcePath);
            FileStream destStream = File.Create(destPath);
            sourceStream.CopyTo(destStream);
            destStream.Seek(0, SeekOrigin.Begin);
            return destStream;
        }

        public static FileStream CopyFileAndOpenFileStream(string sourcePath, string destPath)
        {
            File.Copy(sourcePath, destPath, true);
            return new FileStream(destPath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
        }
    }
}

除了上述与 Open XML 无关的方法之外,您还可以使用以下方法,例如,如果您已经打开了OpenXmlPackage比如一个WordprocessingDocument, SpreadsheetDocument, or PresentationDocument:

public void DoWorkCloningOpenXmlPackage()
{
    using WordprocessingDocument sourceWordDocument = WordprocessingDocument.Open(SourcePath, false);

    // There are multiple overloads of the Clone() method in the Open XML SDK.
    // This one clones the source document to the given destination path and
    // opens it in read-write mode.
    using var wordDocument = (WordprocessingDocument) sourceWordDocument.Clone(DestPath, true);

    ChangeWordprocessingDocument(wordDocument);
}

上述所有方法都可以正确克隆或复制文档。但什么是最有效的呢?

输入我们的基准测试,它使用BenchmarkDotNetNuGet 包:

using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using BenchmarkDotNet.Attributes;
using CodeSnippets.IO;
using CodeSnippets.OpenXml.Wordprocessing;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;

namespace CodeSnippets.Benchmarks.IO
{
    public class FileClonerBenchmark
    {
        #region Setup and Helpers

        private const string SourcePath = "Source.docx";
        private const string DestPath = "Destination.docx";

        [Params(1, 10, 100, 1000)]
        public static int ParagraphCount;

        [GlobalSetup]
        public void GlobalSetup()
        {
            CreateTestDocument(SourcePath);
            CreateTestDocument(DestPath);
        }

        private static void CreateTestDocument(string path)
        {
            const string sentence = "The quick brown fox jumps over the lazy dog.";
            string text = string.Join(" ", Enumerable.Range(0, 22).Select(i => sentence));
            IEnumerable<string> texts = Enumerable.Range(0, ParagraphCount).Select(i => text);
            using WordprocessingDocument unused = WordprocessingDocumentFactory.Create(path, texts);
        }

        private static void ChangeWordprocessingDocument(WordprocessingDocument wordDocument)
        {
            Body body = wordDocument.MainDocumentPart.Document.Body;
            Text text = body.Descendants<Text>().First();
            text.Text = DateTimeOffset.UtcNow.Ticks.ToString();
        }

        #endregion

        #region Benchmarks

        [Benchmark(Baseline = true)]
        public void DoWorkUsingReadAllBytesToMemoryStream()
        {
            using MemoryStream destStream = FileCloner.ReadAllBytesToMemoryStream(SourcePath);

            using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(destStream, true))
            {
                ChangeWordprocessingDocument(wordDocument);
            }

            File.WriteAllBytes(DestPath, destStream.GetBuffer());
        }

        [Benchmark]
        public void DoWorkUsingCopyFileStreamToMemoryStream()
        {
            using MemoryStream destStream = FileCloner.CopyFileStreamToMemoryStream(SourcePath);

            using (WordprocessingDocument wordDocument = WordprocessingDocument.Open(destStream, true))
            {
                ChangeWordprocessingDocument(wordDocument);
            }

            File.WriteAllBytes(DestPath, destStream.GetBuffer());
        }

        [Benchmark]
        public void DoWorkUsingCopyFileStreamToFileStream()
        {
            using FileStream destStream = FileCloner.CopyFileStreamToFileStream(SourcePath, DestPath);
            using WordprocessingDocument wordDocument = WordprocessingDocument.Open(destStream, true);
            ChangeWordprocessingDocument(wordDocument);
        }

        [Benchmark]
        public void DoWorkUsingCopyFileAndOpenFileStream()
        {
            using FileStream destStream = FileCloner.CopyFileAndOpenFileStream(SourcePath, DestPath);
            using WordprocessingDocument wordDocument = WordprocessingDocument.Open(destStream, true);
            ChangeWordprocessingDocument(wordDocument);
        }

        [Benchmark]
        public void DoWorkCloningOpenXmlPackage()
        {
            using WordprocessingDocument sourceWordDocument = WordprocessingDocument.Open(SourcePath, false);
            using var wordDocument = (WordprocessingDocument) sourceWordDocument.Clone(DestPath, true);
            ChangeWordprocessingDocument(wordDocument);
        }

        #endregion
    }
}

上述基准测试运行如下:

using BenchmarkDotNet.Running;
using CodeSnippets.Benchmarks.IO;

namespace CodeSnippets.Benchmarks
{
    public static class Program
    {
        public static void Main()
        {
            BenchmarkRunner.Run<FileClonerBenchmark>();
        }
    }
}

我的机器上的结果是什么?哪种方法最快?

BenchmarkDotNet=v0.12.0, OS=Windows 10.0.18362
Intel Core i7-7500U CPU 2.70GHz (Kaby Lake), 1 CPU, 4 logical and 2 physical cores
.NET Core SDK=3.0.100
  [Host]     : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), X64 RyuJIT
  DefaultJob : .NET Core 3.0.0 (CoreCLR 4.700.19.46205, CoreFX 4.700.19.46214), X64 RyuJIT
| Method                                  | ParaCount |      Mean |     Error |    StdDev |    Median | Ratio |
| --------------------------------------- | --------- | --------: | --------: | --------: | --------: | ----: |
| DoWorkUsingReadAllBytesToMemoryStream   | 1         |  1.548 ms | 0.0298 ms | 0.0279 ms |  1.540 ms |  1.00 |
| DoWorkUsingCopyFileStreamToMemoryStream | 1         |  1.561 ms | 0.0305 ms | 0.0271 ms |  1.556 ms |  1.01 |
| DoWorkUsingCopyFileStreamToFileStream   | 1         |  2.394 ms | 0.0601 ms | 0.1100 ms |  2.354 ms |  1.55 |
| DoWorkUsingCopyFileAndOpenFileStream    | 1         |  3.302 ms | 0.0657 ms | 0.0855 ms |  3.312 ms |  2.12 |
| DoWorkCloningOpenXmlPackage             | 1         |  4.567 ms | 0.1218 ms | 0.3591 ms |  4.557 ms |  3.13 |
|                                         |           |           |           |           |           |       |
| DoWorkUsingReadAllBytesToMemoryStream   | 10        |  1.737 ms | 0.0337 ms | 0.0361 ms |  1.742 ms |  1.00 |
| DoWorkUsingCopyFileStreamToMemoryStream | 10        |  1.752 ms | 0.0347 ms | 0.0571 ms |  1.739 ms |  1.01 |
| DoWorkUsingCopyFileStreamToFileStream   | 10        |  2.505 ms | 0.0390 ms | 0.0326 ms |  2.500 ms |  1.44 |
| DoWorkUsingCopyFileAndOpenFileStream    | 10        |  3.532 ms | 0.0731 ms | 0.1860 ms |  3.455 ms |  2.05 |
| DoWorkCloningOpenXmlPackage             | 10        |  4.446 ms | 0.0880 ms | 0.1470 ms |  4.424 ms |  2.56 |
|                                         |           |           |           |           |           |       |
| DoWorkUsingReadAllBytesToMemoryStream   | 100       |  2.847 ms | 0.0563 ms | 0.0553 ms |  2.857 ms |  1.00 |
| DoWorkUsingCopyFileStreamToMemoryStream | 100       |  2.865 ms | 0.0561 ms | 0.0786 ms |  2.868 ms |  1.02 |
| DoWorkUsingCopyFileStreamToFileStream   | 100       |  3.550 ms | 0.0697 ms | 0.0881 ms |  3.570 ms |  1.25 |
| DoWorkUsingCopyFileAndOpenFileStream    | 100       |  4.456 ms | 0.0877 ms | 0.0861 ms |  4.458 ms |  1.57 |
| DoWorkCloningOpenXmlPackage             | 100       |  5.958 ms | 0.1242 ms | 0.2727 ms |  5.908 ms |  2.10 |
|                                         |           |           |           |           |           |       |
| DoWorkUsingReadAllBytesToMemoryStream   | 1000      | 12.378 ms | 0.2453 ms | 0.2519 ms | 12.442 ms |  1.00 |
| DoWorkUsingCopyFileStreamToMemoryStream | 1000      | 12.538 ms | 0.2070 ms | 0.1835 ms | 12.559 ms |  1.02 |
| DoWorkUsingCopyFileStreamToFileStream   | 1000      | 12.919 ms | 0.2457 ms | 0.2298 ms | 12.939 ms |  1.05 |
| DoWorkUsingCopyFileAndOpenFileStream    | 1000      | 13.728 ms | 0.2803 ms | 0.5196 ms | 13.652 ms |  1.11 |
| DoWorkCloningOpenXmlPackage             | 1000      | 16.868 ms | 0.2174 ms | 0.1927 ms | 16.801 ms |  1.37 |

事实证明DoWorkUsingReadAllBytesToMemoryStream()始终是最快的方法。然而,保证金至DoWorkUsingCopyFileStreamToMemoryStream()很容易有误差范围。这意味着您应该在MemoryStream尽可能进行处理。如果您不必将生成的文档存储在文件系统中,这甚至会比不必要地使用FileStream.

凡是有输出的地方FileStream涉及时,您会看到更“显着”的差异(请注意,如果处理大量文档,一毫秒就会产生差异)。你应该注意使用File.Copy()实际上并不是一个很好的方法。

最后,使用OpenXmlPackage.Clone()方法或其重写之一被证明是最慢的方法。这是因为它涉及比仅仅复制字节更复杂的逻辑。但是,如果您得到的只是对OpenXmlPackage(或者实际上是它的子类之一),Clone()方法及其覆盖是您的最佳选择。

您可以在我的中找到完整的源代码代码片段GitHub 存储库。看着那(这代码片段.Benchmark项目和文件克隆器 class.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

克隆 Office Open XML 文档的最有效方法是什么? 的相关文章

随机推荐

  • RequestFactory 理论:为什么 Locator<>.find() 被如此频繁地调用?

    我是 RequestFactory 的新手 但得到了慷慨的帮助托马斯 布罗耶在查看了下面的文档之后 情况变得好多了 RequestFactory 入门 请求工厂移动部件 GWT 2 4 中的 RequestFactory 更改 但你能解释一
  • 如何在 Python 中重置 TCP 套接字?

    我有一个用 Python 编写的套接字代理 当它从一对通信对等点接收到 RST 时 它将通过让套接字被垃圾收集来关闭与两个对等点的连接 这会导致其他对等方看到 FIN 而不是 RST 这意味着代理有效地将 RST 转换为 FIN 我认为这并
  • Anaconda“无法创建进程”

    我是 Python 新手 我刚刚安装了 Python anaconda python 2 7 在启动 Anaconda 时提示发生了 无法创建进程 的情况 那么这里有人可以帮我吗 我感谢每一个帮助 我遇到了完全相同的错误 因为我的用户名包含
  • SVN:将存储库主干移动到另一个分支(带有历史记录)

    我正在使用带有大量存储库的 SVN 设置 我试图通过将一个的主干移动到另一个的分支来合并一些 旧的是新的主题版本 减去我稍后将应用的一些代码修复 所以这对我来说很有意义 简短版本 我想从 RepositoryA trunk 转到 Repos
  • VB“Financial.Pmt”在 C# 中等效吗?

    Microsoft VisualBasic 程序集中有一个内置函数 我可以在 VB 中这样使用它 Financial Pmt dAPR 100 12 iNumberOfPayments dLoanAmount 1 我当前的项目是用C 编写的
  • 如何使用 jQuery 计算 ASP.NET 中 gridview 的行数

    有谁知道如何使用 jQuery 计算 asp GridView 中的行数 如果没有找到行那么我想做一些事情 A GridView只是呈现为标准 HTML 表格 因此只需计算trGridView 下的元素 var totalRows tr l
  • 在 ListBoxFor 中选择值的挑战

    最近在开发我的第一个 ASP Net MVC2 Web 应用程序时 当我需要在列表框中选择多个值时 我遇到了一些问题 我用一些 jQuery 解决了这个问题 但继续编写了一些非常简单的代码来演示 我使用 EF 作为模型 有两个实体 Cust
  • 如何从我的 Android 应用程序中删除 QUERY_ALL_PACKAGES 权限?

    由于 Google 的反馈 我的 Google Play 更新版本已被拒绝 3 次 应使用不太广泛的应用程序可见性方法 我们无法批准您的应用使用 QUERY ALL PACKAGES 权限 因为声明的任务可以使用不太广泛的应用可见性方法来完
  • stl容器是否使用隐式共享?

    众所周知 Qt 小部件使用隐式共享 所以我对 stl 容器感兴趣std vector std string也使用隐式共享 如果没有 为什么 因为它非常有用 如果答案是肯定的 我们如何确定呢 我需要简单的 C stl 程序 该程序显示 stl
  • 将 html 结果保存到 txt 或 html 文件

    我有一个带有 html 代码的变量 以下是 R 控制台中代码变量的输出 h1 My First Heading h1 p My first paragraph p 我尝试将内容保存到txt文件中 write table code file
  • 什么是卷积神经网络中的“线性投影”[关闭]

    Closed 这个问题不符合堆栈溢出指南 目前不接受答案 我正在阅读剩余学习 我有一个问题 3 2中提到的 线性投影 是什么 一旦得到这个看起来很简单 但无法理解 有人可以提供简单的例子吗 首先 重要的是要了解什么x y and F以及为什
  • Google BigQuery 的 JDBC 驱动程序?

    有谁知道 Google BigQuery 的 JDBC 接口或驱动程序吗 请只使用 Java 我已经不再使用 Python 库了 有一个第 3 方 JDBC 驱动程序 可以从以下位置获取 http code google com p sta
  • 不完整类型的 new 在包含在模板中时编译

    考虑这段代码 有一个明显的编译错误 1 struct A struct B B new A error allocation of incomplete type A Using a unique ptr也无济于事 2 struct A s
  • HMM 前向算法中的下溢

    我正在实现 HMM 的前向算法来计算给定 HMM 发出给定观察序列的概率 我希望我的算法对于下溢具有鲁棒性 我无法在对数空间中工作 因为前向算法需要概率的乘法和加法 避免下溢的最佳方法是什么 我已经阅读了一些关于此的资料 但我得到的最好的建
  • 在 Angular 2 和 Spring MVC 中使用其他表单字段上传文件

    我在尝试着上传文件和其他表单字段内容从我的 Angular 2 前端到 Spring 后端 但不知怎的 我无法做到这一点 这是我的代码 应用程序组件 ts fileChange e this fileList e target files
  • 如何在选项卡布局中的文本旁边设置图标

    我正在使用以下文本和图标制作 Tablayouttutorial 我的问题是如何使图标放置在文本旁边而不是文本上方 我是Android开发新手 希望大家能帮助我 提前谢谢您 非常感谢您的回答 这是我的java文件 public class
  • ClassNotFoundException:eclipse 中 Jetty hello world 中的 javax.servlet.AsyncContext

    我已点击链接http wiki eclipse org Jetty Tutorial Jetty HelloWorld教程 使用 Eclipse 还查看了现有的 stackoverflowhere 我使用聚合 jetty 8 0 0 jar
  • 为什么所有 JavaScript 控制台日志和错误都显示第 1 行(开发人员工具)

    我正在开发一个 Javascript 项目并使用 Chrome F12 开发人员工具进行调试 由于某种原因 所有 console log 输出和错误消息都声称它们发生在line 1我的 js 文件 即 在控制台中每行右侧显示myFile j
  • 逻辑删除消息未从 KTable 状态存储中删除记录?

    我正在创建 KTable 处理来自 KStream 的数据 但是 当我触发具有密钥和空负载的逻辑删除消息时 它不会从 KTable 中删除消息 sample public KStream
  • 克隆 Office Open XML 文档的最有效方法是什么?

    使用 Office Open XML 文档 例如 自 Office 2007 发布以来由 Word Excel 或 PowerPoint 创建的文档 时 您经常需要克隆或复制现有文档 然后对该克隆进行更改 从而创建新的文档文档 在这种情况下