|
| 1 | +/* |
| 2 | + * head part of browser-dist file |
| 3 | + |
| 4 | + */ |
| 5 | + |
| 6 | +(function(global, undefined){ |
| 7 | + |
| 8 | +/* |
| 9 | + * Compare using myers' LCS/SES algorithm |
| 10 | + * ( https://neil.fraser.name/software/diff_match_patch/myers.pdf ) |
| 11 | + |
| 12 | + */ |
| 13 | + |
| 14 | +var compare = function(cnt1, cnt2, splitter){ |
| 15 | + var SPLITTER = splitter || '', |
| 16 | + |
| 17 | + MARK_EMPTY = -1, |
| 18 | + MARK_SAME = 0, |
| 19 | + |
| 20 | + STEP_NOCHANGE = 0, |
| 21 | + STEP_REPLACE = 1, |
| 22 | + STEP_REMOVE = 2, |
| 23 | + STEP_INSERT = 3; |
| 24 | + |
| 25 | + // result object |
| 26 | + var diff = [], |
| 27 | + result = { |
| 28 | + splitter: SPLITTER, |
| 29 | + diff: diff |
| 30 | + }; |
| 31 | + |
| 32 | + // if string equal |
| 33 | + if(cnt1 === cnt2){ |
| 34 | + return result; |
| 35 | + } |
| 36 | + |
| 37 | + // convert string to array |
| 38 | + var arr1 = cnt1.split(SPLITTER), |
| 39 | + arr2 = cnt2.split(SPLITTER); |
| 40 | + |
| 41 | + var N = arr1.length, |
| 42 | + M = arr2.length, |
| 43 | + MAX = N + M, |
| 44 | + steps = Array.apply(null, {length: M+N+1}).map(function(){return [];}), |
| 45 | + furthestReaching = [], |
| 46 | + dist = -1; |
| 47 | + |
| 48 | + furthestReaching[MAX + 1] = 0; |
| 49 | + |
| 50 | + // caculate min distance & log each step |
| 51 | + for(var D = 0; D <= MAX && dist === -1; D++){ |
| 52 | + for(var k = -D, x, y, step; k <= D && dist === -1; k+=2){ |
| 53 | + if(k === -D || (k !== D && furthestReaching[k - 1 + MAX] < furthestReaching[k + 1 + MAX])){ |
| 54 | + x = furthestReaching[k + 1 + MAX]; |
| 55 | + step = STEP_INSERT; |
| 56 | + }else{ |
| 57 | + x = furthestReaching[k - 1 + MAX] + 1; |
| 58 | + step = STEP_REMOVE; |
| 59 | + } |
| 60 | + |
| 61 | + y = x - k; |
| 62 | + steps[x][y] = step; |
| 63 | + |
| 64 | + while(x < N && y < M && arr1[x] === arr2[y]){ |
| 65 | + x++; |
| 66 | + y++; |
| 67 | + steps[x][y] = STEP_NOCHANGE; |
| 68 | + } |
| 69 | + |
| 70 | + furthestReaching[k + MAX] = x; |
| 71 | + |
| 72 | + if(x >= N && y >= M){ |
| 73 | + dist = D; |
| 74 | + } |
| 75 | + } |
| 76 | + } |
| 77 | + |
| 78 | + // get contrast arrays (src & target) by analyze step by step |
| 79 | + var src = [], target = []; |
| 80 | + |
| 81 | + for(var i = N,j = M; i > 0 || j > 0;){ |
| 82 | + switch(steps[i][j]){ |
| 83 | + |
| 84 | + case STEP_NOCHANGE: |
| 85 | + src.unshift(arr1[i-1]); |
| 86 | + target.unshift(MARK_SAME); |
| 87 | + i -= 1; |
| 88 | + j -= 1; |
| 89 | + break; |
| 90 | + |
| 91 | + case STEP_REPLACE: |
| 92 | + src.unshift(arr1[i-1]); |
| 93 | + target.unshift(arr2[j-1]); |
| 94 | + i -= 1; |
| 95 | + j -= 1; |
| 96 | + break; |
| 97 | + |
| 98 | + case STEP_REMOVE: |
| 99 | + src.unshift(arr1[i-1]); |
| 100 | + target.unshift(MARK_EMPTY); |
| 101 | + i -= 1; |
| 102 | + j -= 0; |
| 103 | + break; |
| 104 | + |
| 105 | + case STEP_INSERT: |
| 106 | + src.unshift(MARK_EMPTY); |
| 107 | + target.unshift(arr2[j-1]); |
| 108 | + i -= 0; |
| 109 | + j -= 1; |
| 110 | + break; |
| 111 | + |
| 112 | + } |
| 113 | + } |
| 114 | + |
| 115 | + // convert contrast arrays to diff array |
| 116 | + var l = target.length, |
| 117 | + start, len, to, |
| 118 | + notEmpty = function(s){return s !== MARK_EMPTY;}; |
| 119 | + |
| 120 | + for(i = l - 1; i >= 0;){ |
| 121 | + // join continuous diffs |
| 122 | + for(j = i; target[j] !== MARK_SAME && j >= 0; j--){} |
| 123 | + |
| 124 | + if(j < i){ |
| 125 | + start = src.slice(0, j + 1).filter(notEmpty).length; // start pos of diffs (on src) |
| 126 | + len = src.slice(j + 1, i + 1).filter(notEmpty).length; // length should be replaced (on src) |
| 127 | + to = target.slice(j + 1, i + 1).filter(notEmpty); // new content |
| 128 | + |
| 129 | + diff.unshift( |
| 130 | + to.length ? |
| 131 | + [start, len, to.join(SPLITTER)] : // replace |
| 132 | + [start, len] // remove |
| 133 | + ); |
| 134 | + } |
| 135 | + |
| 136 | + i = j - 1; |
| 137 | + } |
| 138 | + |
| 139 | + return result; |
| 140 | +}; |
| 141 | + |
| 142 | +if(typeof module === "object" && typeof module.exports === "object"){ |
| 143 | + module.exports = compare; |
| 144 | +} |
| 145 | +/* |
| 146 | + * Merge diff results to origin scequence |
| 147 | + |
| 148 | + */ |
| 149 | + |
| 150 | +var merge = function(cnt, compareResult){ |
| 151 | + var splitter = compareResult.splitter, |
| 152 | + diff = compareResult.diff, |
| 153 | + result = cnt.split(splitter); |
| 154 | + |
| 155 | + for(var i = diff.length - 1, item; i >= 0; i--){ |
| 156 | + item = diff[i]; |
| 157 | + result.splice.apply(result, item); |
| 158 | + } |
| 159 | + |
| 160 | + return result.join(splitter); |
| 161 | +}; |
| 162 | + |
| 163 | +if(typeof module === "object" && typeof module.exports === "object"){ |
| 164 | + module.exports = merge; |
| 165 | +} |
| 166 | +/* |
| 167 | + * tail part of browser-dist file |
| 168 | + |
| 169 | + */ |
| 170 | + |
| 171 | +global.diff = { |
| 172 | + compare: compare, |
| 173 | + merge: merge |
| 174 | +}; |
| 175 | + |
| 176 | +})(this); |
0 commit comments