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

本主题演示如何使用 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.
static void DeleteComments(string fileName, string author = "")

调用 DeleteComments 方法

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

if (args is [{ } fileName, { } author])
{
    DeleteComments(fileName, author);
}
else if (args is [{ } fileName2])
{
    DeleteComments(fileName2);
}

代码的工作方式

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

// Get an existing Wordprocessing document.
using (WordprocessingDocument document = WordprocessingDocument.Open(fileName, true))
{

    if (document.MainDocumentPart is null)
    {
        throw new ArgumentNullException("MainDocumentPart 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;
    }

创建注释列表

接下来,代码将执行两个任务:创建要删除的所有注释的列表,以及创建与要删除的批注对应的注释 ID 列表。 给定这些列表,代码既可以从包含注释的注释部件中删除注释,也可以从文档部件中删除对批注的引用。以下代码首先检索元素列表 Comment 。 为了检索列表,它将变量公开的Elements()commentPart集合转换为对象的列表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 调用有效地投影注释列表,检索 IEnumerable<T> 包含所有注释 ID 值的字符串的 。

IEnumerable<string?> commentIds = commentsToDelete.Where(r => r.Id is not null && r.Id.HasValue).Select(r => r.Id?.Value);

删除注释并保存部分

给定集合 commentsToDelete 后,以下代码将遍历需要删除的所有注释并执行删除。

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

删除文档中的注释引用

尽管此时代码已成功删除了所有注释,但是这还不够。 代码还必须从文档部件中删除对注释的引用。 此操作需要三个步骤,因为注释引用包括 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 => 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();
}

示例代码

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

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


// 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)
        {
            throw new ArgumentNullException("MainDocumentPart 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)
        {
            if (c is not null)
            {
                c.Remove();
            }
        }

        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();
        }
    }
}

另请参阅