Word allows comments to be added to a document. This is an aspect of the reviewing
feature-set and is typically used by a second party to provide feedback to the author
without changing the document itself.
The procedure is simple:
You select some range of text with the mouse or Shift+Arrow keys
You press the New Comment button (Review toolbar)
You type or paste in your comment
Comment Anatomy. Each comment has two parts, the comment-reference and the
comment-content:
The comment-refererence, sometimes comment-anchor, is the text you selected before
pressing the New Comment button. It is a range in the document content delimited by
a start marker and an end marker, and containing the id of the comment that refers to
it.
The comment-content is whatever content you typed or pasted in. The content for each
comment is stored in the separate comments-part (part-name word/comments.xml) as a
distinct comment object. Each comment has a unique id, allowing a comment reference to
be associated with its content and vice versa.
Comment Reference. The comment-reference is a range. A range must both start and
end at an even run boundary. Intuitively, a range corresponds to a selection of text
in the Word UI, one formed by dragging with the mouse or using the Shift-Arrow keys.
In general a range can span “run containers”, such as paragraphs, such that the range
begins in one paragraph and ends in a later paragraph. However, a range must enclose
contiguous runs, such that a range that contains only two vertically adjacent cells in
a multi-column table is not possible (even though such a selection with the mouse is
possible).
Comment Content. Interestingly, although commonly used to contain a single line of
plain text, the comment-content can contain essentially any content that can appear in
the document body. This includes rich text with emphasis, runs with a different typeface
and size, both paragraph and character styles, hyperlinks, images, and tables. Note that
tables do not appear in the comment as displayed in the comment-sidebar although they
do apper in the reviewing-pane.
Comment Metadata. Each comment can be assigned author, initals, and date
metadata. In Word, these fields are assigned automatically based on values in Settings>User of the installed Word application. These may be configured automatically in an
enterprise installation, based on the user account, but by default they are empty.
author metadata is required, although silently assigned the empty string by Word if
the user name is not configured. initials is optional, but always set by Word, to the
empty string if not configured. date is also optional, but always set by Word to the
date and time the comment was added (seconds resolution, UTC).
Additional Features. Later versions of Word allow a comment to be resolved. A
comment in this state will appear grayed-out in the Word UI. Later versions of Word also
allow a comment to be replied to, forming a comment thread. Neither of these
features is supported by the initial implementation of comments in python-docx.
The resolved-status and replies features are implemented as extensions and involve two
additional comment-related parts:
commentsExtended.xml - contains completion (resolved) status and parent-id for
threading comment responses; keys to w15:paraId of comment paragraph in
comments.xml
commentsIds.xml - maps w16cid:paraId to w16cid:durableId, not sure what that is
exactly.
Applicability. Note that comments cannot be added to a header or footer and cannot
be nested inside a comment itself. In general the python-docx API will not allow these
operations but if you outsmart it then the resulting comment will either be silently
removed or trigger a repair error when the document is loaded by Word.
A DOCX package does not contain a comments.xml part by default. It is added to the
package when the first comment is added to the document.
A newly-created comment contains a single paragraph
Word starts w:id at 0 and increments from there. It appears to use a
max(comment_ids) + 1 algorithm rather than aggressively filling in id numbering
gaps.
Word-behavior: looks like Word doesn’t allow a “zero-length” comment reference; if you
insert a comment when no text is selected, the word prior to the insertion-point is
selected.
Word allows a comment to be applied to a range that starts before any character and
ends after any later character. However, the XML range-markers can only be placed
between runs. Word accommodates this be breaking runs as necessary to start and stop
at the desired character positions.
Comments.Add(Range,Text)->Comment# -- retrieve comment by array idx, not comment_id key --Comments.Item(idx:Long)->CommentComments.Count()->Long# -- restrict visible comments to those by a particular reviewerComments.ShowBy="Travis McGuillicuddy"
# -- delete comment and all replies to it --Comment.DeleteRecursively()->void# -- open OLE object embedded in comment for editing --Comment.Edit()->void# -- get the "parent" comment when this comment is a reply --Comment.Ancestor()->Comment|Nothing# -- author of this comment, with email and name fields --Comment.Contact->CoAuthorComment.Date->DateComment.Done->boolComment.IsInk->bool# -- content of the comment, contrast with `Reference` below --Comment.Range->Range# -- content within document this comment refers to --Comment.Reference->RangeComment.Replies->Comments# -- described in API docs like the same thing as `Reference` --Comment.Scope->Range
The critical required reference for adding a comment is the range referred to by the
comment; i.e. the “selection” of text that is being commented on. Because this range
must start and end at an even run boundary, it is enough to specify the first and last
run in the range, where a single run can be both the start and end run:
>>> paragraph=document.add_paragraph("Hello, world!")>>> document.add_comment(... runs=paragraph.runs,... text="I have this to say about that"... author="Ethan St. Lee",... initials="SC",... )<docx.comments.Comment object at 0x02468ACE>
A single run can be provided when that is more convenient:
>>> paragraph=document.add_paragraph("Summary: ")>>> run=paragraph.add_run("{{place-summary-here}}>>> document.add_comment(... run,text="The AI model will replace this placeholder with a summary"... )<docx.comments.Comment object at 0x02468ACE>
Note that author and initials are optional parameters; both default to the empty
string.
text is also an optional parameter and also defaults to the empty string. Omitting a
text argument (or passing text=””) produces a comment containing a single paragraph
you can immediately add runs to and add additional paragraphs after:
>>> paragraph=document.add_paragraph("Summary: ")>>> run=paragraph.add_run("{{place-summary-here}}")>>> comment=document.add_comment(run)>>> paragraph=comment.paragraphs[0]>>> paragraph.add_run("The ")>>> paragraph.add_run("AI model").bold=True>>> paragraph.add_run(" will replace this placeholder with a ")>>> paragraph.add_run("summary").bold=True<docx.comments.Comment object at 0x02468ACE>
A method directly on Run may also be convenient, since you will always have the first
run of the range in hand when adding a comment but may not have ready access to the
document object:
>>> runs=find_sequence_of_one_or_more_runs_to_comment_on()>>> runs[0].add_comment(... last_run=runs[-1],... text="The AI model will replace this placeholder with a summary",... )<docx.comments.Comment object at 0x02468ACE>
However, in this situation we would need to qualify the runs as being inside the
document part and not in a header or footer or comment, and perhaps other invalid
comment locations. I believe comments can be applied to footnotes and endnotes though.
w:comment is a block-item container, and can contain any content that can appear
in a document body or table cell, including both paragraphs and tables (and whatever
can go inside those, like images, hyperlinks, etc.
Word places the w:annotationRef-containing run as the first run in the first
paragraph of the comment. I haven’t been able to detect any behavior change caused by
leaving this out or placing it elsewhere in the comment content.
Relationships referenced from within w:comment content are relationships from the
comments part to the image part, hyperlink, etc.
w:commentRangeStart and w:commentRangeEnd elements are optional. The
authoritative position of the comment is the required w:commentReference element.
This means the ending location of a comment anchor can be efficiently found using
XPath.
commentRangeStart and commentRangeEnd are both type CT_MarkupRange and both
belong to EG_RunLevelElts (peers of w:r) which gives them their positioning in the
document structure.
These two markers can occur at the block level, at the run level, or at the table
row or cell level. However Word only seems to use them as peers of w:r. These can
occur as a sibling to:
a paragraph (w:p)
a table (w:tbl)
a run (w:r)
a table row (w:tr)
a table cell (w:tc)
<!-- marker types that appear in `document.xml` to mark the referenced range --><xsd:elementname="commentRangeStart"type="CT_MarkupRange"/><xsd:elementname="commentRangeEnd"type="CT_MarkupRange"/><xsd:elementname="commentReference"type="CT_Markup"/><xsd:complexTypename="CT_MarkupRange"><xsd:attributename="id"type="ST_DecimalNumber"use="required"/><xsd:attributename="displacedByCustomXml"type="ST_DisplacedByCustomXml"use="optional"/></xsd:complexType><xsd:simpleTypename="ST_DisplacedByCustomXml"><xsd:restrictionbase="xsd:string"><xsd:enumerationvalue="next"/><xsd:enumerationvalue="prev"/></xsd:restriction></xsd:simpleType><xsd:complexTypename="CT_Markup"><xsd:attributename="id"type="ST_DecimalNumber"use="required"/></xsd:complexType><!-- CT_Comment (individual comment in comments.xml) consolidated --><xsd:complexTypename="CT_Comment"><!-- denormalized --><xsd:attributename="id"type="ST_DecimalNumber"use="required"/><xsd:attributename="author"type="s:ST_String"use="required"/><xsd:attributename="date"type="ST_DateTime"use="optional"/><xsd:attributename="initials"type="s:ST_String"use="optional"/><xsd:sequence><xsd:choiceminOccurs="0"maxOccurs="unbounded"><xsd:elementname="customXml"type="CT_CustomXmlBlock"/><xsd:elementname="sdt"type="CT_SdtBlock"/><xsd:elementname="p"type="CT_P"minOccurs="0"maxOccurs="unbounded"/><xsd:elementname="tbl"type="CT_Tbl"minOccurs="0"maxOccurs="unbounded"/><xsd:groupref="EG_RunLevelElts"minOccurs="0"maxOccurs="unbounded"/><xsd:elementname="altChunk"type="CT_AltChunk"minOccurs="0"maxOccurs="unbounded"/></xsd:choice></xsd:sequence></xsd:complexType><xsd:groupname="EG_RunLevelElts"><xsd:choice><xsd:elementname="proofErr"minOccurs="0"type="CT_ProofErr"/><xsd:elementname="permStart"minOccurs="0"type="CT_PermStart"/><xsd:elementname="permEnd"minOccurs="0"type="CT_Perm"/><xsd:groupref="EG_RangeMarkupElements"minOccurs="0"maxOccurs="unbounded"/><xsd:elementname="ins"type="CT_RunTrackChange"minOccurs="0"/><xsd:elementname="del"type="CT_RunTrackChange"minOccurs="0"/><xsd:elementname="moveFrom"type="CT_RunTrackChange"/><xsd:elementname="moveTo"type="CT_RunTrackChange"/><xsd:groupref="EG_MathContent"minOccurs="0"maxOccurs="unbounded"/></xsd:choice></xsd:group><!-- referenced types --><xsd:complexTypename="CT_Comment"><xsd:complexContent><xsd:extensionbase="CT_TrackChange"><xsd:sequence><xsd:groupref="EG_BlockLevelElts"minOccurs="0"maxOccurs="unbounded"/></xsd:sequence><xsd:attributename="initials"type="s:ST_String"use="optional"/></xsd:extension></xsd:complexContent></xsd:complexType><xsd:complexTypename="CT_TrackChange"><xsd:complexContent><xsd:extensionbase="CT_Markup"><xsd:attributename="author"type="s:ST_String"use="required"/><xsd:attributename="date"type="ST_DateTime"use="optional"/></xsd:extension></xsd:complexContent></xsd:complexType><xsd:complexTypename="CT_Markup"><xsd:attributename="id"type="ST_DecimalNumber"use="required"/></xsd:complexType><xsd:groupname="EG_BlockLevelElts"><xsd:choice><xsd:groupref="EG_BlockLevelChunkElts"minOccurs="0"maxOccurs="unbounded"/><xsd:elementname="altChunk"type="CT_AltChunk"minOccurs="0"maxOccurs="unbounded"/></xsd:choice></xsd:group><xsd:groupname="EG_BlockLevelChunkElts"><xsd:choice><xsd:groupref="EG_ContentBlockContent"minOccurs="0"maxOccurs="unbounded"/></xsd:choice></xsd:group><xsd:groupname="EG_ContentBlockContent"><xsd:choice><xsd:elementname="customXml"type="CT_CustomXmlBlock"/><xsd:elementname="sdt"type="CT_SdtBlock"/><xsd:elementname="p"type="CT_P"minOccurs="0"maxOccurs="unbounded"/><xsd:elementname="tbl"type="CT_Tbl"minOccurs="0"maxOccurs="unbounded"/><xsd:groupref="EG_RunLevelElts"minOccurs="0"maxOccurs="unbounded"/></xsd:choice></xsd:group><xsd:groupname="EG_RunLevelElts"><xsd:choice><xsd:elementname="proofErr"minOccurs="0"type="CT_ProofErr"/><xsd:elementname="permStart"minOccurs="0"type="CT_PermStart"/><xsd:elementname="permEnd"minOccurs="0"type="CT_Perm"/><xsd:groupref="EG_RangeMarkupElements"minOccurs="0"maxOccurs="unbounded"/><xsd:elementname="ins"type="CT_RunTrackChange"minOccurs="0"/><xsd:elementname="del"type="CT_RunTrackChange"minOccurs="0"/><xsd:elementname="moveFrom"type="CT_RunTrackChange"/><xsd:elementname="moveTo"type="CT_RunTrackChange"/><xsd:groupref="EG_MathContent"minOccurs="0"maxOccurs="unbounded"/></xsd:choice></xsd:group>
Comments#
Word allows comments to be added to a document. This is an aspect of the reviewing feature-set and is typically used by a second party to provide feedback to the author without changing the document itself.
The procedure is simple:
You select some range of text with the mouse or Shift+Arrow keys
You press the New Comment button (Review toolbar)
You type or paste in your comment
Comment Anatomy. Each comment has two parts, the comment-reference and the comment-content:
The comment-refererence, sometimes comment-anchor, is the text you selected before pressing the New Comment button. It is a range in the document content delimited by a start marker and an end marker, and containing the id of the comment that refers to it.
The comment-content is whatever content you typed or pasted in. The content for each comment is stored in the separate comments-part (part-name
word/comments.xml) as a distinct comment object. Each comment has a unique id, allowing a comment reference to be associated with its content and vice versa.Comment Reference. The comment-reference is a range. A range must both start and end at an even run boundary. Intuitively, a range corresponds to a selection of text in the Word UI, one formed by dragging with the mouse or using the Shift-Arrow keys.
In general a range can span “run containers”, such as paragraphs, such that the range begins in one paragraph and ends in a later paragraph. However, a range must enclose contiguous runs, such that a range that contains only two vertically adjacent cells in a multi-column table is not possible (even though such a selection with the mouse is possible).
Comment Content. Interestingly, although commonly used to contain a single line of plain text, the comment-content can contain essentially any content that can appear in the document body. This includes rich text with emphasis, runs with a different typeface and size, both paragraph and character styles, hyperlinks, images, and tables. Note that tables do not appear in the comment as displayed in the comment-sidebar although they do apper in the reviewing-pane.
Comment Metadata. Each comment can be assigned author, initals, and date metadata. In Word, these fields are assigned automatically based on values in
Settings > Userof the installed Word application. These may be configured automatically in an enterprise installation, based on the user account, but by default they are empty.author metadata is required, although silently assigned the empty string by Word if the user name is not configured. initials is optional, but always set by Word, to the empty string if not configured. date is also optional, but always set by Word to the date and time the comment was added (seconds resolution, UTC).
Additional Features. Later versions of Word allow a comment to be resolved. A comment in this state will appear grayed-out in the Word UI. Later versions of Word also allow a comment to be replied to, forming a comment thread. Neither of these features is supported by the initial implementation of comments in python-docx.
The resolved-status and replies features are implemented as extensions and involve two additional comment-related parts:
commentsExtended.xml - contains completion (resolved) status and parent-id for threading comment responses; keys to w15:paraId of comment paragraph in comments.xml
commentsIds.xml - maps w16cid:paraId to w16cid:durableId, not sure what that is exactly.
Applicability. Note that comments cannot be added to a header or footer and cannot be nested inside a comment itself. In general the python-docx API will not allow these operations but if you outsmart it then the resulting comment will either be silently removed or trigger a repair error when the document is loaded by Word.
Word Behavior#
A DOCX package does not contain a
comments.xmlpart by default. It is added to the package when the first comment is added to the document.A newly-created comment contains a single paragraph
Word starts w:id at 0 and increments from there. It appears to use a max(comment_ids) + 1 algorithm rather than aggressively filling in id numbering gaps.
Word-behavior: looks like Word doesn’t allow a “zero-length” comment reference; if you insert a comment when no text is selected, the word prior to the insertion-point is selected.
Word allows a comment to be applied to a range that starts before any character and ends after any later character. However, the XML range-markers can only be placed between runs. Word accommodates this be breaking runs as necessary to start and stop at the desired character positions.
MS API#
Document:
Comments
https://learn.microsoft.com/en-us/office/vba/api/word.comments:
Comment
https://learn.microsoft.com/en-us/office/vba/api/word.comment:
Candidate Protocol#
The critical required reference for adding a comment is the range referred to by the comment; i.e. the “selection” of text that is being commented on. Because this range must start and end at an even run boundary, it is enough to specify the first and last run in the range, where a single run can be both the start and end run:
A single run can be provided when that is more convenient:
Note that author and initials are optional parameters; both default to the empty string.
text is also an optional parameter and also defaults to the empty string. Omitting a text argument (or passing text=””) produces a comment containing a single paragraph you can immediately add runs to and add additional paragraphs after:
A method directly on
Runmay also be convenient, since you will always have the first run of the range in hand when adding a comment but may not have ready access to thedocumentobject:However, in this situation we would need to qualify the runs as being inside the document part and not in a header or footer or comment, and perhaps other invalid comment locations. I believe comments can be applied to footnotes and endnotes though.
Specimen XML#
comments.xml(namespace declarations may vary):Comment reference in document body:
Notes
w:comment is a block-item container, and can contain any content that can appear in a document body or table cell, including both paragraphs and tables (and whatever can go inside those, like images, hyperlinks, etc.
Word places the w:annotationRef-containing run as the first run in the first paragraph of the comment. I haven’t been able to detect any behavior change caused by leaving this out or placing it elsewhere in the comment content.
Relationships referenced from within w:comment content are relationships from the comments part to the image part, hyperlink, etc.
w:commentRangeStart and w:commentRangeEnd elements are optional. The authoritative position of the comment is the required w:commentReference element. This means the ending location of a comment anchor can be efficiently found using XPath.
Schema Excerpt#
Notes:
commentRangeStart and commentRangeEnd are both type CT_MarkupRange and both belong to EG_RunLevelElts (peers of w:r) which gives them their positioning in the document structure.
These two markers can occur at the block level, at the run level, or at the table row or cell level. However Word only seems to use them as peers of w:r. These can occur as a sibling to:
a paragraph (w:p)
a table (w:tbl)
a run (w:r)
a table row (w:tr)
a table cell (w:tc)