Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
This topic shows how to use the classes in the Open XML SDK for Office to reply to existing comments in a presentation programmatically.
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
PresentationMLpackage 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
PresentationMLdocument can include the following: animation, audio, video, and transitions between slides.A
PresentationMLdocument 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 DocumentFormat.OpenXml.Presentation
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 gets or creates the CommentAuthorsPart, and verifies that there is an existing comment authors part. If there is not, it adds one.
// Open the PowerPoint presentation for editing
using (PresentationDocument presentationDocument = PresentationDocument.Open(path, true))
{
// Check if the presentation part exists
if (presentationDocument.PresentationPart is null)
{
Console.WriteLine("No presentation part found in the presentation");
return;
}
else
{
// Prompt the user for the author's name
Console.WriteLine("Please enter the author's name");
string? authorName = Console.ReadLine();
// Ensure the author name is provided
while (authorName is null)
{
Console.WriteLine("Author's name is required. Please enter author's name below");
authorName = Console.ReadLine();
}
// Generate initials from the author's name
string[] splitName = authorName.Split(" ");
string authorInitials = string.Concat(splitName[0].Substring(0, 1), splitName[splitName.Length - 1].Substring(0, 1));
// Get or create the authors part for comment authorship
PowerPointAuthorsPart authorsPart = presentationDocument.PresentationPart.authorsPart ?? presentationDocument.AddNewPart<PowerPointAuthorsPart>();
authorsPart.AuthorList ??= new AuthorList();
Next the code determines if 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.
// Try to find an existing author by name, otherwise create a new author
string? authorId = authorsPart.AuthorList.Descendants<Author>().Where(author => author.Name == authorName).FirstOrDefault()?.UserId;
if (authorId is null)
{
authorId = Guid.NewGuid().ToString("B");
authorsPart.AuthorList.AppendChild(new Author() { Id = authorId, Name = authorName, Initials = authorInitials, UserId = authorId, ProviderId = "Me" });
}
Next the code gets the first slide part and verifies that it exists, then checks if there are any comment parts associated with the slide.
// Get the first slide part in the presentation
SlidePart? slidePart = presentationDocument.PresentationPart.SlideParts?.FirstOrDefault();
if (slidePart is null)
{
Console.WriteLine("No slide part found in the presentation.");
return;
}
else
{
// Check if the slide has any comment parts
if (slidePart.commentParts is null || slidePart.commentParts.Count() == 0)
{
Console.WriteLine("No comments part found for slide 1");
return;
}
The code then retrieves the comment list and then iterates through each comment in the comment list, displays the comment text to the user, and prompts whether they want to reply to each comment.
// Get the comment list
DocumentFormat.OpenXml.Office2021.PowerPoint.Comment.CommentList? commentList =
(DocumentFormat.OpenXml.Office2021.PowerPoint.Comment.CommentList?)(slidePart.commentParts.FirstOrDefault()?.CommentList);
if (commentList is null)
{
Console.WriteLine("No comments found for slide 1");
return;
}
else
{
// Iterate through each comment in the comment list
foreach (DocumentFormat.OpenXml.Office2021.PowerPoint.Comment.Comment comment in commentList)
{
// Display the comment text to the user
Console.WriteLine("Comment:");
Console.WriteLine(comment.ChildElements.Where(c => c is TextBodyType).FirstOrDefault()?.InnerText);
Console.WriteLine("Do you want to reply Y/N");
string? leaveReply = Console.ReadLine();
When the user chooses to reply to a comment, the code prompts for the reply text, then gets or creates a CommentReplyList for the comment and adds the new reply with the appropriate author information and timestamp.
// If the user wants to reply, prompt for the reply text
if (leaveReply is not null && leaveReply.ToUpper() == "Y")
{
Console.WriteLine("What is your reply?");
string? reply = Console.ReadLine();
if (reply is not null)
{
// Get or create the reply list for the comment
CommentReplyList? commentReplyList = comment.Descendants<CommentReplyList>()?.FirstOrDefault();
if (commentReplyList is null)
{
commentReplyList = new CommentReplyList();
comment.AddChild(commentReplyList);
}
// Add the user's reply to the comment
commentReplyList.AppendChild(new CommentReply(
new TextBodyType(
new BodyProperties(),
new Paragraph(
new Run(
new DocumentFormat.OpenXml.Drawing.Text(reply)))))
{
Id = Guid.NewGuid().ToString("B"),
AuthorId = authorId,
Created = new DateTimeValue(DateTime.Now)
});
}
}
Sample Code
Following is the complete code sample showing how to reply to existing comments in a presentation slide with modern PowerPoint comments.
// Open the PowerPoint presentation for editing
using (PresentationDocument presentationDocument = PresentationDocument.Open(path, true))
{
// Check if the presentation part exists
if (presentationDocument.PresentationPart is null)
{
Console.WriteLine("No presentation part found in the presentation");
return;
}
else
{
// Prompt the user for the author's name
Console.WriteLine("Please enter the author's name");
string? authorName = Console.ReadLine();
// Ensure the author name is provided
while (authorName is null)
{
Console.WriteLine("Author's name is required. Please enter author's name below");
authorName = Console.ReadLine();
}
// Generate initials from the author's name
string[] splitName = authorName.Split(" ");
string authorInitials = string.Concat(splitName[0].Substring(0, 1), splitName[splitName.Length - 1].Substring(0, 1));
// Get or create the authors part for comment authorship
PowerPointAuthorsPart authorsPart = presentationDocument.PresentationPart.authorsPart ?? presentationDocument.AddNewPart<PowerPointAuthorsPart>();
authorsPart.AuthorList ??= new AuthorList();
// Try to find an existing author by name, otherwise create a new author
string? authorId = authorsPart.AuthorList.Descendants<Author>().Where(author => author.Name == authorName).FirstOrDefault()?.UserId;
if (authorId is null)
{
authorId = Guid.NewGuid().ToString("B");
authorsPart.AuthorList.AppendChild(new Author() { Id = authorId, Name = authorName, Initials = authorInitials, UserId = authorId, ProviderId = "Me" });
}
// Get the first slide part in the presentation
SlidePart? slidePart = presentationDocument.PresentationPart.SlideParts?.FirstOrDefault();
if (slidePart is null)
{
Console.WriteLine("No slide part found in the presentation.");
return;
}
else
{
// Check if the slide has any comment parts
if (slidePart.commentParts is null || slidePart.commentParts.Count() == 0)
{
Console.WriteLine("No comments part found for slide 1");
return;
}
// Get the comment list
DocumentFormat.OpenXml.Office2021.PowerPoint.Comment.CommentList? commentList =
(DocumentFormat.OpenXml.Office2021.PowerPoint.Comment.CommentList?)(slidePart.commentParts.FirstOrDefault()?.CommentList);
if (commentList is null)
{
Console.WriteLine("No comments found for slide 1");
return;
}
else
{
// Iterate through each comment in the comment list
foreach (DocumentFormat.OpenXml.Office2021.PowerPoint.Comment.Comment comment in commentList)
{
// Display the comment text to the user
Console.WriteLine("Comment:");
Console.WriteLine(comment.ChildElements.Where(c => c is TextBodyType).FirstOrDefault()?.InnerText);
Console.WriteLine("Do you want to reply Y/N");
string? leaveReply = Console.ReadLine();
// If the user wants to reply, prompt for the reply text
if (leaveReply is not null && leaveReply.ToUpper() == "Y")
{
Console.WriteLine("What is your reply?");
string? reply = Console.ReadLine();
if (reply is not null)
{
// Get or create the reply list for the comment
CommentReplyList? commentReplyList = comment.Descendants<CommentReplyList>()?.FirstOrDefault();
if (commentReplyList is null)
{
commentReplyList = new CommentReplyList();
comment.AddChild(commentReplyList);
}
// Add the user's reply to the comment
commentReplyList.AppendChild(new CommentReply(
new TextBodyType(
new BodyProperties(),
new Paragraph(
new Run(
new DocumentFormat.OpenXml.Drawing.Text(reply)))))
{
Id = Guid.NewGuid().ToString("B"),
AuthorId = authorId,
Created = new DateTimeValue(DateTime.Now)
});
}
}
}
}
}
}
}