jsdifflib.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409
  1. /*
  2. * This is part of jsdifflib v1.0. <http://snowtide.com/jsdifflib>
  3. *
  4. * Copyright (c) 2007, Snowtide Informatics Systems, Inc.
  5. * All rights reserved.
  6. *
  7. * Redistribution and use in source and binary forms, with or without modification,
  8. * are permitted provided that the following conditions are met:
  9. *
  10. * * Redistributions of source code must retain the above copyright notice, this
  11. * list of conditions and the following disclaimer.
  12. * * Redistributions in binary form must reproduce the above copyright notice,
  13. * this list of conditions and the following disclaimer in the documentation
  14. * and/or other materials provided with the distribution.
  15. * * Neither the name of the Snowtide Informatics Systems nor the names of its
  16. * contributors may be used to endorse or promote products derived from this
  17. * software without specific prior written permission.
  18. *
  19. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
  20. * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  21. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
  22. * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  23. * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  24. * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
  25. * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  26. * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  27. * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
  28. * DAMAGE.
  29. **/
  30. /* Author: Chas Emerick <cemerick@snowtide.com> */
  31. /* taken from https://github.com/cemerick/jsdifflib */
  32. __whitespace = {" ":true, "\t":true, "\n":true, "\f":true, "\r":true};
  33. difflib = {
  34. defaultJunkFunction: function (c) {
  35. return __whitespace.hasOwnProperty(c);
  36. },
  37. stripLinebreaks: function (str) { return str.replace(/^[\n\r]*|[\n\r]*$/g, ""); },
  38. stringAsLines: function (str) {
  39. var lfpos = str.indexOf("\n");
  40. var crpos = str.indexOf("\r");
  41. var linebreak = ((lfpos > -1 && crpos > -1) || crpos < 0) ? "\n" : "\r";
  42. var lines = str.split(linebreak);
  43. for (var i = 0; i < lines.length; i++) {
  44. lines[i] = difflib.stripLinebreaks(lines[i]);
  45. }
  46. return lines;
  47. },
  48. // iteration-based reduce implementation
  49. __reduce: function (func, list, initial) {
  50. if (initial != null) {
  51. var value = initial;
  52. var idx = 0;
  53. } else if (list) {
  54. var value = list[0];
  55. var idx = 1;
  56. } else {
  57. return null;
  58. }
  59. for (; idx < list.length; idx++) {
  60. value = func(value, list[idx]);
  61. }
  62. return value;
  63. },
  64. // comparison function for sorting lists of numeric tuples
  65. __ntuplecomp: function (a, b) {
  66. var mlen = Math.max(a.length, b.length);
  67. for (var i = 0; i < mlen; i++) {
  68. if (a[i] < b[i]) return -1;
  69. if (a[i] > b[i]) return 1;
  70. }
  71. return a.length == b.length ? 0 : (a.length < b.length ? -1 : 1);
  72. },
  73. __calculate_ratio: function (matches, length) {
  74. return length ? 2.0 * matches / length : 1.0;
  75. },
  76. // returns a function that returns true if a key passed to the returned function
  77. // is in the dict (js object) provided to this function; replaces being able to
  78. // carry around dict.has_key in python...
  79. __isindict: function (dict) {
  80. return function (key) { return dict.hasOwnProperty(key); };
  81. },
  82. // replacement for python's dict.get function -- need easy default values
  83. __dictget: function (dict, key, defaultValue) {
  84. return dict.hasOwnProperty(key) ? dict[key] : defaultValue;
  85. },
  86. SequenceMatcher: function (a, b, isjunk) {
  87. this.set_seqs = function (a, b) {
  88. this.set_seq1(a);
  89. this.set_seq2(b);
  90. }
  91. this.set_seq1 = function (a) {
  92. if (a == this.a) return;
  93. this.a = a;
  94. this.matching_blocks = this.opcodes = null;
  95. }
  96. this.set_seq2 = function (b) {
  97. if (b == this.b) return;
  98. this.b = b;
  99. this.matching_blocks = this.opcodes = this.fullbcount = null;
  100. this.__chain_b();
  101. }
  102. this.__chain_b = function () {
  103. var b = this.b;
  104. var n = b.length;
  105. var b2j = this.b2j = {};
  106. var populardict = {};
  107. for (var i = 0; i < b.length; i++) {
  108. var elt = b[i];
  109. if (b2j.hasOwnProperty(elt)) {
  110. var indices = b2j[elt];
  111. if (n >= 200 && indices.length * 100 > n) {
  112. populardict[elt] = 1;
  113. delete b2j[elt];
  114. } else {
  115. indices.push(i);
  116. }
  117. } else {
  118. b2j[elt] = [i];
  119. }
  120. }
  121. for (var elt in populardict) {
  122. if (populardict.hasOwnProperty(elt)) {
  123. delete b2j[elt];
  124. }
  125. }
  126. var isjunk = this.isjunk;
  127. var junkdict = {};
  128. if (isjunk) {
  129. for (var elt in populardict) {
  130. if (populardict.hasOwnProperty(elt) && isjunk(elt)) {
  131. junkdict[elt] = 1;
  132. delete populardict[elt];
  133. }
  134. }
  135. for (var elt in b2j) {
  136. if (b2j.hasOwnProperty(elt) && isjunk(elt)) {
  137. junkdict[elt] = 1;
  138. delete b2j[elt];
  139. }
  140. }
  141. }
  142. this.isbjunk = difflib.__isindict(junkdict);
  143. this.isbpopular = difflib.__isindict(populardict);
  144. }
  145. this.find_longest_match = function (alo, ahi, blo, bhi) {
  146. var a = this.a;
  147. var b = this.b;
  148. var b2j = this.b2j;
  149. var isbjunk = this.isbjunk;
  150. var besti = alo;
  151. var bestj = blo;
  152. var bestsize = 0;
  153. var j = null;
  154. var j2len = {};
  155. var nothing = [];
  156. for (var i = alo; i < ahi; i++) {
  157. var newj2len = {};
  158. var jdict = difflib.__dictget(b2j, a[i], nothing);
  159. for (var jkey in jdict) {
  160. if (jdict.hasOwnProperty(jkey)) {
  161. j = jdict[jkey];
  162. if (j < blo) continue;
  163. if (j >= bhi) break;
  164. newj2len[j] = k = difflib.__dictget(j2len, j - 1, 0) + 1;
  165. if (k > bestsize) {
  166. besti = i - k + 1;
  167. bestj = j - k + 1;
  168. bestsize = k;
  169. }
  170. }
  171. }
  172. j2len = newj2len;
  173. }
  174. while (besti > alo && bestj > blo && !isbjunk(b[bestj - 1]) && a[besti - 1] == b[bestj - 1]) {
  175. besti--;
  176. bestj--;
  177. bestsize++;
  178. }
  179. while (besti + bestsize < ahi && bestj + bestsize < bhi &&
  180. !isbjunk(b[bestj + bestsize]) &&
  181. a[besti + bestsize] == b[bestj + bestsize]) {
  182. bestsize++;
  183. }
  184. while (besti > alo && bestj > blo && isbjunk(b[bestj - 1]) && a[besti - 1] == b[bestj - 1]) {
  185. besti--;
  186. bestj--;
  187. bestsize++;
  188. }
  189. while (besti + bestsize < ahi && bestj + bestsize < bhi && isbjunk(b[bestj + bestsize]) &&
  190. a[besti + bestsize] == b[bestj + bestsize]) {
  191. bestsize++;
  192. }
  193. return [besti, bestj, bestsize];
  194. }
  195. this.get_matching_blocks = function () {
  196. if (this.matching_blocks != null) return this.matching_blocks;
  197. var la = this.a.length;
  198. var lb = this.b.length;
  199. var queue = [[0, la, 0, lb]];
  200. var matching_blocks = [];
  201. var alo, ahi, blo, bhi, qi, i, j, k, x;
  202. while (queue.length) {
  203. qi = queue.pop();
  204. alo = qi[0];
  205. ahi = qi[1];
  206. blo = qi[2];
  207. bhi = qi[3];
  208. x = this.find_longest_match(alo, ahi, blo, bhi);
  209. i = x[0];
  210. j = x[1];
  211. k = x[2];
  212. if (k) {
  213. matching_blocks.push(x);
  214. if (alo < i && blo < j)
  215. queue.push([alo, i, blo, j]);
  216. if (i+k < ahi && j+k < bhi)
  217. queue.push([i + k, ahi, j + k, bhi]);
  218. }
  219. }
  220. matching_blocks.sort(difflib.__ntuplecomp);
  221. var i1 = j1 = k1 = block = 0;
  222. var non_adjacent = [];
  223. for (var idx in matching_blocks) {
  224. if (matching_blocks.hasOwnProperty(idx)) {
  225. block = matching_blocks[idx];
  226. i2 = block[0];
  227. j2 = block[1];
  228. k2 = block[2];
  229. if (i1 + k1 == i2 && j1 + k1 == j2) {
  230. k1 += k2;
  231. } else {
  232. if (k1) non_adjacent.push([i1, j1, k1]);
  233. i1 = i2;
  234. j1 = j2;
  235. k1 = k2;
  236. }
  237. }
  238. }
  239. if (k1) non_adjacent.push([i1, j1, k1]);
  240. non_adjacent.push([la, lb, 0]);
  241. this.matching_blocks = non_adjacent;
  242. return this.matching_blocks;
  243. }
  244. this.get_opcodes = function () {
  245. if (this.opcodes != null) return this.opcodes;
  246. var i = 0;
  247. var j = 0;
  248. var answer = [];
  249. this.opcodes = answer;
  250. var block, ai, bj, size, tag;
  251. var blocks = this.get_matching_blocks();
  252. for (var idx in blocks) {
  253. if (blocks.hasOwnProperty(idx)) {
  254. block = blocks[idx];
  255. ai = block[0];
  256. bj = block[1];
  257. size = block[2];
  258. tag = '';
  259. if (i < ai && j < bj) {
  260. tag = 'replace';
  261. } else if (i < ai) {
  262. tag = 'delete';
  263. } else if (j < bj) {
  264. tag = 'insert';
  265. }
  266. if (tag) answer.push([tag, i, ai, j, bj]);
  267. i = ai + size;
  268. j = bj + size;
  269. if (size) answer.push(['equal', ai, i, bj, j]);
  270. }
  271. }
  272. return answer;
  273. }
  274. // this is a generator function in the python lib, which of course is not supported in javascript
  275. // the reimplementation builds up the grouped opcodes into a list in their entirety and returns that.
  276. this.get_grouped_opcodes = function (n) {
  277. if (!n) n = 3;
  278. var codes = this.get_opcodes();
  279. if (!codes) codes = [["equal", 0, 1, 0, 1]];
  280. var code, tag, i1, i2, j1, j2;
  281. if (codes[0][0] == 'equal') {
  282. code = codes[0];
  283. tag = code[0];
  284. i1 = code[1];
  285. i2 = code[2];
  286. j1 = code[3];
  287. j2 = code[4];
  288. codes[0] = [tag, Math.max(i1, i2 - n), i2, Math.max(j1, j2 - n), j2];
  289. }
  290. if (codes[codes.length - 1][0] == 'equal') {
  291. code = codes[codes.length - 1];
  292. tag = code[0];
  293. i1 = code[1];
  294. i2 = code[2];
  295. j1 = code[3];
  296. j2 = code[4];
  297. codes[codes.length - 1] = [tag, i1, Math.min(i2, i1 + n), j1, Math.min(j2, j1 + n)];
  298. }
  299. var nn = n + n;
  300. var groups = [];
  301. for (var idx in codes) {
  302. if (codes.hasOwnProperty(idx)) {
  303. code = codes[idx];
  304. tag = code[0];
  305. i1 = code[1];
  306. i2 = code[2];
  307. j1 = code[3];
  308. j2 = code[4];
  309. if (tag == 'equal' && i2 - i1 > nn) {
  310. groups.push([tag, i1, Math.min(i2, i1 + n), j1, Math.min(j2, j1 + n)]);
  311. i1 = Math.max(i1, i2-n);
  312. j1 = Math.max(j1, j2-n);
  313. }
  314. groups.push([tag, i1, i2, j1, j2]);
  315. }
  316. }
  317. if (groups && groups[groups.length - 1][0] == 'equal') groups.pop();
  318. return groups;
  319. }
  320. this.ratio = function () {
  321. matches = difflib.__reduce(
  322. function (sum, triple) { return sum + triple[triple.length - 1]; },
  323. this.get_matching_blocks(), 0);
  324. return difflib.__calculate_ratio(matches, this.a.length + this.b.length);
  325. }
  326. this.quick_ratio = function () {
  327. var fullbcount, elt;
  328. if (this.fullbcount == null) {
  329. this.fullbcount = fullbcount = {};
  330. for (var i = 0; i < this.b.length; i++) {
  331. elt = this.b[i];
  332. fullbcount[elt] = difflib.__dictget(fullbcount, elt, 0) + 1;
  333. }
  334. }
  335. fullbcount = this.fullbcount;
  336. var avail = {};
  337. var availhas = difflib.__isindict(avail);
  338. var matches = numb = 0;
  339. for (var i = 0; i < this.a.length; i++) {
  340. elt = this.a[i];
  341. if (availhas(elt)) {
  342. numb = avail[elt];
  343. } else {
  344. numb = difflib.__dictget(fullbcount, elt, 0);
  345. }
  346. avail[elt] = numb - 1;
  347. if (numb > 0) matches++;
  348. }
  349. return difflib.__calculate_ratio(matches, this.a.length + this.b.length);
  350. }
  351. this.real_quick_ratio = function () {
  352. var la = this.a.length;
  353. var lb = this.b.length;
  354. return _calculate_ratio(Math.min(la, lb), la + lb);
  355. }
  356. this.isjunk = isjunk ? isjunk : difflib.defaultJunkFunction;
  357. this.a = this.b = null;
  358. this.set_seqs(a, b);
  359. }
  360. }