Recursive AI

Combine document reviewer changes in C#

OneDiff is a free, open-source diff library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies.

Collect reviewer changes against one original. This example uses text; Office files aren’t supported.

Terminal
dotnet add package RecursiveAI.OneDiff.Documents

Requires .NET 10 (net10.0); .NET 9 and earlier aren’t supported.

C#
using OneDiff.Documents.Combine;
using OneDiff.Documents.Model;
 
var original = CompareDocument.FromText(
    "Pay in 30 days.");
var result = Combination.Of(original, [
    CompareDocument.FromText("Pay in 60 days."),
    CompareDocument.FromText("Pay in 90 days.")]);
Console.WriteLine(
    $"Conflicts: {result.Conflicts.Length}");
Text
Conflicts: 1

Unzip the example, then dotnet run.

Full program

The original text is “Pay in 30 days.” Alice proposes 60 days and Bob proposes 90. The program keeps both proposals, names their reviewers and reports one conflict. Combination.Of requires at least one copy; an empty list throws ArgumentException.

C#
using OneDiff.Documents.Combine;
using OneDiff.Documents.Model;
 
var original = CompareDocument.FromText("Pay in 30 days.", "base");
var result = Combination.Of(original, [
    CompareDocument.FromText("Pay in 60 days.", "Alice"),
    CompareDocument.FromText("Pay in 90 days.", "Bob")]);
Console.WriteLine($"Changes: {result.Changes.Length}");
Console.WriteLine(
    $"Conflicts: {result.Conflicts.Length}");
foreach (var change in result.Changes)
    Console.WriteLine($"{change.Change.New.Text}; reviewers: " +
        string.Join(", ", change.Reviewers.Select(i => result.Reviewers[i].Name)));

dotnet run prints:

Text
Changes: 2
Conflicts: 1
60 days; reviewers: Alice
90 days; reviewers: Bob

Variations

Two reviewers make the same proposal. The duplicate is retained once with both attributions.

C#
using OneDiff.Documents.Combine;
using OneDiff.Documents.Model;
var original = CompareDocument.FromText("Pay in 30 days.", "base");
var result = Combination.Of(original, [
    CompareDocument.FromText("Pay in 60 days.", "Alice"),
    CompareDocument.FromText("Pay in 60 days.", "Bob")]);
Console.WriteLine($"Changes: {result.Changes.Length}");
Console.WriteLine($"Reviewers: {result.Changes[0].Reviewers.Length}");
Console.WriteLine(
    $"Conflicts: {result.Conflicts.Length}");
Text
Changes: 1
Reviewers: 2
Conflicts: 0

The short answer

Use Combination.Of(original, copies) from OneDiff’s Documents package. The result holds the per-reviewer comparisons, attributed combined changes and conflicts. It is a review result, not a new document in which competing edits have been accepted automatically.

Each copy must be an independent revision of the same original. Comparing Alice against Bob would answer a different question: what differs between their copies, rather than what each reviewer changed. The combination documentation states that the result “never contains a decision between reviewers.”

How do you combine changes from multiple reviewers?

Create one CompareDocument for the original and one for each returned copy. Pass the copies together to Combination.Of. The program above uses named text documents so the reviewers are identifiable in the output. Reviewer indexes follow the order of the supplied copies, and reviewer names come from each copy’s document name.

For each combined change, inspect its Reviewers indexes against the result’s Reviewers collection. The full program uses that mapping to print Alice and Bob rather than unexplained numeric indexes. Keep those identities alongside the proposed text in your review interface.

The inputs here are ordinary strings, not embedded tracked-change records. OneDiff compares the content of each copy with the original to discover edits. The result retains each reviewer’s comparison as well as the combined list; collecting the proposals does not require throwing away their individual histories.

How do you combine identical changes from multiple authors?

When two reviewers make the same change at the same positions in the original, OneDiff represents it as one combined change attributed to both. The variation gives Alice and Bob the identical proposal, “Pay in 60 days.” It prints one change, two reviewers and no conflict.

Sameness requires the same kind, claims and new text, including the relevant formatting effects. A claim describes which part of the original a change affects and what it does there. Merely seeing the same new word somewhere in both copies is not enough. Conversely, a shared edit inside differently grouped changes can have shared edit-level attribution without collapsing the entire changes into one.

This distinction matters when building a review screen: a combined change’s authors and an individual edit’s authors are not always interchangeable. Use the whole-change attribution for the proposal as shown, and EditReviewers when displaying individual edits inside it.

Can you combine document revisions without losing conflicting changes?

Overlapping incompatible claims from different reviewers are flagged rather than resolved. Alice’s 60-day term and Bob’s 90-day term replace the same original range, so both remain visible in one conflict. A node deleted by one reviewer and edited by another also conflicts. Two edits to separate words of the same paragraph do not necessarily conflict.

Conflicts are groups of connected conflicting changes, not simply a count of reviewer pairs. Several reviewers may therefore belong to one conflict. Identical claims do not conflict.

For already-computed comparisons, Combination.From requires the very same original CompareDocument object in every comparison. Reconstructing equal original text in separate objects does not meet that API requirement. Keep and reuse the original object if you build comparisons yourself.

Can you combine reviewer changes in a PDF?

For the PDF package and reader registration call, see Compare PDFs and highlight differences. The Documents layer can compare PDF-derived document models when that reader is registered. This does not make a combination an editable Office document or a final accepted PDF. The package release does not support Office inputs; export supported text-bearing PDFs or use text inputs instead.

Use a three-way text merge when the goal is one merged string with an explicit conflict policy. Use a reviewer combination when the goal is to preserve proposals, authorship and disagreements for a reviewer to decide.

All onediff recipes