向演示文稿中的幻灯片添加批注
本主题演示如何使用 Open XML SDK for Office 中的类以编程方式向演示文稿中的第一张幻灯片添加注释。
基本演示文稿文档结构
PresentationML 文档的基本文档结构包含大量部件,在这些部件中,主部件是包含演示文稿定义的部件。 ISO/IEC 29500(该链接可能指向英文页面) 规范中的以下文本介绍了 PresentationML 包的整体形式。
PresentationML 包的主部件以演示文稿根元素开头。 该元素包含演示文稿,演示文稿又引用幻灯片 列表、幻灯片母版 列表、备注母版 列表和讲义母版 列表。 幻灯片列表指的是演示文稿中的所有幻灯片;幻灯片母版列表指的是演示文稿中使用的全部幻灯片母版;备注母版包含有关备注页格式的信息;讲义母版描述讲义的外观。
讲义 是可提供给访问群体 的一组打印的幻灯片。
除了文本和图形,每个幻灯片还可以包含注释 和备注,可以具有布局,并且可以是一个或多个自定义演示文稿 的组成部分。 注释是供维护演示文稿幻灯片平台的人员参考的批注。 备注是供演示者或访问群体参考的提醒信息或一段文字。
PresentationML 文档可以包括以下其他功能:幻灯片之间的动画、音频、视频和切换效果。
PresentationML 文档不会存储为单个部件中的一个大型正文。 而实现某些功能组合的元素会存储在各个部件中。 例如,文档中的所有注释都存储在一个注释部件中,而每个幻灯片都有自己的部件。
© ISO/IEC29500: 2008.
以下 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 命名空间中找到这些类。 下表列出了 sld、 sldLayout、 sldMaster 和 notesMaster 元素所对应类的类名称。
PresentationML 元素 | Open XML SDK 类 | 说明 |
---|---|---|
Sld | Slide | 演示文稿幻灯片。 它是 SlidePart 的根元素。 |
sldLayout | SlideLayout | 幻灯片版式。 它是 SlideLayoutPart 的根元素。 |
sldMaster | SlideMaster | 幻灯片母版。 它是 SlideMasterPart 的根元素。 |
notesMaster | NotesMaster | 备注母版(或讲义母版)。 它是 NotesMasterPart 的根元素。 |
Comment 元素的结构
注释是附加到幻灯片的文本备注,主要用途是使演示文稿的读者能够向演示文稿作者提供反馈。 每个注释包含无格式文本字符串及有关其作者的信息,并附加到幻灯片上的特定位置。 在编辑演示文稿时,注释是可见的,但在播放幻灯片时不会出现。 显示幻灯片的应用程序决定何时显示注释并确定其外观。
以下 XML 元素指定了附加到幻灯片的单个注释。 它包含注释文本 (text)、注释在幻灯片上的位置 (pos) 以及引用其作者 (authorId)、日期和时间 (dt) 及注释索引 (idx) 的属性。
<p:cm authorId="0" dt="2006-08-28T17:26:44.129" idx="1">
<p:pos x="10" y="10"/>
<p:text>Add diagram to clarify.</p:text>
</p:cm>
下表包含了 cm(注释)元素的成员和属性的定义。
成员/属性 | 定义 |
---|---|
authorId | 引用文档的注释作者列表中的作者的 ID。 |
Dt | 上次修改此注释的日期和时间。 |
idx | 此注释的标识符,在此文档中由此作者创建的所有注释的列表中是唯一的。 作者在文档中的第一个注释的索引是 1。 |
Pos | 注释在幻灯片表面的放置位置信息。 |
text | 注释文本。 |
extLst | 指定具有修改能力的扩展列表,其中定义了元素类型 ext 的所有未来扩展。 扩展列表以及相应的未来扩展用于扩展 PresentationML 框架的存储功能。 这允许在框架中本机存储各种新类型的数据。 |
下面的 XML 架构代码示例定义了 cm 元素的成员以及必需属性和可选属性。
<complexType name="CT_Comment">
<sequence>
<element name="pos" type="a:CT_Point2D" minOccurs="1" maxOccurs="1"/>
<element name="text" type="xsd:string" minOccurs="1" maxOccurs="1"/>
<element name="extLst" type="CT_ExtensionListModify" minOccurs="0" maxOccurs="1"/>
</sequence>
<attribute name="authorId" type="xsd:unsignedInt" use="required"/>
<attribute name="dt" type="xsd:dateTime" use="optional"/>
<attribute name="idx" type="ST_Index" use="required"/>
</complexType>
示例代码
下面的代码示例演示如何向演示文稿文档添加注释。 若要运行程序,可以传入参数:
dotnet run -- [filePath] [initials] [name] [test ...]
注意
[!注释] 若要获取确切的作者姓名和缩写,请打开演示文稿文件,单击"文件"菜单项,然后单击"选项"。 PowerPointOptions 窗口随即打开,并显示“常规”选项卡的内容。 作者姓名和缩写必须与此选项卡中的"用户名"和"缩写"相匹配。
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Presentation;
using System;
using System.Linq;
AddCommentToPresentation(args[0], args[1], args[2], string.Join(' ', args[3..]));
static void AddCommentToPresentation(string file, string initials, string name, string text)
{
using (PresentationDocument doc = PresentationDocument.Open(file, true))
{
// Declare a CommentAuthorsPart object.
CommentAuthorsPart authorsPart;
// If the presentation does not contain a comment authors part, add a new one.
PresentationPart presentationPart = doc.PresentationPart ?? doc.AddPresentationPart();
// Verify that there is an existing comment authors part and add a new one if not.
authorsPart = presentationPart.CommentAuthorsPart ?? presentationPart.AddNewPart<CommentAuthorsPart>();
// Verify that there is a comment author list in the comment authors part and add one if not.
CommentAuthorList authorList = authorsPart.CommentAuthorList ?? new CommentAuthorList();
authorsPart.CommentAuthorList = authorList;
// Declare a new author ID as either the max existing ID + 1 or 1 if there are no existing IDs.
uint authorId = authorList.Elements<CommentAuthor>().Select(a => a.Id?.Value).Max() ?? 0;
authorId++;
// If there is an existing author with matching name and initials, use that author otherwise create a new CommentAuthor.
var foo = authorList.Elements<CommentAuthor>().Where(a => a.Name == name && a.Initials == initials).FirstOrDefault();
CommentAuthor author = foo ??
authorList.AppendChild
(new CommentAuthor()
{
Id = authorId,
Name = name,
Initials = initials,
ColorIndex = 0
});
// get the author id
authorId = author.Id ?? authorId;
// Get the first slide, using the GetFirstSlide method.
SlidePart slidePart1 = GetFirstSlide(doc);
// Declare a comments part.
SlideCommentsPart commentsPart;
// Verify that there is a comments part in the first slide part.
if (slidePart1.GetPartsOfType<SlideCommentsPart>().Count() == 0)
{
// If not, add a new comments part.
commentsPart = slidePart1.AddNewPart<SlideCommentsPart>();
}
else
{
// Else, use the first comments part in the slide part.
commentsPart = slidePart1.GetPartsOfType<SlideCommentsPart>().First();
}
// If the comment list does not exist.
if (commentsPart.CommentList is null)
{
// Add a new comments list.
commentsPart.CommentList = new CommentList();
}
// Get the new comment ID.
uint commentIdx = author.LastIndex is null ? 1 : author.LastIndex + 1;
author.LastIndex = commentIdx;
// Add a new comment.
Comment comment = commentsPart.CommentList.AppendChild<Comment>(
new Comment()
{
AuthorId = authorId,
Index = commentIdx,
DateTime = DateTime.Now
});
// Add the position child node to the comment element.
comment.Append(
new Position() { X = 100, Y = 200 },
new Text() { Text = text });
// Save the comment authors part.
authorList.Save();
// Save the comments part.
commentsPart.CommentList.Save();
}
}
// Get the slide part of the first slide in the presentation document.
static SlidePart GetFirstSlide(PresentationDocument? presentationDocument)
{
// Get relationship ID of the first slide
PresentationPart? part = presentationDocument?.PresentationPart;
SlideId? slideId = part?.Presentation?.SlideIdList?.GetFirstChild<SlideId>();
string? relId = slideId?.RelationshipId;
if (relId is null)
{
throw new ArgumentNullException("The first slide does not contain a relationship ID.");
}
// Get the slide part by the relationship ID.
SlidePart? slidePart = part?.GetPartById(relId) as SlidePart;
if (slidePart is null)
{
throw new ArgumentNullException("The slide part is null.");
}
return slidePart;
}