Track Changes
UniOffice can create tracked changes. It cannot read them. AddInsertedText
and AddDeletedText write the w:ins and w:del markup that Word shows as a
revision attributed to an author, and that is the whole of the feature: there is
no getter for the revisions in an existing document, and no way to accept or
reject one. The insertions_deletions.docx sitting in the example directory is
what the example writes, not something it opens.
| Call | Writes |
|---|---|
p.AddInsertedText(newVal, revNum, author, t, id) | A w:ins element wrapping a run whose text is newVal. |
p.AddDeletedText(oldVal, revNum, author, t, id) | A w:del element wrapping a run whose text is oldVal, marked as deleted text. |
Both take the same five arguments and both append to the end of the paragraph’s current content, so the revision follows whatever runs are already there.
Writing revisions
doc := document.New()
defer doc.Close()
// Tell Word to keep recording edits made by whoever opens the file. Optional:
// the markup below is written either way.
doc.Settings.X().TrackRevisions = wml.NewCT_OnOff()
p := doc.AddParagraph()
p.AddRun().AddText("This is some paragraph text.")
p.AddInsertedText("Some inserted text is here.", "002773B1", "Author 1", time.Now(), 0)
p.AddDeletedText("Some deleted text is there.", "002773B1", "Author 2", time.Now(), 1)The settings flag and the revision markup are independent. Nothing in
AddInsertedText or AddDeletedText consults TrackRevisions, and turning the
flag on does not by itself mark any existing content as changed. The flag only
asks the editor to keep recording once the file is open.
There is no typed setter for it, hence the Settings.X() call and the
wml import. wml.NewCT_OnOff() leaves w:val unset, which OOXML reads as on;
leave the field nil to turn it off.
The id argument becomes w:id on the w:ins or w:del element and Word
expects it to be unique per revision within the document. revNum is written
through unchanged as w:rsidR on the run inside the revision, for insertions
and deletions alike. Word writes eight hexadecimal digits there. UniOffice
validates neither, so a duplicate ID or a malformed revision number reaches the
file as given, and an empty string produces an empty attribute.
Limitations
Nothing in the document package reads revision markup back. Inspecting the
revisions in an opened document means walking Paragraph.X().EG_PContent down
to RunLevelEltsChoice.Ins and .Del against the raw wml types yourself.
That gap reaches further than it looks. Paragraph.Runs() collects runs from
EG_ContentRunContent and never descends into w:ins or w:del, so revision
runs are invisible to it. Document.ExtractText() walks the same path, so
inserted and deleted text is missing from extracted text as well. Anything built
on either, find-and-replace included, silently skips revision content.
Only plain-text runs can be marked. There is no call for an inserted image, an inserted table row, a moved block, or a formatting-only revision, and the paragraph mark itself cannot be marked as inserted or deleted.
Because both calls append, a revision cannot be placed between runs that already exist. Build the paragraph in the order the finished markup should read.
Run the example
The example writes two paragraphs, one carrying an insertion by Author 1 and
one a deletion by Author 2, and saves insertions_deletions.docx. Open the
result in Word with markup shown to see the attribution and timestamps.
git clone https://github.com/unidoc/unioffice-examples.git
cd unioffice-examples/document/track-revisions
go run main.goIf this is your first time using UniOffice, follow the getting started guide to create an API key and set up your development environment.