本主题演示如何使用 Open XML SDK for Office 中的类以编程方式向演示文稿中的第一张幻灯片添加注释。
注意
此示例适用于 PowerPoint 新式注释。 有关经典注释,请查看 GitHub 上的存档示例。
基本演示文稿文档结构
文档的基本文档结构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 这些类。 下表列出了对应于 、、 sldLayout
sldMaster
和 notesMaster
元素的类的sld
类名。
PresentationML 元素 | Open XML SDK 类 | 说明 |
---|---|---|
<sld/> |
Slide | 演示文稿幻灯片。 它是 SlidePart 的根元素。 |
<sldLayout/> |
SlideLayout | 幻灯片版式。 它是 SlideLayoutPart 的根元素。 |
<sldMaster/> |
SlideMaster | 幻灯片母版。 它是 SlideMasterPart 的根元素。 |
<notesMaster/> |
NotesMaster | 备注母版(或讲义母版)。 它是 NotesMasterPart 的根元素。 |
新式注释元素的结构
以下 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>
示例代码的工作方式
示例代码在 using 语句中打开演示文稿文档。 然后对 CommentAuthorsPart 进行实例化,验证是否有现有的注释作者部件。 如果没有,则添加一个。
// create missing PowerPointAuthorsPart if it is null
if (presentationDocument.PresentationPart.authorsPart is null)
{
presentationDocument.PresentationPart.AddNewPart<PowerPointAuthorsPart>();
}
代码确定演示文稿部件中是否存在现有的 PowerPoint 作者部件;如果没有,则添加一个,然后检查是否存在作者列表,并在缺少作者列表时添加一个。 它还验证传入的作者是否在现有作者列表中:如果是,则分配现有作者 ID。 如果没有,它将新作者添加到作者列表中,并分配作者 ID 和参数值。
// Add missing AuthorList if it is null
if (presentationDocument.PresentationPart.authorsPart!.AuthorList is null)
{
presentationDocument.PresentationPart.authorsPart.AuthorList = new AuthorList();
}
// Get the author or create a new one
Author? author = presentationDocument.PresentationPart.authorsPart.AuthorList
.ChildElements.OfType<Author>().Where(a => a.Name?.Value == name).FirstOrDefault();
if (author is null)
{
string authorId = string.Concat("{", Guid.NewGuid(), "}");
string userId = string.Concat(name.Split(" ").FirstOrDefault() ?? "user", "@example.com::", Guid.NewGuid());
author = new Author() { Id = authorId, Name = name, Initials = initials, UserId = userId, ProviderId = string.Empty };
presentationDocument.PresentationPart.authorsPart.AuthorList.AppendChild(author);
}
接下来,代码确定是否存在幻灯片 ID,如果不存在幻灯片 ID,则返回
// Get the Id of the slide to add the comment to
SlideId? slideId = presentationDocument.PresentationPart.Presentation.SlideIdList?.Elements<SlideId>()?.FirstOrDefault();
// If slideId is null, there are no slides, so return
if (slideId is null) return;
在下面的段中,代码获取关系 ID。 如果存在,则用于查找幻灯片部件,否则将拍摄可枚举的幻灯片部件中的第一张幻灯片。 然后,它会验证幻灯片是否有 PowerPoint 注释部分,如果没有,则添加一个。
// Get the relationship id of the slide if it exists
string? relId = slideId.RelationshipId;
// Use the relId to get the slide if it exists, otherwise take the first slide in the sequence
SlidePart slidePart = relId is not null ? (SlidePart)presentationPart.GetPartById(relId)
: presentationDocument.PresentationPart.SlideParts.First();
// If the slide part has comments parts take the first PowerPointCommentsPart
// otherwise add a new one
PowerPointCommentPart powerPointCommentPart = slidePart.commentParts.FirstOrDefault() ?? slidePart.AddNewPart<PowerPointCommentPart>();
代码下方创建新的新式批注,然后向 PowerPoint 注释部件添加注释列表(如果不存在),并将注释添加到该批注列表。
// Create the comment using the new modern comment class DocumentFormat.OpenXml.Office2021.PowerPoint.Comment.Comment
DocumentFormat.OpenXml.Office2021.PowerPoint.Comment.Comment comment = new DocumentFormat.OpenXml.Office2021.PowerPoint.Comment.Comment(
new SlideMonikerList(
new DocumentMoniker(),
new SlideMoniker()
{
CId = cid,
SldId = slideId.Id,
}),
new TextBodyType(
new BodyProperties(),
new ListStyle(),
new Paragraph(new Run(new DocumentFormat.OpenXml.Drawing.Text(text)))))
{
Id = string.Concat("{", Guid.NewGuid(), "}"),
AuthorId = author.Id,
Created = DateTime.Now,
};
// If the comment list does not exist, add one.
powerPointCommentPart.CommentList ??= new DocumentFormat.OpenXml.Office2021.PowerPoint.Comment.CommentList();
// Add the comment to the comment list
powerPointCommentPart.CommentList.AppendChild(comment);
使用新式注释时,幻灯片需要具有正确的扩展列表和扩展。 以下代码确定幻灯片是否已有 SlideExtensionList 和 SlideExtension,如果它们不存在,则将其添加到幻灯片中。
// Get the presentation extension list if it exists
SlideExtensionList? presentationExtensionList = slidePart.Slide.ChildElements.OfType<SlideExtensionList>().FirstOrDefault();
// Create a boolean that determines if this is the slide's first comment
bool isFirstComment = false;
// If the presentation extension list is null, add one and set this as the first comment for the slide
if (presentationExtensionList is null)
{
isFirstComment = true;
slidePart.Slide.AppendChild(new SlideExtensionList());
presentationExtensionList = slidePart.Slide.ChildElements.OfType<SlideExtensionList>().First();
}
// Get the slide extension if it exists
SlideExtension? presentationExtension = presentationExtensionList.ChildElements.OfType<SlideExtension>().FirstOrDefault();
// If the slide extension is null, add it and set this as a new comment
if (presentationExtension is null)
{
isFirstComment = true;
presentationExtensionList.AddChild(new SlideExtension() { Uri = "{6950BFC3-D8DA-4A85-94F7-54DA5524770B}" });
presentationExtension = presentationExtensionList.ChildElements.OfType<SlideExtension>().First();
}
// If this is the first comment for the slide add the comment relationship
if (isFirstComment)
{
presentationExtension.AddChild(new CommentRelationship()
{ Id = slidePart.GetIdOfPart(powerPointCommentPart) });
}
示例代码
下面是完整的代码示例,演示如何将包含新作者或现有作者的新批注添加到包含或不带现有批注的幻灯片。
注意
[!注释] 若要获取确切的作者姓名和缩写,请打开演示文稿文件,单击"文件"菜单项,然后单击"选项"。 PowerPointOptions 窗口随即打开,并显示“常规”选项卡的内容。 作者姓名和缩写必须与此选项卡中的"用户名"和"缩写"相匹配。
static void AddCommentToPresentation(string file, string initials, string name, string text)
{
using (PresentationDocument presentationDocument = PresentationDocument.Open(file, true))
{
PresentationPart presentationPart = presentationDocument?.PresentationPart ?? throw new MissingFieldException("PresentationPart");
// create missing PowerPointAuthorsPart if it is null
if (presentationDocument.PresentationPart.authorsPart is null)
{
presentationDocument.PresentationPart.AddNewPart<PowerPointAuthorsPart>();
}
// Add missing AuthorList if it is null
if (presentationDocument.PresentationPart.authorsPart!.AuthorList is null)
{
presentationDocument.PresentationPart.authorsPart.AuthorList = new AuthorList();
}
// Get the author or create a new one
Author? author = presentationDocument.PresentationPart.authorsPart.AuthorList
.ChildElements.OfType<Author>().Where(a => a.Name?.Value == name).FirstOrDefault();
if (author is null)
{
string authorId = string.Concat("{", Guid.NewGuid(), "}");
string userId = string.Concat(name.Split(" ").FirstOrDefault() ?? "user", "@example.com::", Guid.NewGuid());
author = new Author() { Id = authorId, Name = name, Initials = initials, UserId = userId, ProviderId = string.Empty };
presentationDocument.PresentationPart.authorsPart.AuthorList.AppendChild(author);
}
// Get the Id of the slide to add the comment to
SlideId? slideId = presentationDocument.PresentationPart.Presentation.SlideIdList?.Elements<SlideId>()?.FirstOrDefault();
// If slideId is null, there are no slides, so return
if (slideId is null) return;
Random ran = new Random();
UInt32Value cid = Convert.ToUInt32(ran.Next(100000000, 999999999));
// Get the relationship id of the slide if it exists
string? relId = slideId.RelationshipId;
// Use the relId to get the slide if it exists, otherwise take the first slide in the sequence
SlidePart slidePart = relId is not null ? (SlidePart)presentationPart.GetPartById(relId)
: presentationDocument.PresentationPart.SlideParts.First();
// If the slide part has comments parts take the first PowerPointCommentsPart
// otherwise add a new one
PowerPointCommentPart powerPointCommentPart = slidePart.commentParts.FirstOrDefault() ?? slidePart.AddNewPart<PowerPointCommentPart>();
// Create the comment using the new modern comment class DocumentFormat.OpenXml.Office2021.PowerPoint.Comment.Comment
DocumentFormat.OpenXml.Office2021.PowerPoint.Comment.Comment comment = new DocumentFormat.OpenXml.Office2021.PowerPoint.Comment.Comment(
new SlideMonikerList(
new DocumentMoniker(),
new SlideMoniker()
{
CId = cid,
SldId = slideId.Id,
}),
new TextBodyType(
new BodyProperties(),
new ListStyle(),
new Paragraph(new Run(new DocumentFormat.OpenXml.Drawing.Text(text)))))
{
Id = string.Concat("{", Guid.NewGuid(), "}"),
AuthorId = author.Id,
Created = DateTime.Now,
};
// If the comment list does not exist, add one.
powerPointCommentPart.CommentList ??= new DocumentFormat.OpenXml.Office2021.PowerPoint.Comment.CommentList();
// Add the comment to the comment list
powerPointCommentPart.CommentList.AppendChild(comment);
// Get the presentation extension list if it exists
SlideExtensionList? presentationExtensionList = slidePart.Slide.ChildElements.OfType<SlideExtensionList>().FirstOrDefault();
// Create a boolean that determines if this is the slide's first comment
bool isFirstComment = false;
// If the presentation extension list is null, add one and set this as the first comment for the slide
if (presentationExtensionList is null)
{
isFirstComment = true;
slidePart.Slide.AppendChild(new SlideExtensionList());
presentationExtensionList = slidePart.Slide.ChildElements.OfType<SlideExtensionList>().First();
}
// Get the slide extension if it exists
SlideExtension? presentationExtension = presentationExtensionList.ChildElements.OfType<SlideExtension>().FirstOrDefault();
// If the slide extension is null, add it and set this as a new comment
if (presentationExtension is null)
{
isFirstComment = true;
presentationExtensionList.AddChild(new SlideExtension() { Uri = "{6950BFC3-D8DA-4A85-94F7-54DA5524770B}" });
presentationExtension = presentationExtensionList.ChildElements.OfType<SlideExtension>().First();
}
// If this is the first comment for the slide add the comment relationship
if (isFirstComment)
{
presentationExtension.AddChild(new CommentRelationship()
{ Id = slidePart.GetIdOfPart(powerPointCommentPart) });
}
}
}