feat(diffrow): add processEqualities hook and ensure equalities are processed for unchanged lines (Fixes #219) (#224)

- Introduces a new protected method `processEqualities(String)` in DiffRowGenerator
- Builder exposes `.processEqualities(Function<String,String>)`
- Equal (unchanged) lines now invoke processEqualities()
- Inline diffs remain unchanged (as expected in Option 3)
- Added new test suite DiffRowGeneratorEqualitiesTest
- Updated documentation and Javadoc for new extension point
- Fixes #219 (HTML escaping issue when inline diff by word)

Co-authored-by: Tushar Soni <tushar.soni@siemens.com>
This commit is contained in:
tusharsoni52
2025-12-11 01:48:26 +05:30
committed by GitHub
parent b4b13c5575
commit 696edc4c1a
2 changed files with 128 additions and 2 deletions

View File

@@ -189,6 +189,8 @@ public final class DiffRowGenerator {
private final Function<String, String> lineNormalizer;
private final Function<String, String> processDiffs;
private final Function<InlineDeltaMergeInfo, List<AbstractDelta<String>>> inlineDeltaMerger;
// processor for equal (unchanged) lines
private final Function<String, String> equalityProcessor;
private final boolean showInlineDiffs;
private final boolean replaceOriginalLinefeedInChangesWithSpaces;
@@ -214,6 +216,7 @@ public final class DiffRowGenerator {
lineNormalizer = builder.lineNormalizer;
processDiffs = builder.processDiffs;
inlineDeltaMerger = builder.inlineDeltaMerger;
equalityProcessor = builder.equalityProcessor;
replaceOriginalLinefeedInChangesWithSpaces = builder.replaceOriginalLinefeedInChangesWithSpaces;
@@ -262,7 +265,8 @@ public final class DiffRowGenerator {
// Copy the final matching chunk if any.
for (String line : original.subList(endPos, original.size())) {
diffRows.add(buildDiffRow(Tag.EQUAL, line, line));
String processed = processEqualities(line);
diffRows.add(buildDiffRow(Tag.EQUAL, processed, processed));
}
return diffRows;
}
@@ -276,7 +280,8 @@ public final class DiffRowGenerator {
Chunk<String> rev = delta.getTarget();
for (String line : original.subList(endPos, orig.getPosition())) {
diffRows.add(buildDiffRow(Tag.EQUAL, line, line));
String processed = processEqualities(line);
diffRows.add(buildDiffRow(Tag.EQUAL, processed, processed));
}
switch (delta.getType()) {
@@ -496,6 +501,19 @@ public final class DiffRowGenerator {
}
}
/**
* Hook for processing equal (unchanged) text segments.
* Delegates to the builder-configured equalityProcessor if present.
*
* @author tusharsoni52
* @param text
* @return
*
*/
protected String processEqualities(final String text) {
return equalityProcessor != null ? equalityProcessor.apply(text) : text;
}
/**
* This class used for building the DiffRowGenerator.
*
@@ -521,6 +539,8 @@ public final class DiffRowGenerator {
private boolean replaceOriginalLinefeedInChangesWithSpaces = false;
private Function<InlineDeltaMergeInfo, List<AbstractDelta<String>>> inlineDeltaMerger =
DEFAULT_INLINE_DELTA_MERGER;
// Processor for equalities
private Function<String, String> equalityProcessor = null;
private Builder() {}
@@ -613,6 +633,20 @@ public final class DiffRowGenerator {
return this;
}
/**
* Processor for equal (unchanged) text parts.
* Allows applying the same escaping/transformation as for diffs.
*
* @author tusharsoni52
* @param equalityProcessor
* @return
*
*/
public Builder processEqualities(Function<String, String> equalityProcessor) {
this.equalityProcessor = equalityProcessor;
return this;
}
/**
* Set the column width of generated lines of original and revised
* texts.

View File

@@ -0,0 +1,92 @@
package com.github.difflib.text;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
public class DiffRowGeneratorEqualitiesTest {
@Test
public void testDefaultEqualityProcessingLeavesTextUnchanged() {
DiffRowGenerator generator =
DiffRowGenerator.create().showInlineDiffs(false).build();
List<DiffRow> rows = generator.generateDiffRows(Arrays.asList("hello world"), Arrays.asList("hello world"));
assertEquals(1, rows.size());
assertEquals("hello world", rows.get(0).getOldLine());
assertEquals("hello world", rows.get(0).getNewLine());
assertEquals(DiffRow.Tag.EQUAL, rows.get(0).getTag());
}
@Test
public void testCustomEqualityProcessingIsApplied() {
DiffRowGenerator generator = DiffRowGenerator.create()
.showInlineDiffs(false)
.processEqualities(text -> "[" + text + "]")
.build();
List<DiffRow> rows = generator.generateDiffRows(Arrays.asList("A", "B"), Arrays.asList("A", "B"));
assertEquals(2, rows.size());
assertEquals("[A]", rows.get(0).getOldLine());
assertEquals("[B]", rows.get(1).getOldLine());
}
/**
* Verifies that processEqualities can be used to HTML-escape unchanged
* lines while still working together with the default HTML-oriented
* lineNormalizer.
*/
@Test
public void testHtmlEscapingEqualitiesWorksWithDefaultNormalizer() {
DiffRowGenerator generator = DiffRowGenerator.create()
.showInlineDiffs(true)
.inlineDiffByWord(true)
.processEqualities(s -> s.replace("<", "&lt;").replace(">", "&gt;"))
.build();
// both lines are equal -> Tag.EQUAL, processEqualities is invoked
List<DiffRow> rows = generator.generateDiffRows(Arrays.asList("hello <world>"), Arrays.asList("hello <world>"));
DiffRow row = rows.get(0);
assertTrue(row.getOldLine().contains("&lt;world&gt;"));
assertTrue(row.getNewLine().contains("&lt;world&gt;"));
}
/**
* Ensures equalities are processed while inline diff markup is still
* present somewhere in the line.
*/
@Test
public void testEqualitiesProcessedButInlineDiffStillPresent() {
DiffRowGenerator generator = DiffRowGenerator.create()
.showInlineDiffs(true)
.inlineDiffByWord(true)
.processEqualities(s -> "(" + s + ")")
.build();
List<DiffRow> rows = generator.generateDiffRows(Arrays.asList("hello world"), Arrays.asList("hello there"));
DiffRow row = rows.get(0);
System.out.println("OLD = " + row.getOldLine());
System.out.println("NEW = " + row.getNewLine());
// Row must be CHANGE
assertEquals(DiffRow.Tag.CHANGE, row.getTag());
// Inline diff markup must appear
assertTrue(
row.getOldLine().contains("span") || row.getNewLine().contains("span"),
"Expected inline <span> diff markup in old or new line");
// Equalities inside CHANGE row must NOT be wrapped by processEqualities
// Option 3 does NOT modify inline equalities
assertTrue(row.getOldLine().startsWith("hello "), "Equal (unchanged) inline segment should remain unchanged");
}
}