DiffExtension.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. using System.Collections.Immutable;
  2. using System.Text;
  3. using System.Text.RegularExpressions;
  4. using static Masuit.Tools.TextDiff.DiffOperation;
  5. namespace Masuit.Tools.TextDiff;
  6. public static class DiffExtension
  7. {
  8. /// <summary>
  9. /// 还原文本1
  10. /// </summary>
  11. /// <param name="diffs"></param>
  12. /// <returns></returns>
  13. public static string Text1(this IEnumerable<TextDiffer> diffs) => diffs.Where(d => d.Operation != Insert).Aggregate(new StringBuilder(), (sb, diff) => sb.Append(diff.Text)).ToString();
  14. /// <summary>
  15. /// 还原文本2
  16. /// </summary>
  17. /// <param name="diffs"></param>
  18. /// <returns></returns>
  19. public static string Text2(this IEnumerable<TextDiffer> diffs) => diffs.Where(d => d.Operation != Delete).Aggregate(new StringBuilder(), (sb, diff) => sb.Append(diff.Text)).ToString();
  20. private readonly record struct LevenshteinState(int Insertions, int Deletions, int Levenshtein)
  21. {
  22. public LevenshteinState Consolidate() => new(0, 0, Levenshtein + Math.Max(Insertions, Deletions));
  23. }
  24. /// <summary>
  25. /// 计算Levenshtein距离;插入、删除或替换的字符数
  26. /// </summary>
  27. /// <param name="diffs"></param>
  28. /// <returns></returns>
  29. internal static int Levenshtein(this IEnumerable<TextDiffer> diffs)
  30. {
  31. var state = new LevenshteinState(0, 0, 0);
  32. state = diffs.Aggregate(state, (current, aDiff) => aDiff.Operation switch
  33. {
  34. Insert => current with
  35. {
  36. Insertions = current.Insertions + aDiff.Text.Length
  37. },
  38. Delete => current with
  39. {
  40. Deletions = current.Deletions + aDiff.Text.Length
  41. },
  42. Equal => current.Consolidate(),
  43. _ => throw new IndexOutOfRangeException()
  44. });
  45. return state.Consolidate().Levenshtein;
  46. }
  47. private static char ToDelta(this DiffOperation o) => o switch
  48. {
  49. Delete => '-',
  50. Insert => '+',
  51. Equal => '=',
  52. _ => throw new ArgumentException($"Unknown Operation: {o}")
  53. };
  54. /// <summary>
  55. /// 将diff压缩成一个编码字符串,该字符串描述了将text1转换为text2所需的操作。例如,=3\t-2\t+ing->:保留3个字符,删除2个字符,插入“ing”。操作以制表符分隔。插入的文本使用%xx符号转义。
  56. /// </summary>
  57. /// <param name="diffs"></param>
  58. /// <returns></returns>
  59. public static string ToDelta(this IEnumerable<TextDiffer> diffs)
  60. {
  61. var s = from aDiff in diffs
  62. let sign = aDiff.Operation.ToDelta()
  63. let textToAppend = aDiff.Operation == Insert ? aDiff.Text.UrlEncoded() : aDiff.Text.Length.ToString()
  64. select string.Concat(sign, textToAppend);
  65. return s.Join("\t");
  66. }
  67. private static DiffOperation FromDelta(char c) => c switch
  68. {
  69. '-' => Delete,
  70. '+' => Insert,
  71. '=' => Equal,
  72. _ => throw new ArgumentException($"Invalid Delta Token: {c}")
  73. };
  74. /// <summary>
  75. /// 从差异字符串中恢复diff对象
  76. /// </summary>
  77. /// <param name="text1"></param>
  78. /// <param name="delta"></param>
  79. /// <returns></returns>
  80. public static IEnumerable<TextDiffer> FromDelta(this string text1, string delta)
  81. {
  82. var pointer = 0;
  83. foreach (var token in delta.SplitBy('\t'))
  84. {
  85. if (token.Length == 0)
  86. {
  87. continue;
  88. }
  89. var param = token[1..];
  90. var operation = FromDelta(token[0]);
  91. int n = 0;
  92. if (operation != Insert)
  93. {
  94. if (!int.TryParse(param, out n))
  95. {
  96. throw new ArgumentException($"Invalid number in Diff.FromDelta: {param}");
  97. }
  98. if (pointer > text1.Length - n)
  99. {
  100. throw new ArgumentException($"Delta length ({pointer}) larger than source text length ({text1.Length}).");
  101. }
  102. }
  103. (var text, pointer) = operation switch
  104. {
  105. Insert => (param.Replace("+", "%2b").UrlDecoded(), pointer),
  106. Equal => (text1.Substring(pointer, n), pointer + n),
  107. Delete => (text1.Substring(pointer, n), pointer + n),
  108. _ => throw new ArgumentException($"Unknown Operation: {operation}")
  109. };
  110. yield return TextDiffer.Create(operation, text);
  111. }
  112. if (pointer != text1.Length)
  113. {
  114. throw new ArgumentException($"Delta length ({pointer}) smaller than source text length ({text1.Length}).");
  115. }
  116. }
  117. internal static IEnumerable<TextDiffer> CleanupMergePass1(this IEnumerable<TextDiffer> diffs)
  118. {
  119. var sbDelete = new StringBuilder();
  120. var sbInsert = new StringBuilder();
  121. var lastEquality = TextDiffer.Empty;
  122. using var enumerator = diffs.Concat(TextDiffer.Empty).GetEnumerator();
  123. while (enumerator.MoveNext())
  124. {
  125. var diff = enumerator.Current;
  126. (sbInsert, sbDelete) = diff.Operation switch
  127. {
  128. Insert => (sbInsert.Append(diff.Text), sbDelete),
  129. Delete => (sbInsert, sbDelete.Append(diff.Text)),
  130. _ => (sbInsert, sbDelete)
  131. };
  132. if (diff.Operation == Equal)
  133. {
  134. if (sbInsert.Length > 0 || sbDelete.Length > 0)
  135. {
  136. var prefixLength = TextUtil.CommonPrefix(sbInsert, sbDelete);
  137. if (prefixLength > 0)
  138. {
  139. var commonprefix = sbInsert.ToString(0, prefixLength);
  140. sbInsert.Remove(0, prefixLength);
  141. sbDelete.Remove(0, prefixLength);
  142. lastEquality = lastEquality.Append(commonprefix);
  143. }
  144. var suffixLength = TextUtil.CommonSuffix(sbInsert, sbDelete);
  145. if (suffixLength > 0)
  146. {
  147. var commonsuffix = sbInsert.ToString(sbInsert.Length - suffixLength, suffixLength);
  148. sbInsert.Remove(sbInsert.Length - suffixLength, suffixLength);
  149. sbDelete.Remove(sbDelete.Length - suffixLength, suffixLength);
  150. diff = diff.Prepend(commonsuffix);
  151. }
  152. if (!lastEquality.IsEmpty)
  153. {
  154. yield return lastEquality;
  155. }
  156. if (sbDelete.Length > 0) yield return TextDiffer.Delete(sbDelete.ToString());
  157. if (sbInsert.Length > 0) yield return TextDiffer.Insert(sbInsert.ToString());
  158. lastEquality = diff;
  159. sbDelete.Clear();
  160. sbInsert.Clear();
  161. }
  162. else
  163. {
  164. lastEquality = lastEquality.Append(diff.Text);
  165. }
  166. }
  167. }
  168. if (!lastEquality.IsEmpty)
  169. {
  170. yield return lastEquality;
  171. }
  172. }
  173. internal static IEnumerable<TextDiffer> CleanupMergePass2(this IEnumerable<TextDiffer> input, out bool haschanges)
  174. {
  175. haschanges = false;
  176. var diffs = input.ToList();
  177. for (var i = 1; i < diffs.Count - 1; i++)
  178. {
  179. var previous = diffs[i - 1];
  180. var current = diffs[i];
  181. var next = diffs[i + 1];
  182. if (previous.Operation == Equal && next.Operation == Equal)
  183. {
  184. var currentSpan = current.Text.AsSpan();
  185. var previousSpan = previous.Text.AsSpan();
  186. var nextSpan = next.Text.AsSpan();
  187. if (currentSpan.Length >= previousSpan.Length && currentSpan[^previousSpan.Length..].SequenceEqual(previousSpan))
  188. {
  189. var text = previous.Text + current.Text[..^previous.Text.Length];
  190. diffs[i] = current.Replace(text);
  191. diffs[i + 1] = next.Replace(previous.Text + next.Text);
  192. diffs.Splice(i - 1, 1);
  193. haschanges = true;
  194. }
  195. else if (currentSpan.Length >= nextSpan.Length && currentSpan[..nextSpan.Length].SequenceEqual(nextSpan))
  196. {
  197. diffs[i - 1] = previous.Replace(previous.Text + next.Text);
  198. diffs[i] = current.Replace(current.Text[next.Text.Length..] + next.Text);
  199. diffs.Splice(i + 1, 1);
  200. haschanges = true;
  201. }
  202. }
  203. }
  204. return diffs;
  205. }
  206. internal static IEnumerable<TextDiffer> CleanupMerge(this IEnumerable<TextDiffer> diffs)
  207. {
  208. bool changes;
  209. do
  210. {
  211. diffs = diffs.CleanupMergePass1().CleanupMergePass2(out changes).ToList(); // required to detect if anything changed
  212. } while (changes);
  213. return diffs;
  214. }
  215. readonly record struct EditBetweenEqualities(string Equality1, string Edit, string Equality2)
  216. {
  217. public int Score => DiffCleanupSemanticScore(Equality1, Edit) + DiffCleanupSemanticScore(Edit, Equality2);
  218. private readonly record struct ScoreHelper(string Str, Index I, Regex Regex)
  219. {
  220. char C => Str[I];
  221. public bool IsEmpty => Str.Length == 0;
  222. public bool NonAlphaNumeric => !char.IsLetterOrDigit(C);
  223. public bool IsWhitespace => char.IsWhiteSpace(C);
  224. public bool IsLineBreak => C == '\n' || C == '\r';
  225. public bool IsBlankLine => IsLineBreak && Regex.IsMatch(Str);
  226. }
  227. /// 给定两个字符串,计算一个分数,表示内部边界是否落在逻辑边界上
  228. private static int DiffCleanupSemanticScore(string one, string two) => (h1: new ScoreHelper(one, ^1, BlankLineEnd), h2: new ScoreHelper(two, 0, BlankLineStart)) switch
  229. {
  230. { h1.IsEmpty: true } or { h2.IsEmpty: true } => 6,
  231. { h1.IsBlankLine: true } or { h2.IsBlankLine: true } => 5,
  232. { h1.IsLineBreak: true } or { h2.IsLineBreak: true } => 4,
  233. { h1.NonAlphaNumeric: true } and { h1.IsWhitespace: false } and { h2.IsWhitespace: true } => 3,
  234. { h1.IsWhitespace: true } or { h2.IsWhitespace: true } => 2,
  235. { h1.NonAlphaNumeric: true } or { h2.NonAlphaNumeric: true } => 1,
  236. _ => 0
  237. };
  238. // 将编辑尽可能向左移动
  239. public EditBetweenEqualities ShiftLeft()
  240. {
  241. var commonOffset = TextUtil.CommonSuffix(Equality1, Edit);
  242. if (commonOffset > 0)
  243. {
  244. var commonString = Edit[^commonOffset..];
  245. var equality1 = Equality1[..^commonOffset];
  246. var edit = commonString + Edit[..^commonOffset];
  247. var equality2 = commonString + Equality2;
  248. return new EditBetweenEqualities(Equality1: equality1, Edit: edit, Equality2: equality2);
  249. }
  250. return this;
  251. }
  252. private EditBetweenEqualities ShiftRight() => new(Equality1: Equality1 + Edit[0], Edit: Edit[1..] + Equality2[0], Equality2: Equality2[1..]);
  253. public IEnumerable<EditBetweenEqualities> TraverseRight()
  254. {
  255. var item = this;
  256. while (item.Edit.Length != 0 && item.Equality2.Length != 0 && item.Edit[0] == item.Equality2[0])
  257. {
  258. yield return item = item.ShiftRight();
  259. }
  260. }
  261. public IEnumerable<TextDiffer> ToDiffs(DiffOperation edit)
  262. {
  263. yield return TextDiffer.Equal(Equality1);
  264. yield return TextDiffer.Create(edit, Edit);
  265. yield return TextDiffer.Equal(Equality2);
  266. }
  267. }
  268. /// <summary>
  269. /// 寻找两侧被等式包围的单个编辑,等式可以侧向移动,将编辑与单词边界对齐。
  270. /// e.g: The c<ins>at c</ins>ame. -> The <ins>cat </ins>came.
  271. /// </summary>
  272. /// <param name="diffs"></param>
  273. internal static IEnumerable<TextDiffer> CleanupSemanticLossless(this IEnumerable<TextDiffer> diffs)
  274. {
  275. using var enumerator = diffs.GetEnumerator();
  276. if (!enumerator.MoveNext())
  277. {
  278. yield break;
  279. }
  280. var previous = enumerator.Current;
  281. if (!enumerator.MoveNext())
  282. {
  283. yield return previous;
  284. yield break;
  285. }
  286. var current = enumerator.Current;
  287. while (true)
  288. {
  289. if (!enumerator.MoveNext())
  290. {
  291. yield return previous;
  292. yield return current;
  293. yield break;
  294. }
  295. var next = enumerator.Current;
  296. if (previous.Operation == Equal && next.Operation == Equal)
  297. {
  298. var item = new EditBetweenEqualities(previous.Text, current.Text, next.Text).ShiftLeft();
  299. var best = item.TraverseRight().Aggregate(item, (best, x) => best.Score > x.Score ? best : x);
  300. if (previous.Text != best.Equality1)
  301. {
  302. foreach (var d in best.ToDiffs(current.Operation).Where(d => !d.IsEmpty))
  303. {
  304. yield return d;
  305. }
  306. if (!enumerator.MoveNext())
  307. {
  308. yield break;
  309. }
  310. previous = current;
  311. current = next;
  312. next = enumerator.Current;
  313. }
  314. else
  315. {
  316. yield return previous;
  317. }
  318. }
  319. else
  320. {
  321. yield return previous;
  322. }
  323. previous = current;
  324. current = next;
  325. }
  326. }
  327. private static readonly Regex BlankLineEnd = new("\\n\\r?\\n\\Z", RegexOptions.Compiled);
  328. private static readonly Regex BlankLineStart = new("\\A\\r?\\n\\r?\\n", RegexOptions.Compiled);
  329. /// <summary>
  330. /// 从效率上清理一些无用的差异对象
  331. /// </summary>
  332. /// <param name="input"></param>
  333. /// <param name="diffEditCost"></param>
  334. internal static IEnumerable<TextDiffer> CleanupEfficiency(this IEnumerable<TextDiffer> input, short diffEditCost = 4)
  335. {
  336. var diffs = input.ToList();
  337. var changes = false;
  338. var equalities = new Stack<int>();
  339. var lastEquality = string.Empty;
  340. var insertionBeforeLastEquality = false;
  341. var deletionBeforeLastEquality = false;
  342. var insertionAfterLastEquality = false;
  343. var deletionAfterLastEquality = false;
  344. for (var i = 0; i < diffs.Count; i++)
  345. {
  346. var diff = diffs[i];
  347. if (diff.Operation == Equal)
  348. {
  349. if (diff.Text.Length < diffEditCost && (insertionAfterLastEquality || deletionAfterLastEquality))
  350. {
  351. equalities.Push(i);
  352. (insertionBeforeLastEquality, deletionBeforeLastEquality) = (insertionAfterLastEquality, deletionAfterLastEquality);
  353. lastEquality = diff.Text;
  354. }
  355. else
  356. {
  357. equalities.Clear();
  358. lastEquality = string.Empty;
  359. }
  360. insertionAfterLastEquality = deletionAfterLastEquality = false;
  361. }
  362. else
  363. {
  364. if (diff.Operation == Delete)
  365. {
  366. deletionAfterLastEquality = true;
  367. }
  368. else
  369. {
  370. insertionAfterLastEquality = true;
  371. }
  372. /*
  373. * 分割以下几种情况:
  374. * <ins>A</ins><del>B</del>XY<ins>C</ins><del>D</del>
  375. * <ins>A</ins>X<ins>C</ins><del>D</del>
  376. * <ins>A</ins><del>B</del>X<ins>C</ins>
  377. * <ins>A</del>X<ins>C</ins><del>D</del>
  378. * <ins>A</ins><del>B</del>X<del>C</del>
  379. */
  380. if ((lastEquality.Length != 0) && ((insertionBeforeLastEquality && deletionBeforeLastEquality && insertionAfterLastEquality && deletionAfterLastEquality) || ((lastEquality.Length < diffEditCost / 2) && (insertionBeforeLastEquality ? 1 : 0) + (deletionBeforeLastEquality ? 1 : 0) + (insertionAfterLastEquality ? 1 : 0) + (deletionAfterLastEquality ? 1 : 0) == 3)))
  381. {
  382. diffs.Splice(equalities.Peek(), 1, TextDiffer.Delete(lastEquality), TextDiffer.Insert(lastEquality));
  383. equalities.Pop();
  384. lastEquality = string.Empty;
  385. if (insertionBeforeLastEquality && deletionBeforeLastEquality)
  386. {
  387. insertionAfterLastEquality = deletionAfterLastEquality = true;
  388. equalities.Clear();
  389. }
  390. else
  391. {
  392. if (equalities.Count > 0)
  393. {
  394. equalities.Pop();
  395. }
  396. i = equalities.Count > 0 ? equalities.Peek() : -1;
  397. insertionAfterLastEquality = deletionAfterLastEquality = false;
  398. }
  399. changes = true;
  400. }
  401. }
  402. }
  403. if (changes)
  404. {
  405. return diffs.CleanupMerge();
  406. }
  407. return input;
  408. }
  409. /// <summary>
  410. /// 两个不相关文本的差异可以用巧合的匹配来填充。例如,“mouse”和“sofas”的区别是
  411. /// `[(-1, "m"), (1, "s"), (0, "o"), (-1, "u"), (1, "fa"), (0, "s"), (-1, "e")]`.
  412. /// 虽然这是最佳差异,但人类很难理解。语义清理重写了差异,将其扩展为更易于理解的格式。上述示例将变为: `[(-1, "mouse"), (1, "sofas")]`.
  413. /// </summary>
  414. /// <param name="input"></param>
  415. /// <returns></returns>
  416. public static IImmutableList<TextDiffer> MakeHumanReadable(this IEnumerable<TextDiffer> input) => input.CleanupSemantic().ToImmutableList();
  417. /// <summary>
  418. /// 此函数类似于“OptimizeForReadability”,除了它不是将差异优化为人类可读,而是优化差异以提高机器处理的效率。两种清理类型的结果通常是相同的。CleanupEfficiency就是基于这样的,即由大量小差异编辑组成的差异可能需要更长的时间来处理(在下游应用程序中),或者需要比少量较大差异更多的存储或传输容量。
  419. /// </summary>
  420. /// <param name="input"></param>
  421. /// <param name="diffEditCost">处理新编辑的成本,即处理现有编辑中的额外字符。默认值为4,这意味着如果将差异的长度扩展三个字符可以消除一次编辑,那么这种优化将降低总成本</param>
  422. /// <returns></returns>
  423. public static IImmutableList<TextDiffer> OptimizeForMachineProcessing(this IEnumerable<TextDiffer> input, short diffEditCost = 4) => input.CleanupEfficiency(diffEditCost).ToImmutableList();
  424. /// <summary>
  425. /// 从语法上清理一些无用的差异对象
  426. /// </summary>
  427. /// <param name="input"></param>
  428. internal static List<TextDiffer> CleanupSemantic(this IEnumerable<TextDiffer> input)
  429. {
  430. var diffs = input.ToList();
  431. var equalities = new Stack<int>();
  432. string lastEquality = null;
  433. var pointer = 0;
  434. var lengthInsertions1 = 0;
  435. var lengthDeletions1 = 0;
  436. var lengthInsertions2 = 0;
  437. var lengthDeletions2 = 0;
  438. while (pointer < diffs.Count)
  439. {
  440. if (diffs[pointer].Operation == Equal)
  441. {
  442. equalities.Push(pointer);
  443. lengthInsertions1 = lengthInsertions2;
  444. lengthDeletions1 = lengthDeletions2;
  445. lengthInsertions2 = 0;
  446. lengthDeletions2 = 0;
  447. lastEquality = diffs[pointer].Text;
  448. }
  449. else
  450. {
  451. if (diffs[pointer].Operation == Insert)
  452. {
  453. lengthInsertions2 += diffs[pointer].Text.Length;
  454. }
  455. else
  456. {
  457. lengthDeletions2 += diffs[pointer].Text.Length;
  458. }
  459. if (lastEquality != null && (lastEquality.Length <= Math.Max(lengthInsertions1, lengthDeletions1)) && (lastEquality.Length <= Math.Max(lengthInsertions2, lengthDeletions2)))
  460. {
  461. diffs.Splice(equalities.Peek(), 1, TextDiffer.Delete(lastEquality), TextDiffer.Insert(lastEquality));
  462. equalities.Pop();
  463. if (equalities.Count > 0)
  464. {
  465. equalities.Pop();
  466. }
  467. pointer = equalities.Count > 0 ? equalities.Peek() : -1;
  468. lengthInsertions1 = 0; // Reset the counters.
  469. lengthDeletions1 = 0;
  470. lengthInsertions2 = 0;
  471. lengthDeletions2 = 0;
  472. lastEquality = null;
  473. }
  474. }
  475. pointer++;
  476. }
  477. diffs = diffs.CleanupMerge().CleanupSemanticLossless().ToList();
  478. // 查找删除和插入之间的重叠.
  479. // e.g: <del>abcxxx</del><ins>xxxdef</ins>
  480. // -> <del>abc</del>xxx<ins>def</ins>
  481. // e.g: <del>xxxabc</del><ins>defxxx</ins>
  482. // -> <ins>def</ins>xxx<del>abc</del>
  483. // 只有当重叠与前面或后面的编辑一样大时,才提取重叠
  484. pointer = 1;
  485. while (pointer < diffs.Count)
  486. {
  487. if (diffs[pointer - 1].Operation == Delete && diffs[pointer].Operation == Insert)
  488. {
  489. var deletion = diffs[pointer - 1].Text.AsSpan();
  490. var insertion = diffs[pointer].Text.AsSpan();
  491. var overlapLength1 = TextUtil.CommonOverlap(deletion, insertion);
  492. var overlapLength2 = TextUtil.CommonOverlap(insertion, deletion);
  493. var minLength = Math.Min(deletion.Length, insertion.Length);
  494. TextDiffer[] newdiffs = null;
  495. if ((overlapLength1 >= overlapLength2) && (overlapLength1 >= minLength / 2.0))
  496. {
  497. newdiffs = new[]
  498. {
  499. TextDiffer.Delete(deletion.Slice(0, deletion.Length - overlapLength1).ToArray()),
  500. TextDiffer.Equal(insertion.Slice(0, overlapLength1).ToArray()),
  501. TextDiffer.Insert(insertion[overlapLength1..].ToArray())
  502. };
  503. }
  504. else if ((overlapLength2 >= overlapLength1) && overlapLength2 >= minLength / 2.0)
  505. {
  506. newdiffs = new[]
  507. {
  508. TextDiffer.Insert(insertion.Slice(0, insertion.Length - overlapLength2)),
  509. TextDiffer.Equal(deletion.Slice(0, overlapLength2)),
  510. TextDiffer.Delete(deletion[overlapLength2..])
  511. };
  512. }
  513. if (newdiffs != null)
  514. {
  515. diffs.Splice(pointer - 1, 2, newdiffs);
  516. pointer++;
  517. }
  518. pointer++;
  519. }
  520. pointer++;
  521. }
  522. return diffs;
  523. }
  524. /// <summary>
  525. /// 计算并返回目标文本中的等效位置
  526. /// </summary>
  527. /// <param name="diffs"></param>
  528. /// <param name="location1"></param>
  529. /// <returns></returns>
  530. internal static int FindEquivalentLocation2(this IEnumerable<TextDiffer> diffs, int location1)
  531. {
  532. var chars1 = 0;
  533. var chars2 = 0;
  534. var lastChars1 = 0;
  535. var lastChars2 = 0;
  536. var lastDiff = TextDiffer.Empty;
  537. foreach (var aDiff in diffs)
  538. {
  539. if (aDiff.Operation != Insert)
  540. {
  541. chars1 += aDiff.Text.Length;
  542. }
  543. if (aDiff.Operation != Delete)
  544. {
  545. chars2 += aDiff.Text.Length;
  546. }
  547. if (chars1 > location1)
  548. {
  549. lastDiff = aDiff;
  550. break;
  551. }
  552. lastChars1 = chars1;
  553. lastChars2 = chars2;
  554. }
  555. if (lastDiff.Operation == Delete)
  556. {
  557. return lastChars2;
  558. }
  559. return lastChars2 + (location1 - lastChars1);
  560. }
  561. }