Inserir um comentário em um documento de processamento de texto

Este tópico mostra como usar as classes no Open XML SDK for Office para adicionar programaticamente um comentário ao primeiro parágrafo em um documento de processamento de texto.


Abrir o documento existente para edição

Para abrir um documento existente, instancie a WordprocessingDocument classe conforme mostrado na instrução a seguir using . Na mesma instrução, abra o arquivo de processamento de texto no filepath especificado usando o Open(String, Boolean) método, com o parâmetro booleano definido como true para habilitar a edição no documento.

using (WordprocessingDocument document = WordprocessingDocument.Open(fileName, true))

Com v3.0.0+, o Close() método foi removido em favor de confiar na instrução using. Ele garante que o Dispose() método seja chamado automaticamente quando a chave de fechamento for atingida. O bloco que segue a instrução using estabelece um escopo para o objeto que é criado ou nomeado na instrução using. Como a WordprocessingDocument classe no Open XML SDK salva e fecha automaticamente o objeto como parte de sua IDisposable implementação e porque Dispose() é chamada automaticamente quando você sai do bloco, você não precisa chamar Save() explicitamente ou Dispose() desde que use uma using instrução.


Como funciona o código de exemplo

Depois de abrir o documento, você pode encontrar o primeiro parágrafo para anexar um comentário. O código localiza o primeiro parágrafo chamando o First método de extensão em todos os elementos descendentes do elemento document que são do tipo Paragraph. O First método é um membro da Enumerable classe. A System.Linq.Enumerable classe fornece métodos de extensão para objetos que implementam a IEnumerable<T> interface.

Paragraph firstParagraph = document.MainDocumentPart.Document.Descendants<Paragraph>().First();
wordprocessingCommentsPart.Comments ??= new Comments();
string id = "0";

O código primeiro determina se uma WordprocessingCommentsPart parte existe. Para fazer isso, chame o MainDocumentPart método genérico e GetPartsCountOfTypeespecifique um tipo de WordprocessingCommentsPart.

Se existir uma WordprocessingCommentsPart parte, o código obterá um novo Id valor para o Comment objeto que ele adicionará ao objeto de coleção existente WordprocessingCommentsPartComments . Ele faz isso encontrando o valor de atributo mais alto Id fornecido a um Comment no Comments objeto de coleção, incrementando o valor em um e armazenando-o como o Id valor. Se não houver nenhuma WordprocessingCommentsPart parte, o código criará uma usando o AddNewPart método do objeto e, em seguida, adicionará um Comments objeto de MainDocumentPart coleção a ela.

if (document.MainDocumentPart.GetPartsOfType<WordprocessingCommentsPart>().Count() > 0)
{
    if (wordprocessingCommentsPart.Comments.HasChildren)
    {
        // Obtain an unused ID.
        id = (wordprocessingCommentsPart.Comments.Descendants<Comment>().Select(e =>
        {
            if (e.Id is not null && e.Id.Value is not null)
            {
                return int.Parse(e.Id.Value);
            }
            else
            {
                throw new ArgumentNullException("Comment id and/or value are null.");
            }
        })
            .Max() + 1).ToString();
    }
}

Os Comment objetos and Comments representam elementos de comentários e comentários, respectivamente, no esquema de processamento de texto Open XML. Um Comment deve ser adicionado a um Comments objeto para que o código primeiro instancie um Comments objeto (usando os argumentos authorde cadeia de caracteres , initials, e comments que foram passados para o AddCommentOnFirstParagraph método).

O comentário é representado pelo seguinte exemplo de código WordprocessingML. .

    <w:comment w:id="1" w:initials="User">
      ...
    </w:comment>

Em seguida, o código anexa o Comment ao Comments objeto. Isso cria a estrutura de árvore DOM (modelo de objeto de documento) XML necessária na memória, que consiste em um comments elemento pai com comment elementos filho abaixo dele.

Paragraph p = new Paragraph(new Run(new Text(comment)));
Comment cmt =
    new Comment()
    {
        Id = id,
        Author = author,
        Initials = initials,
        Date = DateTime.Now
    };
cmt.AppendChild(p);
wordprocessingCommentsPart.Comments.AppendChild(cmt);

O exemplo de código WordprocessingML a seguir representa o conteúdo de uma parte de comentários em um documento WordprocessingML.

    <w:comments>
      <w:comment … >
        …
      </w:comment>
    </w:comments>

Com o Comment objeto instanciado, o código associa a Comment a um intervalo no documento Wordprocessing. CommentRangeStart and CommentRangeEnd correspondem aos commentRangeStart elementos and commentRangeEnd no esquema de processamento de texto Open XML. Um CommentRangeStart objeto é dado como argumento para o InsertBefore método do Paragraph objeto e um CommentRangeEnd objeto é passado para o InsertAfter método. Isso cria um intervalo de comentários que se estende imediatamente antes do primeiro caractere do primeiro parágrafo no documento de processamento de texto até imediatamente após o último caractere do primeiro parágrafo.

Um CommentReference objeto representa um commentReference elemento no esquema de processamento de texto Open XML. Um commentReference vincula WordprocessingCommentsPart um comentário específico na parte (o arquivo Comments.xml no pacote Wordprocessing) a um local específico no corpo do documento (a MainDocumentPart parte contida no arquivo Document.xml no pacote Wordprocessing). O id atributo do comentário, commentRangeStart, commentRangeEnd e commentReference é o mesmo para um determinado comentário, portanto, o atributo commentReference id deve corresponder ao valor do atributo de comentário id ao qual ele está vinculado. No exemplo, o código adiciona um commentReference elemento usando a API e instancia um CommentReference objeto, especificando o Id valor e, em seguida, adicionando-o a um Run objeto.

firstParagraph.InsertBefore(new CommentRangeStart()
{ Id = id }, firstParagraph.GetFirstChild<Run>());

// Insert the new CommentRangeEnd after last run of paragraph.
var cmtEnd = firstParagraph.InsertAfter(new CommentRangeEnd()
{ Id = id }, firstParagraph.Elements<Run>().Last());

// Compose a run with CommentReference and insert it.
firstParagraph.InsertAfter(new Run(new CommentReference() { Id = id }), cmtEnd);

Código de exemplo

O exemplo de código a seguir mostra como criar um comentário e associá-lo a um intervalo em um documento de processamento de texto. Para chamar o método AddCommentOnFirstParagraph , passe o caminho do documento, seu nome, suas iniciais e o texto do comentário.

string fileName = args[0];
string author = args[1];
string initials = args[2];
string comment = args[3];

AddCommentOnFirstParagraph(fileName, author, initials, comment);

A seguir está o código de exemplo completo em C# e em Visual Basic.

// Insert a comment on the first paragraph.
static void AddCommentOnFirstParagraph(string fileName, string author, string initials, string comment)
{
    // Use the file name and path passed in as an 
    // argument to open an existing Wordprocessing document. 
    using (WordprocessingDocument document = WordprocessingDocument.Open(fileName, true))
    {
        if (document.MainDocumentPart is null)
        {
            throw new ArgumentNullException("MainDocumentPart and/or Body is null.");
        }

        WordprocessingCommentsPart wordprocessingCommentsPart = document.MainDocumentPart.WordprocessingCommentsPart ?? document.MainDocumentPart.AddNewPart<WordprocessingCommentsPart>();

        // Locate the first paragraph in the document.
        Paragraph firstParagraph = document.MainDocumentPart.Document.Descendants<Paragraph>().First();
        wordprocessingCommentsPart.Comments ??= new Comments();
        string id = "0";

        // Verify that the document contains a 
        // WordProcessingCommentsPart part; if not, add a new one.
        if (document.MainDocumentPart.GetPartsOfType<WordprocessingCommentsPart>().Count() > 0)
        {
            if (wordprocessingCommentsPart.Comments.HasChildren)
            {
                // Obtain an unused ID.
                id = (wordprocessingCommentsPart.Comments.Descendants<Comment>().Select(e =>
                {
                    if (e.Id is not null && e.Id.Value is not null)
                    {
                        return int.Parse(e.Id.Value);
                    }
                    else
                    {
                        throw new ArgumentNullException("Comment id and/or value are null.");
                    }
                })
                    .Max() + 1).ToString();
            }
        }

        // Compose a new Comment and add it to the Comments part.
        Paragraph p = new Paragraph(new Run(new Text(comment)));
        Comment cmt =
            new Comment()
            {
                Id = id,
                Author = author,
                Initials = initials,
                Date = DateTime.Now
            };
        cmt.AppendChild(p);
        wordprocessingCommentsPart.Comments.AppendChild(cmt);

        // Specify the text range for the Comment. 
        // Insert the new CommentRangeStart before the first run of paragraph.
        firstParagraph.InsertBefore(new CommentRangeStart()
        { Id = id }, firstParagraph.GetFirstChild<Run>());

        // Insert the new CommentRangeEnd after last run of paragraph.
        var cmtEnd = firstParagraph.InsertAfter(new CommentRangeEnd()
        { Id = id }, firstParagraph.Elements<Run>().Last());

        // Compose a run with CommentReference and insert it.
        firstParagraph.InsertAfter(new Run(new CommentReference() { Id = id }), cmtEnd);
    }
}

Confira também