Add a comment to a slide in a presentation
This topic shows how to use the classes in the Open XML SDK for Office to add a comment to the first slide in a presentation programmatically.
Note
This sample is for PowerPoint modern comments. For classic comments view the archived sample on GitHub.
Basic Presentation Document Structure
The basic document structure of a PresentationML
document consists of a number of
parts, among which is the main part that contains the presentation
definition. The following text from the ISO/IEC 29500 specification
introduces the overall form of a PresentationML
package.
The main part of a
PresentationML
package starts with a presentation root element. That element contains a presentation, which, in turn, refers to a slide list, a slide master list, a notes master list, and a handout master list. The slide list refers to all of the slides in the presentation; the slide master list refers to the entire slide masters used in the presentation; the notes master contains information about the formatting of notes pages; and the handout master describes how a handout looks.A handout is a printed set of slides that can be provided to an audience.
As well as text and graphics, each slide can contain comments and notes, can have a layout, and can be part of one or more custom presentations. A comment is an annotation intended for the person maintaining the presentation slide deck. A note is a reminder or piece of text intended for the presenter or the audience.
Other features that a
PresentationML
document can include the following: animation, audio, video, and transitions between slides.A
PresentationML
document is not stored as one large body in a single part. Instead, the elements that implement certain groupings of functionality are stored in separate parts. For example, all authors in a document are stored in one authors part while each slide has its own part.ISO/IEC 29500: 2016
The following XML code example represents a presentation that contains two slides denoted by the IDs 267 and 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>
Using the Open XML SDK, you can create document structure and
content using strongly-typed classes that correspond to PresentationML
elements. You can find these classes in the
namespace. The following table lists the class names of the classes that
correspond to the sld
, sldLayout
, sldMaster
, and notesMaster
elements.
PresentationML Element | Open XML SDK Class | Description |
---|---|---|
<sld/> |
Slide | Presentation Slide. It is the root element of SlidePart. |
<sldLayout/> |
SlideLayout | Slide Layout. It is the root element of SlideLayoutPart. |
<sldMaster/> |
SlideMaster | Slide Master. It is the root element of SlideMasterPart. |
<notesMaster/> |
NotesMaster | Notes Master (or handoutMaster). It is the root element of NotesMasterPart. |
The Structure of the Modern Comment Element
The following XML element specifies a single comment.
It contains the text of the comment (t
) and attributes referring to its author
(authorId
), date time created (created
), and comment 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>
The following tables list the definitions of the possible child elements and attributes
of the cm
(comment) element. For the complete definition see MS-PPTX 2.16.3.3 CT_Comment
Attribute | Definition |
---|---|
id | Specifies the ID of a comment or a comment reply. |
authorId | Specifies the author ID of a comment or a comment reply. |
status | Specifies the status of a comment or a comment reply. |
created | Specifies the date time when the comment or comment reply is created. |
startDate | Specifies start date of the comment. |
dueDate | Specifies due date of the comment. |
assignedTo | Specifies a list of authors to whom the comment is assigned. |
complete | Specifies the completion percentage of the comment. |
title | Specifies the title for a comment. |
Child Element | Definition |
---|---|
pc:sldMkLst | Specifies a content moniker that identifies the slide to which the comment is anchored. |
ac:deMkLst | Specifies a content moniker that identifies the drawing element to which the comment is anchored. |
ac:txMkLst | Specifies a content moniker that identifies the text character range to which the comment is anchored. |
unknownAnchor | Specifies an unknown anchor to which the comment is anchored. |
pos | Specifies the position of the comment, relative to the top-left corner of the first object to which the comment is anchored. |
replyLst | Specifies the list of replies to the comment. |
txBody | Specifies the text of a comment or a comment reply. |
extLst | Specifies a list of extensions for a comment or a comment reply. |
The following XML schema example defines the members of the cm
element in addition to the required and
optional attributes.
<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>
How the Sample Code Works
The sample code opens the presentation document in the using statement. Then it instantiates the CommentAuthorsPart, and verifies that there is an existing comment authors part. If there is not, it adds one.
// create missing PowerPointAuthorsPart if it is null
if (presentationDocument.PresentationPart.authorsPart is null)
{
presentationDocument.PresentationPart.AddNewPart<PowerPointAuthorsPart>();
}
The code determines whether there is an existing PowerPoint author part in the presentation part; if not, it adds one, then checks if there is an authors list and adds one if it is missing. It also verifies that the author that is passed in is on the list of existing authors; if so, it assigns the existing author ID. If not, it adds a new author to the list of authors and assigns an author ID and the parameter values.
// 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);
}
Next the code determines if there is a slide id and returns if one does not exist
// 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;
In the segment below, the code gets the relationship ID. If it exists, it is used to find the slide part otherwise the first slide in the slide parts enumerable is taken. Then it verifies that there is a PowerPoint comments part for the slide and if not adds one.
// 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>();
Below the code creates a new modern comment then adds a comment list to the PowerPoint comment part if one does not exist and adds the comment to that comment list.
// 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);
With modern comments the slide needs to have the correct extension list and extension. The following code determines if the slide already has a SlideExtensionList and SlideExtension and adds them to the slide if they are not present.
// 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) });
}
Sample Code
Following is the complete code sample showing how to add a new comment with a new or existing author to a slide with or without existing comments.
Note
To get the exact author name and initials, open the presentation file and click the File menu item, and then click Options. The PowerPointOptions window opens and the content of the General tab is displayed. The author name and initials must match the User name and Initials in this tab.
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) });
}
}
}