使用 SAX (简单 API for XML) 替换Word文档中的文本

本主题演示如何使用 Open XML SDK 使用简单 API for XML (SAX) 方法使用 Open XML SDK 搜索和替换Word文档中的文本。 有关文档的基本结构 WordprocessingML 的详细信息,请参阅 WordprocessingML 文档的结构

为何使用 SAX 方法?

Open XML SDK 提供两种分析 Office Open XML 文件的方法:文档对象模型 (DOM) 和简单 API for XML (SAX) 。 DOM 方法旨在使用强类型类轻松查询和分析 Open XML 文件。 但是,DOM 方法需要将整个 Open XML 部件加载到内存中,这可能导致处理速度变慢,并在处理非常大的部件时出现内存不足异常。 SAX 方法一次在 Open XML 部件中读取一个元素,而无需将整个部分读取到内存中,从而提供对 XML 数据的非缓存、仅向前访问权限,这使得它在读取非常大的部件时是更好的选择。

访问 MainDocumentPart

Word文档的文本存储在 中MainDocumentPart,因此查找和替换文本的第一步是访问Word文档的 MainDocumentPart。 为此,我们首先使用 WordprocessingDocument.Open 方法将文档的路径作为第一个参数传入,并使用第二个参数 true 来指示我们要打开文件进行编辑。 然后, MainDocumentPart 确保 不为 null。

// Open the WordprocessingDocument for editing
using (WordprocessingDocument wordprocessingDocument = WordprocessingDocument.Open(path, true))
{
    // Access the MainDocumentPart and make sure it is not null
    MainDocumentPart? mainDocumentPart = wordprocessingDocument.MainDocumentPart;

    if (mainDocumentPart is not null)

创建内存Stream、OpenXmlReader 和 OpenXmlWriter

使用 DOM 方法来编辑文档时,整个部件将读入内存中,因此,我们可以使用 Open XML SDK 的强类型类访问 Text 类来访问文档的文本并对其进行编辑。 但是,SAX 方法使用 OpenXmlPartReaderOpenXmlPartWriter 类,这些类通过仅向前访问权限访问部件的流。 这样做的优点是,不需要将整个部件加载到内存中,这速度更快,占用的内存更少,但由于同一部分不能同时在多个流中打开,因此无法创建 用于 OpenXmlReader 读取部件的 , OpenXmlWriter 而创建 用于同时编辑同一部件的 。 解决方法是创建一个额外的内存流,并将更新的部件写入新的内存流,然后在释放 和 OpenXmlWriterOpenXmlReader使用该流更新部件。 在下面的代码中,我们创建 MemoryStream 用于存储更新的部件,并为 MainDocumentPartOpenXmlWriter 和 创建 OpenXmlReader ,以便写入MemoryStream

// Create a MemoryStream to store the updated MainDocumentPart
using (MemoryStream memoryStream = new MemoryStream())
{
    // Create an OpenXmlReader to read the main document part
    // and an OpenXmlWriter to write to the MemoryStream
    using (OpenXmlReader reader = OpenXmlPartReader.Create(mainDocumentPart))
    using (OpenXmlWriter writer = OpenXmlPartWriter.Create(memoryStream))

读取部件并写入新Stream

现在,我们有了 一个 OpenXmlReader 读取部件,一个 OpenXmlWriter 要写入到新 MemoryStream ,我们可以使用 Read 方法读取部件中的每个元素。 当读取每个元素时,我们检查如果它是类型Text,如果是,则使用 <xrefDocumentFormat.OpenXml.OpenXmlReader.GetText*> 方法来访问文本并使用 Replace 更新文本。 如果它不是 Text 元素,则我们会将它写入流中,并保持不变。

注意

在Word文档文本可以分为多个Text元素,因此如果要替换一个短语而不是一个单词,则最好一次替换一个单词。

// Write the XML declaration with the version "1.0".
writer.WriteStartDocument();

// Read the elements from the MainDocumentPart
while (reader.Read())
{
    // Check if the element is of type Text
    if (reader.ElementType == typeof(Text))
    {
        // If it is the start of an element write the start element and the updated text
        if (reader.IsStartElement)
        {
            writer.WriteStartElement(reader);

            string text = reader.GetText().Replace(textToReplace, replacementText);

            writer.WriteString(text);

        }
        else
        {
            // Close the element
            writer.WriteEndElement();
        }
    }
    else
    // Write the other XML elements without editing
    {
        if (reader.IsStartElement)
        {
            writer.WriteStartElement(reader);
        }
        else if (reader.IsEndElement)
        {
            writer.WriteEndElement();
        }
    }
}

将新Stream写入 MainDocumentPart

将更新的部件写入内存流后,最后一步是将 的位置设置为 MemoryStream0,并使用 FeedData 方法将 替换为 MainDocumentPart 更新的流。

// Set the MemoryStream's position to 0 and replace the MainDocumentPart
memoryStream.Position = 0;
mainDocumentPart.FeedData(memoryStream);

示例代码

下面是使用 SAX (Simple API for XML) 方法替换Word文档中文本的完整示例代码。

void ReplaceTextWithSAX(string path, string textToReplace, string replacementText)
{
    // Open the WordprocessingDocument for editing
    using (WordprocessingDocument wordprocessingDocument = WordprocessingDocument.Open(path, true))
    {
        // Access the MainDocumentPart and make sure it is not null
        MainDocumentPart? mainDocumentPart = wordprocessingDocument.MainDocumentPart;

        if (mainDocumentPart is not null)
        {
            // Create a MemoryStream to store the updated MainDocumentPart
            using (MemoryStream memoryStream = new MemoryStream())
            {
                // Create an OpenXmlReader to read the main document part
                // and an OpenXmlWriter to write to the MemoryStream
                using (OpenXmlReader reader = OpenXmlPartReader.Create(mainDocumentPart))
                using (OpenXmlWriter writer = OpenXmlPartWriter.Create(memoryStream))
                {
                    // Write the XML declaration with the version "1.0".
                    writer.WriteStartDocument();
                    
                    // Read the elements from the MainDocumentPart
                    while (reader.Read())
                    {
                        // Check if the element is of type Text
                        if (reader.ElementType == typeof(Text))
                        {
                            // If it is the start of an element write the start element and the updated text
                            if (reader.IsStartElement)
                            {
                                writer.WriteStartElement(reader);

                                string text = reader.GetText().Replace(textToReplace, replacementText);

                                writer.WriteString(text);

                            }
                            else
                            {
                                // Close the element
                                writer.WriteEndElement();
                            }
                        }
                        else
                        // Write the other XML elements without editing
                        {
                            if (reader.IsStartElement)
                            {
                                writer.WriteStartElement(reader);
                            }
                            else if (reader.IsEndElement)
                            {
                                writer.WriteEndElement();
                            }
                        }
                    }
                }
                // Set the MemoryStream's position to 0 and replace the MainDocumentPart
                memoryStream.Position = 0;
                mainDocumentPart.FeedData(memoryStream);
            }
        }
    }
}