接受字处理文档中的所有修订

本主题演示如何使用 Open XML SDK for Office 以编程方式接受字处理文档中的所有修订。

WordProcessingML 文档的结构

WordProcessingML 文档的基本文档结构由 documentbody 元素组成,后跟一个或多个块级元素,如表示段落的 p。 一个段落包含一个或多个 r 元素。 r 代表一段连续文本,它是具有一组共同属性(如格式设置)的文本区域。 一段连续文本包含一个或多个 t 元素。 t 元素包含一个文本区域。 以下代码示例显示包含文本"Example text."的文档的 WordprocessingML 标记。

    <w:document xmlns:w="https://schemas.openxmlformats.org/wordprocessingml/2006/main">
      <w:body>
        <w:p>
          <w:r>
            <w:t>Example text.</w:t>
          </w:r>
        </w:p>
      </w:body>
    </w:document>

使用 Open XML SDK,可以使用与 WordprocessingML 元素对应的强类型类创建文档结构和内容。 这些类可在 DocumentFormat.OpenXml.Wordprocessing 命名空间中找到。 下表列出了 documentbodyprt 元素所对应类的类名称。

WordprocessingML 元素 Open XML SDK 类 说明
document Document 主文档部件的根元素
body Body 块级结构(如段落、表格、批注和 ISO/IEC 29500 规范中指定的其他项)的容器。
p Paragraph 段落。
r Run 一段连续文本。
t Text 文本范围。

有关 WordprocessingML 文档的各个部分和元素的整体结构的详细信息,请参阅 WordprocessingML 文档的结构

WordProcessingML 文档的基本文档结构由 documentbody 元素组成,后跟一个或多个块级元素,如表示段落的 p。 一个段落包含一个或多个 r 元素。 r 代表一段连续文本,它是具有一组共同属性(如格式设置)的文本区域。 一段连续文本包含一个或多个 t 元素。 t 元素包含一个文本区域。 以下代码示例显示包含文本"Example text."的文档的 WordprocessingML 标记。

    <w:document xmlns:w="https://schemas.openxmlformats.org/wordprocessingml/2006/main">
      <w:body>
        <w:p>
          <w:r>
            <w:t>Example text.</w:t>
          </w:r>
        </w:p>
      </w:body>
    </w:document>

使用 Open XML SDK,可以使用与 WordprocessingML 元素对应的强类型类创建文档结构和内容。 这些类可在 DocumentFormat.OpenXml.Wordprocessing 命名空间中找到。 下表列出了 documentbodyprt 元素所对应类的类名称。

WordprocessingML 元素 Open XML SDK 类 说明
document Document 主文档部件的根元素
body Body 块级结构(如段落、表格、批注和 ISO/IEC 29500 规范中指定的其他项)的容器。
p Paragraph 段落。
r Run 一段连续文本。
t Text 文本范围。

ParagraphPropertiesChange 元素

在接受修订标记时,会通过删除现有文本或插入新文本来更改段落的属性。 以下部分介绍了代码中用于更改段落内容的三个元素,主要 <w: pPrChange\> (段落属性) 的修订信息、 <w:del> (Deleted Paragraph) 和 <w:ins> (Inserted Table Row) 元素。

ISO/IEC 29500 规范中的以下信息介绍了 ParagraphPropertiesChange 元素 (pPrChange) 。

*pPrChange (段落属性的修订信息)

此元素指定 WordprocessingML 文档中一组段落属性的单处修订的详细信息。

此元素按如下方式存储此修订:

  • 此元素的子元素包含此修订之前应用于此段落的完整段落属性集。

  • 此元素的属性包含有关此修订发生时间(换句话说,这些段落属性何时成为"先前的"段落属性集)的信息。

假设对 WordprocessingML 文档中的一个段落设置了居中格式,并且将此段落属性更改作为修订进行跟踪。 那么,将使用以下 WordprocessingML 标记指定此修订。

    <w:pPr>
      <w:jc w:val="center"/>
      <w:pPrChange w:id="0" w:date="01-01-2006T12:00:00" w:author="Samantha Smith">
        <w:pPr/>
      </w:pPrChange>
    </w:pPr>

元素指定 Samantha Smith 在 01-01-2006 对段落属性进行了修订,而该段落的上一组段落属性是 null 集 (换句话说,元素) 下没有显式存在的段落属性。 pPrpPrChange

© ISO/IEC29500: 2008.

Deleted 元素

ISO/IEC 29500 规范中的以下信息介绍了 deleted 元素 (del) 。

del(删除的段落标记)

此元素表明,应将 WordprocessingML 文档中界定段落结束位置的段落标记视为视为已作为跟踪修订的一部分删除(换句话说,此段落的内容不再由此段落标记界定,而是与下一段落合并在一起 — 但是不应将这些内容自动标记为已删除)。

假设存在一个由两个段落组成(每个段落由一个段落标记 ¶ 界定)的文档:

两个段落,每个段落由 pilcrow 分隔 如果删除了分隔第一段末尾的物理字符,并且此更改被跟踪为修订,则会出现以下结果:

由单个 pilcrow 分隔的两个段落 此修订使用以下 WordprocessingML 表示:

    <w:p>
      <w:pPr>
        <w:rPr>
          <w:del w:id="0" … />
        </w:rPr>
      </w:pPr>
      <w:r>
        <w:t>This is paragraph one.</w:t>
      </w:r>
    </w:p>
    <w:p>
      <w:r>
        <w:t>This is paragraph two.</w:t>
      </w:r>
    </w:p>

第一个段落标记的连续文本属性的 del 元素指定此段落标记已删除,并且已将此删除作为修订进行跟踪。

© ISO/IEC29500: 2008.

Inserted 元素

ISO/IEC 29500 规范中的以下信息介绍了 inserted 元素 (ins) 。

ins(插入的表格行)

此元素指定应将父表格行视为插入的行,并且已将该插入操作作为修订进行跟踪。 此设置不应表示有关此行中的表格单元格或其内容的任何修订状态(它们必须通过单独的修订标记表示),并且应该仅影响表格行本身。

假设存在一个包含两行两列的表格,并且已使用修订将第二行标记为已插入。 此要求将使用以下 WordprocessingML 指定:

    <w:tbl>
      <w:tr>
        <w:tc>
          <w:p/>
        </w:tc>
        <w:tc>
          <w:p/>
        </w:tc>
      </w:tr>
      <w:tr>
        <w:trPr>
          <w:ins w:id="0" … />
        </w:trPr>
        <w:tc>
          <w:p/>
        </w:tc>
        <w:tc>
          <w:p/>
        </w:tc>
      </w:tr>
    </w:tbl>

第二个表格行的表格行属性的 ins 元素表明,此行是插入行,并且已将此插入操作作为修订进行跟踪。

© ISO/IEC29500: 2008.

示例代码

以下代码示例演示如何接受字处理文档中的全部修订。 若要运行程序,可以传入文件路径和作者名称:

dotnet run -- [filePath] [authorName]

运行程序后,打开字处理文件以确保已接受所有修订标记。

using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using System;
using System.Collections.Generic;
using System.Linq;

AcceptAllRevisions(args[0], args[1]);

static void AcceptAllRevisions(string fileName, string authorName)
{
    using (WordprocessingDocument wdDoc = WordprocessingDocument.Open(fileName, true))
    {
        if (wdDoc.MainDocumentPart is null || wdDoc.MainDocumentPart.Document.Body is null)
        {
            throw new ArgumentNullException("MainDocumentPart and/or Body is null.");
        }

        Body body = wdDoc.MainDocumentPart.Document.Body;

        // Handle the formatting changes.
        List<OpenXmlElement> changes = body.Descendants<ParagraphPropertiesChange>()
            .Where(c => c.Author is not null && c.Author.Value == authorName).Cast<OpenXmlElement>().ToList();

        foreach (OpenXmlElement change in changes)
        {
            change.Remove();
        }

        // Handle the deletions.
        List<OpenXmlElement> deletions = body
            .Descendants<Deleted>()
            .Where(c => c.Author is not null && c.Author.Value == authorName)
            .Cast<OpenXmlElement>().ToList();

        deletions.AddRange(body.Descendants<DeletedRun>()
            .Where(c => c.Author is not null && c.Author.Value == authorName).Cast<OpenXmlElement>().ToList());

        deletions.AddRange(body.Descendants<DeletedMathControl>()
            .Where(c => c.Author is not null && c.Author.Value == authorName).Cast<OpenXmlElement>().ToList());

        foreach (OpenXmlElement deletion in deletions)
        {
            deletion.Remove();
        }

        // Handle the insertions.
        List<OpenXmlElement> insertions =
            body.Descendants<Inserted>()
            .Where(c => c.Author is not null && c.Author.Value == authorName).Cast<OpenXmlElement>().ToList();

        insertions.AddRange(body.Descendants<InsertedRun>()
            .Where(c => c.Author is not null && c.Author.Value == authorName).Cast<OpenXmlElement>().ToList());

        insertions.AddRange(body.Descendants<InsertedMathControl>()
            .Where(c => c.Author is not null && c.Author.Value == authorName).Cast<OpenXmlElement>().ToList());

        foreach (OpenXmlElement insertion in insertions)
        {
            // Found new content.
            // Promote them to the same level as node, and then delete the node.
            foreach (var run in insertion.Elements<Run>())
            {
                if (run == insertion.FirstChild)
                {
                    insertion.InsertAfterSelf(new Run(run.OuterXml));
                }
                else
                {
                    OpenXmlElement nextSibling = insertion.NextSibling()!;
                    nextSibling.InsertAfterSelf(new Run(run.OuterXml));
                }
            }

            insertion.RemoveAttribute("rsidR", "https://schemas.openxmlformats.org/wordprocessingml/2006/main");
            insertion.RemoveAttribute("rsidRPr", "https://schemas.openxmlformats.org/wordprocessingml/2006/main");
            insertion.Remove();
        }
    }
}