按所有或特定作者删除字处理文档中的注释

本主题演示如何使用 Open XML SDK for Office 中的类以编程方式删除所有或特定作者在字处理文档中的批注,而无需将文档加载到 Microsoft Word。 本主题包含一个演示此任务的示例 DeleteComments 方法。


DeleteComments 方法

您可以使用 DeleteComments 方法从字处理文档中删除所有注释,或仅删除某个特定作者编写的注释。 如下面的代码所示,该方法接受两个参数:一个指示要修改的文档的名称(字符串),(可选)一个指示要删除其注释的作者的姓名(字符串)。 如果您提供作者姓名,那么代码将删除指定作者编写的注释。 如果您不提供作者姓名,代码将删除所有注释。

    // Delete comments by a specific author. Pass an empty string for the 
    // author to delete all comments, by all authors.
    public static void DeleteComments(string fileName, 
        string author = "")

调用 DeleteComments 方法

若要调用 DeleteComments 方法,请提供所需参数,如下面的代码所示。

    DeleteComments(@"C:\Users\Public\Documents\DeleteComments.docx",
    "David Jones");

代码的工作方式

下面的代码首先使用 WordprocessingDocument.Open 方法打开文档,并指示应打开文档供读/写访问(最后的 true 参数值)。 接下来,代码在从字处理文档的 MainDocumentPart 属性检索对 main 文档部件的引用后,使用 main 文档部件的 WordprocessingCommentsPart 属性检索对注释部件的引用。 如果注释部件缺失,则无需继续处理,因为没有可删除的注释。

    // Get an existing Wordprocessing document.
    using (WordprocessingDocument document =
      WordprocessingDocument.Open(fileName, true))
    {
        // Set commentPart to the document WordprocessingCommentsPart, 
        // if it exists.
        WordprocessingCommentsPart commentPart =
          document.MainDocumentPart.WordprocessingCommentsPart;

        // If no WordprocessingCommentsPart exists, there can be no 
        // comments. Stop execution and return from the method.
        if (commentPart == null)
        {
            return;
        }
        // Code removed here…
    }

创建注释列表

接下来,代码将执行两个任务:创建要删除的所有注释的列表,以及创建与要删除的批注对应的注释 ID 列表。 给定这些列表,代码既可以从包含注释的注释部件中删除注释,也可以从文档部件中删除对批注的引用。以下代码首先检索 Comment 元素列表。 为了检索列表,它将 commentPart 变量公开的 Elements 集合转换为 Comment 对象的列表。

    List<Comment> commentsToDelete =
        commentPart.Comments.Elements<Comment>().ToList();

到目前为止,注释列表已包含所有注释。 如果 Author 参数不是空字符串,则下面的代码会将列表限制为仅包括其 Author 属性与您提供的参数匹配的那些注释。

    if (!String.IsNullOrEmpty(author))
    {
        commentsToDelete = commentsToDelete.
        Where(c => c.Author == author).ToList();
    }

在删除任何注释之前,代码将检索注释 ID 值的列表,以便稍后代码从文档部件中删除匹配的元素。 对 Select 方法的调用有效地投影注释列表,检索包含所有注释 ID 值的字符串的 IEnumerable<T>

    IEnumerable<string> commentIds = 
        commentsToDelete.Select(r => r.Id.Value);

删除注释并保存部分

获得 commentsToDelete 集合后,下面的代码循环访问所有需要删除的注释,并执行删除。 然后代码保存注释部件。

    // Delete each comment in commentToDelete from the 
    // Comments collection.
    foreach (Comment c in commentsToDelete)
    {
        c.Remove();
    }

    // Save the comment part changes.
    commentPart.Comments.Save();

删除文档中的注释引用

尽管此时代码已成功删除了所有注释,但是这还不够。 代码还必须从文档部件中删除对注释的引用。 此操作需要三个步骤,因为注释引用包括 CommentRangeStartCommentRangeEndCommentReference 元素,并且代码必须删除每个注释的所有三个步骤。 在执行任何删除之前,代码首先检索对主文档部件根元素的引用,如下面的代码所示。

    Document doc = document.MainDocumentPart.Document;

获得对文档元素的引用后,下面的代码反复执行三次删除操作,对必须删除的每个不同元素分别执行一次。 在每种情况下,代码都会查找 (CommentRangeStartCommentRangeEndCommentReference) 的正确类型的所有后代,并将列表限制为 Id 属性值包含在 要删除的注释 ID 列表中。 获得要删除的元素列表后,代码会依次删除每个元素。 最后,代码保存文档并随即结束。

    // Delete CommentRangeStart for each
    // deleted comment in the main document.
    List<CommentRangeStart> commentRangeStartToDelete =
        doc.Descendants<CommentRangeStart>().
        Where(c => commentIds.Contains(c.Id.Value)).ToList();
    foreach (CommentRangeStart c in commentRangeStartToDelete)
    {
        c.Remove();
    }

    // Delete CommentRangeEnd for each deleted comment in the main document.
    List<CommentRangeEnd> commentRangeEndToDelete =
        doc.Descendants<CommentRangeEnd>().
        Where(c => commentIds.Contains(c.Id.Value)).ToList();
    foreach (CommentRangeEnd c in commentRangeEndToDelete)
    {
        c.Remove();
    }

    // Delete CommentReference for each deleted comment in the main document.
    List<CommentReference> commentRangeReferenceToDelete =
        doc.Descendants<CommentReference>().
        Where(c => commentIds.Contains(c.Id.Value)).ToList();
    foreach (CommentReference c in commentRangeReferenceToDelete)
    {
        c.Remove();
    }

    // Save changes back to the MainDocumentPart part.
    doc.Save();

示例代码

以下是使用 C# 和 Visual Basic 编写的完整示例代码。

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

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

// Delete comments by a specific author. Pass an empty string for the 
// author to delete all comments, by all authors.
static void DeleteComments(string fileName, string author = "")
{
    // Get an existing Wordprocessing document.
    using (WordprocessingDocument document = WordprocessingDocument.Open(fileName, true))
    {

        if (document.MainDocumentPart is null || document.MainDocumentPart.WordprocessingCommentsPart is null)
        {
            throw new ArgumentNullException("MainDocumentPart and/or WordprocessingCommentsPart is null.");
        }

        // Set commentPart to the document WordprocessingCommentsPart, 
        // if it exists.
        WordprocessingCommentsPart commentPart =
            document.MainDocumentPart.WordprocessingCommentsPart;

        // If no WordprocessingCommentsPart exists, there can be no 
        // comments. Stop execution and return from the method.
        if (commentPart is null)
        {
            return;
        }

        // Create a list of comments by the specified author, or
        // if the author name is empty, all authors.
        List<Comment> commentsToDelete =
            commentPart.Comments.Elements<Comment>().ToList();
        if (!String.IsNullOrEmpty(author))
        {
            commentsToDelete = commentsToDelete.
            Where(c => c.Author == author).ToList();
        }
        IEnumerable<string?> commentIds =
            commentsToDelete.Where(r => r.Id is not null && r.Id.HasValue).Select(r => r.Id?.Value);

        // Delete each comment in commentToDelete from the 
        // Comments collection.
        foreach (Comment c in commentsToDelete)
        {
            c.Remove();
        }

        // Save the comment part change.
        commentPart.Comments.Save();

        Document doc = document.MainDocumentPart.Document;

        // Delete CommentRangeStart for each
        // deleted comment in the main document.
        List<CommentRangeStart> commentRangeStartToDelete =
            doc.Descendants<CommentRangeStart>().
            Where(c => c.Id is not null && c.Id.HasValue && commentIds.Contains(c.Id.Value)).ToList();
        foreach (CommentRangeStart c in commentRangeStartToDelete)
        {
            c.Remove();
        }

        // Delete CommentRangeEnd for each deleted comment in the main document.
        List<CommentRangeEnd> commentRangeEndToDelete =
            doc.Descendants<CommentRangeEnd>().
            Where(c => c.Id is not null && c.Id.HasValue && commentIds.Contains(c.Id.Value)).ToList();
        foreach (CommentRangeEnd c in commentRangeEndToDelete)
        {
            c.Remove();
        }

        // Delete CommentReference for each deleted comment in the main document.
        List<CommentReference> commentRangeReferenceToDelete =
            doc.Descendants<CommentReference>().
            Where(c => c.Id is not null && c.Id.HasValue && commentIds.Contains(c.Id.Value)).ToList();
        foreach (CommentReference c in commentRangeReferenceToDelete)
        {
            c.Remove();
        }

        // Save changes back to the MainDocumentPart part.
        doc.Save();
    }
}

另请参阅