@createiq/htmldiff 1.0.5-beta.3 → 1.0.5-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/HtmlDiff.cjs +67 -1
- package/dist/HtmlDiff.cjs.map +1 -1
- package/dist/HtmlDiff.mjs +67 -1
- package/dist/HtmlDiff.mjs.map +1 -1
- package/package.json +1 -1
- package/src/TableDiff.ts +84 -8
- package/test/HtmlDiff.tables.matrix.spec.ts +40 -0
- package/test/HtmlDiff.tables.spec.ts +80 -0
package/dist/HtmlDiff.cjs
CHANGED
|
@@ -411,7 +411,7 @@ function diffPositionalTable(oldHtml, newHtml, oldTable, newTable, diffCell) {
|
|
|
411
411
|
* sides.
|
|
412
412
|
*/
|
|
413
413
|
function diffStructurallyAlignedTable(oldHtml, newHtml, oldTable, newTable, diffCell) {
|
|
414
|
-
const alignment = pairSimilarUnmatchedRows(lcsAlign(oldTable.rows.map((row) => rowKey(oldHtml, row)), newTable.rows.map((row) => rowKey(newHtml, row))), oldTable, newTable, oldHtml, newHtml);
|
|
414
|
+
const alignment = orderAlignmentForEmission(pairSimilarUnmatchedRows(lcsAlign(oldTable.rows.map((row) => rowKey(oldHtml, row)), newTable.rows.map((row) => rowKey(newHtml, row))), oldTable, newTable, oldHtml, newHtml));
|
|
415
415
|
if (newTable.rows.length === 0) return rebuildStructurallyAlignedTable(oldHtml, newHtml, oldTable, newTable, alignment, diffCell);
|
|
416
416
|
const out = [];
|
|
417
417
|
out.push(newHtml.slice(newTable.tableStart, newTable.rows[0].rowStart));
|
|
@@ -426,6 +426,72 @@ function diffStructurallyAlignedTable(oldHtml, newHtml, oldTable, newTable, diff
|
|
|
426
426
|
out.push(newHtml.slice(cursor, newTable.tableEnd));
|
|
427
427
|
return out.join("");
|
|
428
428
|
}
|
|
429
|
+
/**
|
|
430
|
+
* Reorders the alignment so emission produces rows in the visually-
|
|
431
|
+
* correct order. Each entry is assigned a fractional "position" in
|
|
432
|
+
* new's flow:
|
|
433
|
+
*
|
|
434
|
+
* • Preserved/paired (oldIdx, newIdx): position = newIdx.
|
|
435
|
+
* • Pure insert (null, newIdx): position = newIdx.
|
|
436
|
+
* • Pure delete (oldIdx, null): position = newIdx-of-preserved-just-
|
|
437
|
+
* before-this-oldIdx + 0.5. Dels at the same gap sort by oldIdx so
|
|
438
|
+
* they appear in old's row order. The +0.5 places dels BEFORE any
|
|
439
|
+
* insert at the same gap (insert at newIdx N1+1 has position N1+1
|
|
440
|
+
* which is > N1+0.5), giving the natural "delete first, insert
|
|
441
|
+
* second" reading order at a replaced position.
|
|
442
|
+
*
|
|
443
|
+
* This handles the full range:
|
|
444
|
+
* • Run of unpaired dels at the start (no preserved predecessor):
|
|
445
|
+
* position -0.5, sorted by oldIdx.
|
|
446
|
+
* • Dels in the middle: positioned right after their preceding
|
|
447
|
+
* preserved row.
|
|
448
|
+
* • Dels at the end (no preserved successor): positioned after the
|
|
449
|
+
* last preserved row.
|
|
450
|
+
*
|
|
451
|
+
* Without this reordering, a run of unpaired deletes at low alignment
|
|
452
|
+
* indices got emitted at cursor = first-new-row position — putting
|
|
453
|
+
* all deletes before any preserved row in the output, regardless of
|
|
454
|
+
* where they came from in old.
|
|
455
|
+
*/
|
|
456
|
+
function orderAlignmentForEmission(alignment) {
|
|
457
|
+
const preserved = [];
|
|
458
|
+
for (const a of alignment) if (a.oldIdx !== null && a.newIdx !== null) preserved.push({
|
|
459
|
+
oldIdx: a.oldIdx,
|
|
460
|
+
newIdx: a.newIdx
|
|
461
|
+
});
|
|
462
|
+
preserved.sort((a, b) => a.oldIdx - b.oldIdx);
|
|
463
|
+
function newIdxOfPreservedBefore(oldIdx) {
|
|
464
|
+
let result = -1;
|
|
465
|
+
for (const p of preserved) {
|
|
466
|
+
if (p.oldIdx >= oldIdx) break;
|
|
467
|
+
result = p.newIdx;
|
|
468
|
+
}
|
|
469
|
+
return result;
|
|
470
|
+
}
|
|
471
|
+
const decorated = alignment.map((a, i) => {
|
|
472
|
+
let primary;
|
|
473
|
+
let secondary;
|
|
474
|
+
if (a.newIdx !== null) {
|
|
475
|
+
primary = a.newIdx;
|
|
476
|
+
secondary = a.oldIdx === null ? 1 : 0;
|
|
477
|
+
} else {
|
|
478
|
+
primary = newIdxOfPreservedBefore(a.oldIdx) + .5;
|
|
479
|
+
secondary = a.oldIdx;
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
entry: a,
|
|
483
|
+
primary,
|
|
484
|
+
secondary,
|
|
485
|
+
originalIdx: i
|
|
486
|
+
};
|
|
487
|
+
});
|
|
488
|
+
decorated.sort((a, b) => {
|
|
489
|
+
if (a.primary !== b.primary) return a.primary - b.primary;
|
|
490
|
+
if (a.secondary !== b.secondary) return a.secondary - b.secondary;
|
|
491
|
+
return a.originalIdx - b.originalIdx;
|
|
492
|
+
});
|
|
493
|
+
return decorated.map((d) => d.entry);
|
|
494
|
+
}
|
|
429
495
|
function rebuildStructurallyAlignedTable(oldHtml, newHtml, oldTable, newTable, alignment, diffCell) {
|
|
430
496
|
const out = [];
|
|
431
497
|
out.push(headerSlice(newHtml, newTable, oldHtml, oldTable));
|