Добавить комментарий к слайду в презентации
В этом разделе показано, как использовать классы в пакете SDK Open XML для Office для программного добавления комментария к первому слайду презентации.
Примечание.
Этот пример предназначен для современных комментариев PowerPoint. Для классических комментариев просмотрите архивный пример на сайте GitHub.
Базовая структура документа презентации
Базовая структура PresentationML
документа состоит из нескольких частей, среди которых есть main часть, содержащая определение презентации. В следующем тексте из спецификации ISO/IEC 29500 представлена общая форма PresentationML
пакета.
Main часть
PresentationML
пакета начинается с корневого элемента презентации. Этот элемент содержит презентацию, которая, в свою очередь, ссылается на список слайдов, список образцов слайдов, список образцов заметок и список образцов раздаточных материалов. Список слайдов ссылается на все слайды в презентации; список образцов слайдов ссылается на все образцы слайдов, используемые в презентации; в списке образцов заметок содержатся данные о форматировании страниц заметок, а в списке образцов раздаточных материалов описан внешний вид раздаточных материалов.Раздаточные материалы представляют собой набор распечатанных слайдов, которые можно раздать слушателям для последующего использования.
Наряду с текстом и изображениями слайды могут содержать комментарии, заметки и разметку, а также могут входить в одну или несколько пользовательских презентаций. Комментарий представляет собой примечание, которое адресовано сотруднику, ответственному за обслуживание набора слайдов. Заметка представляет собой напоминание или отрывок текста, предназначенный для докладчика или для слушателей.
Другие функции документа
PresentationML
могут включать следующие: анимацию, звук, видео и переходы между слайдами.Документ
PresentationML
не хранится в виде одного большого текста в одной части. Элементы с определенной группировкой функций хранятся в различных частях. Например, все авторы в документе хранятся в одной части авторов, а каждый слайд имеет свою собственную часть.ISO/IEC 29500: 2016
Указанный ниже пример кода XML описывает презентацию, содержащую 2 слайда с идентификаторами 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>
С помощью пакета SDK Open XML можно создавать структуру документа и содержимое с помощью строго типизированных классов, соответствующих элементам PresentationML. Эти классы можно найти в пространстве имен. В следующей таблице перечислены имена классов, которые соответствуют sld
элементам , sldLayout
, sldMaster
и notesMaster
.
Элемент PresentationML | Класс пакета SDK Open XML | Описание |
---|---|---|
<sld/> |
Slide | Слайд презентации. Это корневой элемент части SlidePart. |
<sldLayout/> |
SlideLayout | Разметка слайда. Это корневой элемент части SlideLayoutPart. |
<sldMaster/> |
SlideMaster | Образец слайда. Это корневой элемент части SlideMasterPart. |
<notesMaster/> |
NotesMaster | Образец заметок (или handoutMaster). Это корневой элемент части NotesMasterPart. |
Структура современного элемента comment
Следующий XML-элемент задает один комментарий.
Он содержит текст комментария (t
) и атрибуты, ссылающиеся на его автора (authorId
), созданнуюcreated
дату () и идентификатор комментария (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
элемента (comment). Полное определение см. в статье MS-PPTX 2.16.3.3 CT_Comment
Атрибут | Определение |
---|---|
id | Указывает идентификатор комментария или ответа примечания. |
authorId | Указывает идентификатор автора комментария или ответа примечания. |
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. Если нет, он добавляет один, а затем проверяет, есть ли список авторов, и добавляет его, если он отсутствует. Он также проверяет, что автор, который передается, входит в список существующих авторов; Если да, он назначает существующий идентификатор автора. Если нет, он добавляет нового автора в список авторов и назначает идентификатор автора и значения параметров.
// 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;
В приведенном ниже сегменте код получает идентификатор связи. Если он существует, он используется для поиска части слайда, в противном случае принимается первый слайд в перечисляемых частях слайда. Затем проверяется, есть ли часть комментариев 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) });
}
}
}