Compartilhar via


Adicionar um comentário a um slide de uma apresentação

Este tópico mostra como utilizar as classes no SDK Open XML para o Office para adicionar um comentário ao primeiro diapositivo numa apresentação através de programação.

Observação

Este exemplo destina-se a comentários modernos do PowerPoint. Para comentários clássicos, veja o exemplo arquivado no GitHub.

Estrutura básica do documento de apresentação

A estrutura de documentos básica de um PresentationML documento consiste em várias partes, entre as quais a main parte que contém a definição da apresentação. O texto seguinte da especificação ISO/IEC 29500 apresenta a forma geral de um PresentationML pacote.

O main parte de um PresentationML pacote começa com um elemento raiz de apresentação. Esse elemento contém uma apresentação que, por sua vez, se refere a uma lista de diapositivos, a uma lista de master de diapositivos, a uma lista de notas master e a um folheto master lista. A lista de diapositivos refere-se a todos os diapositivos na apresentação; A lista de master de diapositivos refere-se a todos os modelos globais de diapositivos utilizados na apresentação; as notas master contêm informações sobre a formatação das páginas de notas; e o master de folheto descreve o aspeto de um folheto.

Um folheto é um conjunto impresso de diapositivos que podem ser fornecidos a uma audiência.

Além de texto e gráficos, cada diapositivo pode conter comentários e notas, pode ter um esquema e pode fazer parte de uma ou mais apresentações personalizadas. Um comentário é uma anotação destinada à pessoa que mantém o conjunto de diapositivos da apresentação. Uma nota é um lembrete ou texto destinado ao apresentador ou à audiência.

Outras funcionalidades que um PresentationML documento pode incluir: animação, áudio, vídeo e transições entre diapositivos.

Um PresentationML documento não é armazenado como um corpo grande numa única parte. Em vez disso, os elementos que implementam determinados agrupamentos de funcionalidades são armazenados em partes separadas. Por exemplo, todos os autores num documento são armazenados numa parte dos autores, enquanto cada diapositivo tem a sua própria parte.

ISO/IEC 29500: 2016

O seguinte exemplo de código XML representa uma apresentação que contém dois diapositivos indicados pelos IDs 267 e 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>

Com o SDK Open XML, pode criar a estrutura e o conteúdo do documento com classes com tipos fortes que correspondem a elementos PresentationML. Pode encontrar estas classes no espaço de nomes. A tabela seguinte lista os nomes das classes que correspondem aos sldelementos , sldLayout, sldMastere notesMaster .

Elemento PresentationML Abrir Classe SDK XML Descrição
<sld/> Slide Diapositivo de Apresentação. É o elemento raiz de SlidePart.
<sldLayout/> SlideLayout Esquema de Diapositivo. É o elemento raiz de SlideLayoutPart.
<sldMaster/> SlideMaster Modelo Global de Diapositivos. É o elemento raiz de SlideMasterPart.
<notesMaster/> NotesMaster Modelo Global de Notas (ou handoutMaster). É o elemento raiz de NotesMasterPart.

A Estrutura do Elemento de Comentário Moderno

O seguinte elemento XML especifica um único comentário. Contém o texto do comentário (t) e os atributos que se referem ao respetivo autor (authorId), data e hora de criação (created) e id de comentário (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>

As tabelas seguintes listam as definições dos possíveis elementos subordinados e atributos do cm elemento (comentário). Para obter a definição completa, consulte MS-PPTX 2.16.3.3 CT_Comment

Atributo Definição
id Especifica o ID de um comentário ou uma resposta de comentário.
authorId Especifica o ID de autor de um comentário ou uma resposta de comentário.
status Especifica a status de um comentário ou uma resposta de comentário.
criadas Especifica a data/hora em que o comentário ou a resposta ao comentário são criados.
startDate Especifica a data de início do comentário.
dueDate Especifica a data para conclusão do comentário.
assignedTo Especifica uma lista de autores a quem o comentário é atribuído.
complete Especifica a percentagem de conclusão do comentário.
title Especifica o título de um comentário.
Elemento Filho Definição
pc:sldMkLst Especifica um nome de conteúdo que identifica o diapositivo ao qual o comentário está ancorado.
ac:deMkLst Especifica um moniker de conteúdo que identifica o elemento de desenho ao qual o comentário está ancorado.
ac:txMkLst Especifica um nome de conteúdo que identifica o intervalo de carateres de texto ao qual o comentário está ancorado.
unknownAnchor Especifica uma âncora desconhecida à qual o comentário está ancorado.
pos Especifica a posição do comentário, relativamente ao canto superior esquerdo do primeiro objeto ao qual o comentário está ancorado.
replyLst Especifica a lista de respostas ao comentário.
txBody Especifica o texto de um comentário ou uma resposta de comentário.
extLst Especifica uma lista de extensões para um comentário ou uma resposta de comentário.

O seguinte exemplo de esquema XML define os membros do elemento para além dos cm atributos obrigatórios e opcionais.

 <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>

Como funciona o código de exemplo

O código de exemplo abre o documento de apresentação na instrução using. Em seguida, instancia o CommentAuthorsPart e verifica se existe uma parte dos autores de comentários. Se não existir, adiciona um.

// create missing PowerPointAuthorsPart if it is null
if (presentationDocument.PresentationPart.authorsPart is null)
{
    presentationDocument.PresentationPart.AddNewPart<PowerPointAuthorsPart>();
}

O código determina se existe uma parte do autor do PowerPoint existente na parte da apresentação; caso contrário, adiciona um e, em seguida, verifica se existe uma lista de autores e adiciona uma se estiver em falta. Verifica também se o autor que é transmitido está na lista de autores existentes; Se for o caso, atribui o ID de autor existente. Caso contrário, adiciona um novo autor à lista de autores e atribui um ID de autor e os valores dos parâmetros.

// 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);
}

Em seguida, o código determina se existe um ID de diapositivo e devolve se um não existe

// 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;

No segmento abaixo, o código obtém o ID da relação. Se existir, é utilizado para localizar a parte do diapositivo, caso contrário, é efetuada a enumeração do primeiro diapositivo nas partes do diapositivo. Em seguida, verifica se existe uma parte de comentários do PowerPoint para o diapositivo e se não adiciona uma.

// 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>();

Abaixo do código, cria um novo comentário moderno e, em seguida, adiciona uma lista de comentários à parte de comentário do PowerPoint se não existir um e adiciona o comentário a essa lista de comentários.

// 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);

Com os comentários modernos, o diapositivo tem de ter a extensão e a lista de extensões corretas. O código seguinte determina se o diapositivo já tem um SlideExtensionList e SlideExtension e adiciona-os ao diapositivo se não estiverem presentes.

// 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) });
}

Código de exemplo

Segue-se o exemplo de código completo que mostra como adicionar um novo comentário com um autor novo ou existente a um diapositivo com ou sem comentários existentes.

Observação

Para obter o nome exato do autor e as iniciais, abra o ficheiro de apresentação, clique no item de menu Ficheiro e, em seguida, clique em Opções. A janela PowerPointOptions é aberta e o conteúdo do separador Geral é apresentado. O nome e as iniciais do autor têm de corresponder ao Nome de utilizador e às Iniciais neste separador.

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) });
        }
    }
}

Confira também