按作者从演示文稿中的所有幻灯片中删除注释

本主题演示如何使用 Open XML SDK for Office 中的类以编程方式删除演示文稿中特定作者的所有注释。

注意

此示例适用于 PowerPoint 新式注释。 有关经典注释,请查看 GitHub 上的存档示例

获取 PresentationDocument 对象

在 Open XML SDK 中 PresentationDocument , 类表示演示文稿文档包。 若要处理演示文稿文档,请先创建 类的 PresentationDocument 实例,然后使用该实例。 若要从文档创建类实例, Open 请调用使用文件路径的方法,并使用布尔值作为第二个参数来指定文档是否可编辑。 若要打开文档进行读/写,请指定此参数的值 true ,如以下 using 语句所示。 在该代码中,fileName 参数是一个字符串,表示要从中打开该文档的文件的路径,author 是“PowerPoint 选项”的“常规”选项卡中显示的用户名。

using (PresentationDocument doc = PresentationDocument.Open(fileName, true))

在 v3.0.0+ 中, Close() 已删除 方法,转而依赖于 using 语句。 这可确保在 Dispose() 到达右大括号时自动调用 方法。 语句后面的 using 块为在 语句中创建 using 或命名的对象建立作用域,在本例 doc中为 。

基本演示文稿文档结构

文档的基本文档结构PresentationML由多个部分组成,其中包括包含表示定义的main部分。 ISO/IEC 29500 规范中的以下文本介绍了包的整体PresentationML形式。

包的main部分PresentationML以演示文稿根元素开头。 该元素包含演示文稿,演示文稿又引用幻灯片 列表、幻灯片母版 列表、备注母版 列表和讲义母版 列表。 幻灯片列表指的是演示文稿中的所有幻灯片;幻灯片母版列表指的是演示文稿中使用的全部幻灯片母版;备注母版包含有关备注页格式的信息;讲义母版描述讲义的外观。

讲义 是可提供给访问群体 的一组打印的幻灯片。

除了文本和图形,每个幻灯片还可以包含注释备注,可以具有布局,并且可以是一个或多个自定义演示文稿 的组成部分。 注释是供维护演示文稿幻灯片平台的人员参考的批注。 备注是供演示者或访问群体参考的提醒信息或一段文字。

文档可以包括以下其他功能 PresentationML动画音频视频和幻灯片之间的 切换 效果。

文档 PresentationML 不作为一个大正文存储在单个部件中。 而实现某些功能组合的元素会存储在各个部件中。 例如,文档中的所有作者都存储在一个作者部件中,而每个幻灯片都有自己的部分。

ISO/IEC 29500:2016

以下 XML 代码示例代表包含用 ID 267 和 256 表示的两个幻灯片的演示文稿。

    <p:presentation xmlns:p="…" … > 
       <p:sldMasterIdLst>
          <p:sldMasterId
             xmlns:rel="https://…/relationships" rel:id="rId1"/>
       </p:sldMasterIdLst>
       <p:notesMasterIdLst>
          <p:notesMasterId
             xmlns:rel="https://…/relationships" rel:id="rId4"/>
       </p:notesMasterIdLst>
       <p:handoutMasterIdLst>
          <p:handoutMasterId
             xmlns:rel="https://…/relationships" rel:id="rId5"/>
       </p:handoutMasterIdLst>
       <p:sldIdLst>
          <p:sldId id="267"
             xmlns:rel="https://…/relationships" rel:id="rId2"/>
          <p:sldId id="256"
             xmlns:rel="https://…/relationships" rel:id="rId3"/>
       </p:sldIdLst>
           <p:sldSz cx="9144000" cy="6858000"/>
       <p:notesSz cx="6858000" cy="9144000"/>
    </p:presentation>

使用 Open XML SDK,可以使用对应于 PresentationML 元素的强类型类创建文档结构和内容。 可以在 命名空间中找到 DocumentFormat.OpenXml.Presentation 这些类。 下表列出了对应于 、、 sldLayoutsldMasternotesMaster 元素的类的sld类名。

PresentationML 元素 Open XML SDK 类 说明
<sld/> Slide 演示文稿幻灯片。 它是 SlidePart 的根元素。
<sldLayout/> SlideLayout 幻灯片版式。 它是 SlideLayoutPart 的根元素。
<sldMaster/> SlideMaster 幻灯片母版。 它是 SlideMasterPart 的根元素。
<notesMaster/> NotesMaster 备注母版(或讲义母版)。 它是 NotesMasterPart 的根元素。

Comment 元素的结构

ISO/IEC 29500(该链接可能指向英文页面) 规范中的以下文本介绍了演示文稿包中的注释。

注释是附加到幻灯片的文本备注,主要用途是使演示文稿的读者能够向演示文稿作者提供反馈。 每个注释包含无格式文本字符串及有关其作者的信息,并附加到幻灯片上的特定位置。 在编辑演示文稿时,注释是可见的,但在播放幻灯片时不会出现。 显示幻灯片的应用程序决定何时显示注释并确定其外观。

© ISO/IEC 29500:2016

新式注释元素的结构

以下 XML 元素指定单个注释。 它包含注释的文本 (t) 和属性引用其作者 (authorId) 、 () created 创建的日期时间,以及注释 ID (id) 。

<p188:cm id="{62A8A96D-E5A8-4BFC-B993-A6EAE3907CAD}" authorId="{CD37207E-7903-4ED4-8AE8-017538D2DF7E}" created="2024-12-30T20:26:06.503">
  <p188:txBody>
      <a:bodyPr/>
      <a:lstStyle/>
      <a:p>
      <a:r>
          <a:t>Needs more cowbell</a:t>
      </a:r>
      </a:p>
  </p188:txBody>
</p188:cm>

下表列出了 (注释) 元素的可能子元素和属性 cm 的定义。 有关完整定义,请参阅 MS-PPTX 2.16.3.3 CT_Comment

属性 定义
id 指定批注或批注回复的 ID。
authorId 指定批注或批注回复的作者 ID。
status 指定批注或批注答复的状态。
已创建 指定创建批注或批注回复的日期时间。
startDate 指定注释的开始日期。
dueDate 指定注释的截止日期。
assignedTo 指定批注分配到的作者列表。
complete 指定注释的完成百分比。
title 指定批注的标题。
子元素 定义
pc:sldMkLst 指定用于标识批注定位到的幻灯片的内容名字对象。
ac:deMkLst 指定用于标识注释定位到的绘图元素的内容名字对象。
ac:txMkLst 指定一个内容名字对象,用于标识注释定位到的文本字符范围。
unknownAnchor 指定注释定位到的未知定位点。
pos 指定注释相对于注释定位到的第一个对象的左上角的位置。
replyLst 指定批注的答复列表。
txBody 指定批注或批注答复的文本。
extLst 指定批注或批注答复的扩展列表。

下面的 XML 架构示例除了定义必需属性和可选属性外,还定义了 元素的成员 cm

 <xsd:complexType name="CT_Comment">
   <xsd:sequence>
     <xsd:group ref="EG_CommentAnchor" minOccurs="1" maxOccurs="1"/>
     <xsd:element name="pos" type="a:CT_Point2D" minOccurs="0" maxOccurs="1"/>
     <xsd:element name="replyLst" type="CT_CommentReplyList" minOccurs="0" maxOccurs="1"/>
     <xsd:group ref="EG_CommentProperties" minOccurs="1" maxOccurs="1"/>
   </xsd:sequence>
   <xsd:attributeGroup ref="AG_CommentProperties"/>
   <xsd:attribute name="startDate" type="xsd:dateTime" use="optional"/>
   <xsd:attribute name="dueDate" type="xsd:dateTime" use="optional"/>
   <xsd:attribute name="assignedTo" type="ST_AuthorIdList" use="optional" default=""/>
   <xsd:attribute name="complete" type="s:ST_PositiveFixedPercentage" default="0%" use="optional"/>
   <xsd:attribute name="title" type="xsd:string" use="optional" default=""/>
 </xsd:complexType>

示例代码的工作方式

打开演示文稿文档进行读/写访问并实例化 PresentationDocument 类后,代码将从批注作者列表中获取指定的批注作者。

// Get the modern comments.
IEnumerable<Author>? commentAuthors = doc.PresentationPart?.authorsPart?.AuthorList.Elements<Author>()
    .Where(x => x.Name is not null && x.Name.HasValue && x.Name.Value!.Equals(author));

通过循环访问匹配的作者和演示文稿中的所有幻灯片,代码将获取所有幻灯片部件,并获取每个幻灯片部件的注释部件。 之后,代码将获取指定作者创建的注释列表,并删除每个注释。 它还确认注释部件不再包含现有注释,然后删除该部件。 它还从注释作者部件中删除注释作者。

// Iterate through all the matching authors.
foreach (Author commentAuthor in commentAuthors)
{
    string? authorId = commentAuthor.Id;
    IEnumerable<SlidePart>? slideParts = doc.PresentationPart?.SlideParts;

    // If there's no author ID or slide parts or slide parts, return.
    if (authorId is null || slideParts is null)
    {
        return;
    }

    // Iterate through all the slides and get the slide parts.
    foreach (SlidePart slide in slideParts)
    {
        IEnumerable<PowerPointCommentPart>? slideCommentsParts = slide.commentParts;

        // Get the list of comments.
        if (slideCommentsParts is not null)
        {
            IEnumerable<Tuple<PowerPointCommentPart, Comment>> commentsTup = slideCommentsParts
                .SelectMany(scp => scp.CommentList.Elements<Comment>()
                .Where(comment => comment.AuthorId is not null && comment.AuthorId == authorId)
                .Select(c => new Tuple<PowerPointCommentPart, Comment>(scp, c)));

            foreach (Tuple<PowerPointCommentPart, Comment> comment in commentsTup)
            {
                // Delete all the comments by the specified author.
                comment.Item1.CommentList.RemoveChild(comment.Item2);

                // If the commentPart has no existing comment.
                if (comment.Item1.CommentList.ChildElements.Count == 0)
                {
                    // Delete this part.
                    slide.DeletePart(comment.Item1);
                }
            }

        }
    }

    // Delete the comment author from the authors part.
    doc.PresentationPart?.authorsPart?.AuthorList.RemoveChild(commentAuthor);
}

示例代码

下面的方法采用源演示文稿文件名和路径以及要删除其注释的注释作者的姓名作为参数。 此方法按指定作者在演示文稿中找到所有注释并删除它们。 然后,此方法将该注释作者从注释作者的列表中删除。

注意

[!注释] 若要获取确切的作者名称,请打开演示文稿文件,单击"文件"菜单项,然后单击"选项"。 此时将打开"PowerPoint 选项"窗口,显示"通用"选项卡的内容。 作者名称必须与此选项卡中的"用户名"相匹配。

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

// Remove all the comments in the slides by a certain x.
static void DeleteCommentsByAuthorInPresentation(string fileName, string author)
{
    using (PresentationDocument doc = PresentationDocument.Open(fileName, true))
    {
        // Get the modern comments.
        IEnumerable<Author>? commentAuthors = doc.PresentationPart?.authorsPart?.AuthorList.Elements<Author>()
            .Where(x => x.Name is not null && x.Name.HasValue && x.Name.Value!.Equals(author));

        if (commentAuthors is null)
        {
            return;
        }

        // Iterate through all the matching authors.
        foreach (Author commentAuthor in commentAuthors)
        {
            string? authorId = commentAuthor.Id;
            IEnumerable<SlidePart>? slideParts = doc.PresentationPart?.SlideParts;

            // If there's no author ID or slide parts or slide parts, return.
            if (authorId is null || slideParts is null)
            {
                return;
            }

            // Iterate through all the slides and get the slide parts.
            foreach (SlidePart slide in slideParts)
            {
                IEnumerable<PowerPointCommentPart>? slideCommentsParts = slide.commentParts;

                // Get the list of comments.
                if (slideCommentsParts is not null)
                {
                    IEnumerable<Tuple<PowerPointCommentPart, Comment>> commentsTup = slideCommentsParts
                        .SelectMany(scp => scp.CommentList.Elements<Comment>()
                        .Where(comment => comment.AuthorId is not null && comment.AuthorId == authorId)
                        .Select(c => new Tuple<PowerPointCommentPart, Comment>(scp, c)));

                    foreach (Tuple<PowerPointCommentPart, Comment> comment in commentsTup)
                    {
                        // Delete all the comments by the specified author.
                        comment.Item1.CommentList.RemoveChild(comment.Item2);

                        // If the commentPart has no existing comment.
                        if (comment.Item1.CommentList.ChildElements.Count == 0)
                        {
                            // Delete this part.
                            slide.DeletePart(comment.Item1);
                        }
                    }

                }
            }

            // Delete the comment author from the authors part.
            doc.PresentationPart?.authorsPart?.AuthorList.RemoveChild(commentAuthor);
        }
    }
}

另请参阅