index.es6.js
1 function Diff() {} 2 Diff.prototype = { 3 diff: function diff(oldString, newString) { 4 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 5 var callback = options.callback; 6 7 if (typeof options === 'function') { 8 callback = options; 9 options = {}; 10 } 11 12 this.options = options; 13 var self = this; 14 15 function done(value) { 16 if (callback) { 17 setTimeout(function () { 18 callback(undefined, value); 19 }, 0); 20 return true; 21 } else { 22 return value; 23 } 24 } // Allow subclasses to massage the input prior to running 25 26 27 oldString = this.castInput(oldString); 28 newString = this.castInput(newString); 29 oldString = this.removeEmpty(this.tokenize(oldString)); 30 newString = this.removeEmpty(this.tokenize(newString)); 31 var newLen = newString.length, 32 oldLen = oldString.length; 33 var editLength = 1; 34 var maxEditLength = newLen + oldLen; 35 var bestPath = [{ 36 newPos: -1, 37 components: [] 38 }]; // Seed editLength = 0, i.e. the content starts with the same values 39 40 var oldPos = this.extractCommon(bestPath[0], newString, oldString, 0); 41 42 if (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen) { 43 // Identity per the equality and tokenizer 44 return done([{ 45 value: this.join(newString), 46 count: newString.length 47 }]); 48 } // Main worker method. checks all permutations of a given edit length for acceptance. 49 50 51 function execEditLength() { 52 for (var diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2) { 53 var basePath = void 0; 54 55 var addPath = bestPath[diagonalPath - 1], 56 removePath = bestPath[diagonalPath + 1], 57 _oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; 58 59 if (addPath) { 60 // No one else is going to attempt to use this value, clear it 61 bestPath[diagonalPath - 1] = undefined; 62 } 63 64 var canAdd = addPath && addPath.newPos + 1 < newLen, 65 canRemove = removePath && 0 <= _oldPos && _oldPos < oldLen; 66 67 if (!canAdd && !canRemove) { 68 // If this path is a terminal then prune 69 bestPath[diagonalPath] = undefined; 70 continue; 71 } // Select the diagonal that we want to branch from. We select the prior 72 // path whose position in the new string is the farthest from the origin 73 // and does not pass the bounds of the diff graph 74 75 76 if (!canAdd || canRemove && addPath.newPos < removePath.newPos) { 77 basePath = clonePath(removePath); 78 self.pushComponent(basePath.components, undefined, true); 79 } else { 80 basePath = addPath; // No need to clone, we've pulled it from the list 81 82 basePath.newPos++; 83 self.pushComponent(basePath.components, true, undefined); 84 } 85 86 _oldPos = self.extractCommon(basePath, newString, oldString, diagonalPath); // If we have hit the end of both strings, then we are done 87 88 if (basePath.newPos + 1 >= newLen && _oldPos + 1 >= oldLen) { 89 return done(buildValues(self, basePath.components, newString, oldString, self.useLongestToken)); 90 } else { 91 // Otherwise track this path as a potential candidate and continue. 92 bestPath[diagonalPath] = basePath; 93 } 94 } 95 96 editLength++; 97 } // Performs the length of edit iteration. Is a bit fugly as this has to support the 98 // sync and async mode which is never fun. Loops over execEditLength until a value 99 // is produced. 100 101 102 if (callback) { 103 (function exec() { 104 setTimeout(function () { 105 // This should not happen, but we want to be safe. 106 107 /* istanbul ignore next */ 108 if (editLength > maxEditLength) { 109 return callback(); 110 } 111 112 if (!execEditLength()) { 113 exec(); 114 } 115 }, 0); 116 })(); 117 } else { 118 while (editLength <= maxEditLength) { 119 var ret = execEditLength(); 120 121 if (ret) { 122 return ret; 123 } 124 } 125 } 126 }, 127 pushComponent: function pushComponent(components, added, removed) { 128 var last = components[components.length - 1]; 129 130 if (last && last.added === added && last.removed === removed) { 131 // We need to clone here as the component clone operation is just 132 // as shallow array clone 133 components[components.length - 1] = { 134 count: last.count + 1, 135 added: added, 136 removed: removed 137 }; 138 } else { 139 components.push({ 140 count: 1, 141 added: added, 142 removed: removed 143 }); 144 } 145 }, 146 extractCommon: function extractCommon(basePath, newString, oldString, diagonalPath) { 147 var newLen = newString.length, 148 oldLen = oldString.length, 149 newPos = basePath.newPos, 150 oldPos = newPos - diagonalPath, 151 commonCount = 0; 152 153 while (newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1])) { 154 newPos++; 155 oldPos++; 156 commonCount++; 157 } 158 159 if (commonCount) { 160 basePath.components.push({ 161 count: commonCount 162 }); 163 } 164 165 basePath.newPos = newPos; 166 return oldPos; 167 }, 168 equals: function equals(left, right) { 169 if (this.options.comparator) { 170 return this.options.comparator(left, right); 171 } else { 172 return left === right || this.options.ignoreCase && left.toLowerCase() === right.toLowerCase(); 173 } 174 }, 175 removeEmpty: function removeEmpty(array) { 176 var ret = []; 177 178 for (var i = 0; i < array.length; i++) { 179 if (array[i]) { 180 ret.push(array[i]); 181 } 182 } 183 184 return ret; 185 }, 186 castInput: function castInput(value) { 187 return value; 188 }, 189 tokenize: function tokenize(value) { 190 return value.split(''); 191 }, 192 join: function join(chars) { 193 return chars.join(''); 194 } 195 }; 196 197 function buildValues(diff, components, newString, oldString, useLongestToken) { 198 var componentPos = 0, 199 componentLen = components.length, 200 newPos = 0, 201 oldPos = 0; 202 203 for (; componentPos < componentLen; componentPos++) { 204 var component = components[componentPos]; 205 206 if (!component.removed) { 207 if (!component.added && useLongestToken) { 208 var value = newString.slice(newPos, newPos + component.count); 209 value = value.map(function (value, i) { 210 var oldValue = oldString[oldPos + i]; 211 return oldValue.length > value.length ? oldValue : value; 212 }); 213 component.value = diff.join(value); 214 } else { 215 component.value = diff.join(newString.slice(newPos, newPos + component.count)); 216 } 217 218 newPos += component.count; // Common case 219 220 if (!component.added) { 221 oldPos += component.count; 222 } 223 } else { 224 component.value = diff.join(oldString.slice(oldPos, oldPos + component.count)); 225 oldPos += component.count; // Reverse add and remove so removes are output first to match common convention 226 // The diffing algorithm is tied to add then remove output and this is the simplest 227 // route to get the desired output with minimal overhead. 228 229 if (componentPos && components[componentPos - 1].added) { 230 var tmp = components[componentPos - 1]; 231 components[componentPos - 1] = components[componentPos]; 232 components[componentPos] = tmp; 233 } 234 } 235 } // Special case handle for when one terminal is ignored (i.e. whitespace). 236 // For this case we merge the terminal into the prior string and drop the change. 237 // This is only available for string mode. 238 239 240 var lastComponent = components[componentLen - 1]; 241 242 if (componentLen > 1 && typeof lastComponent.value === 'string' && (lastComponent.added || lastComponent.removed) && diff.equals('', lastComponent.value)) { 243 components[componentLen - 2].value += lastComponent.value; 244 components.pop(); 245 } 246 247 return components; 248 } 249 250 function clonePath(path) { 251 return { 252 newPos: path.newPos, 253 components: path.components.slice(0) 254 }; 255 } 256 257 var characterDiff = new Diff(); 258 function diffChars(oldStr, newStr, options) { 259 return characterDiff.diff(oldStr, newStr, options); 260 } 261 262 function generateOptions(options, defaults) { 263 if (typeof options === 'function') { 264 defaults.callback = options; 265 } else if (options) { 266 for (var name in options) { 267 /* istanbul ignore else */ 268 if (options.hasOwnProperty(name)) { 269 defaults[name] = options[name]; 270 } 271 } 272 } 273 274 return defaults; 275 } 276 277 // 278 // Ranges and exceptions: 279 // Latin-1 Supplement, 0080–00FF 280 // - U+00D7 × Multiplication sign 281 // - U+00F7 ÷ Division sign 282 // Latin Extended-A, 0100–017F 283 // Latin Extended-B, 0180–024F 284 // IPA Extensions, 0250–02AF 285 // Spacing Modifier Letters, 02B0–02FF 286 // - U+02C7 ˇ ˇ Caron 287 // - U+02D8 ˘ ˘ Breve 288 // - U+02D9 ˙ ˙ Dot Above 289 // - U+02DA ˚ ˚ Ring Above 290 // - U+02DB ˛ ˛ Ogonek 291 // - U+02DC ˜ ˜ Small Tilde 292 // - U+02DD ˝ ˝ Double Acute Accent 293 // Latin Extended Additional, 1E00–1EFF 294 295 var extendedWordChars = /^[A-Za-z\xC0-\u02C6\u02C8-\u02D7\u02DE-\u02FF\u1E00-\u1EFF]+$/; 296 var reWhitespace = /\S/; 297 var wordDiff = new Diff(); 298 299 wordDiff.equals = function (left, right) { 300 if (this.options.ignoreCase) { 301 left = left.toLowerCase(); 302 right = right.toLowerCase(); 303 } 304 305 return left === right || this.options.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right); 306 }; 307 308 wordDiff.tokenize = function (value) { 309 // All whitespace symbols except newline group into one token, each newline - in separate token 310 var tokens = value.split(/([^\S\r\n]+|[()[\]{}'"\r\n]|\b)/); // Join the boundary splits that we do not consider to be boundaries. This is primarily the extended Latin character set. 311 312 for (var i = 0; i < tokens.length - 1; i++) { 313 // If we have an empty string in the next field and we have only word chars before and after, merge 314 if (!tokens[i + 1] && tokens[i + 2] && extendedWordChars.test(tokens[i]) && extendedWordChars.test(tokens[i + 2])) { 315 tokens[i] += tokens[i + 2]; 316 tokens.splice(i + 1, 2); 317 i--; 318 } 319 } 320 321 return tokens; 322 }; 323 324 function diffWords(oldStr, newStr, options) { 325 options = generateOptions(options, { 326 ignoreWhitespace: true 327 }); 328 return wordDiff.diff(oldStr, newStr, options); 329 } 330 function diffWordsWithSpace(oldStr, newStr, options) { 331 return wordDiff.diff(oldStr, newStr, options); 332 } 333 334 var lineDiff = new Diff(); 335 336 lineDiff.tokenize = function (value) { 337 var retLines = [], 338 linesAndNewlines = value.split(/(\n|\r\n)/); // Ignore the final empty token that occurs if the string ends with a new line 339 340 if (!linesAndNewlines[linesAndNewlines.length - 1]) { 341 linesAndNewlines.pop(); 342 } // Merge the content and line separators into single tokens 343 344 345 for (var i = 0; i < linesAndNewlines.length; i++) { 346 var line = linesAndNewlines[i]; 347 348 if (i % 2 && !this.options.newlineIsToken) { 349 retLines[retLines.length - 1] += line; 350 } else { 351 if (this.options.ignoreWhitespace) { 352 line = line.trim(); 353 } 354 355 retLines.push(line); 356 } 357 } 358 359 return retLines; 360 }; 361 362 function diffLines(oldStr, newStr, callback) { 363 return lineDiff.diff(oldStr, newStr, callback); 364 } 365 function diffTrimmedLines(oldStr, newStr, callback) { 366 var options = generateOptions(callback, { 367 ignoreWhitespace: true 368 }); 369 return lineDiff.diff(oldStr, newStr, options); 370 } 371 372 var sentenceDiff = new Diff(); 373 374 sentenceDiff.tokenize = function (value) { 375 return value.split(/(\S.+?[.!?])(?=\s+|$)/); 376 }; 377 378 function diffSentences(oldStr, newStr, callback) { 379 return sentenceDiff.diff(oldStr, newStr, callback); 380 } 381 382 var cssDiff = new Diff(); 383 384 cssDiff.tokenize = function (value) { 385 return value.split(/([{}:;,]|\s+)/); 386 }; 387 388 function diffCss(oldStr, newStr, callback) { 389 return cssDiff.diff(oldStr, newStr, callback); 390 } 391 392 function _typeof(obj) { 393 "@babel/helpers - typeof"; 394 395 if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { 396 _typeof = function (obj) { 397 return typeof obj; 398 }; 399 } else { 400 _typeof = function (obj) { 401 return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; 402 }; 403 } 404 405 return _typeof(obj); 406 } 407 408 function _toConsumableArray(arr) { 409 return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); 410 } 411 412 function _arrayWithoutHoles(arr) { 413 if (Array.isArray(arr)) return _arrayLikeToArray(arr); 414 } 415 416 function _iterableToArray(iter) { 417 if (typeof Symbol !== "undefined" && Symbol.iterator in Object(iter)) return Array.from(iter); 418 } 419 420 function _unsupportedIterableToArray(o, minLen) { 421 if (!o) return; 422 if (typeof o === "string") return _arrayLikeToArray(o, minLen); 423 var n = Object.prototype.toString.call(o).slice(8, -1); 424 if (n === "Object" && o.constructor) n = o.constructor.name; 425 if (n === "Map" || n === "Set") return Array.from(o); 426 if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); 427 } 428 429 function _arrayLikeToArray(arr, len) { 430 if (len == null || len > arr.length) len = arr.length; 431 432 for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; 433 434 return arr2; 435 } 436 437 function _nonIterableSpread() { 438 throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); 439 } 440 441 var objectPrototypeToString = Object.prototype.toString; 442 var jsonDiff = new Diff(); // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a 443 // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: 444 445 jsonDiff.useLongestToken = true; 446 jsonDiff.tokenize = lineDiff.tokenize; 447 448 jsonDiff.castInput = function (value) { 449 var _this$options = this.options, 450 undefinedReplacement = _this$options.undefinedReplacement, 451 _this$options$stringi = _this$options.stringifyReplacer, 452 stringifyReplacer = _this$options$stringi === void 0 ? function (k, v) { 453 return typeof v === 'undefined' ? undefinedReplacement : v; 454 } : _this$options$stringi; 455 return typeof value === 'string' ? value : JSON.stringify(canonicalize(value, null, null, stringifyReplacer), stringifyReplacer, ' '); 456 }; 457 458 jsonDiff.equals = function (left, right) { 459 return Diff.prototype.equals.call(jsonDiff, left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1')); 460 }; 461 462 function diffJson(oldObj, newObj, options) { 463 return jsonDiff.diff(oldObj, newObj, options); 464 } // This function handles the presence of circular references by bailing out when encountering an 465 // object that is already on the "stack" of items being processed. Accepts an optional replacer 466 467 function canonicalize(obj, stack, replacementStack, replacer, key) { 468 stack = stack || []; 469 replacementStack = replacementStack || []; 470 471 if (replacer) { 472 obj = replacer(key, obj); 473 } 474 475 var i; 476 477 for (i = 0; i < stack.length; i += 1) { 478 if (stack[i] === obj) { 479 return replacementStack[i]; 480 } 481 } 482 483 var canonicalizedObj; 484 485 if ('[object Array]' === objectPrototypeToString.call(obj)) { 486 stack.push(obj); 487 canonicalizedObj = new Array(obj.length); 488 replacementStack.push(canonicalizedObj); 489 490 for (i = 0; i < obj.length; i += 1) { 491 canonicalizedObj[i] = canonicalize(obj[i], stack, replacementStack, replacer, key); 492 } 493 494 stack.pop(); 495 replacementStack.pop(); 496 return canonicalizedObj; 497 } 498 499 if (obj && obj.toJSON) { 500 obj = obj.toJSON(); 501 } 502 503 if (_typeof(obj) === 'object' && obj !== null) { 504 stack.push(obj); 505 canonicalizedObj = {}; 506 replacementStack.push(canonicalizedObj); 507 508 var sortedKeys = [], 509 _key; 510 511 for (_key in obj) { 512 /* istanbul ignore else */ 513 if (obj.hasOwnProperty(_key)) { 514 sortedKeys.push(_key); 515 } 516 } 517 518 sortedKeys.sort(); 519 520 for (i = 0; i < sortedKeys.length; i += 1) { 521 _key = sortedKeys[i]; 522 canonicalizedObj[_key] = canonicalize(obj[_key], stack, replacementStack, replacer, _key); 523 } 524 525 stack.pop(); 526 replacementStack.pop(); 527 } else { 528 canonicalizedObj = obj; 529 } 530 531 return canonicalizedObj; 532 } 533 534 var arrayDiff = new Diff(); 535 536 arrayDiff.tokenize = function (value) { 537 return value.slice(); 538 }; 539 540 arrayDiff.join = arrayDiff.removeEmpty = function (value) { 541 return value; 542 }; 543 544 function diffArrays(oldArr, newArr, callback) { 545 return arrayDiff.diff(oldArr, newArr, callback); 546 } 547 548 function parsePatch(uniDiff) { 549 var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; 550 var diffstr = uniDiff.split(/\r\n|[\n\v\f\r\x85]/), 551 delimiters = uniDiff.match(/\r\n|[\n\v\f\r\x85]/g) || [], 552 list = [], 553 i = 0; 554 555 function parseIndex() { 556 var index = {}; 557 list.push(index); // Parse diff metadata 558 559 while (i < diffstr.length) { 560 var line = diffstr[i]; // File header found, end parsing diff metadata 561 562 if (/^(\-\-\-|\+\+\+|@@)\s/.test(line)) { 563 break; 564 } // Diff index 565 566 567 var header = /^(?:Index:|diff(?: -r \w+)+)\s+(.+?)\s*$/.exec(line); 568 569 if (header) { 570 index.index = header[1]; 571 } 572 573 i++; 574 } // Parse file headers if they are defined. Unified diff requires them, but 575 // there's no technical issues to have an isolated hunk without file header 576 577 578 parseFileHeader(index); 579 parseFileHeader(index); // Parse hunks 580 581 index.hunks = []; 582 583 while (i < diffstr.length) { 584 var _line = diffstr[i]; 585 586 if (/^(Index:|diff|\-\-\-|\+\+\+)\s/.test(_line)) { 587 break; 588 } else if (/^@@/.test(_line)) { 589 index.hunks.push(parseHunk()); 590 } else if (_line && options.strict) { 591 // Ignore unexpected content unless in strict mode 592 throw new Error('Unknown line ' + (i + 1) + ' ' + JSON.stringify(_line)); 593 } else { 594 i++; 595 } 596 } 597 } // Parses the --- and +++ headers, if none are found, no lines 598 // are consumed. 599 600 601 function parseFileHeader(index) { 602 var fileHeader = /^(---|\+\+\+)\s+(.*)$/.exec(diffstr[i]); 603 604 if (fileHeader) { 605 var keyPrefix = fileHeader[1] === '---' ? 'old' : 'new'; 606 var data = fileHeader[2].split('\t', 2); 607 var fileName = data[0].replace(/\\\\/g, '\\'); 608 609 if (/^".*"$/.test(fileName)) { 610 fileName = fileName.substr(1, fileName.length - 2); 611 } 612 613 index[keyPrefix + 'FileName'] = fileName; 614 index[keyPrefix + 'Header'] = (data[1] || '').trim(); 615 i++; 616 } 617 } // Parses a hunk 618 // This assumes that we are at the start of a hunk. 619 620 621 function parseHunk() { 622 var chunkHeaderIndex = i, 623 chunkHeaderLine = diffstr[i++], 624 chunkHeader = chunkHeaderLine.split(/@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/); 625 var hunk = { 626 oldStart: +chunkHeader[1], 627 oldLines: typeof chunkHeader[2] === 'undefined' ? 1 : +chunkHeader[2], 628 newStart: +chunkHeader[3], 629 newLines: typeof chunkHeader[4] === 'undefined' ? 1 : +chunkHeader[4], 630 lines: [], 631 linedelimiters: [] 632 }; // Unified Diff Format quirk: If the chunk size is 0, 633 // the first number is one lower than one would expect. 634 // https://www.artima.com/weblogs/viewpost.jsp?thread=164293 635 636 if (hunk.oldLines === 0) { 637 hunk.oldStart += 1; 638 } 639 640 if (hunk.newLines === 0) { 641 hunk.newStart += 1; 642 } 643 644 var addCount = 0, 645 removeCount = 0; 646 647 for (; i < diffstr.length; i++) { 648 // Lines starting with '---' could be mistaken for the "remove line" operation 649 // But they could be the header for the next file. Therefore prune such cases out. 650 if (diffstr[i].indexOf('--- ') === 0 && i + 2 < diffstr.length && diffstr[i + 1].indexOf('+++ ') === 0 && diffstr[i + 2].indexOf('@@') === 0) { 651 break; 652 } 653 654 var operation = diffstr[i].length == 0 && i != diffstr.length - 1 ? ' ' : diffstr[i][0]; 655 656 if (operation === '+' || operation === '-' || operation === ' ' || operation === '\\') { 657 hunk.lines.push(diffstr[i]); 658 hunk.linedelimiters.push(delimiters[i] || '\n'); 659 660 if (operation === '+') { 661 addCount++; 662 } else if (operation === '-') { 663 removeCount++; 664 } else if (operation === ' ') { 665 addCount++; 666 removeCount++; 667 } 668 } else { 669 break; 670 } 671 } // Handle the empty block count case 672 673 674 if (!addCount && hunk.newLines === 1) { 675 hunk.newLines = 0; 676 } 677 678 if (!removeCount && hunk.oldLines === 1) { 679 hunk.oldLines = 0; 680 } // Perform optional sanity checking 681 682 683 if (options.strict) { 684 if (addCount !== hunk.newLines) { 685 throw new Error('Added line count did not match for hunk at line ' + (chunkHeaderIndex + 1)); 686 } 687 688 if (removeCount !== hunk.oldLines) { 689 throw new Error('Removed line count did not match for hunk at line ' + (chunkHeaderIndex + 1)); 690 } 691 } 692 693 return hunk; 694 } 695 696 while (i < diffstr.length) { 697 parseIndex(); 698 } 699 700 return list; 701 } 702 703 // Iterator that traverses in the range of [min, max], stepping 704 // by distance from a given start position. I.e. for [0, 4], with 705 // start of 2, this will iterate 2, 3, 1, 4, 0. 706 function distanceIterator (start, minLine, maxLine) { 707 var wantForward = true, 708 backwardExhausted = false, 709 forwardExhausted = false, 710 localOffset = 1; 711 return function iterator() { 712 if (wantForward && !forwardExhausted) { 713 if (backwardExhausted) { 714 localOffset++; 715 } else { 716 wantForward = false; 717 } // Check if trying to fit beyond text length, and if not, check it fits 718 // after offset location (or desired location on first iteration) 719 720 721 if (start + localOffset <= maxLine) { 722 return localOffset; 723 } 724 725 forwardExhausted = true; 726 } 727 728 if (!backwardExhausted) { 729 if (!forwardExhausted) { 730 wantForward = true; 731 } // Check if trying to fit before text beginning, and if not, check it fits 732 // before offset location 733 734 735 if (minLine <= start - localOffset) { 736 return -localOffset++; 737 } 738 739 backwardExhausted = true; 740 return iterator(); 741 } // We tried to fit hunk before text beginning and beyond text length, then 742 // hunk can't fit on the text. Return undefined 743 744 }; 745 } 746 747 function applyPatch(source, uniDiff) { 748 var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {}; 749 750 if (typeof uniDiff === 'string') { 751 uniDiff = parsePatch(uniDiff); 752 } 753 754 if (Array.isArray(uniDiff)) { 755 if (uniDiff.length > 1) { 756 throw new Error('applyPatch only works with a single input.'); 757 } 758 759 uniDiff = uniDiff[0]; 760 } // Apply the diff to the input 761 762 763 var lines = source.split(/\r\n|[\n\v\f\r\x85]/), 764 delimiters = source.match(/\r\n|[\n\v\f\r\x85]/g) || [], 765 hunks = uniDiff.hunks, 766 compareLine = options.compareLine || function (lineNumber, line, operation, patchContent) { 767 return line === patchContent; 768 }, 769 errorCount = 0, 770 fuzzFactor = options.fuzzFactor || 0, 771 minLine = 0, 772 offset = 0, 773 removeEOFNL, 774 addEOFNL; 775 /** 776 * Checks if the hunk exactly fits on the provided location 777 */ 778 779 780 function hunkFits(hunk, toPos) { 781 for (var j = 0; j < hunk.lines.length; j++) { 782 var line = hunk.lines[j], 783 operation = line.length > 0 ? line[0] : ' ', 784 content = line.length > 0 ? line.substr(1) : line; 785 786 if (operation === ' ' || operation === '-') { 787 // Context sanity check 788 if (!compareLine(toPos + 1, lines[toPos], operation, content)) { 789 errorCount++; 790 791 if (errorCount > fuzzFactor) { 792 return false; 793 } 794 } 795 796 toPos++; 797 } 798 } 799 800 return true; 801 } // Search best fit offsets for each hunk based on the previous ones 802 803 804 for (var i = 0; i < hunks.length; i++) { 805 var hunk = hunks[i], 806 maxLine = lines.length - hunk.oldLines, 807 localOffset = 0, 808 toPos = offset + hunk.oldStart - 1; 809 var iterator = distanceIterator(toPos, minLine, maxLine); 810 811 for (; localOffset !== undefined; localOffset = iterator()) { 812 if (hunkFits(hunk, toPos + localOffset)) { 813 hunk.offset = offset += localOffset; 814 break; 815 } 816 } 817 818 if (localOffset === undefined) { 819 return false; 820 } // Set lower text limit to end of the current hunk, so next ones don't try 821 // to fit over already patched text 822 823 824 minLine = hunk.offset + hunk.oldStart + hunk.oldLines; 825 } // Apply patch hunks 826 827 828 var diffOffset = 0; 829 830 for (var _i = 0; _i < hunks.length; _i++) { 831 var _hunk = hunks[_i], 832 _toPos = _hunk.oldStart + _hunk.offset + diffOffset - 1; 833 834 diffOffset += _hunk.newLines - _hunk.oldLines; 835 836 for (var j = 0; j < _hunk.lines.length; j++) { 837 var line = _hunk.lines[j], 838 operation = line.length > 0 ? line[0] : ' ', 839 content = line.length > 0 ? line.substr(1) : line, 840 delimiter = _hunk.linedelimiters[j]; 841 842 if (operation === ' ') { 843 _toPos++; 844 } else if (operation === '-') { 845 lines.splice(_toPos, 1); 846 delimiters.splice(_toPos, 1); 847 /* istanbul ignore else */ 848 } else if (operation === '+') { 849 lines.splice(_toPos, 0, content); 850 delimiters.splice(_toPos, 0, delimiter); 851 _toPos++; 852 } else if (operation === '\\') { 853 var previousOperation = _hunk.lines[j - 1] ? _hunk.lines[j - 1][0] : null; 854 855 if (previousOperation === '+') { 856 removeEOFNL = true; 857 } else if (previousOperation === '-') { 858 addEOFNL = true; 859 } 860 } 861 } 862 } // Handle EOFNL insertion/removal 863 864 865 if (removeEOFNL) { 866 while (!lines[lines.length - 1]) { 867 lines.pop(); 868 delimiters.pop(); 869 } 870 } else if (addEOFNL) { 871 lines.push(''); 872 delimiters.push('\n'); 873 } 874 875 for (var _k = 0; _k < lines.length - 1; _k++) { 876 lines[_k] = lines[_k] + delimiters[_k]; 877 } 878 879 return lines.join(''); 880 } // Wrapper that supports multiple file patches via callbacks. 881 882 function applyPatches(uniDiff, options) { 883 if (typeof uniDiff === 'string') { 884 uniDiff = parsePatch(uniDiff); 885 } 886 887 var currentIndex = 0; 888 889 function processIndex() { 890 var index = uniDiff[currentIndex++]; 891 892 if (!index) { 893 return options.complete(); 894 } 895 896 options.loadFile(index, function (err, data) { 897 if (err) { 898 return options.complete(err); 899 } 900 901 var updatedContent = applyPatch(data, index, options); 902 options.patched(index, updatedContent, function (err) { 903 if (err) { 904 return options.complete(err); 905 } 906 907 processIndex(); 908 }); 909 }); 910 } 911 912 processIndex(); 913 } 914 915 function structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { 916 if (!options) { 917 options = {}; 918 } 919 920 if (typeof options.context === 'undefined') { 921 options.context = 4; 922 } 923 924 var diff = diffLines(oldStr, newStr, options); 925 diff.push({ 926 value: '', 927 lines: [] 928 }); // Append an empty value to make cleanup easier 929 930 function contextLines(lines) { 931 return lines.map(function (entry) { 932 return ' ' + entry; 933 }); 934 } 935 936 var hunks = []; 937 var oldRangeStart = 0, 938 newRangeStart = 0, 939 curRange = [], 940 oldLine = 1, 941 newLine = 1; 942 943 var _loop = function _loop(i) { 944 var current = diff[i], 945 lines = current.lines || current.value.replace(/\n$/, '').split('\n'); 946 current.lines = lines; 947 948 if (current.added || current.removed) { 949 var _curRange; 950 951 // If we have previous context, start with that 952 if (!oldRangeStart) { 953 var prev = diff[i - 1]; 954 oldRangeStart = oldLine; 955 newRangeStart = newLine; 956 957 if (prev) { 958 curRange = options.context > 0 ? contextLines(prev.lines.slice(-options.context)) : []; 959 oldRangeStart -= curRange.length; 960 newRangeStart -= curRange.length; 961 } 962 } // Output our changes 963 964 965 (_curRange = curRange).push.apply(_curRange, _toConsumableArray(lines.map(function (entry) { 966 return (current.added ? '+' : '-') + entry; 967 }))); // Track the updated file position 968 969 970 if (current.added) { 971 newLine += lines.length; 972 } else { 973 oldLine += lines.length; 974 } 975 } else { 976 // Identical context lines. Track line changes 977 if (oldRangeStart) { 978 // Close out any changes that have been output (or join overlapping) 979 if (lines.length <= options.context * 2 && i < diff.length - 2) { 980 var _curRange2; 981 982 // Overlapping 983 (_curRange2 = curRange).push.apply(_curRange2, _toConsumableArray(contextLines(lines))); 984 } else { 985 var _curRange3; 986 987 // end the range and output 988 var contextSize = Math.min(lines.length, options.context); 989 990 (_curRange3 = curRange).push.apply(_curRange3, _toConsumableArray(contextLines(lines.slice(0, contextSize)))); 991 992 var hunk = { 993 oldStart: oldRangeStart, 994 oldLines: oldLine - oldRangeStart + contextSize, 995 newStart: newRangeStart, 996 newLines: newLine - newRangeStart + contextSize, 997 lines: curRange 998 }; 999 1000 if (i >= diff.length - 2 && lines.length <= options.context) { 1001 // EOF is inside this hunk 1002 var oldEOFNewline = /\n$/.test(oldStr); 1003 var newEOFNewline = /\n$/.test(newStr); 1004 var noNlBeforeAdds = lines.length == 0 && curRange.length > hunk.oldLines; 1005 1006 if (!oldEOFNewline && noNlBeforeAdds && oldStr.length > 0) { 1007 // special case: old has no eol and no trailing context; no-nl can end up before adds 1008 // however, if the old file is empty, do not output the no-nl line 1009 curRange.splice(hunk.oldLines, 0, '\\ No newline at end of file'); 1010 } 1011 1012 if (!oldEOFNewline && !noNlBeforeAdds || !newEOFNewline) { 1013 curRange.push('\\ No newline at end of file'); 1014 } 1015 } 1016 1017 hunks.push(hunk); 1018 oldRangeStart = 0; 1019 newRangeStart = 0; 1020 curRange = []; 1021 } 1022 } 1023 1024 oldLine += lines.length; 1025 newLine += lines.length; 1026 } 1027 }; 1028 1029 for (var i = 0; i < diff.length; i++) { 1030 _loop(i); 1031 } 1032 1033 return { 1034 oldFileName: oldFileName, 1035 newFileName: newFileName, 1036 oldHeader: oldHeader, 1037 newHeader: newHeader, 1038 hunks: hunks 1039 }; 1040 } 1041 function formatPatch(diff) { 1042 var ret = []; 1043 1044 if (diff.oldFileName == diff.newFileName) { 1045 ret.push('Index: ' + diff.oldFileName); 1046 } 1047 1048 ret.push('==================================================================='); 1049 ret.push('--- ' + diff.oldFileName + (typeof diff.oldHeader === 'undefined' ? '' : '\t' + diff.oldHeader)); 1050 ret.push('+++ ' + diff.newFileName + (typeof diff.newHeader === 'undefined' ? '' : '\t' + diff.newHeader)); 1051 1052 for (var i = 0; i < diff.hunks.length; i++) { 1053 var hunk = diff.hunks[i]; // Unified Diff Format quirk: If the chunk size is 0, 1054 // the first number is one lower than one would expect. 1055 // https://www.artima.com/weblogs/viewpost.jsp?thread=164293 1056 1057 if (hunk.oldLines === 0) { 1058 hunk.oldStart -= 1; 1059 } 1060 1061 if (hunk.newLines === 0) { 1062 hunk.newStart -= 1; 1063 } 1064 1065 ret.push('@@ -' + hunk.oldStart + ',' + hunk.oldLines + ' +' + hunk.newStart + ',' + hunk.newLines + ' @@'); 1066 ret.push.apply(ret, hunk.lines); 1067 } 1068 1069 return ret.join('\n') + '\n'; 1070 } 1071 function createTwoFilesPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options) { 1072 return formatPatch(structuredPatch(oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader, options)); 1073 } 1074 function createPatch(fileName, oldStr, newStr, oldHeader, newHeader, options) { 1075 return createTwoFilesPatch(fileName, fileName, oldStr, newStr, oldHeader, newHeader, options); 1076 } 1077 1078 function arrayEqual(a, b) { 1079 if (a.length !== b.length) { 1080 return false; 1081 } 1082 1083 return arrayStartsWith(a, b); 1084 } 1085 function arrayStartsWith(array, start) { 1086 if (start.length > array.length) { 1087 return false; 1088 } 1089 1090 for (var i = 0; i < start.length; i++) { 1091 if (start[i] !== array[i]) { 1092 return false; 1093 } 1094 } 1095 1096 return true; 1097 } 1098 1099 function calcLineCount(hunk) { 1100 var _calcOldNewLineCount = calcOldNewLineCount(hunk.lines), 1101 oldLines = _calcOldNewLineCount.oldLines, 1102 newLines = _calcOldNewLineCount.newLines; 1103 1104 if (oldLines !== undefined) { 1105 hunk.oldLines = oldLines; 1106 } else { 1107 delete hunk.oldLines; 1108 } 1109 1110 if (newLines !== undefined) { 1111 hunk.newLines = newLines; 1112 } else { 1113 delete hunk.newLines; 1114 } 1115 } 1116 function merge(mine, theirs, base) { 1117 mine = loadPatch(mine, base); 1118 theirs = loadPatch(theirs, base); 1119 var ret = {}; // For index we just let it pass through as it doesn't have any necessary meaning. 1120 // Leaving sanity checks on this to the API consumer that may know more about the 1121 // meaning in their own context. 1122 1123 if (mine.index || theirs.index) { 1124 ret.index = mine.index || theirs.index; 1125 } 1126 1127 if (mine.newFileName || theirs.newFileName) { 1128 if (!fileNameChanged(mine)) { 1129 // No header or no change in ours, use theirs (and ours if theirs does not exist) 1130 ret.oldFileName = theirs.oldFileName || mine.oldFileName; 1131 ret.newFileName = theirs.newFileName || mine.newFileName; 1132 ret.oldHeader = theirs.oldHeader || mine.oldHeader; 1133 ret.newHeader = theirs.newHeader || mine.newHeader; 1134 } else if (!fileNameChanged(theirs)) { 1135 // No header or no change in theirs, use ours 1136 ret.oldFileName = mine.oldFileName; 1137 ret.newFileName = mine.newFileName; 1138 ret.oldHeader = mine.oldHeader; 1139 ret.newHeader = mine.newHeader; 1140 } else { 1141 // Both changed... figure it out 1142 ret.oldFileName = selectField(ret, mine.oldFileName, theirs.oldFileName); 1143 ret.newFileName = selectField(ret, mine.newFileName, theirs.newFileName); 1144 ret.oldHeader = selectField(ret, mine.oldHeader, theirs.oldHeader); 1145 ret.newHeader = selectField(ret, mine.newHeader, theirs.newHeader); 1146 } 1147 } 1148 1149 ret.hunks = []; 1150 var mineIndex = 0, 1151 theirsIndex = 0, 1152 mineOffset = 0, 1153 theirsOffset = 0; 1154 1155 while (mineIndex < mine.hunks.length || theirsIndex < theirs.hunks.length) { 1156 var mineCurrent = mine.hunks[mineIndex] || { 1157 oldStart: Infinity 1158 }, 1159 theirsCurrent = theirs.hunks[theirsIndex] || { 1160 oldStart: Infinity 1161 }; 1162 1163 if (hunkBefore(mineCurrent, theirsCurrent)) { 1164 // This patch does not overlap with any of the others, yay. 1165 ret.hunks.push(cloneHunk(mineCurrent, mineOffset)); 1166 mineIndex++; 1167 theirsOffset += mineCurrent.newLines - mineCurrent.oldLines; 1168 } else if (hunkBefore(theirsCurrent, mineCurrent)) { 1169 // This patch does not overlap with any of the others, yay. 1170 ret.hunks.push(cloneHunk(theirsCurrent, theirsOffset)); 1171 theirsIndex++; 1172 mineOffset += theirsCurrent.newLines - theirsCurrent.oldLines; 1173 } else { 1174 // Overlap, merge as best we can 1175 var mergedHunk = { 1176 oldStart: Math.min(mineCurrent.oldStart, theirsCurrent.oldStart), 1177 oldLines: 0, 1178 newStart: Math.min(mineCurrent.newStart + mineOffset, theirsCurrent.oldStart + theirsOffset), 1179 newLines: 0, 1180 lines: [] 1181 }; 1182 mergeLines(mergedHunk, mineCurrent.oldStart, mineCurrent.lines, theirsCurrent.oldStart, theirsCurrent.lines); 1183 theirsIndex++; 1184 mineIndex++; 1185 ret.hunks.push(mergedHunk); 1186 } 1187 } 1188 1189 return ret; 1190 } 1191 1192 function loadPatch(param, base) { 1193 if (typeof param === 'string') { 1194 if (/^@@/m.test(param) || /^Index:/m.test(param)) { 1195 return parsePatch(param)[0]; 1196 } 1197 1198 if (!base) { 1199 throw new Error('Must provide a base reference or pass in a patch'); 1200 } 1201 1202 return structuredPatch(undefined, undefined, base, param); 1203 } 1204 1205 return param; 1206 } 1207 1208 function fileNameChanged(patch) { 1209 return patch.newFileName && patch.newFileName !== patch.oldFileName; 1210 } 1211 1212 function selectField(index, mine, theirs) { 1213 if (mine === theirs) { 1214 return mine; 1215 } else { 1216 index.conflict = true; 1217 return { 1218 mine: mine, 1219 theirs: theirs 1220 }; 1221 } 1222 } 1223 1224 function hunkBefore(test, check) { 1225 return test.oldStart < check.oldStart && test.oldStart + test.oldLines < check.oldStart; 1226 } 1227 1228 function cloneHunk(hunk, offset) { 1229 return { 1230 oldStart: hunk.oldStart, 1231 oldLines: hunk.oldLines, 1232 newStart: hunk.newStart + offset, 1233 newLines: hunk.newLines, 1234 lines: hunk.lines 1235 }; 1236 } 1237 1238 function mergeLines(hunk, mineOffset, mineLines, theirOffset, theirLines) { 1239 // This will generally result in a conflicted hunk, but there are cases where the context 1240 // is the only overlap where we can successfully merge the content here. 1241 var mine = { 1242 offset: mineOffset, 1243 lines: mineLines, 1244 index: 0 1245 }, 1246 their = { 1247 offset: theirOffset, 1248 lines: theirLines, 1249 index: 0 1250 }; // Handle any leading content 1251 1252 insertLeading(hunk, mine, their); 1253 insertLeading(hunk, their, mine); // Now in the overlap content. Scan through and select the best changes from each. 1254 1255 while (mine.index < mine.lines.length && their.index < their.lines.length) { 1256 var mineCurrent = mine.lines[mine.index], 1257 theirCurrent = their.lines[their.index]; 1258 1259 if ((mineCurrent[0] === '-' || mineCurrent[0] === '+') && (theirCurrent[0] === '-' || theirCurrent[0] === '+')) { 1260 // Both modified ... 1261 mutualChange(hunk, mine, their); 1262 } else if (mineCurrent[0] === '+' && theirCurrent[0] === ' ') { 1263 var _hunk$lines; 1264 1265 // Mine inserted 1266 (_hunk$lines = hunk.lines).push.apply(_hunk$lines, _toConsumableArray(collectChange(mine))); 1267 } else if (theirCurrent[0] === '+' && mineCurrent[0] === ' ') { 1268 var _hunk$lines2; 1269 1270 // Theirs inserted 1271 (_hunk$lines2 = hunk.lines).push.apply(_hunk$lines2, _toConsumableArray(collectChange(their))); 1272 } else if (mineCurrent[0] === '-' && theirCurrent[0] === ' ') { 1273 // Mine removed or edited 1274 removal(hunk, mine, their); 1275 } else if (theirCurrent[0] === '-' && mineCurrent[0] === ' ') { 1276 // Their removed or edited 1277 removal(hunk, their, mine, true); 1278 } else if (mineCurrent === theirCurrent) { 1279 // Context identity 1280 hunk.lines.push(mineCurrent); 1281 mine.index++; 1282 their.index++; 1283 } else { 1284 // Context mismatch 1285 conflict(hunk, collectChange(mine), collectChange(their)); 1286 } 1287 } // Now push anything that may be remaining 1288 1289 1290 insertTrailing(hunk, mine); 1291 insertTrailing(hunk, their); 1292 calcLineCount(hunk); 1293 } 1294 1295 function mutualChange(hunk, mine, their) { 1296 var myChanges = collectChange(mine), 1297 theirChanges = collectChange(their); 1298 1299 if (allRemoves(myChanges) && allRemoves(theirChanges)) { 1300 // Special case for remove changes that are supersets of one another 1301 if (arrayStartsWith(myChanges, theirChanges) && skipRemoveSuperset(their, myChanges, myChanges.length - theirChanges.length)) { 1302 var _hunk$lines3; 1303 1304 (_hunk$lines3 = hunk.lines).push.apply(_hunk$lines3, _toConsumableArray(myChanges)); 1305 1306 return; 1307 } else if (arrayStartsWith(theirChanges, myChanges) && skipRemoveSuperset(mine, theirChanges, theirChanges.length - myChanges.length)) { 1308 var _hunk$lines4; 1309 1310 (_hunk$lines4 = hunk.lines).push.apply(_hunk$lines4, _toConsumableArray(theirChanges)); 1311 1312 return; 1313 } 1314 } else if (arrayEqual(myChanges, theirChanges)) { 1315 var _hunk$lines5; 1316 1317 (_hunk$lines5 = hunk.lines).push.apply(_hunk$lines5, _toConsumableArray(myChanges)); 1318 1319 return; 1320 } 1321 1322 conflict(hunk, myChanges, theirChanges); 1323 } 1324 1325 function removal(hunk, mine, their, swap) { 1326 var myChanges = collectChange(mine), 1327 theirChanges = collectContext(their, myChanges); 1328 1329 if (theirChanges.merged) { 1330 var _hunk$lines6; 1331 1332 (_hunk$lines6 = hunk.lines).push.apply(_hunk$lines6, _toConsumableArray(theirChanges.merged)); 1333 } else { 1334 conflict(hunk, swap ? theirChanges : myChanges, swap ? myChanges : theirChanges); 1335 } 1336 } 1337 1338 function conflict(hunk, mine, their) { 1339 hunk.conflict = true; 1340 hunk.lines.push({ 1341 conflict: true, 1342 mine: mine, 1343 theirs: their 1344 }); 1345 } 1346 1347 function insertLeading(hunk, insert, their) { 1348 while (insert.offset < their.offset && insert.index < insert.lines.length) { 1349 var line = insert.lines[insert.index++]; 1350 hunk.lines.push(line); 1351 insert.offset++; 1352 } 1353 } 1354 1355 function insertTrailing(hunk, insert) { 1356 while (insert.index < insert.lines.length) { 1357 var line = insert.lines[insert.index++]; 1358 hunk.lines.push(line); 1359 } 1360 } 1361 1362 function collectChange(state) { 1363 var ret = [], 1364 operation = state.lines[state.index][0]; 1365 1366 while (state.index < state.lines.length) { 1367 var line = state.lines[state.index]; // Group additions that are immediately after subtractions and treat them as one "atomic" modify change. 1368 1369 if (operation === '-' && line[0] === '+') { 1370 operation = '+'; 1371 } 1372 1373 if (operation === line[0]) { 1374 ret.push(line); 1375 state.index++; 1376 } else { 1377 break; 1378 } 1379 } 1380 1381 return ret; 1382 } 1383 1384 function collectContext(state, matchChanges) { 1385 var changes = [], 1386 merged = [], 1387 matchIndex = 0, 1388 contextChanges = false, 1389 conflicted = false; 1390 1391 while (matchIndex < matchChanges.length && state.index < state.lines.length) { 1392 var change = state.lines[state.index], 1393 match = matchChanges[matchIndex]; // Once we've hit our add, then we are done 1394 1395 if (match[0] === '+') { 1396 break; 1397 } 1398 1399 contextChanges = contextChanges || change[0] !== ' '; 1400 merged.push(match); 1401 matchIndex++; // Consume any additions in the other block as a conflict to attempt 1402 // to pull in the remaining context after this 1403 1404 if (change[0] === '+') { 1405 conflicted = true; 1406 1407 while (change[0] === '+') { 1408 changes.push(change); 1409 change = state.lines[++state.index]; 1410 } 1411 } 1412 1413 if (match.substr(1) === change.substr(1)) { 1414 changes.push(change); 1415 state.index++; 1416 } else { 1417 conflicted = true; 1418 } 1419 } 1420 1421 if ((matchChanges[matchIndex] || '')[0] === '+' && contextChanges) { 1422 conflicted = true; 1423 } 1424 1425 if (conflicted) { 1426 return changes; 1427 } 1428 1429 while (matchIndex < matchChanges.length) { 1430 merged.push(matchChanges[matchIndex++]); 1431 } 1432 1433 return { 1434 merged: merged, 1435 changes: changes 1436 }; 1437 } 1438 1439 function allRemoves(changes) { 1440 return changes.reduce(function (prev, change) { 1441 return prev && change[0] === '-'; 1442 }, true); 1443 } 1444 1445 function skipRemoveSuperset(state, removeChanges, delta) { 1446 for (var i = 0; i < delta; i++) { 1447 var changeContent = removeChanges[removeChanges.length - delta + i].substr(1); 1448 1449 if (state.lines[state.index + i] !== ' ' + changeContent) { 1450 return false; 1451 } 1452 } 1453 1454 state.index += delta; 1455 return true; 1456 } 1457 1458 function calcOldNewLineCount(lines) { 1459 var oldLines = 0; 1460 var newLines = 0; 1461 lines.forEach(function (line) { 1462 if (typeof line !== 'string') { 1463 var myCount = calcOldNewLineCount(line.mine); 1464 var theirCount = calcOldNewLineCount(line.theirs); 1465 1466 if (oldLines !== undefined) { 1467 if (myCount.oldLines === theirCount.oldLines) { 1468 oldLines += myCount.oldLines; 1469 } else { 1470 oldLines = undefined; 1471 } 1472 } 1473 1474 if (newLines !== undefined) { 1475 if (myCount.newLines === theirCount.newLines) { 1476 newLines += myCount.newLines; 1477 } else { 1478 newLines = undefined; 1479 } 1480 } 1481 } else { 1482 if (newLines !== undefined && (line[0] === '+' || line[0] === ' ')) { 1483 newLines++; 1484 } 1485 1486 if (oldLines !== undefined && (line[0] === '-' || line[0] === ' ')) { 1487 oldLines++; 1488 } 1489 } 1490 }); 1491 return { 1492 oldLines: oldLines, 1493 newLines: newLines 1494 }; 1495 } 1496 1497 // See: http://code.google.com/p/google-diff-match-patch/wiki/API 1498 function convertChangesToDMP(changes) { 1499 var ret = [], 1500 change, 1501 operation; 1502 1503 for (var i = 0; i < changes.length; i++) { 1504 change = changes[i]; 1505 1506 if (change.added) { 1507 operation = 1; 1508 } else if (change.removed) { 1509 operation = -1; 1510 } else { 1511 operation = 0; 1512 } 1513 1514 ret.push([operation, change.value]); 1515 } 1516 1517 return ret; 1518 } 1519 1520 function convertChangesToXML(changes) { 1521 var ret = []; 1522 1523 for (var i = 0; i < changes.length; i++) { 1524 var change = changes[i]; 1525 1526 if (change.added) { 1527 ret.push('<ins>'); 1528 } else if (change.removed) { 1529 ret.push('<del>'); 1530 } 1531 1532 ret.push(escapeHTML(change.value)); 1533 1534 if (change.added) { 1535 ret.push('</ins>'); 1536 } else if (change.removed) { 1537 ret.push('</del>'); 1538 } 1539 } 1540 1541 return ret.join(''); 1542 } 1543 1544 function escapeHTML(s) { 1545 var n = s; 1546 n = n.replace(/&/g, '&'); 1547 n = n.replace(/</g, '<'); 1548 n = n.replace(/>/g, '>'); 1549 n = n.replace(/"/g, '"'); 1550 return n; 1551 } 1552 1553 export { Diff, applyPatch, applyPatches, canonicalize, convertChangesToDMP, convertChangesToXML, createPatch, createTwoFilesPatch, diffArrays, diffChars, diffCss, diffJson, diffLines, diffSentences, diffTrimmedLines, diffWords, diffWordsWithSpace, merge, parsePatch, structuredPatch };