使用 Open XML SDK 2.0 删除 Word 2010 文档中所有作者或特定作者所做的注释

Office 可视操作方法

**摘要:**在 Open XML SDK 2.0 中使用强类型类可从 Word 文档中删除所有作者或特定作者所做的注释,而无需将该文档加载到 Microsoft Word 中。

上次修改时间: 2015年3月9日

适用范围: Excel 2010 | Office 2010 | Open XML | PowerPoint 2010 | VBA | Word 2010

**发布时间:**2011 年 1 月

**供稿人:**Ken Getz,MCW Technologies, LLC(该链接可能指向英文页面)

概述

虽然可利用 Open XML 文件格式删除 Microsoft Word 文档中的注释,但执行此操作需要做一些工作。Open XML SDK 2.0 添加了可授予对 Open XML 文件格式的访问权的强类型类。这样一来,SDK 将简化检索注释列表并删除注释这一任务。此直观操作方法附带的代码示例介绍如何使用 SDK 来实现这个目标。

编码

此直观操作方法附带的示例包括从 Word 2007 或 Word 2010 文档中删除一个作者或所有作者所做的注释所需的代码。以下各节详细说明了代码。

设置引用

若要使用 Open XML SDK 2.0 中的代码,您必须向您的项目添加两个引用。虽然示例项目已包含这些引用,但您必须在您的代码中显式引用以下程序集:

  • WindowsBase - 可以根据您创建的项目的类型为您设置此引用。

  • DocumentFormat.OpenXml - 由 Open XML SDK 2.0 安装。

您还应将下面的 using/Imports 语句添加到代码文件的顶部。

Imports DocumentFormat.OpenXml.Packaging
Imports DocumentFormat.OpenXml.Wordprocessing
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;

检查过程

WDDeleteComments 过程接受两个字符串参数:要修改的文档的名称和(可选)要删除其注释的作者的姓名。如果您提供一个作者姓名,则代码将删除该指定作者所做的注释;反之,代码将删除所有注释。

Public Sub WDDeleteComments(
 ByVal fileName As String, Optional ByVal author As String = "")
public static void WDDeleteComments(
  string fileName, string author = "")

此过程将通过删除文档中的所有注释或仅删除特定作者所做的注释来修改您指定的文档。若要调用此过程,请传递参数值,如示例代码中所示。在此示例中,确认您的计算机上有一个名为 C:\temp\Comments.docx 的文档。在您运行示例代码之前,还请确保该文件至少包含一个注释。

下面的示例描述如何调用 WDDeleteComments 来删除指定文档中的所有注释。若要将结果限制为特定作者所做的注释,请将该作者的姓名作为第二个参数添加。

WDDeleteComments("C:\temp\comments.docx")
WDDeleteComments(@"C:\temp\comments.docx");

访问文档

代码首先会使用 [WordprocessingDocument.Open] 方法打开文档。在将最终的 True 参数设置为 true 的情况下,此调用将指示应打开文档以供读写访问。接下来,代码将检索对字处理文档的 [MainDocumentPart.WordprocessingCommentsPart] 属性的引用。如果缺少注释部分,则没有要删除的注释,因此将无需继续。

Using document As WordprocessingDocument = WordprocessingDocument.Open(fileName, True)
  ' Set commentPart to the document WordprocessingCommentsPart, 
  ' if it exists.
  Dim commentPart As WordprocessingCommentsPart =
    document.MainDocumentPart.WordprocessingCommentsPart

  ' If no WordprocessingCommentsPart exists, there can be no comments. 
  ' Stop execution and return from the method.
  If (commentPart Is Nothing) Then
    Return
  End If
  ' Code removed here…
End Using
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 对象的列表。

Dim commentsToDelete As List(Of Comment) = _
 commentPart.Comments.Elements(Of Comment)().ToList()
List<Comment> commentsToDelete =
  commentPart.Comments.Elements<Comment>().ToList();

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

If Not String.IsNullOrEmpty(author) Then
  commentsToDelete = commentsToDelete.
   Where(Function(c) c.Author = author).ToList()
End If
if (!String.IsNullOrEmpty(author))
{
  commentsToDelete = commentsToDelete.
    Where(c => c.Author == author).ToList();
}

在删除任何注释之前,代码将检索注释 ID 值的列表。之后,代码将使用这些值来删除文档部分中的匹配元素。调用 Select 方法可有效计划注释列表,并检索包含所有注释 ID 值的 Strings 的 IEnumerable。

Dim commentIds As IEnumerable(Of String) =
  commentsToDelete.Select(Function(r) r.Id.Value)
IEnumerable<string> commentIds = 
  commentsToDelete.Select(r => r.Id.Value);

删除注释并保存部分

在给定 commentsToDelete 集合的情况下,代码可轻松循环访问需要删除的所有注释并执行删除。然后,代码将保存注释部分。

For Each c As Comment In commentsToDelete
  c.Remove()
Next
' Save comment part change.
commentPart.Comments.Save()
foreach (Comment c in commentsToDelete)
{
  c.Remove();
}
// Save comment part change.
commentPart.Comments.Save();

删除文档中的注释引用

尽管代码现已成功删除所有注释,但仍需执行其他操作:代码还必须删除对文档部分中的注释的引用。此操作包含三个步骤:注释引用包括 [CommentRangeStart][CommentRangeEnd][CommentReference] 元素,并且代码必须为每个注释删除这三个元素。

在执行任何删除操作之前,代码会先检索对文档部分本身的引用。

Dim doc As Document = document.MainDocumentPart.Document
Document doc = document.MainDocumentPart.Document;

在假定存在对文档的引用的情况下,代码可反复执行三次删除操作,每次操作均针对不同的元素。在每种情况下,代码都会查找正确类型的所有后代:[CommentRangeStart][CommentRangeEnd][CommentReference]。然后,代码会限制列表仅包含其 [Id.Value] 属性包含在要删除的注释 ID 列表中的后代。在给定要删除的元素列表的情况下,代码会依次删除每个元素。

Dim commentRangeStartToDelete As List(Of CommentRangeStart) = _
  doc.Descendants(Of CommentRangeStart). _
  Where(Function(c) commentIds.Contains(c.Id.Value)).ToList()
For Each c As CommentRangeStart In commentRangeStartToDelete
  c.Remove()
Next

' Delete CommentRangeEnd for each deleted comment within main document.
Dim commentRangeEndToDelete As List(Of CommentRangeEnd) = _
 doc.Descendants(Of CommentRangeEnd). _
 Where(Function(c) commentIds.Contains(c.Id.Value)).ToList()
For Each c As CommentRangeEnd In commentRangeEndToDelete
  c.Remove()
Next

' Delete CommentReference within main document.
Dim commentRangeReferenceToDelete As List(Of CommentReference) = _
 doc.Descendants(Of CommentReference). _
 Where(Function(c) commentIds.Contains(c.Id.Value)).ToList
For Each c As CommentReference In commentRangeReferenceToDelete
  c.Remove()
Next
// Delete CommentRangeStart within 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 within 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 within main document.
List<CommentReference> commentRangeReferenceToDelete =
  doc.Descendants<CommentReference>().
  Where(c => commentIds.Contains(c.Id.Value)).ToList();
foreach (CommentReference c in commentRangeReferenceToDelete)
{
  c.Remove();
}

代码最后会保存文档。

doc.Save()
doc.Save();

示例过程

下面的代码示例包含完整的示例过程。

Public Sub WDDeleteComments(ByVal fileName As String,
                            Optional ByVal author As String = "")

  ' Get an existing Wordprocessing document.
  Using document As WordprocessingDocument =
    WordprocessingDocument.Open(fileName, True)
    ' Set commentPart to the document 
    ' WordprocessingCommentsPart, if it exists.
    Dim commentPart As WordprocessingCommentsPart =
      document.MainDocumentPart.WordprocessingCommentsPart

    ' If no WordprocessingCommentsPart exists, there can be no comments. 
    ' Stop execution and return from the method.
    If (commentPart Is Nothing) Then
      Return
    End If

    ' Create a list of comments by the specified author, or
    ' if the author name is empty, all authors.
    Dim commentsToDelete As List(Of Comment) = _
     commentPart.Comments.Elements(Of Comment)().ToList()
    If Not String.IsNullOrEmpty(author) Then
      commentsToDelete = commentsToDelete.
        Where(Function(c) c.Author = author).ToList()
    End If
    Dim commentIds As IEnumerable(Of String) =
      commentsToDelete.Select(Function(r) r.Id.Value)

    ' Delete each comment in commentToDelete from the Comments collection
    For Each c As Comment In commentsToDelete
      c.Remove()
    Next

    ' Save comment part change.
    commentPart.Comments.Save()

    Dim doc As Document = document.MainDocumentPart.Document

    ' Delete the CommentRangeStart for each 
    ' deleted comment within main document.
    Dim commentRangeStartToDelete As List(Of CommentRangeStart) = _
      doc.Descendants(Of CommentRangeStart). _
      Where(Function(c) commentIds.Contains(c.Id.Value)).ToList()
    For Each c As CommentRangeStart In commentRangeStartToDelete
      c.Remove()
    Next

    ' Delete CommentRangeEnd for each deleted comment within main document.
    Dim commentRangeEndToDelete As List(Of CommentRangeEnd) = _
     doc.Descendants(Of CommentRangeEnd). _
     Where(Function(c) commentIds.Contains(c.Id.Value)).ToList()
    For Each c As CommentRangeEnd In commentRangeEndToDelete
      c.Remove()
    Next

    ' Delete CommentReference within main document.
    Dim commentRangeReferenceToDelete As List(Of CommentReference) = _
     doc.Descendants(Of CommentReference). _
     Where(Function(c) commentIds.Contains(c.Id.Value)).ToList
    For Each c As CommentReference In commentRangeReferenceToDelete
      c.Remove()
    Next

    ' Save main document.
    doc.Save()
  End Using
End Sub
public static void WDDeleteComments(string fileName, 
  string author = "")
{
  // 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;
    }

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

    // Create a list of comments by the specified author.
    if (!String.IsNullOrEmpty(author))
    {
      commentsToDelete = commentsToDelete.
        Where(c => c.Author == author).ToList();
    }
    IEnumerable<string> commentIds = 
      commentsToDelete.Select(r => r.Id.Value);

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

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

    Document doc = document.MainDocumentPart.Document;

    // Delete CommentRangeStart within 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 within 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 within 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();
  }
}
读取

此直观操作方法附带的示例描述了一些代码,这些代码将删除 Word 文档中的注释。若要使用该示例,必须安装 Open XML SDK 2.0(可通过"浏览"一节中列出的链接获得)。该示例还将使用作为 Open XML SDK 2.0 代码示例集的一部分包含的修改后的代码。"浏览"一节还包括指向完整代码示例集的链接,但您无需下载并安装代码示例即可使用该示例。

示例应用程序仅演示了在修改文档结构时可与之交互的由 Open XML SDK 2.0 提供的一些可用属性和方法。有关详细信息,请查看 Open XML SDK 2.0 Productivity Tool 附带的文档:单击应用程序窗口左下角的"Open XML SDK 文档"选项卡,并搜索要研究的类。尽管该文档当前不包含代码示例,但借助此处所示的示例和文档,您应能成功修改示例应用程序。

观看

观看视频

观看视频(该链接可能指向英文页面) | 时长:00:20:29

单击以获取代码

获取代码(该链接可能指向英文页面)

浏览

关于作者
Ken Getz 是 MCW Technologies 的高级顾问。他是 ASP.NET Developers Jumpstart(《ASP.NET 开发人员入门》,Addison-Wesley,2002)、Access Developer's Handbook(《Access 开发人员手册》,Sybex,2001)和 VBA Developer's Handbook, 2nd Edition(《VBA 开发人员手册第 2 版》,Sybex,2001)的合著者。