Insert a comment into a word processing document
This topic shows how to use the classes in the Open XML SDK for Office to programmatically add a comment to the first paragraph in a word processing document.
Open the Existing Document for Editing
To open an existing document, instantiate the WordprocessingDocument class as shown in
the following using
statement. In the same
statement, open the word processing file at the specified filepath by
using the Open(String, Boolean)
method, with the Boolean parameter set to true
to enable
editing in the document.
using (WordprocessingDocument document = WordprocessingDocument.Open(fileName, true))
With v3.0.0+ the Close() method
has been removed in favor of relying on the using statement.
It ensures that the Dispose() method is automatically called
when the closing brace is reached. The block that follows the using
statement establishes a scope for the object that is created or named in
the using statement. Because the WordprocessingDocument class in the Open XML SDK
automatically saves and closes the object as part of its IDisposable implementation, and because
Dispose() is automatically called when you
exit the block, you do not have to explicitly call Save() or
Dispose() as long as you use a using
statement.
How the Sample Code Works
After you open the document, you can find the first paragraph to attach
a comment. The code finds the first paragraph by calling the First
extension method on all the descendant elements of the document element
that are of type Paragraph. The First
method is a member
of the Enumerable class. The System.Linq.Enumerable
class
provides extension methods for objects that implement the IEnumerable<T> interface.
Paragraph firstParagraph = document.MainDocumentPart.Document.Descendants<Paragraph>().First();
wordprocessingCommentsPart.Comments ??= new Comments();
string id = "0";
The code first determines whether a WordprocessingCommentsPart
part exists. To do this, call the MainDocumentPart generic method,
GetPartsCountOfType
, and specify a kind of WordprocessingCommentsPart
.
If a WordprocessingCommentsPart
part exists, the code obtains a new Id
value for
the Comment object that it will add to the
existing WordprocessingCommentsPart
Comments
collection object. It does this by finding the highest Id
attribute value
given to a Comment
in the Comments
collection object, incrementing the
value by one, and then storing that as the Id
value.If no WordprocessingCommentsPart
part exists, the code
creates one using the AddNewPart
method of the MainDocumentPart object and then adds a
Comments
collection object to it.
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();
}
}
The Comment
and Comments
objects represent comment and comments
elements, respectively, in the Open XML Wordprocessing schema. A Comment
must be added to a Comments
object so the code first instantiates a
Comments
object (using the string arguments author
, initials
,
and comments
that were passed in to the AddCommentOnFirstParagraph
method).
The comment is represented by the following WordprocessingML code example. .
<w:comment w:id="1" w:initials="User">
...
</w:comment>
The code then appends the Comment
to the Comments
object. This
creates the required XML document object model (DOM) tree structure in
memory which consists of a comments
parent element with comment
child elements under
it.
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);
The following WordprocessingML code example represents the content of a comments part in a WordprocessingML document.
<w:comments>
<w:comment … >
…
</w:comment>
</w:comments>
With the Comment
object instantiated, the code associates the Comment
with a range in
the Wordprocessing document. CommentRangeStart and
CommentRangeEnd objects correspond to the
commentRangeStart
and commentRangeEnd
elements in the Open XML Wordprocessing schema.
A CommentRangeStart
object is given as the argument to the InsertBefore
method of the Paragraph object and a CommentRangeEnd
object is passed to the InsertAfter method.
This creates a comment range that extends from immediately before the first character of the first paragraph
in the Wordprocessing document to immediately after the last character of the first paragraph.
A CommentReference object represents a
commentReference
element in the Open XML Wordprocessing schema. A
commentReference links a specific comment in the WordprocessingCommentsPart
part (the Comments.xml
file in the Wordprocessing package) to a specific location in the
document body (the MainDocumentPart
part
contained in the Document.xml file in the Wordprocessing package). The
id
attribute of the comment,
commentRangeStart, commentRangeEnd, and commentReference is the same for
a given comment, so the commentReference id
attribute must match the comment id
attribute
value that it links to. In the sample, the code adds a commentReference
element by using the API, and
instantiates a CommentReference
object,
specifying the Id
value, and then adds it to a Run object.
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);
Sample Code
The following code example shows how to create a comment and associate
it with a range in a word processing document. To call the method AddCommentOnFirstParagraph
pass in the path of
the document, your name, your initials, and the comment text.
string fileName = args[0];
string author = args[1];
string initials = args[2];
string comment = args[3];
AddCommentOnFirstParagraph(fileName, author, initials, comment);
Following is the complete sample code in both C# and 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);
}
}