cleanupSemantic.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. 'use strict';
  2. Object.defineProperty(exports, '__esModule', {
  3. value: true
  4. });
  5. exports.cleanupSemantic = exports.DIFF_INSERT = exports.DIFF_DELETE = exports.DIFF_EQUAL = exports.Diff = void 0;
  6. function _defineProperty(obj, key, value) {
  7. if (key in obj) {
  8. Object.defineProperty(obj, key, {
  9. value: value,
  10. enumerable: true,
  11. configurable: true,
  12. writable: true
  13. });
  14. } else {
  15. obj[key] = value;
  16. }
  17. return obj;
  18. }
  19. /**
  20. * Diff Match and Patch
  21. * Copyright 2018 The diff-match-patch Authors.
  22. * https://github.com/google/diff-match-patch
  23. *
  24. * Licensed under the Apache License, Version 2.0 (the "License");
  25. * you may not use this file except in compliance with the License.
  26. * You may obtain a copy of the License at
  27. *
  28. * http://www.apache.org/licenses/LICENSE-2.0
  29. *
  30. * Unless required by applicable law or agreed to in writing, software
  31. * distributed under the License is distributed on an "AS IS" BASIS,
  32. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  33. * See the License for the specific language governing permissions and
  34. * limitations under the License.
  35. */
  36. /**
  37. * @fileoverview Computes the difference between two texts to create a patch.
  38. * Applies the patch onto another text, allowing for errors.
  39. * @author fraser@google.com (Neil Fraser)
  40. */
  41. /**
  42. * CHANGES by pedrottimark to diff_match_patch_uncompressed.ts file:
  43. *
  44. * 1. Delete anything not needed to use diff_cleanupSemantic method
  45. * 2. Convert from prototype properties to var declarations
  46. * 3. Convert Diff to class from constructor and prototype
  47. * 4. Add type annotations for arguments and return values
  48. * 5. Add exports
  49. */
  50. /**
  51. * The data structure representing a diff is an array of tuples:
  52. * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']]
  53. * which means: delete 'Hello', add 'Goodbye' and keep ' world.'
  54. */
  55. var DIFF_DELETE = -1;
  56. exports.DIFF_DELETE = DIFF_DELETE;
  57. var DIFF_INSERT = 1;
  58. exports.DIFF_INSERT = DIFF_INSERT;
  59. var DIFF_EQUAL = 0;
  60. /**
  61. * Class representing one diff tuple.
  62. * Attempts to look like a two-element array (which is what this used to be).
  63. * @param {number} op Operation, one of: DIFF_DELETE, DIFF_INSERT, DIFF_EQUAL.
  64. * @param {string} text Text to be deleted, inserted, or retained.
  65. * @constructor
  66. */
  67. exports.DIFF_EQUAL = DIFF_EQUAL;
  68. class Diff {
  69. constructor(op, text) {
  70. _defineProperty(this, 0, void 0);
  71. _defineProperty(this, 1, void 0);
  72. this[0] = op;
  73. this[1] = text;
  74. }
  75. }
  76. /**
  77. * Determine the common prefix of two strings.
  78. * @param {string} text1 First string.
  79. * @param {string} text2 Second string.
  80. * @return {number} The number of characters common to the start of each
  81. * string.
  82. */
  83. exports.Diff = Diff;
  84. var diff_commonPrefix = function (text1, text2) {
  85. // Quick check for common null cases.
  86. if (!text1 || !text2 || text1.charAt(0) != text2.charAt(0)) {
  87. return 0;
  88. } // Binary search.
  89. // Performance analysis: https://neil.fraser.name/news/2007/10/09/
  90. var pointermin = 0;
  91. var pointermax = Math.min(text1.length, text2.length);
  92. var pointermid = pointermax;
  93. var pointerstart = 0;
  94. while (pointermin < pointermid) {
  95. if (
  96. text1.substring(pointerstart, pointermid) ==
  97. text2.substring(pointerstart, pointermid)
  98. ) {
  99. pointermin = pointermid;
  100. pointerstart = pointermin;
  101. } else {
  102. pointermax = pointermid;
  103. }
  104. pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
  105. }
  106. return pointermid;
  107. };
  108. /**
  109. * Determine the common suffix of two strings.
  110. * @param {string} text1 First string.
  111. * @param {string} text2 Second string.
  112. * @return {number} The number of characters common to the end of each string.
  113. */
  114. var diff_commonSuffix = function (text1, text2) {
  115. // Quick check for common null cases.
  116. if (
  117. !text1 ||
  118. !text2 ||
  119. text1.charAt(text1.length - 1) != text2.charAt(text2.length - 1)
  120. ) {
  121. return 0;
  122. } // Binary search.
  123. // Performance analysis: https://neil.fraser.name/news/2007/10/09/
  124. var pointermin = 0;
  125. var pointermax = Math.min(text1.length, text2.length);
  126. var pointermid = pointermax;
  127. var pointerend = 0;
  128. while (pointermin < pointermid) {
  129. if (
  130. text1.substring(text1.length - pointermid, text1.length - pointerend) ==
  131. text2.substring(text2.length - pointermid, text2.length - pointerend)
  132. ) {
  133. pointermin = pointermid;
  134. pointerend = pointermin;
  135. } else {
  136. pointermax = pointermid;
  137. }
  138. pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin);
  139. }
  140. return pointermid;
  141. };
  142. /**
  143. * Determine if the suffix of one string is the prefix of another.
  144. * @param {string} text1 First string.
  145. * @param {string} text2 Second string.
  146. * @return {number} The number of characters common to the end of the first
  147. * string and the start of the second string.
  148. * @private
  149. */
  150. var diff_commonOverlap_ = function (text1, text2) {
  151. // Cache the text lengths to prevent multiple calls.
  152. var text1_length = text1.length;
  153. var text2_length = text2.length; // Eliminate the null case.
  154. if (text1_length == 0 || text2_length == 0) {
  155. return 0;
  156. } // Truncate the longer string.
  157. if (text1_length > text2_length) {
  158. text1 = text1.substring(text1_length - text2_length);
  159. } else if (text1_length < text2_length) {
  160. text2 = text2.substring(0, text1_length);
  161. }
  162. var text_length = Math.min(text1_length, text2_length); // Quick check for the worst case.
  163. if (text1 == text2) {
  164. return text_length;
  165. } // Start by looking for a single character match
  166. // and increase length until no match is found.
  167. // Performance analysis: https://neil.fraser.name/news/2010/11/04/
  168. var best = 0;
  169. var length = 1;
  170. while (true) {
  171. var pattern = text1.substring(text_length - length);
  172. var found = text2.indexOf(pattern);
  173. if (found == -1) {
  174. return best;
  175. }
  176. length += found;
  177. if (
  178. found == 0 ||
  179. text1.substring(text_length - length) == text2.substring(0, length)
  180. ) {
  181. best = length;
  182. length++;
  183. }
  184. }
  185. };
  186. /**
  187. * Reduce the number of edits by eliminating semantically trivial equalities.
  188. * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
  189. */
  190. var diff_cleanupSemantic = function (diffs) {
  191. var changes = false;
  192. var equalities = []; // Stack of indices where equalities are found.
  193. var equalitiesLength = 0; // Keeping our own length var is faster in JS.
  194. /** @type {?string} */
  195. var lastEquality = null; // Always equal to diffs[equalities[equalitiesLength - 1]][1]
  196. var pointer = 0; // Index of current position.
  197. // Number of characters that changed prior to the equality.
  198. var length_insertions1 = 0;
  199. var length_deletions1 = 0; // Number of characters that changed after the equality.
  200. var length_insertions2 = 0;
  201. var length_deletions2 = 0;
  202. while (pointer < diffs.length) {
  203. if (diffs[pointer][0] == DIFF_EQUAL) {
  204. // Equality found.
  205. equalities[equalitiesLength++] = pointer;
  206. length_insertions1 = length_insertions2;
  207. length_deletions1 = length_deletions2;
  208. length_insertions2 = 0;
  209. length_deletions2 = 0;
  210. lastEquality = diffs[pointer][1];
  211. } else {
  212. // An insertion or deletion.
  213. if (diffs[pointer][0] == DIFF_INSERT) {
  214. length_insertions2 += diffs[pointer][1].length;
  215. } else {
  216. length_deletions2 += diffs[pointer][1].length;
  217. } // Eliminate an equality that is smaller or equal to the edits on both
  218. // sides of it.
  219. if (
  220. lastEquality &&
  221. lastEquality.length <=
  222. Math.max(length_insertions1, length_deletions1) &&
  223. lastEquality.length <= Math.max(length_insertions2, length_deletions2)
  224. ) {
  225. // Duplicate record.
  226. diffs.splice(
  227. equalities[equalitiesLength - 1],
  228. 0,
  229. new Diff(DIFF_DELETE, lastEquality)
  230. ); // Change second copy to insert.
  231. diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; // Throw away the equality we just deleted.
  232. equalitiesLength--; // Throw away the previous equality (it needs to be reevaluated).
  233. equalitiesLength--;
  234. pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1;
  235. length_insertions1 = 0; // Reset the counters.
  236. length_deletions1 = 0;
  237. length_insertions2 = 0;
  238. length_deletions2 = 0;
  239. lastEquality = null;
  240. changes = true;
  241. }
  242. }
  243. pointer++;
  244. } // Normalize the diff.
  245. if (changes) {
  246. diff_cleanupMerge(diffs);
  247. }
  248. diff_cleanupSemanticLossless(diffs); // Find any overlaps between deletions and insertions.
  249. // e.g: <del>abcxxx</del><ins>xxxdef</ins>
  250. // -> <del>abc</del>xxx<ins>def</ins>
  251. // e.g: <del>xxxabc</del><ins>defxxx</ins>
  252. // -> <ins>def</ins>xxx<del>abc</del>
  253. // Only extract an overlap if it is as big as the edit ahead or behind it.
  254. pointer = 1;
  255. while (pointer < diffs.length) {
  256. if (
  257. diffs[pointer - 1][0] == DIFF_DELETE &&
  258. diffs[pointer][0] == DIFF_INSERT
  259. ) {
  260. var deletion = diffs[pointer - 1][1];
  261. var insertion = diffs[pointer][1];
  262. var overlap_length1 = diff_commonOverlap_(deletion, insertion);
  263. var overlap_length2 = diff_commonOverlap_(insertion, deletion);
  264. if (overlap_length1 >= overlap_length2) {
  265. if (
  266. overlap_length1 >= deletion.length / 2 ||
  267. overlap_length1 >= insertion.length / 2
  268. ) {
  269. // Overlap found. Insert an equality and trim the surrounding edits.
  270. diffs.splice(
  271. pointer,
  272. 0,
  273. new Diff(DIFF_EQUAL, insertion.substring(0, overlap_length1))
  274. );
  275. diffs[pointer - 1][1] = deletion.substring(
  276. 0,
  277. deletion.length - overlap_length1
  278. );
  279. diffs[pointer + 1][1] = insertion.substring(overlap_length1);
  280. pointer++;
  281. }
  282. } else {
  283. if (
  284. overlap_length2 >= deletion.length / 2 ||
  285. overlap_length2 >= insertion.length / 2
  286. ) {
  287. // Reverse overlap found.
  288. // Insert an equality and swap and trim the surrounding edits.
  289. diffs.splice(
  290. pointer,
  291. 0,
  292. new Diff(DIFF_EQUAL, deletion.substring(0, overlap_length2))
  293. );
  294. diffs[pointer - 1][0] = DIFF_INSERT;
  295. diffs[pointer - 1][1] = insertion.substring(
  296. 0,
  297. insertion.length - overlap_length2
  298. );
  299. diffs[pointer + 1][0] = DIFF_DELETE;
  300. diffs[pointer + 1][1] = deletion.substring(overlap_length2);
  301. pointer++;
  302. }
  303. }
  304. pointer++;
  305. }
  306. pointer++;
  307. }
  308. };
  309. /**
  310. * Look for single edits surrounded on both sides by equalities
  311. * which can be shifted sideways to align the edit to a word boundary.
  312. * e.g: The c<ins>at c</ins>ame. -> The <ins>cat </ins>came.
  313. * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
  314. */
  315. exports.cleanupSemantic = diff_cleanupSemantic;
  316. var diff_cleanupSemanticLossless = function (diffs) {
  317. /**
  318. * Given two strings, compute a score representing whether the internal
  319. * boundary falls on logical boundaries.
  320. * Scores range from 6 (best) to 0 (worst).
  321. * Closure, but does not reference any external variables.
  322. * @param {string} one First string.
  323. * @param {string} two Second string.
  324. * @return {number} The score.
  325. * @private
  326. */
  327. function diff_cleanupSemanticScore_(one, two) {
  328. if (!one || !two) {
  329. // Edges are the best.
  330. return 6;
  331. } // Each port of this function behaves slightly differently due to
  332. // subtle differences in each language's definition of things like
  333. // 'whitespace'. Since this function's purpose is largely cosmetic,
  334. // the choice has been made to use each language's native features
  335. // rather than force total conformity.
  336. var char1 = one.charAt(one.length - 1);
  337. var char2 = two.charAt(0);
  338. var nonAlphaNumeric1 = char1.match(nonAlphaNumericRegex_);
  339. var nonAlphaNumeric2 = char2.match(nonAlphaNumericRegex_);
  340. var whitespace1 = nonAlphaNumeric1 && char1.match(whitespaceRegex_);
  341. var whitespace2 = nonAlphaNumeric2 && char2.match(whitespaceRegex_);
  342. var lineBreak1 = whitespace1 && char1.match(linebreakRegex_);
  343. var lineBreak2 = whitespace2 && char2.match(linebreakRegex_);
  344. var blankLine1 = lineBreak1 && one.match(blanklineEndRegex_);
  345. var blankLine2 = lineBreak2 && two.match(blanklineStartRegex_);
  346. if (blankLine1 || blankLine2) {
  347. // Five points for blank lines.
  348. return 5;
  349. } else if (lineBreak1 || lineBreak2) {
  350. // Four points for line breaks.
  351. return 4;
  352. } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) {
  353. // Three points for end of sentences.
  354. return 3;
  355. } else if (whitespace1 || whitespace2) {
  356. // Two points for whitespace.
  357. return 2;
  358. } else if (nonAlphaNumeric1 || nonAlphaNumeric2) {
  359. // One point for non-alphanumeric.
  360. return 1;
  361. }
  362. return 0;
  363. }
  364. var pointer = 1; // Intentionally ignore the first and last element (don't need checking).
  365. while (pointer < diffs.length - 1) {
  366. if (
  367. diffs[pointer - 1][0] == DIFF_EQUAL &&
  368. diffs[pointer + 1][0] == DIFF_EQUAL
  369. ) {
  370. // This is a single edit surrounded by equalities.
  371. var equality1 = diffs[pointer - 1][1];
  372. var edit = diffs[pointer][1];
  373. var equality2 = diffs[pointer + 1][1]; // First, shift the edit as far left as possible.
  374. var commonOffset = diff_commonSuffix(equality1, edit);
  375. if (commonOffset) {
  376. var commonString = edit.substring(edit.length - commonOffset);
  377. equality1 = equality1.substring(0, equality1.length - commonOffset);
  378. edit = commonString + edit.substring(0, edit.length - commonOffset);
  379. equality2 = commonString + equality2;
  380. } // Second, step character by character right, looking for the best fit.
  381. var bestEquality1 = equality1;
  382. var bestEdit = edit;
  383. var bestEquality2 = equality2;
  384. var bestScore =
  385. diff_cleanupSemanticScore_(equality1, edit) +
  386. diff_cleanupSemanticScore_(edit, equality2);
  387. while (edit.charAt(0) === equality2.charAt(0)) {
  388. equality1 += edit.charAt(0);
  389. edit = edit.substring(1) + equality2.charAt(0);
  390. equality2 = equality2.substring(1);
  391. var score =
  392. diff_cleanupSemanticScore_(equality1, edit) +
  393. diff_cleanupSemanticScore_(edit, equality2); // The >= encourages trailing rather than leading whitespace on edits.
  394. if (score >= bestScore) {
  395. bestScore = score;
  396. bestEquality1 = equality1;
  397. bestEdit = edit;
  398. bestEquality2 = equality2;
  399. }
  400. }
  401. if (diffs[pointer - 1][1] != bestEquality1) {
  402. // We have an improvement, save it back to the diff.
  403. if (bestEquality1) {
  404. diffs[pointer - 1][1] = bestEquality1;
  405. } else {
  406. diffs.splice(pointer - 1, 1);
  407. pointer--;
  408. }
  409. diffs[pointer][1] = bestEdit;
  410. if (bestEquality2) {
  411. diffs[pointer + 1][1] = bestEquality2;
  412. } else {
  413. diffs.splice(pointer + 1, 1);
  414. pointer--;
  415. }
  416. }
  417. }
  418. pointer++;
  419. }
  420. }; // Define some regex patterns for matching boundaries.
  421. var nonAlphaNumericRegex_ = /[^a-zA-Z0-9]/;
  422. var whitespaceRegex_ = /\s/;
  423. var linebreakRegex_ = /[\r\n]/;
  424. var blanklineEndRegex_ = /\n\r?\n$/;
  425. var blanklineStartRegex_ = /^\r?\n\r?\n/;
  426. /**
  427. * Reorder and merge like edit sections. Merge equalities.
  428. * Any edit section can move as long as it doesn't cross an equality.
  429. * @param {!Array.<!diff_match_patch.Diff>} diffs Array of diff tuples.
  430. */
  431. var diff_cleanupMerge = function (diffs) {
  432. // Add a dummy entry at the end.
  433. diffs.push(new Diff(DIFF_EQUAL, ''));
  434. var pointer = 0;
  435. var count_delete = 0;
  436. var count_insert = 0;
  437. var text_delete = '';
  438. var text_insert = '';
  439. var commonlength;
  440. while (pointer < diffs.length) {
  441. switch (diffs[pointer][0]) {
  442. case DIFF_INSERT:
  443. count_insert++;
  444. text_insert += diffs[pointer][1];
  445. pointer++;
  446. break;
  447. case DIFF_DELETE:
  448. count_delete++;
  449. text_delete += diffs[pointer][1];
  450. pointer++;
  451. break;
  452. case DIFF_EQUAL:
  453. // Upon reaching an equality, check for prior redundancies.
  454. if (count_delete + count_insert > 1) {
  455. if (count_delete !== 0 && count_insert !== 0) {
  456. // Factor out any common prefixies.
  457. commonlength = diff_commonPrefix(text_insert, text_delete);
  458. if (commonlength !== 0) {
  459. if (
  460. pointer - count_delete - count_insert > 0 &&
  461. diffs[pointer - count_delete - count_insert - 1][0] ==
  462. DIFF_EQUAL
  463. ) {
  464. diffs[
  465. pointer - count_delete - count_insert - 1
  466. ][1] += text_insert.substring(0, commonlength);
  467. } else {
  468. diffs.splice(
  469. 0,
  470. 0,
  471. new Diff(DIFF_EQUAL, text_insert.substring(0, commonlength))
  472. );
  473. pointer++;
  474. }
  475. text_insert = text_insert.substring(commonlength);
  476. text_delete = text_delete.substring(commonlength);
  477. } // Factor out any common suffixies.
  478. commonlength = diff_commonSuffix(text_insert, text_delete);
  479. if (commonlength !== 0) {
  480. diffs[pointer][1] =
  481. text_insert.substring(text_insert.length - commonlength) +
  482. diffs[pointer][1];
  483. text_insert = text_insert.substring(
  484. 0,
  485. text_insert.length - commonlength
  486. );
  487. text_delete = text_delete.substring(
  488. 0,
  489. text_delete.length - commonlength
  490. );
  491. }
  492. } // Delete the offending records and add the merged ones.
  493. pointer -= count_delete + count_insert;
  494. diffs.splice(pointer, count_delete + count_insert);
  495. if (text_delete.length) {
  496. diffs.splice(pointer, 0, new Diff(DIFF_DELETE, text_delete));
  497. pointer++;
  498. }
  499. if (text_insert.length) {
  500. diffs.splice(pointer, 0, new Diff(DIFF_INSERT, text_insert));
  501. pointer++;
  502. }
  503. pointer++;
  504. } else if (pointer !== 0 && diffs[pointer - 1][0] == DIFF_EQUAL) {
  505. // Merge this equality with the previous one.
  506. diffs[pointer - 1][1] += diffs[pointer][1];
  507. diffs.splice(pointer, 1);
  508. } else {
  509. pointer++;
  510. }
  511. count_insert = 0;
  512. count_delete = 0;
  513. text_delete = '';
  514. text_insert = '';
  515. break;
  516. }
  517. }
  518. if (diffs[diffs.length - 1][1] === '') {
  519. diffs.pop(); // Remove the dummy entry at the end.
  520. } // Second pass: look for single edits surrounded on both sides by equalities
  521. // which can be shifted sideways to eliminate an equality.
  522. // e.g: A<ins>BA</ins>C -> <ins>AB</ins>AC
  523. var changes = false;
  524. pointer = 1; // Intentionally ignore the first and last element (don't need checking).
  525. while (pointer < diffs.length - 1) {
  526. if (
  527. diffs[pointer - 1][0] == DIFF_EQUAL &&
  528. diffs[pointer + 1][0] == DIFF_EQUAL
  529. ) {
  530. // This is a single edit surrounded by equalities.
  531. if (
  532. diffs[pointer][1].substring(
  533. diffs[pointer][1].length - diffs[pointer - 1][1].length
  534. ) == diffs[pointer - 1][1]
  535. ) {
  536. // Shift the edit over the previous equality.
  537. diffs[pointer][1] =
  538. diffs[pointer - 1][1] +
  539. diffs[pointer][1].substring(
  540. 0,
  541. diffs[pointer][1].length - diffs[pointer - 1][1].length
  542. );
  543. diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1];
  544. diffs.splice(pointer - 1, 1);
  545. changes = true;
  546. } else if (
  547. diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) ==
  548. diffs[pointer + 1][1]
  549. ) {
  550. // Shift the edit over the next equality.
  551. diffs[pointer - 1][1] += diffs[pointer + 1][1];
  552. diffs[pointer][1] =
  553. diffs[pointer][1].substring(diffs[pointer + 1][1].length) +
  554. diffs[pointer + 1][1];
  555. diffs.splice(pointer + 1, 1);
  556. changes = true;
  557. }
  558. }
  559. pointer++;
  560. } // If shifts were made, the diff needs reordering and another shift sweep.
  561. if (changes) {
  562. diff_cleanupMerge(diffs);
  563. }
  564. };