プレゼンテーションのスライドにコメントを追加する
このトピックでは、Open XML SDK for Office のクラスを使用して、プログラムを使用してプレゼンテーションの最初のスライドにコメントを追加する方法について説明します。
注:
このサンプルは、最新のコメントPowerPoint用です。 クラシック コメントについては、 GitHub でアーカイブされたサンプルを参照してください。
プレゼンテーション ドキュメントの基本構造
PresentationML
ドキュメントの基本的なドキュメント構造は、多数のパーツで構成され、その中にはプレゼンテーション定義を含むメインパーツがあります。
ISO/IEC 29500 仕様の次のテキストでは、PresentationML
パッケージの全体的な形式について説明します。
PresentationML
パッケージのメイン部分は、プレゼンテーション ルート要素で始まります。 この要素にはプレゼンテーションが含まれており、プレゼンテーションは スライド リスト、スライド マスター リスト、ノート マスター リスト、配布資料マスター リストを参照します。 スライド リストはプレゼンテーション内のすべてのスライドを参照します。スライド マスター リストはプレゼンテーションで使用されるスライド マスター全体を参照します。ノート マスターにはノート ページの書式設定に関する情報が含まれます。配布資料マスターは配布資料がどのように表示されるかを示します。配布資料とは、聴衆に提供できるように一連のスライドを印刷したものです。
テキストやグラフィックのように、各スライドにはコメントとノートを含めることができ、レイアウトを指定したり、1 つ以上のカスタム プレゼンテーションに組み込んだりできます。 コメントは、プレゼンテーション スライド デッキをメンテナンスする人向けの注釈です。 ノートは、プレゼンテーションの発表者または参加者向けのリマインダーやメモです。
PresentationML
ドキュメントには、アニメーション、オーディオ、ビデオ、スライド間の切り替えなどのその他の機能があります。
PresentationML
ドキュメントは、1 つのパーツに 1 つの大きな本文として格納されません。 その代わりに、特定のグループの機能を実現する要素が別個のパーツに格納されます。 たとえば、ドキュメント内のすべての作成者は 1 つの作成者パーツに格納され、各スライドには独自のパーツがあります。ISO/IEC 29500: 2016
次の XML コードの例は、267 と 256 という ID で示される 2 つのスライドを含むプレゼンテーションを表します。
<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 要素に対応する厳密に型指定されたクラスを使用して、ドキュメント構造とコンテンツを作成できます。 これらのクラスは、 名前空間にあります。 次の表に、 sld
、 sldLayout
、 sldMaster
、および notesMaster
の各要素に対応するクラスのクラス名を示します。
PresentationML 要素 | Open XML SDK クラス | 説明 |
---|---|---|
<sld/> |
Slide | プレゼンテーション スライド。 SlidePart のルート要素 |
<sldLayout/> |
SlideLayout | スライド レイアウト。 SlideLayoutPart のルート要素 |
<sldMaster/> |
SlideMaster | スライド マスター。 SlideMasterPart のルート要素 |
<notesMaster/> |
NotesMaster | ノート マスター (または handoutMaster)。 NotesMasterPart のルート要素 |
モダン コメント要素の構造
次の XML 要素は、1 つのコメントを指定します。
これには、コメント (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 があるかどうかを判断し、存在しない場合は を返します
// 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) });
}
}
}