segment-comparer
v2.1.0
Published
Identify what has been added, removed, and unchanged by comparing two segments
Maintainers
Readme
SegmentComparer (JavaScript)
JavaScript port of the SegmentComparer library. Identify what has been added, removed, and unchanged by comparing two segments.
See also: .NET version: https://www.nuget.org/packages/SegmentComparer
Installation
Copy the library files into your project or use as ES6 modules.
Quick Start
import { Comparer, ComparisonUnit } from './segment-comparer/index.js';
const comparer = new Comparer();
const original = \"The quick brown fox\";
const updated = \"The quick red fox\";
// Compare segments
const result = comparer.compareSegment(\"seg-1\", original, updated);
// Access results
const added = result.ComparisonUnits.filter(u => u.Type === ComparisonUnit.ComparisonType.New);
const removed = result.ComparisonUnits.filter(u => u.Type === ComparisonUnit.ComparisonType.Removed);
const same = result.ComparisonUnits.filter(u => u.Type === ComparisonUnit.ComparisonType.Identical);
console.log(\"Added:\", added.map(u => u.Text).join(\", \"));
console.log(\"Removed:\", removed.map(u => u.Text).join(\", \"));
console.log(\"Unchanged:\", same.map(u => u.Text).join(\", \"));
// Get edit distance
const editDistance = comparer.getSegmentEditDistance(original, updated, true);
console.log(`Edit distance: ${editDistance.distance} of ${editDistance.relative}`);API
Comparer
compareSegment(segmentId, original, updated, includeTags, tagVisualType, semanticGrouping, comparisonType)
Compare two segments and return detailed comparison results.
Parameters:
segmentId(string): Segment identifieroriginal(string|Array): Original segment text or array of Section objectsupdated(string|Array): Updated segment text or array of Section objectsincludeTags(boolean, default: true): Include tags in comparisontagVisualType(number, default: 2):- 0 = NoTagText
- 1 = PartialTagText
- 2 = FullTagText
- 3 = None
semanticGrouping(boolean, default: true): Consolidate adjacent similar changescomparisonType(number, default: 0):- 0 = Words
- 1 = Characters
Returns: ComparisonResult object containing:
Id: Segment IDComparisonUnits: Array of comparison unitsEditDistance: Object with distance and relative valuesOriginalWordCount: Word count statistics for originalUpdatedWordCount: Word count statistics for updated
getSegmentEditDistance(original, updated, includeTags)
Get the Damerau-Levenshtein edit distance between two segments.
Parameters:
original(string|Array): Original segmentupdated(string|Array): Updated segmentincludeTags(boolean, default: true): Include tags in calculation
Returns: Object with { distance, relative } properties
Building Highlighted Output
Markdown-Safe Output
function buildMarkdown(result) {
let output = '';
for (const unit of result.ComparisonUnits) {
const text = unit.Text;
if (unit.Type === ComparisonUnit.ComparisonType.Removed) {
output += `~~${text}~~`;
} else {
output += text;
}
}
return output;
}
const markdown = buildMarkdown(result);
console.log(markdown); // \"The quick ~~brown~~ red fox\"HTML with Inline Styles
function buildHtmlInline(result) {
const styles = {
[ComparisonUnit.ComparisonType.New]: 'color:#1976d2;',
[ComparisonUnit.ComparisonType.Removed]: 'color:#d32f2f;text-decoration:line-through;text-decoration-thickness:2px;text-decoration-color:#d32f2f;',
[ComparisonUnit.ComparisonType.Identical]: 'color:#000;'
};
let html = '<div class=\"diff\">';
for (const unit of result.ComparisonUnits) {
const text = unit.Text.replace(/</g, '<').replace(/>/g, '>');
const style = styles[unit.Type] || 'color:#000;';
html += `<span style=\"${style}\">${text}</span>`;
}
html += '</div>';
return html;
}
const html = buildHtmlInline(result);For Word Add-in Usage
// In your Word task pane
async function insertComparison(result) {
await Word.run(async (context) => {
const range = context.document.getSelection();
for (const unit of result.ComparisonUnits) {
const textRange = range.insertText(unit.Text, Word.InsertLocation.end);
if (unit.Type === ComparisonUnit.ComparisonType.New) {
textRange.font.color = '#1976d2';
} else if (unit.Type === ComparisonUnit.ComparisonType.Removed) {
textRange.font.color = '#d32f2f';
textRange.font.strikeThrough = true;
}
}
await context.sync();
});
}Character-Level Comparison
For character-by-character comparison instead of word-by-word:
const result = comparer.compareSegment(
\"seg-1\",
original,
updated,
true, // includeTags
2, // tagVisualType
true, // semanticGrouping
1 // comparisonType: 1 = Characters
);Working with Tags
The library can parse and handle XML-like tags in segments:
const original = \"Hello <b>world</b>\";
const updated = \"Hello <b>universe</b>\";
const result = comparer.compareSegment(\"seg-1\", original, updated);
// Tags are preserved in the comparison
// Access sections to see text vs tag elements
for (const unit of result.ComparisonUnits) {
console.log(unit.Text, unit.TextType);
// TextType will be either ComparisonUnit.ContentType.Text or .Tag
}File Structure
segment-comparer/
├── index.js # Main entry point
├── Comparer.js # Main Comparer class
├── core/
│ ├── Helper.js # Utility functions
│ └── TextComparer.js # Comparison engine
└── structure/
├── Section.js # Base Section class
├── Text.js # Text section class
├── Tag.js # Tag section class
├── ComparisonUnit.js # Comparison unit class
├── ComparisonResult.js # Result container class
├── EditDistance.js # Edit distance class
├── WordCount.js # Word count statistics
└── Constants.js # Library constantsBrowser Compatibility
This library uses ES6 modules and modern JavaScript features. Ensure your environment supports:
- ES6 modules (import/export)
- Classes
- Template literals
- Array methods (map, filter, reduce)
- Unicode regex with
uflag
For older browsers, you may need to transpile with Babel.
License
Copyright © Patrick Andrew Hartnett 2025
Now let's create a simple example HTML file to demonstrate usage:
```html
<!DOCTYPE html>
<html lang=\"en\">
<head>
<meta charset=\"UTF-8\">
<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
<title>SegmentComparer Demo</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
.input-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
input, textarea {
width: 100%;
padding: 8px;
box-sizing: border-box;
}
textarea {
min-height: 80px;
font-family: monospace;
}
button {
background-color: #1976d2;
color: white;
padding: 10px 20px;
border: none;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #1565c0;
}
.results {
margin-top: 30px;
padding: 20px;
background-color: #f5f5f5;
border-radius: 4px;
}
.diff {
padding: 15px;
background-color: white;
border: 1px solid #ddd;
border-radius: 4px;
margin-top: 10px;
line-height: 1.6;
}
.stats {
margin-top: 15px;
padding: 10px;
background-color: #e3f2fd;
border-radius: 4px;
}
</style>
</head>
<body>
<h1>SegmentComparer Demo</h1>
<div class=\"input-group\">
<label for=\"original\">Original Text:</label>
<textarea id=\"original\">The quick brown fox jumps over the lazy dog</textarea>
</div>
<div class=\"input-group\">
<label for=\"updated\">Updated Text:</label>
<textarea id=\"updated\">The quick red fox jumps over the sleeping dog</textarea>
</div>
<button onclick=\"compareSegments()\">Compare Segments</button>
<div class=\"results\" id=\"results\" style=\"display: none;\">
<h2>Results</h2>
<div id=\"output\"></div>
<div class=\"stats\" id=\"stats\"></div>
</div>
<script type=\"module\">
import { Comparer, ComparisonUnit } from './index.js';
window.compareSegments = function() {
const original = document.getElementById('original').value;
const updated = document.getElementById('updated').value;
const comparer = new Comparer();
const result = comparer.compareSegment('demo', original, updated);
// Build HTML output
let html = '<div class=\"diff\">';
const styles = {
[ComparisonUnit.ComparisonType.New]: 'color:#1976d2;',
[ComparisonUnit.ComparisonType.Removed]: 'color:#d32f2f;text-decoration:line-through;text-decoration-thickness:2px;',
[ComparisonUnit.ComparisonType.Identical]: 'color:#000;'
};
for (const unit of result.ComparisonUnits) {
const text = unit.Text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>');
const style = styles[unit.Type] || 'color:#000;';
html += `<span style=\"${style}\">${text}</span>`;
}
html += '</div>';
// Build stats
const editDist = comparer.getSegmentEditDistance(original, updated, true);
const percentage = editDist.relative > 0
? ((editDist.distance / editDist.relative) * 100).toFixed(1)
: 0;
const stats = `
<strong>Statistics:</strong><br>
Edit Distance: ${editDist.distance} / ${editDist.relative} (${percentage}%)<br>
Original: ${result.OriginalWordCount.Words} words, ${result.OriginalWordCount.Characters} chars<br>
Updated: ${result.UpdatedWordCount.Words} words, ${result.UpdatedWordCount.Characters} chars
`;
document.getElementById('output').innerHTML = html;
document.getElementById('stats').innerHTML = stats;
document.getElementById('results').style.display = 'block';
};
</script>
</body>
</html>
### Usage:
Simply copy the library into your project and import:
```javascript
import { Comparer, ComparisonUnit } from './path/to/segment-comparer/index.js';
const comparer = new Comparer();
const result = comparer.compareSegment('seg-1', originalText, updatedText);