Skip to content

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.

CallWrites
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.go

If this is your first time using UniOffice, follow the getting started guide to create an API key and set up your development environment.

View the full source
Last updated on