├── .github └── workflows │ └── test.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── index.js ├── package-lock.json ├── package.json └── test └── index.js /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test diff match patch 2 | on: 3 | workflow_dispatch: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v3 13 | 14 | - name: Setup Node.js 15 | uses: actions/setup-node@v3 16 | with: 17 | node-version: 16 18 | 19 | - name: Install 20 | run: npm ci 21 | 22 | - name: Run tests 23 | run: npm test 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | lib-cov 2 | *.seed 3 | *.log 4 | *.csv 5 | *.dat 6 | *.out 7 | *.pid 8 | *.gz 9 | pids 10 | logs 11 | results 12 | npm-debug.log 13 | node_modules 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test 2 | .travis.yml -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2006 Google Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # diff-match-patch 2 | 3 | npm package for https://github.com/google/diff-match-patch 4 | 5 | [![Build Status](https://img.shields.io/travis/JackuB/diff-match-patch/master.svg)](https://travis-ci.org/JackuB/diff-match-patch) 6 | [![Dependency Status](https://img.shields.io/david/JackuB/diff-match-patch.svg)](https://david-dm.org/JackuB/diff-match-patch) 7 | [![NPM version](https://img.shields.io/npm/v/diff-match-patch.svg)](https://www.npmjs.com/package/diff-match-patch) 8 | [![Known Vulnerabilities](https://snyk.io/test/github/JackuB/diff-match-patch/badge.svg)](https://snyk.io/test/github/JackuB/diff-match-patch) 9 | 10 | ## Installation 11 | 12 | npm install diff-match-patch 13 | 14 | ## API 15 | 16 | [Source](https://github.com/google/diff-match-patch/wiki/API) 17 | 18 | ### Initialization 19 | 20 | The first step is to create a new `diff_match_patch` object. This object contains various properties which set the behaviour of the algorithms, as well as the following methods/functions: 21 | 22 | ### diff_main(text1, text2) → diffs 23 | 24 | An array of differences is computed which describe the transformation of text1 into text2. Each difference is an array (JavaScript, Lua) or tuple (Python) or Diff object (C++, C#, Objective C, Java). The first element specifies if it is an insertion (1), a deletion (-1) or an equality (0). The second element specifies the affected text. 25 | 26 | ```diff_main("Good dog", "Bad dog") → [(-1, "Goo"), (1, "Ba"), (0, "d dog")]``` 27 | 28 | Despite the large number of optimisations used in this function, diff can take a while to compute. The `diff_match_patch.Diff_Timeout` property is available to set how many seconds any diff's exploration phase may take. The default value is 1.0. A value of 0 disables the timeout and lets diff run until completion. Should diff timeout, the return value will still be a valid difference, though probably non-optimal. 29 | 30 | ### diff_cleanupSemantic(diffs) → null 31 | 32 | A diff of two unrelated texts can be filled with coincidental matches. For example, the diff of "mouse" and "sofas" is `[(-1, "m"), (1, "s"), (0, "o"), (-1, "u"), (1, "fa"), (0, "s"), (-1, "e")]`. While this is the optimum diff, it is difficult for humans to understand. Semantic cleanup rewrites the diff, expanding it into a more intelligible format. The above example would become: `[(-1, "mouse"), (1, "sofas")]`. If a diff is to be human-readable, it should be passed to `diff_cleanupSemantic`. 33 | 34 | ### diff_cleanupEfficiency(diffs) → null 35 | 36 | This function is similar to `diff_cleanupSemantic`, except that instead of optimising a diff to be human-readable, it optimises the diff to be efficient for machine processing. The results of both cleanup types are often the same. 37 | 38 | The efficiency cleanup is based on the observation that a diff made up of large numbers of small diffs edits may take longer to process (in downstream applications) or take more capacity to store or transmit than a smaller number of larger diffs. The `diff_match_patch.Diff_EditCost` property sets what the cost of handling a new edit is in terms of handling extra characters in an existing edit. The default value is 4, which means if expanding the length of a diff by three characters can eliminate one edit, then that optimisation will reduce the total costs. 39 | 40 | ### diff_levenshtein(diffs) → int 41 | 42 | Given a diff, measure its Levenshtein distance in terms of the number of inserted, deleted or substituted characters. The minimum distance is 0 which means equality, the maximum distance is the length of the longer string. 43 | 44 | ### diff_prettyHtml(diffs) → html 45 | 46 | Takes a diff array and returns a pretty HTML sequence. This function is mainly intended as an example from which to write ones own display functions. 47 | 48 | ### match_main(text, pattern, loc) → location 49 | 50 | Given a text to search, a pattern to search for and an expected location in the text near which to find the pattern, return the location which matches closest. The function will search for the best match based on both the number of character errors between the pattern and the potential match, as well as the distance between the expected location and the potential match. 51 | 52 | The following example is a classic dilemma. There are two potential matches, one is close to the expected location but contains a one character error, the other is far from the expected location but is exactly the pattern sought after: `match_main("abc12345678901234567890abbc", "abc", 26)` Which result is returned (0 or 24) is determined by the `diff_match_patch.Match_Distance` property. An exact letter match which is 'distance' characters away from the fuzzy location would score as a complete mismatch. For example, a distance of '0' requires the match be at the exact location specified, whereas a threshold of '1000' would require a perfect match to be within 800 characters of the expected location to be found using a 0.8 threshold (see below). The larger Match_Distance is, the slower match_main() may take to compute. This variable defaults to 1000. 53 | 54 | Another property is `diff_match_patch.Match_Threshold` which determines the cut-off value for a valid match. If Match_Threshold is closer to 0, the requirements for accuracy increase. If Match_Threshold is closer to 1 then it is more likely that a match will be found. The larger Match_Threshold is, the slower match_main() may take to compute. This variable defaults to 0.5. If no match is found, the function returns -1. 55 | 56 | ### patch_make(text1, text2) → patches 57 | 58 | ### patch_make(diffs) → patches 59 | 60 | ### patch_make(text1, diffs) → patches 61 | 62 | Given two texts, or an already computed list of differences, return an array of patch objects. The third form (text1, diffs) is preferred, use it if you happen to have that data available, otherwise this function will compute the missing pieces. 63 | 64 | ### patch_toText(patches) → text 65 | 66 | Reduces an array of patch objects to a block of text which looks extremely similar to the standard GNU diff/patch format. This text may be stored or transmitted. 67 | 68 | ### patch_fromText(text) → patches 69 | 70 | Parses a block of text (which was presumably created by the patch_toText function) and returns an array of patch objects. 71 | 72 | ### patch_apply(patches, text1) → [text2, results] 73 | 74 | Applies a list of patches to text1. The first element of the return value is the newly patched text. The second element is an array of true/false values indicating which of the patches were successfully applied. [Note that this second element is not too useful since large patches may get broken up internally, resulting in a longer results list than the input with no way to figure out which patch succeeded or failed. A more informative API is in development.] 75 | 76 | The previously mentioned Match_Distance and Match_Threshold properties are used to evaluate patch application on text which does not match exactly. In addition, the `diff_match_patch.Patch_DeleteThreshold` property determines how closely the text within a major (~64 character) delete needs to match the expected text. If Patch_DeleteThreshold is closer to 0, then the deleted text must match the expected text more closely. If Patch_DeleteThreshold is closer to 1, then the deleted text may contain anything. In most use cases Patch_DeleteThreshold should just be set to the same value as Match_Threshold. 77 | 78 | 79 | ## Usage 80 | ```javascript 81 | import DiffMatchPatch from 'diff-match-patch'; 82 | 83 | const dmp = new DiffMatchPatch(); 84 | const diff = dmp.diff_main('dogs bark', 'cats bark'); 85 | 86 | // You can also use the following properties: 87 | DiffMatchPatch.DIFF_DELETE = -1; 88 | DiffMatchPatch.DIFF_INSERT = 1; 89 | DiffMatchPatch.DIFF_EQUAL = 0; 90 | ``` 91 | 92 | ## License 93 | 94 | http://www.apache.org/licenses/LICENSE-2.0 95 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Diff Match and Patch 3 | * Copyright 2018 The diff-match-patch Authors. 4 | * https://github.com/google/diff-match-patch 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | /** 20 | * @fileoverview Computes the difference between two texts to create a patch. 21 | * Applies the patch onto another text, allowing for errors. 22 | * @author fraser@google.com (Neil Fraser) 23 | */ 24 | 25 | /** 26 | * Class containing the diff, match and patch methods. 27 | * @constructor 28 | */ 29 | var diff_match_patch = function() { 30 | 31 | // Defaults. 32 | // Redefine these in your program to override the defaults. 33 | 34 | // Number of seconds to map a diff before giving up (0 for infinity). 35 | this.Diff_Timeout = 1.0; 36 | // Cost of an empty edit operation in terms of edit characters. 37 | this.Diff_EditCost = 4; 38 | // At what point is no match declared (0.0 = perfection, 1.0 = very loose). 39 | this.Match_Threshold = 0.5; 40 | // How far to search for a match (0 = exact location, 1000+ = broad match). 41 | // A match this many characters away from the expected location will add 42 | // 1.0 to the score (0.0 is a perfect match). 43 | this.Match_Distance = 1000; 44 | // When deleting a large block of text (over ~64 characters), how close do 45 | // the contents have to be to match the expected contents. (0.0 = perfection, 46 | // 1.0 = very loose). Note that Match_Threshold controls how closely the 47 | // end points of a delete need to match. 48 | this.Patch_DeleteThreshold = 0.5; 49 | // Chunk size for context length. 50 | this.Patch_Margin = 4; 51 | 52 | // The number of bits in an int. 53 | this.Match_MaxBits = 32; 54 | }; 55 | 56 | 57 | // DIFF FUNCTIONS 58 | 59 | 60 | /** 61 | * The data structure representing a diff is an array of tuples: 62 | * [[DIFF_DELETE, 'Hello'], [DIFF_INSERT, 'Goodbye'], [DIFF_EQUAL, ' world.']] 63 | * which means: delete 'Hello', add 'Goodbye' and keep ' world.' 64 | */ 65 | var DIFF_DELETE = -1; 66 | var DIFF_INSERT = 1; 67 | var DIFF_EQUAL = 0; 68 | 69 | /** 70 | * Class representing one diff tuple. 71 | * ~Attempts to look like a two-element array (which is what this used to be).~ 72 | * Constructor returns an actual two-element array, to allow destructing @JackuB 73 | * See https://github.com/JackuB/diff-match-patch/issues/14 for details 74 | * @param {number} op Operation, one of: DIFF_DELETE, DIFF_INSERT, DIFF_EQUAL. 75 | * @param {string} text Text to be deleted, inserted, or retained. 76 | * @constructor 77 | */ 78 | diff_match_patch.Diff = function(op, text) { 79 | return [op, text]; 80 | }; 81 | 82 | /** 83 | * Find the differences between two texts. Simplifies the problem by stripping 84 | * any common prefix or suffix off the texts before diffing. 85 | * @param {string} text1 Old string to be diffed. 86 | * @param {string} text2 New string to be diffed. 87 | * @param {boolean=} opt_checklines Optional speedup flag. If present and false, 88 | * then don't run a line-level diff first to identify the changed areas. 89 | * Defaults to true, which does a faster, slightly less optimal diff. 90 | * @param {number=} opt_deadline Optional time when the diff should be complete 91 | * by. Used internally for recursive calls. Users should set DiffTimeout 92 | * instead. 93 | * @return {!Array.} Array of diff tuples. 94 | */ 95 | diff_match_patch.prototype.diff_main = function(text1, text2, opt_checklines, 96 | opt_deadline) { 97 | // Set a deadline by which time the diff must be complete. 98 | if (typeof opt_deadline == 'undefined') { 99 | if (this.Diff_Timeout <= 0) { 100 | opt_deadline = Number.MAX_VALUE; 101 | } else { 102 | opt_deadline = (new Date).getTime() + this.Diff_Timeout * 1000; 103 | } 104 | } 105 | var deadline = opt_deadline; 106 | 107 | // Check for null inputs. 108 | if (text1 == null || text2 == null) { 109 | throw new Error('Null input. (diff_main)'); 110 | } 111 | 112 | // Check for equality (speedup). 113 | if (text1 == text2) { 114 | if (text1) { 115 | return [new diff_match_patch.Diff(DIFF_EQUAL, text1)]; 116 | } 117 | return []; 118 | } 119 | 120 | if (typeof opt_checklines == 'undefined') { 121 | opt_checklines = true; 122 | } 123 | var checklines = opt_checklines; 124 | 125 | // Trim off common prefix (speedup). 126 | var commonlength = this.diff_commonPrefix(text1, text2); 127 | var commonprefix = text1.substring(0, commonlength); 128 | text1 = text1.substring(commonlength); 129 | text2 = text2.substring(commonlength); 130 | 131 | // Trim off common suffix (speedup). 132 | commonlength = this.diff_commonSuffix(text1, text2); 133 | var commonsuffix = text1.substring(text1.length - commonlength); 134 | text1 = text1.substring(0, text1.length - commonlength); 135 | text2 = text2.substring(0, text2.length - commonlength); 136 | 137 | // Compute the diff on the middle block. 138 | var diffs = this.diff_compute_(text1, text2, checklines, deadline); 139 | 140 | // Restore the prefix and suffix. 141 | if (commonprefix) { 142 | diffs.unshift(new diff_match_patch.Diff(DIFF_EQUAL, commonprefix)); 143 | } 144 | if (commonsuffix) { 145 | diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, commonsuffix)); 146 | } 147 | this.diff_cleanupMerge(diffs); 148 | return diffs; 149 | }; 150 | 151 | 152 | /** 153 | * Find the differences between two texts. Assumes that the texts do not 154 | * have any common prefix or suffix. 155 | * @param {string} text1 Old string to be diffed. 156 | * @param {string} text2 New string to be diffed. 157 | * @param {boolean} checklines Speedup flag. If false, then don't run a 158 | * line-level diff first to identify the changed areas. 159 | * If true, then run a faster, slightly less optimal diff. 160 | * @param {number} deadline Time when the diff should be complete by. 161 | * @return {!Array.} Array of diff tuples. 162 | * @private 163 | */ 164 | diff_match_patch.prototype.diff_compute_ = function(text1, text2, checklines, 165 | deadline) { 166 | var diffs; 167 | 168 | if (!text1) { 169 | // Just add some text (speedup). 170 | return [new diff_match_patch.Diff(DIFF_INSERT, text2)]; 171 | } 172 | 173 | if (!text2) { 174 | // Just delete some text (speedup). 175 | return [new diff_match_patch.Diff(DIFF_DELETE, text1)]; 176 | } 177 | 178 | var longtext = text1.length > text2.length ? text1 : text2; 179 | var shorttext = text1.length > text2.length ? text2 : text1; 180 | var i = longtext.indexOf(shorttext); 181 | if (i != -1) { 182 | // Shorter text is inside the longer text (speedup). 183 | diffs = [new diff_match_patch.Diff(DIFF_INSERT, longtext.substring(0, i)), 184 | new diff_match_patch.Diff(DIFF_EQUAL, shorttext), 185 | new diff_match_patch.Diff(DIFF_INSERT, 186 | longtext.substring(i + shorttext.length))]; 187 | // Swap insertions for deletions if diff is reversed. 188 | if (text1.length > text2.length) { 189 | diffs[0][0] = diffs[2][0] = DIFF_DELETE; 190 | } 191 | return diffs; 192 | } 193 | 194 | if (shorttext.length == 1) { 195 | // Single character string. 196 | // After the previous speedup, the character can't be an equality. 197 | return [new diff_match_patch.Diff(DIFF_DELETE, text1), 198 | new diff_match_patch.Diff(DIFF_INSERT, text2)]; 199 | } 200 | 201 | // Check to see if the problem can be split in two. 202 | var hm = this.diff_halfMatch_(text1, text2); 203 | if (hm) { 204 | // A half-match was found, sort out the return data. 205 | var text1_a = hm[0]; 206 | var text1_b = hm[1]; 207 | var text2_a = hm[2]; 208 | var text2_b = hm[3]; 209 | var mid_common = hm[4]; 210 | // Send both pairs off for separate processing. 211 | var diffs_a = this.diff_main(text1_a, text2_a, checklines, deadline); 212 | var diffs_b = this.diff_main(text1_b, text2_b, checklines, deadline); 213 | // Merge the results. 214 | return diffs_a.concat([new diff_match_patch.Diff(DIFF_EQUAL, mid_common)], 215 | diffs_b); 216 | } 217 | 218 | if (checklines && text1.length > 100 && text2.length > 100) { 219 | return this.diff_lineMode_(text1, text2, deadline); 220 | } 221 | 222 | return this.diff_bisect_(text1, text2, deadline); 223 | }; 224 | 225 | 226 | /** 227 | * Do a quick line-level diff on both strings, then rediff the parts for 228 | * greater accuracy. 229 | * This speedup can produce non-minimal diffs. 230 | * @param {string} text1 Old string to be diffed. 231 | * @param {string} text2 New string to be diffed. 232 | * @param {number} deadline Time when the diff should be complete by. 233 | * @return {!Array.} Array of diff tuples. 234 | * @private 235 | */ 236 | diff_match_patch.prototype.diff_lineMode_ = function(text1, text2, deadline) { 237 | // Scan the text on a line-by-line basis first. 238 | var a = this.diff_linesToChars_(text1, text2); 239 | text1 = a.chars1; 240 | text2 = a.chars2; 241 | var linearray = a.lineArray; 242 | 243 | var diffs = this.diff_main(text1, text2, false, deadline); 244 | 245 | // Convert the diff back to original text. 246 | this.diff_charsToLines_(diffs, linearray); 247 | // Eliminate freak matches (e.g. blank lines) 248 | this.diff_cleanupSemantic(diffs); 249 | 250 | // Rediff any replacement blocks, this time character-by-character. 251 | // Add a dummy entry at the end. 252 | diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, '')); 253 | var pointer = 0; 254 | var count_delete = 0; 255 | var count_insert = 0; 256 | var text_delete = ''; 257 | var text_insert = ''; 258 | while (pointer < diffs.length) { 259 | switch (diffs[pointer][0]) { 260 | case DIFF_INSERT: 261 | count_insert++; 262 | text_insert += diffs[pointer][1]; 263 | break; 264 | case DIFF_DELETE: 265 | count_delete++; 266 | text_delete += diffs[pointer][1]; 267 | break; 268 | case DIFF_EQUAL: 269 | // Upon reaching an equality, check for prior redundancies. 270 | if (count_delete >= 1 && count_insert >= 1) { 271 | // Delete the offending records and add the merged ones. 272 | diffs.splice(pointer - count_delete - count_insert, 273 | count_delete + count_insert); 274 | pointer = pointer - count_delete - count_insert; 275 | var subDiff = 276 | this.diff_main(text_delete, text_insert, false, deadline); 277 | for (var j = subDiff.length - 1; j >= 0; j--) { 278 | diffs.splice(pointer, 0, subDiff[j]); 279 | } 280 | pointer = pointer + subDiff.length; 281 | } 282 | count_insert = 0; 283 | count_delete = 0; 284 | text_delete = ''; 285 | text_insert = ''; 286 | break; 287 | } 288 | pointer++; 289 | } 290 | diffs.pop(); // Remove the dummy entry at the end. 291 | 292 | return diffs; 293 | }; 294 | 295 | 296 | /** 297 | * Find the 'middle snake' of a diff, split the problem in two 298 | * and return the recursively constructed diff. 299 | * See Myers 1986 paper: An O(ND) Difference Algorithm and Its Variations. 300 | * @param {string} text1 Old string to be diffed. 301 | * @param {string} text2 New string to be diffed. 302 | * @param {number} deadline Time at which to bail if not yet complete. 303 | * @return {!Array.} Array of diff tuples. 304 | * @private 305 | */ 306 | diff_match_patch.prototype.diff_bisect_ = function(text1, text2, deadline) { 307 | // Cache the text lengths to prevent multiple calls. 308 | var text1_length = text1.length; 309 | var text2_length = text2.length; 310 | var max_d = Math.ceil((text1_length + text2_length) / 2); 311 | var v_offset = max_d; 312 | var v_length = 2 * max_d; 313 | var v1 = new Array(v_length); 314 | var v2 = new Array(v_length); 315 | // Setting all elements to -1 is faster in Chrome & Firefox than mixing 316 | // integers and undefined. 317 | for (var x = 0; x < v_length; x++) { 318 | v1[x] = -1; 319 | v2[x] = -1; 320 | } 321 | v1[v_offset + 1] = 0; 322 | v2[v_offset + 1] = 0; 323 | var delta = text1_length - text2_length; 324 | // If the total number of characters is odd, then the front path will collide 325 | // with the reverse path. 326 | var front = (delta % 2 != 0); 327 | // Offsets for start and end of k loop. 328 | // Prevents mapping of space beyond the grid. 329 | var k1start = 0; 330 | var k1end = 0; 331 | var k2start = 0; 332 | var k2end = 0; 333 | for (var d = 0; d < max_d; d++) { 334 | // Bail out if deadline is reached. 335 | if ((new Date()).getTime() > deadline) { 336 | break; 337 | } 338 | 339 | // Walk the front path one step. 340 | for (var k1 = -d + k1start; k1 <= d - k1end; k1 += 2) { 341 | var k1_offset = v_offset + k1; 342 | var x1; 343 | if (k1 == -d || (k1 != d && v1[k1_offset - 1] < v1[k1_offset + 1])) { 344 | x1 = v1[k1_offset + 1]; 345 | } else { 346 | x1 = v1[k1_offset - 1] + 1; 347 | } 348 | var y1 = x1 - k1; 349 | while (x1 < text1_length && y1 < text2_length && 350 | text1.charAt(x1) == text2.charAt(y1)) { 351 | x1++; 352 | y1++; 353 | } 354 | v1[k1_offset] = x1; 355 | if (x1 > text1_length) { 356 | // Ran off the right of the graph. 357 | k1end += 2; 358 | } else if (y1 > text2_length) { 359 | // Ran off the bottom of the graph. 360 | k1start += 2; 361 | } else if (front) { 362 | var k2_offset = v_offset + delta - k1; 363 | if (k2_offset >= 0 && k2_offset < v_length && v2[k2_offset] != -1) { 364 | // Mirror x2 onto top-left coordinate system. 365 | var x2 = text1_length - v2[k2_offset]; 366 | if (x1 >= x2) { 367 | // Overlap detected. 368 | return this.diff_bisectSplit_(text1, text2, x1, y1, deadline); 369 | } 370 | } 371 | } 372 | } 373 | 374 | // Walk the reverse path one step. 375 | for (var k2 = -d + k2start; k2 <= d - k2end; k2 += 2) { 376 | var k2_offset = v_offset + k2; 377 | var x2; 378 | if (k2 == -d || (k2 != d && v2[k2_offset - 1] < v2[k2_offset + 1])) { 379 | x2 = v2[k2_offset + 1]; 380 | } else { 381 | x2 = v2[k2_offset - 1] + 1; 382 | } 383 | var y2 = x2 - k2; 384 | while (x2 < text1_length && y2 < text2_length && 385 | text1.charAt(text1_length - x2 - 1) == 386 | text2.charAt(text2_length - y2 - 1)) { 387 | x2++; 388 | y2++; 389 | } 390 | v2[k2_offset] = x2; 391 | if (x2 > text1_length) { 392 | // Ran off the left of the graph. 393 | k2end += 2; 394 | } else if (y2 > text2_length) { 395 | // Ran off the top of the graph. 396 | k2start += 2; 397 | } else if (!front) { 398 | var k1_offset = v_offset + delta - k2; 399 | if (k1_offset >= 0 && k1_offset < v_length && v1[k1_offset] != -1) { 400 | var x1 = v1[k1_offset]; 401 | var y1 = v_offset + x1 - k1_offset; 402 | // Mirror x2 onto top-left coordinate system. 403 | x2 = text1_length - x2; 404 | if (x1 >= x2) { 405 | // Overlap detected. 406 | return this.diff_bisectSplit_(text1, text2, x1, y1, deadline); 407 | } 408 | } 409 | } 410 | } 411 | } 412 | // Diff took too long and hit the deadline or 413 | // number of diffs equals number of characters, no commonality at all. 414 | return [new diff_match_patch.Diff(DIFF_DELETE, text1), 415 | new diff_match_patch.Diff(DIFF_INSERT, text2)]; 416 | }; 417 | 418 | 419 | /** 420 | * Given the location of the 'middle snake', split the diff in two parts 421 | * and recurse. 422 | * @param {string} text1 Old string to be diffed. 423 | * @param {string} text2 New string to be diffed. 424 | * @param {number} x Index of split point in text1. 425 | * @param {number} y Index of split point in text2. 426 | * @param {number} deadline Time at which to bail if not yet complete. 427 | * @return {!Array.} Array of diff tuples. 428 | * @private 429 | */ 430 | diff_match_patch.prototype.diff_bisectSplit_ = function(text1, text2, x, y, 431 | deadline) { 432 | var text1a = text1.substring(0, x); 433 | var text2a = text2.substring(0, y); 434 | var text1b = text1.substring(x); 435 | var text2b = text2.substring(y); 436 | 437 | // Compute both diffs serially. 438 | var diffs = this.diff_main(text1a, text2a, false, deadline); 439 | var diffsb = this.diff_main(text1b, text2b, false, deadline); 440 | 441 | return diffs.concat(diffsb); 442 | }; 443 | 444 | 445 | /** 446 | * Split two texts into an array of strings. Reduce the texts to a string of 447 | * hashes where each Unicode character represents one line. 448 | * @param {string} text1 First string. 449 | * @param {string} text2 Second string. 450 | * @return {{chars1: string, chars2: string, lineArray: !Array.}} 451 | * An object containing the encoded text1, the encoded text2 and 452 | * the array of unique strings. 453 | * The zeroth element of the array of unique strings is intentionally blank. 454 | * @private 455 | */ 456 | diff_match_patch.prototype.diff_linesToChars_ = function(text1, text2) { 457 | var lineArray = []; // e.g. lineArray[4] == 'Hello\n' 458 | var lineHash = {}; // e.g. lineHash['Hello\n'] == 4 459 | 460 | // '\x00' is a valid character, but various debuggers don't like it. 461 | // So we'll insert a junk entry to avoid generating a null character. 462 | lineArray[0] = ''; 463 | 464 | /** 465 | * Split a text into an array of strings. Reduce the texts to a string of 466 | * hashes where each Unicode character represents one line. 467 | * Modifies linearray and linehash through being a closure. 468 | * @param {string} text String to encode. 469 | * @return {string} Encoded string. 470 | * @private 471 | */ 472 | function diff_linesToCharsMunge_(text) { 473 | var chars = ''; 474 | // Walk the text, pulling out a substring for each line. 475 | // text.split('\n') would would temporarily double our memory footprint. 476 | // Modifying text would create many large strings to garbage collect. 477 | var lineStart = 0; 478 | var lineEnd = -1; 479 | // Keeping our own length variable is faster than looking it up. 480 | var lineArrayLength = lineArray.length; 481 | while (lineEnd < text.length - 1) { 482 | lineEnd = text.indexOf('\n', lineStart); 483 | if (lineEnd == -1) { 484 | lineEnd = text.length - 1; 485 | } 486 | var line = text.substring(lineStart, lineEnd + 1); 487 | 488 | if (lineHash.hasOwnProperty ? lineHash.hasOwnProperty(line) : 489 | (lineHash[line] !== undefined)) { 490 | chars += String.fromCharCode(lineHash[line]); 491 | } else { 492 | if (lineArrayLength == maxLines) { 493 | // Bail out at 65535 because 494 | // String.fromCharCode(65536) == String.fromCharCode(0) 495 | line = text.substring(lineStart); 496 | lineEnd = text.length; 497 | } 498 | chars += String.fromCharCode(lineArrayLength); 499 | lineHash[line] = lineArrayLength; 500 | lineArray[lineArrayLength++] = line; 501 | } 502 | lineStart = lineEnd + 1; 503 | } 504 | return chars; 505 | } 506 | // Allocate 2/3rds of the space for text1, the rest for text2. 507 | var maxLines = 40000; 508 | var chars1 = diff_linesToCharsMunge_(text1); 509 | maxLines = 65535; 510 | var chars2 = diff_linesToCharsMunge_(text2); 511 | return {chars1: chars1, chars2: chars2, lineArray: lineArray}; 512 | }; 513 | 514 | 515 | /** 516 | * Rehydrate the text in a diff from a string of line hashes to real lines of 517 | * text. 518 | * @param {!Array.} diffs Array of diff tuples. 519 | * @param {!Array.} lineArray Array of unique strings. 520 | * @private 521 | */ 522 | diff_match_patch.prototype.diff_charsToLines_ = function(diffs, lineArray) { 523 | for (var i = 0; i < diffs.length; i++) { 524 | var chars = diffs[i][1]; 525 | var text = []; 526 | for (var j = 0; j < chars.length; j++) { 527 | text[j] = lineArray[chars.charCodeAt(j)]; 528 | } 529 | diffs[i][1] = text.join(''); 530 | } 531 | }; 532 | 533 | 534 | /** 535 | * Determine the common prefix of two strings. 536 | * @param {string} text1 First string. 537 | * @param {string} text2 Second string. 538 | * @return {number} The number of characters common to the start of each 539 | * string. 540 | */ 541 | diff_match_patch.prototype.diff_commonPrefix = function(text1, text2) { 542 | // Quick check for common null cases. 543 | if (!text1 || !text2 || text1.charAt(0) != text2.charAt(0)) { 544 | return 0; 545 | } 546 | // Binary search. 547 | // Performance analysis: https://neil.fraser.name/news/2007/10/09/ 548 | var pointermin = 0; 549 | var pointermax = Math.min(text1.length, text2.length); 550 | var pointermid = pointermax; 551 | var pointerstart = 0; 552 | while (pointermin < pointermid) { 553 | if (text1.substring(pointerstart, pointermid) == 554 | text2.substring(pointerstart, pointermid)) { 555 | pointermin = pointermid; 556 | pointerstart = pointermin; 557 | } else { 558 | pointermax = pointermid; 559 | } 560 | pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); 561 | } 562 | return pointermid; 563 | }; 564 | 565 | 566 | /** 567 | * Determine the common suffix of two strings. 568 | * @param {string} text1 First string. 569 | * @param {string} text2 Second string. 570 | * @return {number} The number of characters common to the end of each string. 571 | */ 572 | diff_match_patch.prototype.diff_commonSuffix = function(text1, text2) { 573 | // Quick check for common null cases. 574 | if (!text1 || !text2 || 575 | text1.charAt(text1.length - 1) != text2.charAt(text2.length - 1)) { 576 | return 0; 577 | } 578 | // Binary search. 579 | // Performance analysis: https://neil.fraser.name/news/2007/10/09/ 580 | var pointermin = 0; 581 | var pointermax = Math.min(text1.length, text2.length); 582 | var pointermid = pointermax; 583 | var pointerend = 0; 584 | while (pointermin < pointermid) { 585 | if (text1.substring(text1.length - pointermid, text1.length - pointerend) == 586 | text2.substring(text2.length - pointermid, text2.length - pointerend)) { 587 | pointermin = pointermid; 588 | pointerend = pointermin; 589 | } else { 590 | pointermax = pointermid; 591 | } 592 | pointermid = Math.floor((pointermax - pointermin) / 2 + pointermin); 593 | } 594 | return pointermid; 595 | }; 596 | 597 | 598 | /** 599 | * Determine if the suffix of one string is the prefix of another. 600 | * @param {string} text1 First string. 601 | * @param {string} text2 Second string. 602 | * @return {number} The number of characters common to the end of the first 603 | * string and the start of the second string. 604 | * @private 605 | */ 606 | diff_match_patch.prototype.diff_commonOverlap_ = function(text1, text2) { 607 | // Cache the text lengths to prevent multiple calls. 608 | var text1_length = text1.length; 609 | var text2_length = text2.length; 610 | // Eliminate the null case. 611 | if (text1_length == 0 || text2_length == 0) { 612 | return 0; 613 | } 614 | // Truncate the longer string. 615 | if (text1_length > text2_length) { 616 | text1 = text1.substring(text1_length - text2_length); 617 | } else if (text1_length < text2_length) { 618 | text2 = text2.substring(0, text1_length); 619 | } 620 | var text_length = Math.min(text1_length, text2_length); 621 | // Quick check for the worst case. 622 | if (text1 == text2) { 623 | return text_length; 624 | } 625 | 626 | // Start by looking for a single character match 627 | // and increase length until no match is found. 628 | // Performance analysis: https://neil.fraser.name/news/2010/11/04/ 629 | var best = 0; 630 | var length = 1; 631 | while (true) { 632 | var pattern = text1.substring(text_length - length); 633 | var found = text2.indexOf(pattern); 634 | if (found == -1) { 635 | return best; 636 | } 637 | length += found; 638 | if (found == 0 || text1.substring(text_length - length) == 639 | text2.substring(0, length)) { 640 | best = length; 641 | length++; 642 | } 643 | } 644 | }; 645 | 646 | 647 | /** 648 | * Do the two texts share a substring which is at least half the length of the 649 | * longer text? 650 | * This speedup can produce non-minimal diffs. 651 | * @param {string} text1 First string. 652 | * @param {string} text2 Second string. 653 | * @return {Array.} Five element Array, containing the prefix of 654 | * text1, the suffix of text1, the prefix of text2, the suffix of 655 | * text2 and the common middle. Or null if there was no match. 656 | * @private 657 | */ 658 | diff_match_patch.prototype.diff_halfMatch_ = function(text1, text2) { 659 | if (this.Diff_Timeout <= 0) { 660 | // Don't risk returning a non-optimal diff if we have unlimited time. 661 | return null; 662 | } 663 | var longtext = text1.length > text2.length ? text1 : text2; 664 | var shorttext = text1.length > text2.length ? text2 : text1; 665 | if (longtext.length < 4 || shorttext.length * 2 < longtext.length) { 666 | return null; // Pointless. 667 | } 668 | var dmp = this; // 'this' becomes 'window' in a closure. 669 | 670 | /** 671 | * Does a substring of shorttext exist within longtext such that the substring 672 | * is at least half the length of longtext? 673 | * Closure, but does not reference any external variables. 674 | * @param {string} longtext Longer string. 675 | * @param {string} shorttext Shorter string. 676 | * @param {number} i Start index of quarter length substring within longtext. 677 | * @return {Array.} Five element Array, containing the prefix of 678 | * longtext, the suffix of longtext, the prefix of shorttext, the suffix 679 | * of shorttext and the common middle. Or null if there was no match. 680 | * @private 681 | */ 682 | function diff_halfMatchI_(longtext, shorttext, i) { 683 | // Start with a 1/4 length substring at position i as a seed. 684 | var seed = longtext.substring(i, i + Math.floor(longtext.length / 4)); 685 | var j = -1; 686 | var best_common = ''; 687 | var best_longtext_a, best_longtext_b, best_shorttext_a, best_shorttext_b; 688 | while ((j = shorttext.indexOf(seed, j + 1)) != -1) { 689 | var prefixLength = dmp.diff_commonPrefix(longtext.substring(i), 690 | shorttext.substring(j)); 691 | var suffixLength = dmp.diff_commonSuffix(longtext.substring(0, i), 692 | shorttext.substring(0, j)); 693 | if (best_common.length < suffixLength + prefixLength) { 694 | best_common = shorttext.substring(j - suffixLength, j) + 695 | shorttext.substring(j, j + prefixLength); 696 | best_longtext_a = longtext.substring(0, i - suffixLength); 697 | best_longtext_b = longtext.substring(i + prefixLength); 698 | best_shorttext_a = shorttext.substring(0, j - suffixLength); 699 | best_shorttext_b = shorttext.substring(j + prefixLength); 700 | } 701 | } 702 | if (best_common.length * 2 >= longtext.length) { 703 | return [best_longtext_a, best_longtext_b, 704 | best_shorttext_a, best_shorttext_b, best_common]; 705 | } else { 706 | return null; 707 | } 708 | } 709 | 710 | // First check if the second quarter is the seed for a half-match. 711 | var hm1 = diff_halfMatchI_(longtext, shorttext, 712 | Math.ceil(longtext.length / 4)); 713 | // Check again based on the third quarter. 714 | var hm2 = diff_halfMatchI_(longtext, shorttext, 715 | Math.ceil(longtext.length / 2)); 716 | var hm; 717 | if (!hm1 && !hm2) { 718 | return null; 719 | } else if (!hm2) { 720 | hm = hm1; 721 | } else if (!hm1) { 722 | hm = hm2; 723 | } else { 724 | // Both matched. Select the longest. 725 | hm = hm1[4].length > hm2[4].length ? hm1 : hm2; 726 | } 727 | 728 | // A half-match was found, sort out the return data. 729 | var text1_a, text1_b, text2_a, text2_b; 730 | if (text1.length > text2.length) { 731 | text1_a = hm[0]; 732 | text1_b = hm[1]; 733 | text2_a = hm[2]; 734 | text2_b = hm[3]; 735 | } else { 736 | text2_a = hm[0]; 737 | text2_b = hm[1]; 738 | text1_a = hm[2]; 739 | text1_b = hm[3]; 740 | } 741 | var mid_common = hm[4]; 742 | return [text1_a, text1_b, text2_a, text2_b, mid_common]; 743 | }; 744 | 745 | 746 | /** 747 | * Reduce the number of edits by eliminating semantically trivial equalities. 748 | * @param {!Array.} diffs Array of diff tuples. 749 | */ 750 | diff_match_patch.prototype.diff_cleanupSemantic = function(diffs) { 751 | var changes = false; 752 | var equalities = []; // Stack of indices where equalities are found. 753 | var equalitiesLength = 0; // Keeping our own length var is faster in JS. 754 | /** @type {?string} */ 755 | var lastEquality = null; 756 | // Always equal to diffs[equalities[equalitiesLength - 1]][1] 757 | var pointer = 0; // Index of current position. 758 | // Number of characters that changed prior to the equality. 759 | var length_insertions1 = 0; 760 | var length_deletions1 = 0; 761 | // Number of characters that changed after the equality. 762 | var length_insertions2 = 0; 763 | var length_deletions2 = 0; 764 | while (pointer < diffs.length) { 765 | if (diffs[pointer][0] == DIFF_EQUAL) { // Equality found. 766 | equalities[equalitiesLength++] = pointer; 767 | length_insertions1 = length_insertions2; 768 | length_deletions1 = length_deletions2; 769 | length_insertions2 = 0; 770 | length_deletions2 = 0; 771 | lastEquality = diffs[pointer][1]; 772 | } else { // An insertion or deletion. 773 | if (diffs[pointer][0] == DIFF_INSERT) { 774 | length_insertions2 += diffs[pointer][1].length; 775 | } else { 776 | length_deletions2 += diffs[pointer][1].length; 777 | } 778 | // Eliminate an equality that is smaller or equal to the edits on both 779 | // sides of it. 780 | if (lastEquality && (lastEquality.length <= 781 | Math.max(length_insertions1, length_deletions1)) && 782 | (lastEquality.length <= Math.max(length_insertions2, 783 | length_deletions2))) { 784 | // Duplicate record. 785 | diffs.splice(equalities[equalitiesLength - 1], 0, 786 | new diff_match_patch.Diff(DIFF_DELETE, lastEquality)); 787 | // Change second copy to insert. 788 | diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; 789 | // Throw away the equality we just deleted. 790 | equalitiesLength--; 791 | // Throw away the previous equality (it needs to be reevaluated). 792 | equalitiesLength--; 793 | pointer = equalitiesLength > 0 ? equalities[equalitiesLength - 1] : -1; 794 | length_insertions1 = 0; // Reset the counters. 795 | length_deletions1 = 0; 796 | length_insertions2 = 0; 797 | length_deletions2 = 0; 798 | lastEquality = null; 799 | changes = true; 800 | } 801 | } 802 | pointer++; 803 | } 804 | 805 | // Normalize the diff. 806 | if (changes) { 807 | this.diff_cleanupMerge(diffs); 808 | } 809 | this.diff_cleanupSemanticLossless(diffs); 810 | 811 | // Find any overlaps between deletions and insertions. 812 | // e.g: abcxxxxxxdef 813 | // -> abcxxxdef 814 | // e.g: xxxabcdefxxx 815 | // -> defxxxabc 816 | // Only extract an overlap if it is as big as the edit ahead or behind it. 817 | pointer = 1; 818 | while (pointer < diffs.length) { 819 | if (diffs[pointer - 1][0] == DIFF_DELETE && 820 | diffs[pointer][0] == DIFF_INSERT) { 821 | var deletion = diffs[pointer - 1][1]; 822 | var insertion = diffs[pointer][1]; 823 | var overlap_length1 = this.diff_commonOverlap_(deletion, insertion); 824 | var overlap_length2 = this.diff_commonOverlap_(insertion, deletion); 825 | if (overlap_length1 >= overlap_length2) { 826 | if (overlap_length1 >= deletion.length / 2 || 827 | overlap_length1 >= insertion.length / 2) { 828 | // Overlap found. Insert an equality and trim the surrounding edits. 829 | diffs.splice(pointer, 0, new diff_match_patch.Diff(DIFF_EQUAL, 830 | insertion.substring(0, overlap_length1))); 831 | diffs[pointer - 1][1] = 832 | deletion.substring(0, deletion.length - overlap_length1); 833 | diffs[pointer + 1][1] = insertion.substring(overlap_length1); 834 | pointer++; 835 | } 836 | } else { 837 | if (overlap_length2 >= deletion.length / 2 || 838 | overlap_length2 >= insertion.length / 2) { 839 | // Reverse overlap found. 840 | // Insert an equality and swap and trim the surrounding edits. 841 | diffs.splice(pointer, 0, new diff_match_patch.Diff(DIFF_EQUAL, 842 | deletion.substring(0, overlap_length2))); 843 | diffs[pointer - 1][0] = DIFF_INSERT; 844 | diffs[pointer - 1][1] = 845 | insertion.substring(0, insertion.length - overlap_length2); 846 | diffs[pointer + 1][0] = DIFF_DELETE; 847 | diffs[pointer + 1][1] = 848 | deletion.substring(overlap_length2); 849 | pointer++; 850 | } 851 | } 852 | pointer++; 853 | } 854 | pointer++; 855 | } 856 | }; 857 | 858 | 859 | /** 860 | * Look for single edits surrounded on both sides by equalities 861 | * which can be shifted sideways to align the edit to a word boundary. 862 | * e.g: The cat came. -> The cat came. 863 | * @param {!Array.} diffs Array of diff tuples. 864 | */ 865 | diff_match_patch.prototype.diff_cleanupSemanticLossless = function(diffs) { 866 | /** 867 | * Given two strings, compute a score representing whether the internal 868 | * boundary falls on logical boundaries. 869 | * Scores range from 6 (best) to 0 (worst). 870 | * Closure, but does not reference any external variables. 871 | * @param {string} one First string. 872 | * @param {string} two Second string. 873 | * @return {number} The score. 874 | * @private 875 | */ 876 | function diff_cleanupSemanticScore_(one, two) { 877 | if (!one || !two) { 878 | // Edges are the best. 879 | return 6; 880 | } 881 | 882 | // Each port of this function behaves slightly differently due to 883 | // subtle differences in each language's definition of things like 884 | // 'whitespace'. Since this function's purpose is largely cosmetic, 885 | // the choice has been made to use each language's native features 886 | // rather than force total conformity. 887 | var char1 = one.charAt(one.length - 1); 888 | var char2 = two.charAt(0); 889 | var nonAlphaNumeric1 = char1.match(diff_match_patch.nonAlphaNumericRegex_); 890 | var nonAlphaNumeric2 = char2.match(diff_match_patch.nonAlphaNumericRegex_); 891 | var whitespace1 = nonAlphaNumeric1 && 892 | char1.match(diff_match_patch.whitespaceRegex_); 893 | var whitespace2 = nonAlphaNumeric2 && 894 | char2.match(diff_match_patch.whitespaceRegex_); 895 | var lineBreak1 = whitespace1 && 896 | char1.match(diff_match_patch.linebreakRegex_); 897 | var lineBreak2 = whitespace2 && 898 | char2.match(diff_match_patch.linebreakRegex_); 899 | var blankLine1 = lineBreak1 && 900 | one.match(diff_match_patch.blanklineEndRegex_); 901 | var blankLine2 = lineBreak2 && 902 | two.match(diff_match_patch.blanklineStartRegex_); 903 | 904 | if (blankLine1 || blankLine2) { 905 | // Five points for blank lines. 906 | return 5; 907 | } else if (lineBreak1 || lineBreak2) { 908 | // Four points for line breaks. 909 | return 4; 910 | } else if (nonAlphaNumeric1 && !whitespace1 && whitespace2) { 911 | // Three points for end of sentences. 912 | return 3; 913 | } else if (whitespace1 || whitespace2) { 914 | // Two points for whitespace. 915 | return 2; 916 | } else if (nonAlphaNumeric1 || nonAlphaNumeric2) { 917 | // One point for non-alphanumeric. 918 | return 1; 919 | } 920 | return 0; 921 | } 922 | 923 | var pointer = 1; 924 | // Intentionally ignore the first and last element (don't need checking). 925 | while (pointer < diffs.length - 1) { 926 | if (diffs[pointer - 1][0] == DIFF_EQUAL && 927 | diffs[pointer + 1][0] == DIFF_EQUAL) { 928 | // This is a single edit surrounded by equalities. 929 | var equality1 = diffs[pointer - 1][1]; 930 | var edit = diffs[pointer][1]; 931 | var equality2 = diffs[pointer + 1][1]; 932 | 933 | // First, shift the edit as far left as possible. 934 | var commonOffset = this.diff_commonSuffix(equality1, edit); 935 | if (commonOffset) { 936 | var commonString = edit.substring(edit.length - commonOffset); 937 | equality1 = equality1.substring(0, equality1.length - commonOffset); 938 | edit = commonString + edit.substring(0, edit.length - commonOffset); 939 | equality2 = commonString + equality2; 940 | } 941 | 942 | // Second, step character by character right, looking for the best fit. 943 | var bestEquality1 = equality1; 944 | var bestEdit = edit; 945 | var bestEquality2 = equality2; 946 | var bestScore = diff_cleanupSemanticScore_(equality1, edit) + 947 | diff_cleanupSemanticScore_(edit, equality2); 948 | while (edit.charAt(0) === equality2.charAt(0)) { 949 | equality1 += edit.charAt(0); 950 | edit = edit.substring(1) + equality2.charAt(0); 951 | equality2 = equality2.substring(1); 952 | var score = diff_cleanupSemanticScore_(equality1, edit) + 953 | diff_cleanupSemanticScore_(edit, equality2); 954 | // The >= encourages trailing rather than leading whitespace on edits. 955 | if (score >= bestScore) { 956 | bestScore = score; 957 | bestEquality1 = equality1; 958 | bestEdit = edit; 959 | bestEquality2 = equality2; 960 | } 961 | } 962 | 963 | if (diffs[pointer - 1][1] != bestEquality1) { 964 | // We have an improvement, save it back to the diff. 965 | if (bestEquality1) { 966 | diffs[pointer - 1][1] = bestEquality1; 967 | } else { 968 | diffs.splice(pointer - 1, 1); 969 | pointer--; 970 | } 971 | diffs[pointer][1] = bestEdit; 972 | if (bestEquality2) { 973 | diffs[pointer + 1][1] = bestEquality2; 974 | } else { 975 | diffs.splice(pointer + 1, 1); 976 | pointer--; 977 | } 978 | } 979 | } 980 | pointer++; 981 | } 982 | }; 983 | 984 | // Define some regex patterns for matching boundaries. 985 | diff_match_patch.nonAlphaNumericRegex_ = /[^a-zA-Z0-9]/; 986 | diff_match_patch.whitespaceRegex_ = /\s/; 987 | diff_match_patch.linebreakRegex_ = /[\r\n]/; 988 | diff_match_patch.blanklineEndRegex_ = /\n\r?\n$/; 989 | diff_match_patch.blanklineStartRegex_ = /^\r?\n\r?\n/; 990 | 991 | /** 992 | * Reduce the number of edits by eliminating operationally trivial equalities. 993 | * @param {!Array.} diffs Array of diff tuples. 994 | */ 995 | diff_match_patch.prototype.diff_cleanupEfficiency = function(diffs) { 996 | var changes = false; 997 | var equalities = []; // Stack of indices where equalities are found. 998 | var equalitiesLength = 0; // Keeping our own length var is faster in JS. 999 | /** @type {?string} */ 1000 | var lastEquality = null; 1001 | // Always equal to diffs[equalities[equalitiesLength - 1]][1] 1002 | var pointer = 0; // Index of current position. 1003 | // Is there an insertion operation before the last equality. 1004 | var pre_ins = false; 1005 | // Is there a deletion operation before the last equality. 1006 | var pre_del = false; 1007 | // Is there an insertion operation after the last equality. 1008 | var post_ins = false; 1009 | // Is there a deletion operation after the last equality. 1010 | var post_del = false; 1011 | while (pointer < diffs.length) { 1012 | if (diffs[pointer][0] == DIFF_EQUAL) { // Equality found. 1013 | if (diffs[pointer][1].length < this.Diff_EditCost && 1014 | (post_ins || post_del)) { 1015 | // Candidate found. 1016 | equalities[equalitiesLength++] = pointer; 1017 | pre_ins = post_ins; 1018 | pre_del = post_del; 1019 | lastEquality = diffs[pointer][1]; 1020 | } else { 1021 | // Not a candidate, and can never become one. 1022 | equalitiesLength = 0; 1023 | lastEquality = null; 1024 | } 1025 | post_ins = post_del = false; 1026 | } else { // An insertion or deletion. 1027 | if (diffs[pointer][0] == DIFF_DELETE) { 1028 | post_del = true; 1029 | } else { 1030 | post_ins = true; 1031 | } 1032 | /* 1033 | * Five types to be split: 1034 | * ABXYCD 1035 | * AXCD 1036 | * ABXC 1037 | * AXCD 1038 | * ABXC 1039 | */ 1040 | if (lastEquality && ((pre_ins && pre_del && post_ins && post_del) || 1041 | ((lastEquality.length < this.Diff_EditCost / 2) && 1042 | (pre_ins + pre_del + post_ins + post_del) == 3))) { 1043 | // Duplicate record. 1044 | diffs.splice(equalities[equalitiesLength - 1], 0, 1045 | new diff_match_patch.Diff(DIFF_DELETE, lastEquality)); 1046 | // Change second copy to insert. 1047 | diffs[equalities[equalitiesLength - 1] + 1][0] = DIFF_INSERT; 1048 | equalitiesLength--; // Throw away the equality we just deleted; 1049 | lastEquality = null; 1050 | if (pre_ins && pre_del) { 1051 | // No changes made which could affect previous entry, keep going. 1052 | post_ins = post_del = true; 1053 | equalitiesLength = 0; 1054 | } else { 1055 | equalitiesLength--; // Throw away the previous equality. 1056 | pointer = equalitiesLength > 0 ? 1057 | equalities[equalitiesLength - 1] : -1; 1058 | post_ins = post_del = false; 1059 | } 1060 | changes = true; 1061 | } 1062 | } 1063 | pointer++; 1064 | } 1065 | 1066 | if (changes) { 1067 | this.diff_cleanupMerge(diffs); 1068 | } 1069 | }; 1070 | 1071 | 1072 | /** 1073 | * Reorder and merge like edit sections. Merge equalities. 1074 | * Any edit section can move as long as it doesn't cross an equality. 1075 | * @param {!Array.} diffs Array of diff tuples. 1076 | */ 1077 | diff_match_patch.prototype.diff_cleanupMerge = function(diffs) { 1078 | // Add a dummy entry at the end. 1079 | diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, '')); 1080 | var pointer = 0; 1081 | var count_delete = 0; 1082 | var count_insert = 0; 1083 | var text_delete = ''; 1084 | var text_insert = ''; 1085 | var commonlength; 1086 | while (pointer < diffs.length) { 1087 | switch (diffs[pointer][0]) { 1088 | case DIFF_INSERT: 1089 | count_insert++; 1090 | text_insert += diffs[pointer][1]; 1091 | pointer++; 1092 | break; 1093 | case DIFF_DELETE: 1094 | count_delete++; 1095 | text_delete += diffs[pointer][1]; 1096 | pointer++; 1097 | break; 1098 | case DIFF_EQUAL: 1099 | // Upon reaching an equality, check for prior redundancies. 1100 | if (count_delete + count_insert > 1) { 1101 | if (count_delete !== 0 && count_insert !== 0) { 1102 | // Factor out any common prefixies. 1103 | commonlength = this.diff_commonPrefix(text_insert, text_delete); 1104 | if (commonlength !== 0) { 1105 | if ((pointer - count_delete - count_insert) > 0 && 1106 | diffs[pointer - count_delete - count_insert - 1][0] == 1107 | DIFF_EQUAL) { 1108 | diffs[pointer - count_delete - count_insert - 1][1] += 1109 | text_insert.substring(0, commonlength); 1110 | } else { 1111 | diffs.splice(0, 0, new diff_match_patch.Diff(DIFF_EQUAL, 1112 | text_insert.substring(0, commonlength))); 1113 | pointer++; 1114 | } 1115 | text_insert = text_insert.substring(commonlength); 1116 | text_delete = text_delete.substring(commonlength); 1117 | } 1118 | // Factor out any common suffixies. 1119 | commonlength = this.diff_commonSuffix(text_insert, text_delete); 1120 | if (commonlength !== 0) { 1121 | diffs[pointer][1] = text_insert.substring(text_insert.length - 1122 | commonlength) + diffs[pointer][1]; 1123 | text_insert = text_insert.substring(0, text_insert.length - 1124 | commonlength); 1125 | text_delete = text_delete.substring(0, text_delete.length - 1126 | commonlength); 1127 | } 1128 | } 1129 | // Delete the offending records and add the merged ones. 1130 | pointer -= count_delete + count_insert; 1131 | diffs.splice(pointer, count_delete + count_insert); 1132 | if (text_delete.length) { 1133 | diffs.splice(pointer, 0, 1134 | new diff_match_patch.Diff(DIFF_DELETE, text_delete)); 1135 | pointer++; 1136 | } 1137 | if (text_insert.length) { 1138 | diffs.splice(pointer, 0, 1139 | new diff_match_patch.Diff(DIFF_INSERT, text_insert)); 1140 | pointer++; 1141 | } 1142 | pointer++; 1143 | } else if (pointer !== 0 && diffs[pointer - 1][0] == DIFF_EQUAL) { 1144 | // Merge this equality with the previous one. 1145 | diffs[pointer - 1][1] += diffs[pointer][1]; 1146 | diffs.splice(pointer, 1); 1147 | } else { 1148 | pointer++; 1149 | } 1150 | count_insert = 0; 1151 | count_delete = 0; 1152 | text_delete = ''; 1153 | text_insert = ''; 1154 | break; 1155 | } 1156 | } 1157 | if (diffs[diffs.length - 1][1] === '') { 1158 | diffs.pop(); // Remove the dummy entry at the end. 1159 | } 1160 | 1161 | // Second pass: look for single edits surrounded on both sides by equalities 1162 | // which can be shifted sideways to eliminate an equality. 1163 | // e.g: ABAC -> ABAC 1164 | var changes = false; 1165 | pointer = 1; 1166 | // Intentionally ignore the first and last element (don't need checking). 1167 | while (pointer < diffs.length - 1) { 1168 | if (diffs[pointer - 1][0] == DIFF_EQUAL && 1169 | diffs[pointer + 1][0] == DIFF_EQUAL) { 1170 | // This is a single edit surrounded by equalities. 1171 | if (diffs[pointer][1].substring(diffs[pointer][1].length - 1172 | diffs[pointer - 1][1].length) == diffs[pointer - 1][1]) { 1173 | // Shift the edit over the previous equality. 1174 | diffs[pointer][1] = diffs[pointer - 1][1] + 1175 | diffs[pointer][1].substring(0, diffs[pointer][1].length - 1176 | diffs[pointer - 1][1].length); 1177 | diffs[pointer + 1][1] = diffs[pointer - 1][1] + diffs[pointer + 1][1]; 1178 | diffs.splice(pointer - 1, 1); 1179 | changes = true; 1180 | } else if (diffs[pointer][1].substring(0, diffs[pointer + 1][1].length) == 1181 | diffs[pointer + 1][1]) { 1182 | // Shift the edit over the next equality. 1183 | diffs[pointer - 1][1] += diffs[pointer + 1][1]; 1184 | diffs[pointer][1] = 1185 | diffs[pointer][1].substring(diffs[pointer + 1][1].length) + 1186 | diffs[pointer + 1][1]; 1187 | diffs.splice(pointer + 1, 1); 1188 | changes = true; 1189 | } 1190 | } 1191 | pointer++; 1192 | } 1193 | // If shifts were made, the diff needs reordering and another shift sweep. 1194 | if (changes) { 1195 | this.diff_cleanupMerge(diffs); 1196 | } 1197 | }; 1198 | 1199 | 1200 | /** 1201 | * loc is a location in text1, compute and return the equivalent location in 1202 | * text2. 1203 | * e.g. 'The cat' vs 'The big cat', 1->1, 5->8 1204 | * @param {!Array.} diffs Array of diff tuples. 1205 | * @param {number} loc Location within text1. 1206 | * @return {number} Location within text2. 1207 | */ 1208 | diff_match_patch.prototype.diff_xIndex = function(diffs, loc) { 1209 | var chars1 = 0; 1210 | var chars2 = 0; 1211 | var last_chars1 = 0; 1212 | var last_chars2 = 0; 1213 | var x; 1214 | for (x = 0; x < diffs.length; x++) { 1215 | if (diffs[x][0] !== DIFF_INSERT) { // Equality or deletion. 1216 | chars1 += diffs[x][1].length; 1217 | } 1218 | if (diffs[x][0] !== DIFF_DELETE) { // Equality or insertion. 1219 | chars2 += diffs[x][1].length; 1220 | } 1221 | if (chars1 > loc) { // Overshot the location. 1222 | break; 1223 | } 1224 | last_chars1 = chars1; 1225 | last_chars2 = chars2; 1226 | } 1227 | // Was the location was deleted? 1228 | if (diffs.length != x && diffs[x][0] === DIFF_DELETE) { 1229 | return last_chars2; 1230 | } 1231 | // Add the remaining character length. 1232 | return last_chars2 + (loc - last_chars1); 1233 | }; 1234 | 1235 | 1236 | /** 1237 | * Convert a diff array into a pretty HTML report. 1238 | * @param {!Array.} diffs Array of diff tuples. 1239 | * @return {string} HTML representation. 1240 | */ 1241 | diff_match_patch.prototype.diff_prettyHtml = function(diffs) { 1242 | var html = []; 1243 | var pattern_amp = /&/g; 1244 | var pattern_lt = //g; 1246 | var pattern_para = /\n/g; 1247 | for (var x = 0; x < diffs.length; x++) { 1248 | var op = diffs[x][0]; // Operation (insert, delete, equal) 1249 | var data = diffs[x][1]; // Text of change. 1250 | var text = data.replace(pattern_amp, '&').replace(pattern_lt, '<') 1251 | .replace(pattern_gt, '>').replace(pattern_para, '¶
'); 1252 | switch (op) { 1253 | case DIFF_INSERT: 1254 | html[x] = '' + text + ''; 1255 | break; 1256 | case DIFF_DELETE: 1257 | html[x] = '' + text + ''; 1258 | break; 1259 | case DIFF_EQUAL: 1260 | html[x] = '' + text + ''; 1261 | break; 1262 | } 1263 | } 1264 | return html.join(''); 1265 | }; 1266 | 1267 | 1268 | /** 1269 | * Compute and return the source text (all equalities and deletions). 1270 | * @param {!Array.} diffs Array of diff tuples. 1271 | * @return {string} Source text. 1272 | */ 1273 | diff_match_patch.prototype.diff_text1 = function(diffs) { 1274 | var text = []; 1275 | for (var x = 0; x < diffs.length; x++) { 1276 | if (diffs[x][0] !== DIFF_INSERT) { 1277 | text[x] = diffs[x][1]; 1278 | } 1279 | } 1280 | return text.join(''); 1281 | }; 1282 | 1283 | 1284 | /** 1285 | * Compute and return the destination text (all equalities and insertions). 1286 | * @param {!Array.} diffs Array of diff tuples. 1287 | * @return {string} Destination text. 1288 | */ 1289 | diff_match_patch.prototype.diff_text2 = function(diffs) { 1290 | var text = []; 1291 | for (var x = 0; x < diffs.length; x++) { 1292 | if (diffs[x][0] !== DIFF_DELETE) { 1293 | text[x] = diffs[x][1]; 1294 | } 1295 | } 1296 | return text.join(''); 1297 | }; 1298 | 1299 | 1300 | /** 1301 | * Compute the Levenshtein distance; the number of inserted, deleted or 1302 | * substituted characters. 1303 | * @param {!Array.} diffs Array of diff tuples. 1304 | * @return {number} Number of changes. 1305 | */ 1306 | diff_match_patch.prototype.diff_levenshtein = function(diffs) { 1307 | var levenshtein = 0; 1308 | var insertions = 0; 1309 | var deletions = 0; 1310 | for (var x = 0; x < diffs.length; x++) { 1311 | var op = diffs[x][0]; 1312 | var data = diffs[x][1]; 1313 | switch (op) { 1314 | case DIFF_INSERT: 1315 | insertions += data.length; 1316 | break; 1317 | case DIFF_DELETE: 1318 | deletions += data.length; 1319 | break; 1320 | case DIFF_EQUAL: 1321 | // A deletion and an insertion is one substitution. 1322 | levenshtein += Math.max(insertions, deletions); 1323 | insertions = 0; 1324 | deletions = 0; 1325 | break; 1326 | } 1327 | } 1328 | levenshtein += Math.max(insertions, deletions); 1329 | return levenshtein; 1330 | }; 1331 | 1332 | 1333 | /** 1334 | * Crush the diff into an encoded string which describes the operations 1335 | * required to transform text1 into text2. 1336 | * E.g. =3\t-2\t+ing -> Keep 3 chars, delete 2 chars, insert 'ing'. 1337 | * Operations are tab-separated. Inserted text is escaped using %xx notation. 1338 | * @param {!Array.} diffs Array of diff tuples. 1339 | * @return {string} Delta text. 1340 | */ 1341 | diff_match_patch.prototype.diff_toDelta = function(diffs) { 1342 | var text = []; 1343 | for (var x = 0; x < diffs.length; x++) { 1344 | switch (diffs[x][0]) { 1345 | case DIFF_INSERT: 1346 | text[x] = '+' + encodeURI(diffs[x][1]); 1347 | break; 1348 | case DIFF_DELETE: 1349 | text[x] = '-' + diffs[x][1].length; 1350 | break; 1351 | case DIFF_EQUAL: 1352 | text[x] = '=' + diffs[x][1].length; 1353 | break; 1354 | } 1355 | } 1356 | return text.join('\t').replace(/%20/g, ' '); 1357 | }; 1358 | 1359 | 1360 | /** 1361 | * Given the original text1, and an encoded string which describes the 1362 | * operations required to transform text1 into text2, compute the full diff. 1363 | * @param {string} text1 Source string for the diff. 1364 | * @param {string} delta Delta text. 1365 | * @return {!Array.} Array of diff tuples. 1366 | * @throws {!Error} If invalid input. 1367 | */ 1368 | diff_match_patch.prototype.diff_fromDelta = function(text1, delta) { 1369 | var diffs = []; 1370 | var diffsLength = 0; // Keeping our own length var is faster in JS. 1371 | var pointer = 0; // Cursor in text1 1372 | var tokens = delta.split(/\t/g); 1373 | for (var x = 0; x < tokens.length; x++) { 1374 | // Each token begins with a one character parameter which specifies the 1375 | // operation of this token (delete, insert, equality). 1376 | var param = tokens[x].substring(1); 1377 | switch (tokens[x].charAt(0)) { 1378 | case '+': 1379 | try { 1380 | diffs[diffsLength++] = 1381 | new diff_match_patch.Diff(DIFF_INSERT, decodeURI(param)); 1382 | } catch (ex) { 1383 | // Malformed URI sequence. 1384 | throw new Error('Illegal escape in diff_fromDelta: ' + param); 1385 | } 1386 | break; 1387 | case '-': 1388 | // Fall through. 1389 | case '=': 1390 | var n = parseInt(param, 10); 1391 | if (isNaN(n) || n < 0) { 1392 | throw new Error('Invalid number in diff_fromDelta: ' + param); 1393 | } 1394 | var text = text1.substring(pointer, pointer += n); 1395 | if (tokens[x].charAt(0) == '=') { 1396 | diffs[diffsLength++] = new diff_match_patch.Diff(DIFF_EQUAL, text); 1397 | } else { 1398 | diffs[diffsLength++] = new diff_match_patch.Diff(DIFF_DELETE, text); 1399 | } 1400 | break; 1401 | default: 1402 | // Blank tokens are ok (from a trailing \t). 1403 | // Anything else is an error. 1404 | if (tokens[x]) { 1405 | throw new Error('Invalid diff operation in diff_fromDelta: ' + 1406 | tokens[x]); 1407 | } 1408 | } 1409 | } 1410 | if (pointer != text1.length) { 1411 | throw new Error('Delta length (' + pointer + 1412 | ') does not equal source text length (' + text1.length + ').'); 1413 | } 1414 | return diffs; 1415 | }; 1416 | 1417 | 1418 | // MATCH FUNCTIONS 1419 | 1420 | 1421 | /** 1422 | * Locate the best instance of 'pattern' in 'text' near 'loc'. 1423 | * @param {string} text The text to search. 1424 | * @param {string} pattern The pattern to search for. 1425 | * @param {number} loc The location to search around. 1426 | * @return {number} Best match index or -1. 1427 | */ 1428 | diff_match_patch.prototype.match_main = function(text, pattern, loc) { 1429 | // Check for null inputs. 1430 | if (text == null || pattern == null || loc == null) { 1431 | throw new Error('Null input. (match_main)'); 1432 | } 1433 | 1434 | loc = Math.max(0, Math.min(loc, text.length)); 1435 | if (text == pattern) { 1436 | // Shortcut (potentially not guaranteed by the algorithm) 1437 | return 0; 1438 | } else if (!text.length) { 1439 | // Nothing to match. 1440 | return -1; 1441 | } else if (text.substring(loc, loc + pattern.length) == pattern) { 1442 | // Perfect match at the perfect spot! (Includes case of null pattern) 1443 | return loc; 1444 | } else { 1445 | // Do a fuzzy compare. 1446 | return this.match_bitap_(text, pattern, loc); 1447 | } 1448 | }; 1449 | 1450 | 1451 | /** 1452 | * Locate the best instance of 'pattern' in 'text' near 'loc' using the 1453 | * Bitap algorithm. 1454 | * @param {string} text The text to search. 1455 | * @param {string} pattern The pattern to search for. 1456 | * @param {number} loc The location to search around. 1457 | * @return {number} Best match index or -1. 1458 | * @private 1459 | */ 1460 | diff_match_patch.prototype.match_bitap_ = function(text, pattern, loc) { 1461 | if (pattern.length > this.Match_MaxBits) { 1462 | throw new Error('Pattern too long for this browser.'); 1463 | } 1464 | 1465 | // Initialise the alphabet. 1466 | var s = this.match_alphabet_(pattern); 1467 | 1468 | var dmp = this; // 'this' becomes 'window' in a closure. 1469 | 1470 | /** 1471 | * Compute and return the score for a match with e errors and x location. 1472 | * Accesses loc and pattern through being a closure. 1473 | * @param {number} e Number of errors in match. 1474 | * @param {number} x Location of match. 1475 | * @return {number} Overall score for match (0.0 = good, 1.0 = bad). 1476 | * @private 1477 | */ 1478 | function match_bitapScore_(e, x) { 1479 | var accuracy = e / pattern.length; 1480 | var proximity = Math.abs(loc - x); 1481 | if (!dmp.Match_Distance) { 1482 | // Dodge divide by zero error. 1483 | return proximity ? 1.0 : accuracy; 1484 | } 1485 | return accuracy + (proximity / dmp.Match_Distance); 1486 | } 1487 | 1488 | // Highest score beyond which we give up. 1489 | var score_threshold = this.Match_Threshold; 1490 | // Is there a nearby exact match? (speedup) 1491 | var best_loc = text.indexOf(pattern, loc); 1492 | if (best_loc != -1) { 1493 | score_threshold = Math.min(match_bitapScore_(0, best_loc), score_threshold); 1494 | // What about in the other direction? (speedup) 1495 | best_loc = text.lastIndexOf(pattern, loc + pattern.length); 1496 | if (best_loc != -1) { 1497 | score_threshold = 1498 | Math.min(match_bitapScore_(0, best_loc), score_threshold); 1499 | } 1500 | } 1501 | 1502 | // Initialise the bit arrays. 1503 | var matchmask = 1 << (pattern.length - 1); 1504 | best_loc = -1; 1505 | 1506 | var bin_min, bin_mid; 1507 | var bin_max = pattern.length + text.length; 1508 | var last_rd; 1509 | for (var d = 0; d < pattern.length; d++) { 1510 | // Scan for the best match; each iteration allows for one more error. 1511 | // Run a binary search to determine how far from 'loc' we can stray at this 1512 | // error level. 1513 | bin_min = 0; 1514 | bin_mid = bin_max; 1515 | while (bin_min < bin_mid) { 1516 | if (match_bitapScore_(d, loc + bin_mid) <= score_threshold) { 1517 | bin_min = bin_mid; 1518 | } else { 1519 | bin_max = bin_mid; 1520 | } 1521 | bin_mid = Math.floor((bin_max - bin_min) / 2 + bin_min); 1522 | } 1523 | // Use the result from this iteration as the maximum for the next. 1524 | bin_max = bin_mid; 1525 | var start = Math.max(1, loc - bin_mid + 1); 1526 | var finish = Math.min(loc + bin_mid, text.length) + pattern.length; 1527 | 1528 | var rd = Array(finish + 2); 1529 | rd[finish + 1] = (1 << d) - 1; 1530 | for (var j = finish; j >= start; j--) { 1531 | // The alphabet (s) is a sparse hash, so the following line generates 1532 | // warnings. 1533 | var charMatch = s[text.charAt(j - 1)]; 1534 | if (d === 0) { // First pass: exact match. 1535 | rd[j] = ((rd[j + 1] << 1) | 1) & charMatch; 1536 | } else { // Subsequent passes: fuzzy match. 1537 | rd[j] = (((rd[j + 1] << 1) | 1) & charMatch) | 1538 | (((last_rd[j + 1] | last_rd[j]) << 1) | 1) | 1539 | last_rd[j + 1]; 1540 | } 1541 | if (rd[j] & matchmask) { 1542 | var score = match_bitapScore_(d, j - 1); 1543 | // This match will almost certainly be better than any existing match. 1544 | // But check anyway. 1545 | if (score <= score_threshold) { 1546 | // Told you so. 1547 | score_threshold = score; 1548 | best_loc = j - 1; 1549 | if (best_loc > loc) { 1550 | // When passing loc, don't exceed our current distance from loc. 1551 | start = Math.max(1, 2 * loc - best_loc); 1552 | } else { 1553 | // Already passed loc, downhill from here on in. 1554 | break; 1555 | } 1556 | } 1557 | } 1558 | } 1559 | // No hope for a (better) match at greater error levels. 1560 | if (match_bitapScore_(d + 1, loc) > score_threshold) { 1561 | break; 1562 | } 1563 | last_rd = rd; 1564 | } 1565 | return best_loc; 1566 | }; 1567 | 1568 | 1569 | /** 1570 | * Initialise the alphabet for the Bitap algorithm. 1571 | * @param {string} pattern The text to encode. 1572 | * @return {!Object} Hash of character locations. 1573 | * @private 1574 | */ 1575 | diff_match_patch.prototype.match_alphabet_ = function(pattern) { 1576 | var s = {}; 1577 | for (var i = 0; i < pattern.length; i++) { 1578 | s[pattern.charAt(i)] = 0; 1579 | } 1580 | for (var i = 0; i < pattern.length; i++) { 1581 | s[pattern.charAt(i)] |= 1 << (pattern.length - i - 1); 1582 | } 1583 | return s; 1584 | }; 1585 | 1586 | 1587 | // PATCH FUNCTIONS 1588 | 1589 | 1590 | /** 1591 | * Increase the context until it is unique, 1592 | * but don't let the pattern expand beyond Match_MaxBits. 1593 | * @param {!diff_match_patch.patch_obj} patch The patch to grow. 1594 | * @param {string} text Source text. 1595 | * @private 1596 | */ 1597 | diff_match_patch.prototype.patch_addContext_ = function(patch, text) { 1598 | if (text.length == 0) { 1599 | return; 1600 | } 1601 | if (patch.start2 === null) { 1602 | throw Error('patch not initialized'); 1603 | } 1604 | var pattern = text.substring(patch.start2, patch.start2 + patch.length1); 1605 | var padding = 0; 1606 | 1607 | // Look for the first and last matches of pattern in text. If two different 1608 | // matches are found, increase the pattern length. 1609 | while (text.indexOf(pattern) != text.lastIndexOf(pattern) && 1610 | pattern.length < this.Match_MaxBits - this.Patch_Margin - 1611 | this.Patch_Margin) { 1612 | padding += this.Patch_Margin; 1613 | pattern = text.substring(patch.start2 - padding, 1614 | patch.start2 + patch.length1 + padding); 1615 | } 1616 | // Add one chunk for good luck. 1617 | padding += this.Patch_Margin; 1618 | 1619 | // Add the prefix. 1620 | var prefix = text.substring(patch.start2 - padding, patch.start2); 1621 | if (prefix) { 1622 | patch.diffs.unshift(new diff_match_patch.Diff(DIFF_EQUAL, prefix)); 1623 | } 1624 | // Add the suffix. 1625 | var suffix = text.substring(patch.start2 + patch.length1, 1626 | patch.start2 + patch.length1 + padding); 1627 | if (suffix) { 1628 | patch.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, suffix)); 1629 | } 1630 | 1631 | // Roll back the start points. 1632 | patch.start1 -= prefix.length; 1633 | patch.start2 -= prefix.length; 1634 | // Extend the lengths. 1635 | patch.length1 += prefix.length + suffix.length; 1636 | patch.length2 += prefix.length + suffix.length; 1637 | }; 1638 | 1639 | 1640 | /** 1641 | * Compute a list of patches to turn text1 into text2. 1642 | * Use diffs if provided, otherwise compute it ourselves. 1643 | * There are four ways to call this function, depending on what data is 1644 | * available to the caller: 1645 | * Method 1: 1646 | * a = text1, b = text2 1647 | * Method 2: 1648 | * a = diffs 1649 | * Method 3 (optimal): 1650 | * a = text1, b = diffs 1651 | * Method 4 (deprecated, use method 3): 1652 | * a = text1, b = text2, c = diffs 1653 | * 1654 | * @param {string|!Array.} a text1 (methods 1,3,4) or 1655 | * Array of diff tuples for text1 to text2 (method 2). 1656 | * @param {string|!Array.=} opt_b text2 (methods 1,4) or 1657 | * Array of diff tuples for text1 to text2 (method 3) or undefined (method 2). 1658 | * @param {string|!Array.=} opt_c Array of diff tuples 1659 | * for text1 to text2 (method 4) or undefined (methods 1,2,3). 1660 | * @return {!Array.} Array of Patch objects. 1661 | */ 1662 | diff_match_patch.prototype.patch_make = function(a, opt_b, opt_c) { 1663 | var text1, diffs; 1664 | if (typeof a == 'string' && typeof opt_b == 'string' && 1665 | typeof opt_c == 'undefined') { 1666 | // Method 1: text1, text2 1667 | // Compute diffs from text1 and text2. 1668 | text1 = /** @type {string} */(a); 1669 | diffs = this.diff_main(text1, /** @type {string} */(opt_b), true); 1670 | if (diffs.length > 2) { 1671 | this.diff_cleanupSemantic(diffs); 1672 | this.diff_cleanupEfficiency(diffs); 1673 | } 1674 | } else if (a && typeof a == 'object' && typeof opt_b == 'undefined' && 1675 | typeof opt_c == 'undefined') { 1676 | // Method 2: diffs 1677 | // Compute text1 from diffs. 1678 | diffs = /** @type {!Array.} */(a); 1679 | text1 = this.diff_text1(diffs); 1680 | } else if (typeof a == 'string' && opt_b && typeof opt_b == 'object' && 1681 | typeof opt_c == 'undefined') { 1682 | // Method 3: text1, diffs 1683 | text1 = /** @type {string} */(a); 1684 | diffs = /** @type {!Array.} */(opt_b); 1685 | } else if (typeof a == 'string' && typeof opt_b == 'string' && 1686 | opt_c && typeof opt_c == 'object') { 1687 | // Method 4: text1, text2, diffs 1688 | // text2 is not used. 1689 | text1 = /** @type {string} */(a); 1690 | diffs = /** @type {!Array.} */(opt_c); 1691 | } else { 1692 | throw new Error('Unknown call format to patch_make.'); 1693 | } 1694 | 1695 | if (diffs.length === 0) { 1696 | return []; // Get rid of the null case. 1697 | } 1698 | var patches = []; 1699 | var patch = new diff_match_patch.patch_obj(); 1700 | var patchDiffLength = 0; // Keeping our own length var is faster in JS. 1701 | var char_count1 = 0; // Number of characters into the text1 string. 1702 | var char_count2 = 0; // Number of characters into the text2 string. 1703 | // Start with text1 (prepatch_text) and apply the diffs until we arrive at 1704 | // text2 (postpatch_text). We recreate the patches one by one to determine 1705 | // context info. 1706 | var prepatch_text = text1; 1707 | var postpatch_text = text1; 1708 | for (var x = 0; x < diffs.length; x++) { 1709 | var diff_type = diffs[x][0]; 1710 | var diff_text = diffs[x][1]; 1711 | 1712 | if (!patchDiffLength && diff_type !== DIFF_EQUAL) { 1713 | // A new patch starts here. 1714 | patch.start1 = char_count1; 1715 | patch.start2 = char_count2; 1716 | } 1717 | 1718 | switch (diff_type) { 1719 | case DIFF_INSERT: 1720 | patch.diffs[patchDiffLength++] = diffs[x]; 1721 | patch.length2 += diff_text.length; 1722 | postpatch_text = postpatch_text.substring(0, char_count2) + diff_text + 1723 | postpatch_text.substring(char_count2); 1724 | break; 1725 | case DIFF_DELETE: 1726 | patch.length1 += diff_text.length; 1727 | patch.diffs[patchDiffLength++] = diffs[x]; 1728 | postpatch_text = postpatch_text.substring(0, char_count2) + 1729 | postpatch_text.substring(char_count2 + 1730 | diff_text.length); 1731 | break; 1732 | case DIFF_EQUAL: 1733 | if (diff_text.length <= 2 * this.Patch_Margin && 1734 | patchDiffLength && diffs.length != x + 1) { 1735 | // Small equality inside a patch. 1736 | patch.diffs[patchDiffLength++] = diffs[x]; 1737 | patch.length1 += diff_text.length; 1738 | patch.length2 += diff_text.length; 1739 | } else if (diff_text.length >= 2 * this.Patch_Margin) { 1740 | // Time for a new patch. 1741 | if (patchDiffLength) { 1742 | this.patch_addContext_(patch, prepatch_text); 1743 | patches.push(patch); 1744 | patch = new diff_match_patch.patch_obj(); 1745 | patchDiffLength = 0; 1746 | // Unlike Unidiff, our patch lists have a rolling context. 1747 | // https://github.com/google/diff-match-patch/wiki/Unidiff 1748 | // Update prepatch text & pos to reflect the application of the 1749 | // just completed patch. 1750 | prepatch_text = postpatch_text; 1751 | char_count1 = char_count2; 1752 | } 1753 | } 1754 | break; 1755 | } 1756 | 1757 | // Update the current character count. 1758 | if (diff_type !== DIFF_INSERT) { 1759 | char_count1 += diff_text.length; 1760 | } 1761 | if (diff_type !== DIFF_DELETE) { 1762 | char_count2 += diff_text.length; 1763 | } 1764 | } 1765 | // Pick up the leftover patch if not empty. 1766 | if (patchDiffLength) { 1767 | this.patch_addContext_(patch, prepatch_text); 1768 | patches.push(patch); 1769 | } 1770 | 1771 | return patches; 1772 | }; 1773 | 1774 | 1775 | /** 1776 | * Given an array of patches, return another array that is identical. 1777 | * @param {!Array.} patches Array of Patch objects. 1778 | * @return {!Array.} Array of Patch objects. 1779 | */ 1780 | diff_match_patch.prototype.patch_deepCopy = function(patches) { 1781 | // Making deep copies is hard in JavaScript. 1782 | var patchesCopy = []; 1783 | for (var x = 0; x < patches.length; x++) { 1784 | var patch = patches[x]; 1785 | var patchCopy = new diff_match_patch.patch_obj(); 1786 | patchCopy.diffs = []; 1787 | for (var y = 0; y < patch.diffs.length; y++) { 1788 | patchCopy.diffs[y] = 1789 | new diff_match_patch.Diff(patch.diffs[y][0], patch.diffs[y][1]); 1790 | } 1791 | patchCopy.start1 = patch.start1; 1792 | patchCopy.start2 = patch.start2; 1793 | patchCopy.length1 = patch.length1; 1794 | patchCopy.length2 = patch.length2; 1795 | patchesCopy[x] = patchCopy; 1796 | } 1797 | return patchesCopy; 1798 | }; 1799 | 1800 | 1801 | /** 1802 | * Merge a set of patches onto the text. Return a patched text, as well 1803 | * as a list of true/false values indicating which patches were applied. 1804 | * @param {!Array.} patches Array of Patch objects. 1805 | * @param {string} text Old text. 1806 | * @return {!Array.>} Two element Array, containing the 1807 | * new text and an array of boolean values. 1808 | */ 1809 | diff_match_patch.prototype.patch_apply = function(patches, text) { 1810 | if (patches.length == 0) { 1811 | return [text, []]; 1812 | } 1813 | 1814 | // Deep copy the patches so that no changes are made to originals. 1815 | patches = this.patch_deepCopy(patches); 1816 | 1817 | var nullPadding = this.patch_addPadding(patches); 1818 | text = nullPadding + text + nullPadding; 1819 | 1820 | this.patch_splitMax(patches); 1821 | // delta keeps track of the offset between the expected and actual location 1822 | // of the previous patch. If there are patches expected at positions 10 and 1823 | // 20, but the first patch was found at 12, delta is 2 and the second patch 1824 | // has an effective expected position of 22. 1825 | var delta = 0; 1826 | var results = []; 1827 | for (var x = 0; x < patches.length; x++) { 1828 | var expected_loc = patches[x].start2 + delta; 1829 | var text1 = this.diff_text1(patches[x].diffs); 1830 | var start_loc; 1831 | var end_loc = -1; 1832 | if (text1.length > this.Match_MaxBits) { 1833 | // patch_splitMax will only provide an oversized pattern in the case of 1834 | // a monster delete. 1835 | start_loc = this.match_main(text, text1.substring(0, this.Match_MaxBits), 1836 | expected_loc); 1837 | if (start_loc != -1) { 1838 | end_loc = this.match_main(text, 1839 | text1.substring(text1.length - this.Match_MaxBits), 1840 | expected_loc + text1.length - this.Match_MaxBits); 1841 | if (end_loc == -1 || start_loc >= end_loc) { 1842 | // Can't find valid trailing context. Drop this patch. 1843 | start_loc = -1; 1844 | } 1845 | } 1846 | } else { 1847 | start_loc = this.match_main(text, text1, expected_loc); 1848 | } 1849 | if (start_loc == -1) { 1850 | // No match found. :( 1851 | results[x] = false; 1852 | // Subtract the delta for this failed patch from subsequent patches. 1853 | delta -= patches[x].length2 - patches[x].length1; 1854 | } else { 1855 | // Found a match. :) 1856 | results[x] = true; 1857 | delta = start_loc - expected_loc; 1858 | var text2; 1859 | if (end_loc == -1) { 1860 | text2 = text.substring(start_loc, start_loc + text1.length); 1861 | } else { 1862 | text2 = text.substring(start_loc, end_loc + this.Match_MaxBits); 1863 | } 1864 | if (text1 == text2) { 1865 | // Perfect match, just shove the replacement text in. 1866 | text = text.substring(0, start_loc) + 1867 | this.diff_text2(patches[x].diffs) + 1868 | text.substring(start_loc + text1.length); 1869 | } else { 1870 | // Imperfect match. Run a diff to get a framework of equivalent 1871 | // indices. 1872 | var diffs = this.diff_main(text1, text2, false); 1873 | if (text1.length > this.Match_MaxBits && 1874 | this.diff_levenshtein(diffs) / text1.length > 1875 | this.Patch_DeleteThreshold) { 1876 | // The end points match, but the content is unacceptably bad. 1877 | results[x] = false; 1878 | } else { 1879 | this.diff_cleanupSemanticLossless(diffs); 1880 | var index1 = 0; 1881 | var index2; 1882 | for (var y = 0; y < patches[x].diffs.length; y++) { 1883 | var mod = patches[x].diffs[y]; 1884 | if (mod[0] !== DIFF_EQUAL) { 1885 | index2 = this.diff_xIndex(diffs, index1); 1886 | } 1887 | if (mod[0] === DIFF_INSERT) { // Insertion 1888 | text = text.substring(0, start_loc + index2) + mod[1] + 1889 | text.substring(start_loc + index2); 1890 | } else if (mod[0] === DIFF_DELETE) { // Deletion 1891 | text = text.substring(0, start_loc + index2) + 1892 | text.substring(start_loc + this.diff_xIndex(diffs, 1893 | index1 + mod[1].length)); 1894 | } 1895 | if (mod[0] !== DIFF_DELETE) { 1896 | index1 += mod[1].length; 1897 | } 1898 | } 1899 | } 1900 | } 1901 | } 1902 | } 1903 | // Strip the padding off. 1904 | text = text.substring(nullPadding.length, text.length - nullPadding.length); 1905 | return [text, results]; 1906 | }; 1907 | 1908 | 1909 | /** 1910 | * Add some padding on text start and end so that edges can match something. 1911 | * Intended to be called only from within patch_apply. 1912 | * @param {!Array.} patches Array of Patch objects. 1913 | * @return {string} The padding string added to each side. 1914 | */ 1915 | diff_match_patch.prototype.patch_addPadding = function(patches) { 1916 | var paddingLength = this.Patch_Margin; 1917 | var nullPadding = ''; 1918 | for (var x = 1; x <= paddingLength; x++) { 1919 | nullPadding += String.fromCharCode(x); 1920 | } 1921 | 1922 | // Bump all the patches forward. 1923 | for (var x = 0; x < patches.length; x++) { 1924 | patches[x].start1 += paddingLength; 1925 | patches[x].start2 += paddingLength; 1926 | } 1927 | 1928 | // Add some padding on start of first diff. 1929 | var patch = patches[0]; 1930 | var diffs = patch.diffs; 1931 | if (diffs.length == 0 || diffs[0][0] != DIFF_EQUAL) { 1932 | // Add nullPadding equality. 1933 | diffs.unshift(new diff_match_patch.Diff(DIFF_EQUAL, nullPadding)); 1934 | patch.start1 -= paddingLength; // Should be 0. 1935 | patch.start2 -= paddingLength; // Should be 0. 1936 | patch.length1 += paddingLength; 1937 | patch.length2 += paddingLength; 1938 | } else if (paddingLength > diffs[0][1].length) { 1939 | // Grow first equality. 1940 | var extraLength = paddingLength - diffs[0][1].length; 1941 | diffs[0][1] = nullPadding.substring(diffs[0][1].length) + diffs[0][1]; 1942 | patch.start1 -= extraLength; 1943 | patch.start2 -= extraLength; 1944 | patch.length1 += extraLength; 1945 | patch.length2 += extraLength; 1946 | } 1947 | 1948 | // Add some padding on end of last diff. 1949 | patch = patches[patches.length - 1]; 1950 | diffs = patch.diffs; 1951 | if (diffs.length == 0 || diffs[diffs.length - 1][0] != DIFF_EQUAL) { 1952 | // Add nullPadding equality. 1953 | diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, nullPadding)); 1954 | patch.length1 += paddingLength; 1955 | patch.length2 += paddingLength; 1956 | } else if (paddingLength > diffs[diffs.length - 1][1].length) { 1957 | // Grow last equality. 1958 | var extraLength = paddingLength - diffs[diffs.length - 1][1].length; 1959 | diffs[diffs.length - 1][1] += nullPadding.substring(0, extraLength); 1960 | patch.length1 += extraLength; 1961 | patch.length2 += extraLength; 1962 | } 1963 | 1964 | return nullPadding; 1965 | }; 1966 | 1967 | 1968 | /** 1969 | * Look through the patches and break up any which are longer than the maximum 1970 | * limit of the match algorithm. 1971 | * Intended to be called only from within patch_apply. 1972 | * @param {!Array.} patches Array of Patch objects. 1973 | */ 1974 | diff_match_patch.prototype.patch_splitMax = function(patches) { 1975 | var patch_size = this.Match_MaxBits; 1976 | for (var x = 0; x < patches.length; x++) { 1977 | if (patches[x].length1 <= patch_size) { 1978 | continue; 1979 | } 1980 | var bigpatch = patches[x]; 1981 | // Remove the big old patch. 1982 | patches.splice(x--, 1); 1983 | var start1 = bigpatch.start1; 1984 | var start2 = bigpatch.start2; 1985 | var precontext = ''; 1986 | while (bigpatch.diffs.length !== 0) { 1987 | // Create one of several smaller patches. 1988 | var patch = new diff_match_patch.patch_obj(); 1989 | var empty = true; 1990 | patch.start1 = start1 - precontext.length; 1991 | patch.start2 = start2 - precontext.length; 1992 | if (precontext !== '') { 1993 | patch.length1 = patch.length2 = precontext.length; 1994 | patch.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, precontext)); 1995 | } 1996 | while (bigpatch.diffs.length !== 0 && 1997 | patch.length1 < patch_size - this.Patch_Margin) { 1998 | var diff_type = bigpatch.diffs[0][0]; 1999 | var diff_text = bigpatch.diffs[0][1]; 2000 | if (diff_type === DIFF_INSERT) { 2001 | // Insertions are harmless. 2002 | patch.length2 += diff_text.length; 2003 | start2 += diff_text.length; 2004 | patch.diffs.push(bigpatch.diffs.shift()); 2005 | empty = false; 2006 | } else if (diff_type === DIFF_DELETE && patch.diffs.length == 1 && 2007 | patch.diffs[0][0] == DIFF_EQUAL && 2008 | diff_text.length > 2 * patch_size) { 2009 | // This is a large deletion. Let it pass in one chunk. 2010 | patch.length1 += diff_text.length; 2011 | start1 += diff_text.length; 2012 | empty = false; 2013 | patch.diffs.push(new diff_match_patch.Diff(diff_type, diff_text)); 2014 | bigpatch.diffs.shift(); 2015 | } else { 2016 | // Deletion or equality. Only take as much as we can stomach. 2017 | diff_text = diff_text.substring(0, 2018 | patch_size - patch.length1 - this.Patch_Margin); 2019 | patch.length1 += diff_text.length; 2020 | start1 += diff_text.length; 2021 | if (diff_type === DIFF_EQUAL) { 2022 | patch.length2 += diff_text.length; 2023 | start2 += diff_text.length; 2024 | } else { 2025 | empty = false; 2026 | } 2027 | patch.diffs.push(new diff_match_patch.Diff(diff_type, diff_text)); 2028 | if (diff_text == bigpatch.diffs[0][1]) { 2029 | bigpatch.diffs.shift(); 2030 | } else { 2031 | bigpatch.diffs[0][1] = 2032 | bigpatch.diffs[0][1].substring(diff_text.length); 2033 | } 2034 | } 2035 | } 2036 | // Compute the head context for the next patch. 2037 | precontext = this.diff_text2(patch.diffs); 2038 | precontext = 2039 | precontext.substring(precontext.length - this.Patch_Margin); 2040 | // Append the end context for this patch. 2041 | var postcontext = this.diff_text1(bigpatch.diffs) 2042 | .substring(0, this.Patch_Margin); 2043 | if (postcontext !== '') { 2044 | patch.length1 += postcontext.length; 2045 | patch.length2 += postcontext.length; 2046 | if (patch.diffs.length !== 0 && 2047 | patch.diffs[patch.diffs.length - 1][0] === DIFF_EQUAL) { 2048 | patch.diffs[patch.diffs.length - 1][1] += postcontext; 2049 | } else { 2050 | patch.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, postcontext)); 2051 | } 2052 | } 2053 | if (!empty) { 2054 | patches.splice(++x, 0, patch); 2055 | } 2056 | } 2057 | } 2058 | }; 2059 | 2060 | 2061 | /** 2062 | * Take a list of patches and return a textual representation. 2063 | * @param {!Array.} patches Array of Patch objects. 2064 | * @return {string} Text representation of patches. 2065 | */ 2066 | diff_match_patch.prototype.patch_toText = function(patches) { 2067 | var text = []; 2068 | for (var x = 0; x < patches.length; x++) { 2069 | text[x] = patches[x]; 2070 | } 2071 | return text.join(''); 2072 | }; 2073 | 2074 | 2075 | /** 2076 | * Parse a textual representation of patches and return a list of Patch objects. 2077 | * @param {string} textline Text representation of patches. 2078 | * @return {!Array.} Array of Patch objects. 2079 | * @throws {!Error} If invalid input. 2080 | */ 2081 | diff_match_patch.prototype.patch_fromText = function(textline) { 2082 | var patches = []; 2083 | if (!textline) { 2084 | return patches; 2085 | } 2086 | var text = textline.split('\n'); 2087 | var textPointer = 0; 2088 | var patchHeader = /^@@ -(\d+),?(\d*) \+(\d+),?(\d*) @@$/; 2089 | while (textPointer < text.length) { 2090 | var m = text[textPointer].match(patchHeader); 2091 | if (!m) { 2092 | throw new Error('Invalid patch string: ' + text[textPointer]); 2093 | } 2094 | var patch = new diff_match_patch.patch_obj(); 2095 | patches.push(patch); 2096 | patch.start1 = parseInt(m[1], 10); 2097 | if (m[2] === '') { 2098 | patch.start1--; 2099 | patch.length1 = 1; 2100 | } else if (m[2] == '0') { 2101 | patch.length1 = 0; 2102 | } else { 2103 | patch.start1--; 2104 | patch.length1 = parseInt(m[2], 10); 2105 | } 2106 | 2107 | patch.start2 = parseInt(m[3], 10); 2108 | if (m[4] === '') { 2109 | patch.start2--; 2110 | patch.length2 = 1; 2111 | } else if (m[4] == '0') { 2112 | patch.length2 = 0; 2113 | } else { 2114 | patch.start2--; 2115 | patch.length2 = parseInt(m[4], 10); 2116 | } 2117 | textPointer++; 2118 | 2119 | while (textPointer < text.length) { 2120 | var sign = text[textPointer].charAt(0); 2121 | try { 2122 | var line = decodeURI(text[textPointer].substring(1)); 2123 | } catch (ex) { 2124 | // Malformed URI sequence. 2125 | throw new Error('Illegal escape in patch_fromText: ' + line); 2126 | } 2127 | if (sign == '-') { 2128 | // Deletion. 2129 | patch.diffs.push(new diff_match_patch.Diff(DIFF_DELETE, line)); 2130 | } else if (sign == '+') { 2131 | // Insertion. 2132 | patch.diffs.push(new diff_match_patch.Diff(DIFF_INSERT, line)); 2133 | } else if (sign == ' ') { 2134 | // Minor equality. 2135 | patch.diffs.push(new diff_match_patch.Diff(DIFF_EQUAL, line)); 2136 | } else if (sign == '@') { 2137 | // Start of next patch. 2138 | break; 2139 | } else if (sign === '') { 2140 | // Blank line? Whatever. 2141 | } else { 2142 | // WTF? 2143 | throw new Error('Invalid patch mode "' + sign + '" in: ' + line); 2144 | } 2145 | textPointer++; 2146 | } 2147 | } 2148 | return patches; 2149 | }; 2150 | 2151 | 2152 | /** 2153 | * Class representing one patch operation. 2154 | * @constructor 2155 | */ 2156 | diff_match_patch.patch_obj = function() { 2157 | /** @type {!Array.} */ 2158 | this.diffs = []; 2159 | /** @type {?number} */ 2160 | this.start1 = null; 2161 | /** @type {?number} */ 2162 | this.start2 = null; 2163 | /** @type {number} */ 2164 | this.length1 = 0; 2165 | /** @type {number} */ 2166 | this.length2 = 0; 2167 | }; 2168 | 2169 | 2170 | /** 2171 | * Emulate GNU diff's format. 2172 | * Header: @@ -382,8 +481,9 @@ 2173 | * Indices are printed as 1-based, not 0-based. 2174 | * @return {string} The GNU diff string. 2175 | */ 2176 | diff_match_patch.patch_obj.prototype.toString = function() { 2177 | var coords1, coords2; 2178 | if (this.length1 === 0) { 2179 | coords1 = this.start1 + ',0'; 2180 | } else if (this.length1 == 1) { 2181 | coords1 = this.start1 + 1; 2182 | } else { 2183 | coords1 = (this.start1 + 1) + ',' + this.length1; 2184 | } 2185 | if (this.length2 === 0) { 2186 | coords2 = this.start2 + ',0'; 2187 | } else if (this.length2 == 1) { 2188 | coords2 = this.start2 + 1; 2189 | } else { 2190 | coords2 = (this.start2 + 1) + ',' + this.length2; 2191 | } 2192 | var text = ['@@ -' + coords1 + ' +' + coords2 + ' @@\n']; 2193 | var op; 2194 | // Escape the body of the patch with %xx notation. 2195 | for (var x = 0; x < this.diffs.length; x++) { 2196 | switch (this.diffs[x][0]) { 2197 | case DIFF_INSERT: 2198 | op = '+'; 2199 | break; 2200 | case DIFF_DELETE: 2201 | op = '-'; 2202 | break; 2203 | case DIFF_EQUAL: 2204 | op = ' '; 2205 | break; 2206 | } 2207 | text[x + 1] = op + encodeURI(this.diffs[x][1]) + '\n'; 2208 | } 2209 | return text.join('').replace(/%20/g, ' '); 2210 | }; 2211 | 2212 | 2213 | // The following export code was added by @ForbesLindesay 2214 | module.exports = diff_match_patch; 2215 | module.exports['diff_match_patch'] = diff_match_patch; 2216 | module.exports['DIFF_DELETE'] = DIFF_DELETE; 2217 | module.exports['DIFF_INSERT'] = DIFF_INSERT; 2218 | module.exports['DIFF_EQUAL'] = DIFF_EQUAL; -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "diff-match-patch", 3 | "version": "1.0.5", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "diff-match-patch", 9 | "version": "1.0.5", 10 | "license": "Apache-2.0", 11 | "devDependencies": { 12 | "testit": "^3.0.0" 13 | } 14 | }, 15 | "node_modules/ansi-styles": { 16 | "version": "3.2.1", 17 | "dev": true, 18 | "license": "MIT", 19 | "dependencies": { 20 | "color-convert": "^1.9.0" 21 | }, 22 | "engines": { 23 | "node": ">=4" 24 | } 25 | }, 26 | "node_modules/asap": { 27 | "version": "2.0.6", 28 | "dev": true, 29 | "license": "MIT" 30 | }, 31 | "node_modules/chalk": { 32 | "version": "2.4.2", 33 | "dev": true, 34 | "license": "MIT", 35 | "dependencies": { 36 | "ansi-styles": "^3.2.1", 37 | "escape-string-regexp": "^1.0.5", 38 | "supports-color": "^5.3.0" 39 | }, 40 | "engines": { 41 | "node": ">=4" 42 | } 43 | }, 44 | "node_modules/color-convert": { 45 | "version": "1.9.3", 46 | "dev": true, 47 | "license": "MIT", 48 | "dependencies": { 49 | "color-name": "1.1.3" 50 | } 51 | }, 52 | "node_modules/color-name": { 53 | "version": "1.1.3", 54 | "dev": true, 55 | "license": "MIT" 56 | }, 57 | "node_modules/escape-string-regexp": { 58 | "version": "1.0.5", 59 | "dev": true, 60 | "license": "MIT", 61 | "engines": { 62 | "node": ">=0.8.0" 63 | } 64 | }, 65 | "node_modules/has-flag": { 66 | "version": "3.0.0", 67 | "dev": true, 68 | "license": "MIT", 69 | "engines": { 70 | "node": ">=4" 71 | } 72 | }, 73 | "node_modules/is-browser": { 74 | "version": "2.1.0", 75 | "dev": true, 76 | "license": "MIT" 77 | }, 78 | "node_modules/ms": { 79 | "version": "2.1.2", 80 | "dev": true, 81 | "license": "MIT" 82 | }, 83 | "node_modules/promise": { 84 | "version": "8.1.0", 85 | "dev": true, 86 | "license": "MIT", 87 | "dependencies": { 88 | "asap": "~2.0.6" 89 | } 90 | }, 91 | "node_modules/supports-color": { 92 | "version": "5.5.0", 93 | "dev": true, 94 | "license": "MIT", 95 | "dependencies": { 96 | "has-flag": "^3.0.0" 97 | }, 98 | "engines": { 99 | "node": ">=4" 100 | } 101 | }, 102 | "node_modules/test-result": { 103 | "version": "2.0.0", 104 | "dev": true, 105 | "license": "MIT" 106 | }, 107 | "node_modules/testit": { 108 | "version": "3.1.0", 109 | "dev": true, 110 | "license": "MIT", 111 | "dependencies": { 112 | "chalk": "^2.0.0", 113 | "is-browser": "^2.0.1", 114 | "ms": "^2.1.1", 115 | "promise": "^8.0.1", 116 | "test-result": "^2.0.0", 117 | "wtfnode": "^0.8.0" 118 | } 119 | }, 120 | "node_modules/wtfnode": { 121 | "version": "0.8.1", 122 | "dev": true, 123 | "license": "ISC", 124 | "bin": { 125 | "wtfnode": "proxy.js" 126 | } 127 | } 128 | }, 129 | "dependencies": { 130 | "ansi-styles": { 131 | "version": "3.2.1", 132 | "dev": true, 133 | "requires": { 134 | "color-convert": "^1.9.0" 135 | } 136 | }, 137 | "asap": { 138 | "version": "2.0.6", 139 | "dev": true 140 | }, 141 | "chalk": { 142 | "version": "2.4.2", 143 | "dev": true, 144 | "requires": { 145 | "ansi-styles": "^3.2.1", 146 | "escape-string-regexp": "^1.0.5", 147 | "supports-color": "^5.3.0" 148 | } 149 | }, 150 | "color-convert": { 151 | "version": "1.9.3", 152 | "dev": true, 153 | "requires": { 154 | "color-name": "1.1.3" 155 | } 156 | }, 157 | "color-name": { 158 | "version": "1.1.3", 159 | "dev": true 160 | }, 161 | "escape-string-regexp": { 162 | "version": "1.0.5", 163 | "dev": true 164 | }, 165 | "has-flag": { 166 | "version": "3.0.0", 167 | "dev": true 168 | }, 169 | "is-browser": { 170 | "version": "2.1.0", 171 | "dev": true 172 | }, 173 | "ms": { 174 | "version": "2.1.2", 175 | "dev": true 176 | }, 177 | "promise": { 178 | "version": "8.1.0", 179 | "dev": true, 180 | "requires": { 181 | "asap": "~2.0.6" 182 | } 183 | }, 184 | "supports-color": { 185 | "version": "5.5.0", 186 | "dev": true, 187 | "requires": { 188 | "has-flag": "^3.0.0" 189 | } 190 | }, 191 | "test-result": { 192 | "version": "2.0.0", 193 | "dev": true 194 | }, 195 | "testit": { 196 | "version": "3.1.0", 197 | "dev": true, 198 | "requires": { 199 | "chalk": "^2.0.0", 200 | "is-browser": "^2.0.1", 201 | "ms": "^2.1.1", 202 | "promise": "^8.0.1", 203 | "test-result": "^2.0.0", 204 | "wtfnode": "^0.8.0" 205 | } 206 | }, 207 | "wtfnode": { 208 | "version": "0.8.1", 209 | "dev": true 210 | } 211 | } 212 | } 213 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "diff-match-patch", 3 | "version": "1.0.5", 4 | "description": "npm package for https://github.com/google/diff-match-patch", 5 | "keywords": [ 6 | "diff", 7 | "diff-match-patch", 8 | "google-diff-match-patch" 9 | ], 10 | "dependencies": {}, 11 | "devDependencies": { 12 | "testit": "^3.0.0" 13 | }, 14 | "scripts": { 15 | "test": "node test" 16 | }, 17 | "repository": { 18 | "type": "git", 19 | "url": "https://github.com/JackuB/diff-match-patch.git" 20 | }, 21 | "license": "Apache-2.0" 22 | } 23 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var assert = require('assert'); 4 | var test = require('testit'); 5 | var diff_match_patch = require('../'); 6 | var DIFF_DELETE = diff_match_patch.DIFF_DELETE; 7 | var DIFF_INSERT = diff_match_patch.DIFF_INSERT; 8 | var DIFF_EQUAL = diff_match_patch.DIFF_EQUAL; 9 | 10 | 11 | 12 | /** 13 | * Diff Match and Patch -- Test Harness 14 | * Copyright 2018 The diff-match-patch Authors. 15 | * https://github.com/google/diff-match-patch 16 | * 17 | * Licensed under the Apache License, Version 2.0 (the "License"); 18 | * you may not use this file except in compliance with the License. 19 | * You may obtain a copy of the License at 20 | * 21 | * http://www.apache.org/licenses/LICENSE-2.0 22 | * 23 | * Unless required by applicable law or agreed to in writing, software 24 | * distributed under the License is distributed on an "AS IS" BASIS, 25 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26 | * See the License for the specific language governing permissions and 27 | * limitations under the License. 28 | */ 29 | 30 | 31 | // If expected and actual are the equivalent, pass the test. 32 | function assertEquivalent(msg, expected, actual) { 33 | if (typeof actual == 'undefined') { 34 | // msg is optional. 35 | actual = expected; 36 | expected = msg; 37 | msg = 'Expected: \'' + expected + '\' Actual: \'' + actual + '\''; 38 | } 39 | if (_equivalent(expected, actual)) { 40 | return assertEquals(msg, String(expected), String(actual)); 41 | } else { 42 | return assertEquals(msg, expected, actual); 43 | } 44 | } 45 | 46 | 47 | // Are a and b the equivalent? -- Recursive. 48 | function _equivalent(a, b) { 49 | if (a == b) { 50 | return true; 51 | } 52 | if (typeof a == 'object' && typeof b == 'object' && a !== null && b !== null) { 53 | if (a.toString() != b.toString()) { 54 | return false; 55 | } 56 | for (var p in a) { 57 | if (a.hasOwnProperty(p) && !_equivalent(a[p], b[p])) { 58 | return false; 59 | } 60 | } 61 | for (var p in b) { 62 | if (a.hasOwnProperty(p) && !_equivalent(a[p], b[p])) { 63 | return false; 64 | } 65 | } 66 | return true; 67 | } 68 | return false; 69 | } 70 | 71 | 72 | function diff_rebuildtexts(diffs) { 73 | // Construct the two texts which made up the diff originally. 74 | var text1 = ''; 75 | var text2 = ''; 76 | for (var x = 0; x < diffs.length; x++) { 77 | if (diffs[x][0] != DIFF_INSERT) { 78 | text1 += diffs[x][1]; 79 | } 80 | if (diffs[x][0] != DIFF_DELETE) { 81 | text2 += diffs[x][1]; 82 | } 83 | } 84 | return [text1, text2]; 85 | } 86 | 87 | var dmp = new diff_match_patch(); 88 | 89 | 90 | // DIFF TEST FUNCTIONS 91 | 92 | 93 | function testDiffCommonPrefix() { 94 | // Detect any common prefix. 95 | // Null case. 96 | assertEquals(0, dmp.diff_commonPrefix('abc', 'xyz')); 97 | 98 | // Non-null case. 99 | assertEquals(4, dmp.diff_commonPrefix('1234abcdef', '1234xyz')); 100 | 101 | // Whole case. 102 | assertEquals(4, dmp.diff_commonPrefix('1234', '1234xyz')); 103 | } 104 | 105 | function testDiffCommonSuffix() { 106 | // Detect any common suffix. 107 | // Null case. 108 | assertEquals(0, dmp.diff_commonSuffix('abc', 'xyz')); 109 | 110 | // Non-null case. 111 | assertEquals(4, dmp.diff_commonSuffix('abcdef1234', 'xyz1234')); 112 | 113 | // Whole case. 114 | assertEquals(4, dmp.diff_commonSuffix('1234', 'xyz1234')); 115 | } 116 | 117 | function testDiffCommonOverlap() { 118 | // Detect any suffix/prefix overlap. 119 | // Null case. 120 | assertEquals(0, dmp.diff_commonOverlap_('', 'abcd')); 121 | 122 | // Whole case. 123 | assertEquals(3, dmp.diff_commonOverlap_('abc', 'abcd')); 124 | 125 | // No overlap. 126 | assertEquals(0, dmp.diff_commonOverlap_('123456', 'abcd')); 127 | 128 | // Overlap. 129 | assertEquals(3, dmp.diff_commonOverlap_('123456xxx', 'xxxabcd')); 130 | 131 | // Unicode. 132 | // Some overly clever languages (C#) may treat ligatures as equal to their 133 | // component letters. E.g. U+FB01 == 'fi' 134 | assertEquals(0, dmp.diff_commonOverlap_('fi', '\ufb01i')); 135 | } 136 | 137 | function testDiffHalfMatch() { 138 | // Detect a halfmatch. 139 | dmp.Diff_Timeout = 1; 140 | // No match. 141 | assertEquals(null, dmp.diff_halfMatch_('1234567890', 'abcdef')); 142 | 143 | assertEquals(null, dmp.diff_halfMatch_('12345', '23')); 144 | 145 | // Single Match. 146 | assertEquivalent(['12', '90', 'a', 'z', '345678'], dmp.diff_halfMatch_('1234567890', 'a345678z')); 147 | 148 | assertEquivalent(['a', 'z', '12', '90', '345678'], dmp.diff_halfMatch_('a345678z', '1234567890')); 149 | 150 | assertEquivalent(['abc', 'z', '1234', '0', '56789'], dmp.diff_halfMatch_('abc56789z', '1234567890')); 151 | 152 | assertEquivalent(['a', 'xyz', '1', '7890', '23456'], dmp.diff_halfMatch_('a23456xyz', '1234567890')); 153 | 154 | // Multiple Matches. 155 | assertEquivalent(['12123', '123121', 'a', 'z', '1234123451234'], dmp.diff_halfMatch_('121231234123451234123121', 'a1234123451234z')); 156 | 157 | assertEquivalent(['', '-=-=-=-=-=', 'x', '', 'x-=-=-=-=-=-=-='], dmp.diff_halfMatch_('x-=-=-=-=-=-=-=-=-=-=-=-=', 'xx-=-=-=-=-=-=-=')); 158 | 159 | assertEquivalent(['-=-=-=-=-=', '', '', 'y', '-=-=-=-=-=-=-=y'], dmp.diff_halfMatch_('-=-=-=-=-=-=-=-=-=-=-=-=y', '-=-=-=-=-=-=-=yy')); 160 | 161 | // Non-optimal halfmatch. 162 | // Optimal diff would be -q+x=H-i+e=lloHe+Hu=llo-Hew+y not -qHillo+x=HelloHe-w+Hulloy 163 | assertEquivalent(['qHillo', 'w', 'x', 'Hulloy', 'HelloHe'], dmp.diff_halfMatch_('qHilloHelloHew', 'xHelloHeHulloy')); 164 | 165 | // Optimal no halfmatch. 166 | dmp.Diff_Timeout = 0; 167 | assertEquals(null, dmp.diff_halfMatch_('qHilloHelloHew', 'xHelloHeHulloy')); 168 | } 169 | 170 | function testDiffLinesToChars() { 171 | function assertLinesToCharsResultEquals(a, b) { 172 | assertEquals(a.chars1, b.chars1); 173 | assertEquals(a.chars2, b.chars2); 174 | assertEquivalent(a.lineArray, b.lineArray); 175 | } 176 | 177 | // Convert lines down to characters. 178 | assertLinesToCharsResultEquals({chars1: '\x01\x02\x01', chars2: '\x02\x01\x02', lineArray: ['', 'alpha\n', 'beta\n']}, dmp.diff_linesToChars_('alpha\nbeta\nalpha\n', 'beta\nalpha\nbeta\n')); 179 | 180 | assertLinesToCharsResultEquals({chars1: '', chars2: '\x01\x02\x03\x03', lineArray: ['', 'alpha\r\n', 'beta\r\n', '\r\n']}, dmp.diff_linesToChars_('', 'alpha\r\nbeta\r\n\r\n\r\n')); 181 | 182 | assertLinesToCharsResultEquals({chars1: '\x01', chars2: '\x02', lineArray: ['', 'a', 'b']}, dmp.diff_linesToChars_('a', 'b')); 183 | 184 | // More than 256 to reveal any 8-bit limitations. 185 | var n = 300; 186 | var lineList = []; 187 | var charList = []; 188 | for (var i = 1; i < n + 1; i++) { 189 | lineList[i - 1] = i + '\n'; 190 | charList[i - 1] = String.fromCharCode(i); 191 | } 192 | assertEquals(n, lineList.length); 193 | var lines = lineList.join(''); 194 | var chars = charList.join(''); 195 | assertEquals(n, chars.length); 196 | lineList.unshift(''); 197 | assertLinesToCharsResultEquals({chars1: chars, chars2: '', lineArray: lineList}, dmp.diff_linesToChars_(lines, '')); 198 | } 199 | 200 | function testDiffCharsToLines() { 201 | // Convert chars up to lines. 202 | var diffs = [[DIFF_EQUAL, '\x01\x02\x01'], [DIFF_INSERT, '\x02\x01\x02']]; 203 | dmp.diff_charsToLines_(diffs, ['', 'alpha\n', 'beta\n']); 204 | assertEquivalent([[DIFF_EQUAL, 'alpha\nbeta\nalpha\n'], [DIFF_INSERT, 'beta\nalpha\nbeta\n']], diffs); 205 | 206 | // More than 256 to reveal any 8-bit limitations. 207 | var n = 300; 208 | var lineList = []; 209 | var charList = []; 210 | for (var i = 1; i < n + 1; i++) { 211 | lineList[i - 1] = i + '\n'; 212 | charList[i - 1] = String.fromCharCode(i); 213 | } 214 | assertEquals(n, lineList.length); 215 | var lines = lineList.join(''); 216 | var chars = charList.join(''); 217 | assertEquals(n, chars.length); 218 | lineList.unshift(''); 219 | var diffs = [[DIFF_DELETE, chars]]; 220 | dmp.diff_charsToLines_(diffs, lineList); 221 | assertEquivalent([[DIFF_DELETE, lines]], diffs); 222 | 223 | // More than 65536 to verify any 16-bit limitation. 224 | lineList = []; 225 | for (var i = 0; i < 66000; i++) { 226 | lineList[i] = i + '\n'; 227 | } 228 | chars = lineList.join(''); 229 | var results = dmp.diff_linesToChars_(chars, ''); 230 | diffs = [[DIFF_INSERT, results.chars1]]; 231 | dmp.diff_charsToLines_(diffs, results.lineArray); 232 | assertEquals(chars, diffs[0][1]); 233 | } 234 | 235 | function testDiffCleanupMerge() { 236 | // Cleanup a messy diff. 237 | // Null case. 238 | var diffs = []; 239 | dmp.diff_cleanupMerge(diffs); 240 | assertEquivalent([], diffs); 241 | 242 | // No change case. 243 | diffs = [[DIFF_EQUAL, 'a'], [DIFF_DELETE, 'b'], [DIFF_INSERT, 'c']]; 244 | dmp.diff_cleanupMerge(diffs); 245 | assertEquivalent([[DIFF_EQUAL, 'a'], [DIFF_DELETE, 'b'], [DIFF_INSERT, 'c']], diffs); 246 | 247 | // Merge equalities. 248 | diffs = [[DIFF_EQUAL, 'a'], [DIFF_EQUAL, 'b'], [DIFF_EQUAL, 'c']]; 249 | dmp.diff_cleanupMerge(diffs); 250 | assertEquivalent([[DIFF_EQUAL, 'abc']], diffs); 251 | 252 | // Merge deletions. 253 | diffs = [[DIFF_DELETE, 'a'], [DIFF_DELETE, 'b'], [DIFF_DELETE, 'c']]; 254 | dmp.diff_cleanupMerge(diffs); 255 | assertEquivalent([[DIFF_DELETE, 'abc']], diffs); 256 | 257 | // Merge insertions. 258 | diffs = [[DIFF_INSERT, 'a'], [DIFF_INSERT, 'b'], [DIFF_INSERT, 'c']]; 259 | dmp.diff_cleanupMerge(diffs); 260 | assertEquivalent([[DIFF_INSERT, 'abc']], diffs); 261 | 262 | // Merge interweave. 263 | diffs = [[DIFF_DELETE, 'a'], [DIFF_INSERT, 'b'], [DIFF_DELETE, 'c'], [DIFF_INSERT, 'd'], [DIFF_EQUAL, 'e'], [DIFF_EQUAL, 'f']]; 264 | dmp.diff_cleanupMerge(diffs); 265 | assertEquivalent([[DIFF_DELETE, 'ac'], [DIFF_INSERT, 'bd'], [DIFF_EQUAL, 'ef']], diffs); 266 | 267 | // Prefix and suffix detection. 268 | diffs = [[DIFF_DELETE, 'a'], [DIFF_INSERT, 'abc'], [DIFF_DELETE, 'dc']]; 269 | dmp.diff_cleanupMerge(diffs); 270 | assertEquivalent([[DIFF_EQUAL, 'a'], [DIFF_DELETE, 'd'], [DIFF_INSERT, 'b'], [DIFF_EQUAL, 'c']], diffs); 271 | 272 | // Prefix and suffix detection with equalities. 273 | diffs = [[DIFF_EQUAL, 'x'], [DIFF_DELETE, 'a'], [DIFF_INSERT, 'abc'], [DIFF_DELETE, 'dc'], [DIFF_EQUAL, 'y']]; 274 | dmp.diff_cleanupMerge(diffs); 275 | assertEquivalent([[DIFF_EQUAL, 'xa'], [DIFF_DELETE, 'd'], [DIFF_INSERT, 'b'], [DIFF_EQUAL, 'cy']], diffs); 276 | 277 | // Slide edit left. 278 | diffs = [[DIFF_EQUAL, 'a'], [DIFF_INSERT, 'ba'], [DIFF_EQUAL, 'c']]; 279 | dmp.diff_cleanupMerge(diffs); 280 | assertEquivalent([[DIFF_INSERT, 'ab'], [DIFF_EQUAL, 'ac']], diffs); 281 | 282 | // Slide edit right. 283 | diffs = [[DIFF_EQUAL, 'c'], [DIFF_INSERT, 'ab'], [DIFF_EQUAL, 'a']]; 284 | dmp.diff_cleanupMerge(diffs); 285 | assertEquivalent([[DIFF_EQUAL, 'ca'], [DIFF_INSERT, 'ba']], diffs); 286 | 287 | // Slide edit left recursive. 288 | diffs = [[DIFF_EQUAL, 'a'], [DIFF_DELETE, 'b'], [DIFF_EQUAL, 'c'], [DIFF_DELETE, 'ac'], [DIFF_EQUAL, 'x']]; 289 | dmp.diff_cleanupMerge(diffs); 290 | assertEquivalent([[DIFF_DELETE, 'abc'], [DIFF_EQUAL, 'acx']], diffs); 291 | 292 | // Slide edit right recursive. 293 | diffs = [[DIFF_EQUAL, 'x'], [DIFF_DELETE, 'ca'], [DIFF_EQUAL, 'c'], [DIFF_DELETE, 'b'], [DIFF_EQUAL, 'a']]; 294 | dmp.diff_cleanupMerge(diffs); 295 | assertEquivalent([[DIFF_EQUAL, 'xca'], [DIFF_DELETE, 'cba']], diffs); 296 | 297 | // Empty merge. 298 | diffs = [[DIFF_DELETE, 'b'], [DIFF_INSERT, 'ab'], [DIFF_EQUAL, 'c']]; 299 | dmp.diff_cleanupMerge(diffs); 300 | assertEquivalent([[DIFF_INSERT, 'a'], [DIFF_EQUAL, 'bc']], diffs); 301 | 302 | // Empty equality. 303 | diffs = [[DIFF_EQUAL, ''], [DIFF_INSERT, 'a'], [DIFF_EQUAL, 'b']]; 304 | dmp.diff_cleanupMerge(diffs); 305 | assertEquivalent([[DIFF_INSERT, 'a'], [DIFF_EQUAL, 'b']], diffs); 306 | } 307 | 308 | function testDiffCleanupSemanticLossless() { 309 | // Slide diffs to match logical boundaries. 310 | // Null case. 311 | var diffs = []; 312 | dmp.diff_cleanupSemanticLossless(diffs); 313 | assertEquivalent([], diffs); 314 | 315 | // Blank lines. 316 | diffs = [[DIFF_EQUAL, 'AAA\r\n\r\nBBB'], [DIFF_INSERT, '\r\nDDD\r\n\r\nBBB'], [DIFF_EQUAL, '\r\nEEE']]; 317 | dmp.diff_cleanupSemanticLossless(diffs); 318 | assertEquivalent([[DIFF_EQUAL, 'AAA\r\n\r\n'], [DIFF_INSERT, 'BBB\r\nDDD\r\n\r\n'], [DIFF_EQUAL, 'BBB\r\nEEE']], diffs); 319 | 320 | // Line boundaries. 321 | diffs = [[DIFF_EQUAL, 'AAA\r\nBBB'], [DIFF_INSERT, ' DDD\r\nBBB'], [DIFF_EQUAL, ' EEE']]; 322 | dmp.diff_cleanupSemanticLossless(diffs); 323 | assertEquivalent([[DIFF_EQUAL, 'AAA\r\n'], [DIFF_INSERT, 'BBB DDD\r\n'], [DIFF_EQUAL, 'BBB EEE']], diffs); 324 | 325 | // Word boundaries. 326 | diffs = [[DIFF_EQUAL, 'The c'], [DIFF_INSERT, 'ow and the c'], [DIFF_EQUAL, 'at.']]; 327 | dmp.diff_cleanupSemanticLossless(diffs); 328 | assertEquivalent([[DIFF_EQUAL, 'The '], [DIFF_INSERT, 'cow and the '], [DIFF_EQUAL, 'cat.']], diffs); 329 | 330 | // Alphanumeric boundaries. 331 | diffs = [[DIFF_EQUAL, 'The-c'], [DIFF_INSERT, 'ow-and-the-c'], [DIFF_EQUAL, 'at.']]; 332 | dmp.diff_cleanupSemanticLossless(diffs); 333 | assertEquivalent([[DIFF_EQUAL, 'The-'], [DIFF_INSERT, 'cow-and-the-'], [DIFF_EQUAL, 'cat.']], diffs); 334 | 335 | // Hitting the start. 336 | diffs = [[DIFF_EQUAL, 'a'], [DIFF_DELETE, 'a'], [DIFF_EQUAL, 'ax']]; 337 | dmp.diff_cleanupSemanticLossless(diffs); 338 | assertEquivalent([[DIFF_DELETE, 'a'], [DIFF_EQUAL, 'aax']], diffs); 339 | 340 | // Hitting the end. 341 | diffs = [[DIFF_EQUAL, 'xa'], [DIFF_DELETE, 'a'], [DIFF_EQUAL, 'a']]; 342 | dmp.diff_cleanupSemanticLossless(diffs); 343 | assertEquivalent([[DIFF_EQUAL, 'xaa'], [DIFF_DELETE, 'a']], diffs); 344 | 345 | // Sentence boundaries. 346 | diffs = [[DIFF_EQUAL, 'The xxx. The '], [DIFF_INSERT, 'zzz. The '], [DIFF_EQUAL, 'yyy.']]; 347 | dmp.diff_cleanupSemanticLossless(diffs); 348 | assertEquivalent([[DIFF_EQUAL, 'The xxx.'], [DIFF_INSERT, ' The zzz.'], [DIFF_EQUAL, ' The yyy.']], diffs); 349 | } 350 | 351 | function testDiffCleanupSemantic() { 352 | // Cleanup semantically trivial equalities. 353 | // Null case. 354 | var diffs = []; 355 | dmp.diff_cleanupSemantic(diffs); 356 | assertEquivalent([], diffs); 357 | 358 | // No elimination #1. 359 | diffs = [[DIFF_DELETE, 'ab'], [DIFF_INSERT, 'cd'], [DIFF_EQUAL, '12'], [DIFF_DELETE, 'e']]; 360 | dmp.diff_cleanupSemantic(diffs); 361 | assertEquivalent([[DIFF_DELETE, 'ab'], [DIFF_INSERT, 'cd'], [DIFF_EQUAL, '12'], [DIFF_DELETE, 'e']], diffs); 362 | 363 | // No elimination #2. 364 | diffs = [[DIFF_DELETE, 'abc'], [DIFF_INSERT, 'ABC'], [DIFF_EQUAL, '1234'], [DIFF_DELETE, 'wxyz']]; 365 | dmp.diff_cleanupSemantic(diffs); 366 | assertEquivalent([[DIFF_DELETE, 'abc'], [DIFF_INSERT, 'ABC'], [DIFF_EQUAL, '1234'], [DIFF_DELETE, 'wxyz']], diffs); 367 | 368 | // Simple elimination. 369 | diffs = [[DIFF_DELETE, 'a'], [DIFF_EQUAL, 'b'], [DIFF_DELETE, 'c']]; 370 | dmp.diff_cleanupSemantic(diffs); 371 | assertEquivalent([[DIFF_DELETE, 'abc'], [DIFF_INSERT, 'b']], diffs); 372 | 373 | // Backpass elimination. 374 | diffs = [[DIFF_DELETE, 'ab'], [DIFF_EQUAL, 'cd'], [DIFF_DELETE, 'e'], [DIFF_EQUAL, 'f'], [DIFF_INSERT, 'g']]; 375 | dmp.diff_cleanupSemantic(diffs); 376 | assertEquivalent([[DIFF_DELETE, 'abcdef'], [DIFF_INSERT, 'cdfg']], diffs); 377 | 378 | // Multiple eliminations. 379 | diffs = [[DIFF_INSERT, '1'], [DIFF_EQUAL, 'A'], [DIFF_DELETE, 'B'], [DIFF_INSERT, '2'], [DIFF_EQUAL, '_'], [DIFF_INSERT, '1'], [DIFF_EQUAL, 'A'], [DIFF_DELETE, 'B'], [DIFF_INSERT, '2']]; 380 | dmp.diff_cleanupSemantic(diffs); 381 | assertEquivalent([[DIFF_DELETE, 'AB_AB'], [DIFF_INSERT, '1A2_1A2']], diffs); 382 | 383 | // Word boundaries. 384 | diffs = [[DIFF_EQUAL, 'The c'], [DIFF_DELETE, 'ow and the c'], [DIFF_EQUAL, 'at.']]; 385 | dmp.diff_cleanupSemantic(diffs); 386 | assertEquivalent([[DIFF_EQUAL, 'The '], [DIFF_DELETE, 'cow and the '], [DIFF_EQUAL, 'cat.']], diffs); 387 | 388 | // No overlap elimination. 389 | diffs = [[DIFF_DELETE, 'abcxx'], [DIFF_INSERT, 'xxdef']]; 390 | dmp.diff_cleanupSemantic(diffs); 391 | assertEquivalent([[DIFF_DELETE, 'abcxx'], [DIFF_INSERT, 'xxdef']], diffs); 392 | 393 | // Overlap elimination. 394 | diffs = [[DIFF_DELETE, 'abcxxx'], [DIFF_INSERT, 'xxxdef']]; 395 | dmp.diff_cleanupSemantic(diffs); 396 | assertEquivalent([[DIFF_DELETE, 'abc'], [DIFF_EQUAL, 'xxx'], [DIFF_INSERT, 'def']], diffs); 397 | 398 | // Reverse overlap elimination. 399 | diffs = [[DIFF_DELETE, 'xxxabc'], [DIFF_INSERT, 'defxxx']]; 400 | dmp.diff_cleanupSemantic(diffs); 401 | assertEquivalent([[DIFF_INSERT, 'def'], [DIFF_EQUAL, 'xxx'], [DIFF_DELETE, 'abc']], diffs); 402 | 403 | // Two overlap eliminations. 404 | diffs = [[DIFF_DELETE, 'abcd1212'], [DIFF_INSERT, '1212efghi'], [DIFF_EQUAL, '----'], [DIFF_DELETE, 'A3'], [DIFF_INSERT, '3BC']]; 405 | dmp.diff_cleanupSemantic(diffs); 406 | assertEquivalent([[DIFF_DELETE, 'abcd'], [DIFF_EQUAL, '1212'], [DIFF_INSERT, 'efghi'], [DIFF_EQUAL, '----'], [DIFF_DELETE, 'A'], [DIFF_EQUAL, '3'], [DIFF_INSERT, 'BC']], diffs); 407 | } 408 | 409 | function testDiffCleanupEfficiency() { 410 | // Cleanup operationally trivial equalities. 411 | dmp.Diff_EditCost = 4; 412 | // Null case. 413 | var diffs = []; 414 | dmp.diff_cleanupEfficiency(diffs); 415 | assertEquivalent([], diffs); 416 | 417 | // No elimination. 418 | diffs = [[DIFF_DELETE, 'ab'], [DIFF_INSERT, '12'], [DIFF_EQUAL, 'wxyz'], [DIFF_DELETE, 'cd'], [DIFF_INSERT, '34']]; 419 | dmp.diff_cleanupEfficiency(diffs); 420 | assertEquivalent([[DIFF_DELETE, 'ab'], [DIFF_INSERT, '12'], [DIFF_EQUAL, 'wxyz'], [DIFF_DELETE, 'cd'], [DIFF_INSERT, '34']], diffs); 421 | 422 | // Four-edit elimination. 423 | diffs = [[DIFF_DELETE, 'ab'], [DIFF_INSERT, '12'], [DIFF_EQUAL, 'xyz'], [DIFF_DELETE, 'cd'], [DIFF_INSERT, '34']]; 424 | dmp.diff_cleanupEfficiency(diffs); 425 | assertEquivalent([[DIFF_DELETE, 'abxyzcd'], [DIFF_INSERT, '12xyz34']], diffs); 426 | 427 | // Three-edit elimination. 428 | diffs = [[DIFF_INSERT, '12'], [DIFF_EQUAL, 'x'], [DIFF_DELETE, 'cd'], [DIFF_INSERT, '34']]; 429 | dmp.diff_cleanupEfficiency(diffs); 430 | assertEquivalent([[DIFF_DELETE, 'xcd'], [DIFF_INSERT, '12x34']], diffs); 431 | 432 | // Backpass elimination. 433 | diffs = [[DIFF_DELETE, 'ab'], [DIFF_INSERT, '12'], [DIFF_EQUAL, 'xy'], [DIFF_INSERT, '34'], [DIFF_EQUAL, 'z'], [DIFF_DELETE, 'cd'], [DIFF_INSERT, '56']]; 434 | dmp.diff_cleanupEfficiency(diffs); 435 | assertEquivalent([[DIFF_DELETE, 'abxyzcd'], [DIFF_INSERT, '12xy34z56']], diffs); 436 | 437 | // High cost elimination. 438 | dmp.Diff_EditCost = 5; 439 | diffs = [[DIFF_DELETE, 'ab'], [DIFF_INSERT, '12'], [DIFF_EQUAL, 'wxyz'], [DIFF_DELETE, 'cd'], [DIFF_INSERT, '34']]; 440 | dmp.diff_cleanupEfficiency(diffs); 441 | assertEquivalent([[DIFF_DELETE, 'abwxyzcd'], [DIFF_INSERT, '12wxyz34']], diffs); 442 | dmp.Diff_EditCost = 4; 443 | } 444 | 445 | function testDiffPrettyHtml() { 446 | // Pretty print. 447 | var diffs = [[DIFF_EQUAL, 'a\n'], [DIFF_DELETE, 'b'], [DIFF_INSERT, 'c&d']]; 448 | assertEquals('
<B>b</B>c&d', dmp.diff_prettyHtml(diffs)); 449 | } 450 | 451 | function testDiffText() { 452 | // Compute the source and destination texts. 453 | var diffs = [[DIFF_EQUAL, 'jump'], [DIFF_DELETE, 's'], [DIFF_INSERT, 'ed'], [DIFF_EQUAL, ' over '], [DIFF_DELETE, 'the'], [DIFF_INSERT, 'a'], [DIFF_EQUAL, ' lazy']]; 454 | assertEquals('jumps over the lazy', dmp.diff_text1(diffs)); 455 | 456 | assertEquals('jumped over a lazy', dmp.diff_text2(diffs)); 457 | } 458 | 459 | function testDiffDelta() { 460 | // Convert a diff into delta string. 461 | var diffs = [[DIFF_EQUAL, 'jump'], [DIFF_DELETE, 's'], [DIFF_INSERT, 'ed'], [DIFF_EQUAL, ' over '], [DIFF_DELETE, 'the'], [DIFF_INSERT, 'a'], [DIFF_EQUAL, ' lazy'], [DIFF_INSERT, 'old dog']]; 462 | var text1 = dmp.diff_text1(diffs); 463 | assertEquals('jumps over the lazy', text1); 464 | 465 | var delta = dmp.diff_toDelta(diffs); 466 | assertEquals('=4\t-1\t+ed\t=6\t-3\t+a\t=5\t+old dog', delta); 467 | 468 | // Convert delta string into a diff. 469 | assertEquivalent(diffs, dmp.diff_fromDelta(text1, delta)); 470 | 471 | // Generates error (19 != 20). 472 | try { 473 | dmp.diff_fromDelta(text1 + 'x', delta); 474 | assertEquals(Error, null); 475 | } catch (e) { 476 | // Exception expected. 477 | } 478 | 479 | // Generates error (19 != 18). 480 | try { 481 | dmp.diff_fromDelta(text1.substring(1), delta); 482 | assertEquals(Error, null); 483 | } catch (e) { 484 | // Exception expected. 485 | } 486 | 487 | // Generates error (%c3%xy invalid Unicode). 488 | try { 489 | dmp.diff_fromDelta('', '+%c3%xy'); 490 | assertEquals(Error, null); 491 | } catch (e) { 492 | // Exception expected. 493 | } 494 | 495 | // Test deltas with special characters. 496 | diffs = [[DIFF_EQUAL, '\u0680 \x00 \t %'], [DIFF_DELETE, '\u0681 \x01 \n ^'], [DIFF_INSERT, '\u0682 \x02 \\ |']]; 497 | text1 = dmp.diff_text1(diffs); 498 | assertEquals('\u0680 \x00 \t %\u0681 \x01 \n ^', text1); 499 | 500 | delta = dmp.diff_toDelta(diffs); 501 | assertEquals('=7\t-7\t+%DA%82 %02 %5C %7C', delta); 502 | 503 | // Convert delta string into a diff. 504 | assertEquivalent(diffs, dmp.diff_fromDelta(text1, delta)); 505 | 506 | // Verify pool of unchanged characters. 507 | diffs = [[DIFF_INSERT, 'A-Z a-z 0-9 - _ . ! ~ * \' ( ) ; / ? : @ & = + $ , # ']]; 508 | var text2 = dmp.diff_text2(diffs); 509 | assertEquals('A-Z a-z 0-9 - _ . ! ~ * \' ( ) ; / ? : @ & = + $ , # ', text2); 510 | 511 | delta = dmp.diff_toDelta(diffs); 512 | assertEquals('+A-Z a-z 0-9 - _ . ! ~ * \' ( ) ; / ? : @ & = + $ , # ', delta); 513 | 514 | // Convert delta string into a diff. 515 | assertEquivalent(diffs, dmp.diff_fromDelta('', delta)); 516 | 517 | // 160 kb string. 518 | var a = 'abcdefghij'; 519 | for (var i = 0; i < 14; i++) { 520 | a += a; 521 | } 522 | diffs = [[DIFF_INSERT, a]]; 523 | delta = dmp.diff_toDelta(diffs); 524 | assertEquals('+' + a, delta); 525 | 526 | // Convert delta string into a diff. 527 | assertEquivalent(diffs, dmp.diff_fromDelta('', delta)); 528 | } 529 | 530 | function testDiffXIndex() { 531 | // Translate a location in text1 to text2. 532 | // Translation on equality. 533 | assertEquals(5, dmp.diff_xIndex([[DIFF_DELETE, 'a'], [DIFF_INSERT, '1234'], [DIFF_EQUAL, 'xyz']], 2)); 534 | 535 | // Translation on deletion. 536 | assertEquals(1, dmp.diff_xIndex([[DIFF_EQUAL, 'a'], [DIFF_DELETE, '1234'], [DIFF_EQUAL, 'xyz']], 3)); 537 | } 538 | 539 | function testDiffLevenshtein() { 540 | // Levenshtein with trailing equality. 541 | assertEquals(4, dmp.diff_levenshtein([[DIFF_DELETE, 'abc'], [DIFF_INSERT, '1234'], [DIFF_EQUAL, 'xyz']])); 542 | // Levenshtein with leading equality. 543 | assertEquals(4, dmp.diff_levenshtein([[DIFF_EQUAL, 'xyz'], [DIFF_DELETE, 'abc'], [DIFF_INSERT, '1234']])); 544 | // Levenshtein with middle equality. 545 | assertEquals(7, dmp.diff_levenshtein([[DIFF_DELETE, 'abc'], [DIFF_EQUAL, 'xyz'], [DIFF_INSERT, '1234']])); 546 | } 547 | 548 | function testDiffBisect() { 549 | // Normal. 550 | var a = 'cat'; 551 | var b = 'map'; 552 | // Since the resulting diff hasn't been normalized, it would be ok if 553 | // the insertion and deletion pairs are swapped. 554 | // If the order changes, tweak this test as required. 555 | assertEquivalent([[DIFF_DELETE, 'c'], [DIFF_INSERT, 'm'], [DIFF_EQUAL, 'a'], [DIFF_DELETE, 't'], [DIFF_INSERT, 'p']], dmp.diff_bisect_(a, b, Number.MAX_VALUE)); 556 | 557 | // Timeout. 558 | assertEquivalent([[DIFF_DELETE, 'cat'], [DIFF_INSERT, 'map']], dmp.diff_bisect_(a, b, 0)); 559 | } 560 | 561 | function testDiffMain() { 562 | // Perform a trivial diff. 563 | // Null case. 564 | assertEquivalent([], dmp.diff_main('', '', false)); 565 | 566 | // Equality. 567 | assertEquivalent([[DIFF_EQUAL, 'abc']], dmp.diff_main('abc', 'abc', false)); 568 | 569 | // Simple insertion. 570 | assertEquivalent([[DIFF_EQUAL, 'ab'], [DIFF_INSERT, '123'], [DIFF_EQUAL, 'c']], dmp.diff_main('abc', 'ab123c', false)); 571 | 572 | // Simple deletion. 573 | assertEquivalent([[DIFF_EQUAL, 'a'], [DIFF_DELETE, '123'], [DIFF_EQUAL, 'bc']], dmp.diff_main('a123bc', 'abc', false)); 574 | 575 | // Two insertions. 576 | assertEquivalent([[DIFF_EQUAL, 'a'], [DIFF_INSERT, '123'], [DIFF_EQUAL, 'b'], [DIFF_INSERT, '456'], [DIFF_EQUAL, 'c']], dmp.diff_main('abc', 'a123b456c', false)); 577 | 578 | // Two deletions. 579 | assertEquivalent([[DIFF_EQUAL, 'a'], [DIFF_DELETE, '123'], [DIFF_EQUAL, 'b'], [DIFF_DELETE, '456'], [DIFF_EQUAL, 'c']], dmp.diff_main('a123b456c', 'abc', false)); 580 | 581 | // Perform a real diff. 582 | // Switch off the timeout. 583 | dmp.Diff_Timeout = 0; 584 | // Simple cases. 585 | assertEquivalent([[DIFF_DELETE, 'a'], [DIFF_INSERT, 'b']], dmp.diff_main('a', 'b', false)); 586 | 587 | assertEquivalent([[DIFF_DELETE, 'Apple'], [DIFF_INSERT, 'Banana'], [DIFF_EQUAL, 's are a'], [DIFF_INSERT, 'lso'], [DIFF_EQUAL, ' fruit.']], dmp.diff_main('Apples are a fruit.', 'Bananas are also fruit.', false)); 588 | 589 | assertEquivalent([[DIFF_DELETE, 'a'], [DIFF_INSERT, '\u0680'], [DIFF_EQUAL, 'x'], [DIFF_DELETE, '\t'], [DIFF_INSERT, '\0']], dmp.diff_main('ax\t', '\u0680x\0', false)); 590 | 591 | // Overlaps. 592 | assertEquivalent([[DIFF_DELETE, '1'], [DIFF_EQUAL, 'a'], [DIFF_DELETE, 'y'], [DIFF_EQUAL, 'b'], [DIFF_DELETE, '2'], [DIFF_INSERT, 'xab']], dmp.diff_main('1ayb2', 'abxab', false)); 593 | 594 | assertEquivalent([[DIFF_INSERT, 'xaxcx'], [DIFF_EQUAL, 'abc'], [DIFF_DELETE, 'y']], dmp.diff_main('abcy', 'xaxcxabc', false)); 595 | 596 | assertEquivalent([[DIFF_DELETE, 'ABCD'], [DIFF_EQUAL, 'a'], [DIFF_DELETE, '='], [DIFF_INSERT, '-'], [DIFF_EQUAL, 'bcd'], [DIFF_DELETE, '='], [DIFF_INSERT, '-'], [DIFF_EQUAL, 'efghijklmnopqrs'], [DIFF_DELETE, 'EFGHIJKLMNOefg']], dmp.diff_main('ABCDa=bcd=efghijklmnopqrsEFGHIJKLMNOefg', 'a-bcd-efghijklmnopqrs', false)); 597 | 598 | // Large equality. 599 | assertEquivalent([[DIFF_INSERT, ' '], [DIFF_EQUAL, 'a'], [DIFF_INSERT, 'nd'], [DIFF_EQUAL, ' [[Pennsylvania]]'], [DIFF_DELETE, ' and [[New']], dmp.diff_main('a [[Pennsylvania]] and [[New', ' and [[Pennsylvania]]', false)); 600 | 601 | // Timeout. 602 | dmp.Diff_Timeout = 0.1; // 100ms 603 | var a = '`Twas brillig, and the slithy toves\nDid gyre and gimble in the wabe:\nAll mimsy were the borogoves,\nAnd the mome raths outgrabe.\n'; 604 | var b = 'I am the very model of a modern major general,\nI\'ve information vegetable, animal, and mineral,\nI know the kings of England, and I quote the fights historical,\nFrom Marathon to Waterloo, in order categorical.\n'; 605 | // Increase the text lengths by 1024 times to ensure a timeout. 606 | for (var i = 0; i < 10; i++) { 607 | a += a; 608 | b += b; 609 | } 610 | var startTime = (new Date()).getTime(); 611 | dmp.diff_main(a, b); 612 | var endTime = (new Date()).getTime(); 613 | // Test that we took at least the timeout period. 614 | assertTrue(dmp.Diff_Timeout * 1000 <= endTime - startTime); 615 | // Test that we didn't take forever (be forgiving). 616 | // Theoretically this test could fail very occasionally if the 617 | // OS task swaps or locks up for a second at the wrong moment. 618 | assertTrue(dmp.Diff_Timeout * 1000 * 2 > endTime - startTime); 619 | dmp.Diff_Timeout = 0; 620 | 621 | // Test the linemode speedup. 622 | // Must be long to pass the 100 char cutoff. 623 | // Simple line-mode. 624 | a = '1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n'; 625 | b = 'abcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\nabcdefghij\n'; 626 | assertEquivalent(dmp.diff_main(a, b, false), dmp.diff_main(a, b, true)); 627 | 628 | // Single line-mode. 629 | a = '1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890'; 630 | b = 'abcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghij'; 631 | assertEquivalent(dmp.diff_main(a, b, false), dmp.diff_main(a, b, true)); 632 | 633 | // Overlap line-mode. 634 | a = '1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n1234567890\n'; 635 | b = 'abcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n1234567890\n1234567890\n1234567890\nabcdefghij\n'; 636 | var texts_linemode = diff_rebuildtexts(dmp.diff_main(a, b, true)); 637 | var texts_textmode = diff_rebuildtexts(dmp.diff_main(a, b, false)); 638 | assertEquivalent(texts_textmode, texts_linemode); 639 | 640 | // Test null inputs. 641 | try { 642 | dmp.diff_main(null, null); 643 | assertEquals(Error, null); 644 | } catch (e) { 645 | // Exception expected. 646 | } 647 | } 648 | 649 | 650 | // MATCH TEST FUNCTIONS 651 | 652 | 653 | function testMatchAlphabet() { 654 | // Initialise the bitmasks for Bitap. 655 | // Unique. 656 | assertEquivalent({'a':4, 'b':2, 'c':1}, dmp.match_alphabet_('abc')); 657 | 658 | // Duplicates. 659 | assertEquivalent({'a':37, 'b':18, 'c':8}, dmp.match_alphabet_('abcaba')); 660 | } 661 | 662 | function testMatchBitap() { 663 | // Bitap algorithm. 664 | dmp.Match_Distance = 100; 665 | dmp.Match_Threshold = 0.5; 666 | // Exact matches. 667 | assertEquals(5, dmp.match_bitap_('abcdefghijk', 'fgh', 5)); 668 | 669 | assertEquals(5, dmp.match_bitap_('abcdefghijk', 'fgh', 0)); 670 | 671 | // Fuzzy matches. 672 | assertEquals(4, dmp.match_bitap_('abcdefghijk', 'efxhi', 0)); 673 | 674 | assertEquals(2, dmp.match_bitap_('abcdefghijk', 'cdefxyhijk', 5)); 675 | 676 | assertEquals(-1, dmp.match_bitap_('abcdefghijk', 'bxy', 1)); 677 | 678 | // Overflow. 679 | assertEquals(2, dmp.match_bitap_('123456789xx0', '3456789x0', 2)); 680 | 681 | // Threshold test. 682 | dmp.Match_Threshold = 0.4; 683 | assertEquals(4, dmp.match_bitap_('abcdefghijk', 'efxyhi', 1)); 684 | 685 | dmp.Match_Threshold = 0.3; 686 | assertEquals(-1, dmp.match_bitap_('abcdefghijk', 'efxyhi', 1)); 687 | 688 | dmp.Match_Threshold = 0.0; 689 | assertEquals(1, dmp.match_bitap_('abcdefghijk', 'bcdef', 1)); 690 | dmp.Match_Threshold = 0.5; 691 | 692 | // Multiple select. 693 | assertEquals(0, dmp.match_bitap_('abcdexyzabcde', 'abccde', 3)); 694 | 695 | assertEquals(8, dmp.match_bitap_('abcdexyzabcde', 'abccde', 5)); 696 | 697 | // Distance test. 698 | dmp.Match_Distance = 10; // Strict location. 699 | assertEquals(-1, dmp.match_bitap_('abcdefghijklmnopqrstuvwxyz', 'abcdefg', 24)); 700 | 701 | assertEquals(0, dmp.match_bitap_('abcdefghijklmnopqrstuvwxyz', 'abcdxxefg', 1)); 702 | 703 | dmp.Match_Distance = 1000; // Loose location. 704 | assertEquals(0, dmp.match_bitap_('abcdefghijklmnopqrstuvwxyz', 'abcdefg', 24)); 705 | } 706 | 707 | function testMatchMain() { 708 | // Full match. 709 | // Shortcut matches. 710 | assertEquals(0, dmp.match_main('abcdef', 'abcdef', 1000)); 711 | 712 | assertEquals(-1, dmp.match_main('', 'abcdef', 1)); 713 | 714 | assertEquals(3, dmp.match_main('abcdef', '', 3)); 715 | 716 | assertEquals(3, dmp.match_main('abcdef', 'de', 3)); 717 | 718 | // Beyond end match. 719 | assertEquals(3, dmp.match_main("abcdef", "defy", 4)); 720 | 721 | // Oversized pattern. 722 | assertEquals(0, dmp.match_main("abcdef", "abcdefy", 0)); 723 | 724 | // Complex match. 725 | assertEquals(4, dmp.match_main('I am the very model of a modern major general.', ' that berry ', 5)); 726 | 727 | // Test null inputs. 728 | try { 729 | dmp.match_main(null, null, 0); 730 | assertEquals(Error, null); 731 | } catch (e) { 732 | // Exception expected. 733 | } 734 | } 735 | 736 | 737 | // PATCH TEST FUNCTIONS 738 | 739 | 740 | function testPatchObj() { 741 | // Patch Object. 742 | var p = new diff_match_patch.patch_obj(); 743 | p.start1 = 20; 744 | p.start2 = 21; 745 | p.length1 = 18; 746 | p.length2 = 17; 747 | p.diffs = [[DIFF_EQUAL, 'jump'], [DIFF_DELETE, 's'], [DIFF_INSERT, 'ed'], [DIFF_EQUAL, ' over '], [DIFF_DELETE, 'the'], [DIFF_INSERT, 'a'], [DIFF_EQUAL, '\nlaz']]; 748 | var strp = p.toString(); 749 | assertEquals('@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n %0Alaz\n', strp); 750 | } 751 | 752 | function testPatchFromText() { 753 | assertEquivalent([], dmp.patch_fromText(strp)); 754 | 755 | var strp = '@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n %0Alaz\n'; 756 | assertEquals(strp, dmp.patch_fromText(strp)[0].toString()); 757 | 758 | assertEquals('@@ -1 +1 @@\n-a\n+b\n', dmp.patch_fromText('@@ -1 +1 @@\n-a\n+b\n')[0].toString()); 759 | 760 | assertEquals('@@ -1,3 +0,0 @@\n-abc\n', dmp.patch_fromText('@@ -1,3 +0,0 @@\n-abc\n')[0].toString()); 761 | 762 | assertEquals('@@ -0,0 +1,3 @@\n+abc\n', dmp.patch_fromText('@@ -0,0 +1,3 @@\n+abc\n')[0].toString()); 763 | 764 | // Generates error. 765 | try { 766 | dmp.patch_fromText('Bad\nPatch\n'); 767 | assertEquals(Error, null); 768 | } catch (e) { 769 | // Exception expected. 770 | } 771 | } 772 | 773 | function testPatchToText() { 774 | var strp = '@@ -21,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n laz\n'; 775 | var p = dmp.patch_fromText(strp); 776 | assertEquals(strp, dmp.patch_toText(p)); 777 | 778 | strp = '@@ -1,9 +1,9 @@\n-f\n+F\n oo+fooba\n@@ -7,9 +7,9 @@\n obar\n-,\n+.\n tes\n'; 779 | p = dmp.patch_fromText(strp); 780 | assertEquals(strp, dmp.patch_toText(p)); 781 | } 782 | 783 | function testPatchAddContext() { 784 | dmp.Patch_Margin = 4; 785 | var p = dmp.patch_fromText('@@ -21,4 +21,10 @@\n-jump\n+somersault\n')[0]; 786 | dmp.patch_addContext_(p, 'The quick brown fox jumps over the lazy dog.'); 787 | assertEquals('@@ -17,12 +17,18 @@\n fox \n-jump\n+somersault\n s ov\n', p.toString()); 788 | 789 | // Same, but not enough trailing context. 790 | p = dmp.patch_fromText('@@ -21,4 +21,10 @@\n-jump\n+somersault\n')[0]; 791 | dmp.patch_addContext_(p, 'The quick brown fox jumps.'); 792 | assertEquals('@@ -17,10 +17,16 @@\n fox \n-jump\n+somersault\n s.\n', p.toString()); 793 | 794 | // Same, but not enough leading context. 795 | p = dmp.patch_fromText('@@ -3 +3,2 @@\n-e\n+at\n')[0]; 796 | dmp.patch_addContext_(p, 'The quick brown fox jumps.'); 797 | assertEquals('@@ -1,7 +1,8 @@\n Th\n-e\n+at\n qui\n', p.toString()); 798 | 799 | // Same, but with ambiguity. 800 | p = dmp.patch_fromText('@@ -3 +3,2 @@\n-e\n+at\n')[0]; 801 | dmp.patch_addContext_(p, 'The quick brown fox jumps. The quick brown fox crashes.'); 802 | assertEquals('@@ -1,27 +1,28 @@\n Th\n-e\n+at\n quick brown fox jumps. \n', p.toString()); 803 | } 804 | 805 | function testPatchMake() { 806 | // Null case. 807 | var patches = dmp.patch_make('', ''); 808 | assertEquals('', dmp.patch_toText(patches)); 809 | 810 | var text1 = 'The quick brown fox jumps over the lazy dog.'; 811 | var text2 = 'That quick brown fox jumped over a lazy dog.'; 812 | // Text2+Text1 inputs. 813 | var expectedPatch = '@@ -1,8 +1,7 @@\n Th\n-at\n+e\n qui\n@@ -21,17 +21,18 @@\n jump\n-ed\n+s\n over \n-a\n+the\n laz\n'; 814 | // The second patch must be "-21,17 +21,18", not "-22,17 +21,18" due to rolling context. 815 | patches = dmp.patch_make(text2, text1); 816 | assertEquals(expectedPatch, dmp.patch_toText(patches)); 817 | 818 | // Text1+Text2 inputs. 819 | expectedPatch = '@@ -1,11 +1,12 @@\n Th\n-e\n+at\n quick b\n@@ -22,18 +22,17 @@\n jump\n-s\n+ed\n over \n-the\n+a\n laz\n'; 820 | patches = dmp.patch_make(text1, text2); 821 | assertEquals(expectedPatch, dmp.patch_toText(patches)); 822 | 823 | // Diff input. 824 | var diffs = dmp.diff_main(text1, text2, false); 825 | patches = dmp.patch_make(diffs); 826 | assertEquals(expectedPatch, dmp.patch_toText(patches)); 827 | 828 | // Text1+Diff inputs. 829 | patches = dmp.patch_make(text1, diffs); 830 | assertEquals(expectedPatch, dmp.patch_toText(patches)); 831 | 832 | // Text1+Text2+Diff inputs (deprecated). 833 | patches = dmp.patch_make(text1, text2, diffs); 834 | assertEquals(expectedPatch, dmp.patch_toText(patches)); 835 | 836 | // Character encoding. 837 | patches = dmp.patch_make('`1234567890-=[]\\;\',./', '~!@#$%^&*()_+{}|:"<>?'); 838 | assertEquals('@@ -1,21 +1,21 @@\n-%601234567890-=%5B%5D%5C;\',./\n+~!@#$%25%5E&*()_+%7B%7D%7C:%22%3C%3E?\n', dmp.patch_toText(patches)); 839 | 840 | // Character decoding. 841 | diffs = [[DIFF_DELETE, '`1234567890-=[]\\;\',./'], [DIFF_INSERT, '~!@#$%^&*()_+{}|:"<>?']]; 842 | assertEquivalent(diffs, dmp.patch_fromText('@@ -1,21 +1,21 @@\n-%601234567890-=%5B%5D%5C;\',./\n+~!@#$%25%5E&*()_+%7B%7D%7C:%22%3C%3E?\n')[0].diffs); 843 | 844 | // Long string with repeats. 845 | text1 = ''; 846 | for (var x = 0; x < 100; x++) { 847 | text1 += 'abcdef'; 848 | } 849 | text2 = text1 + '123'; 850 | expectedPatch = '@@ -573,28 +573,31 @@\n cdefabcdefabcdefabcdefabcdef\n+123\n'; 851 | patches = dmp.patch_make(text1, text2); 852 | assertEquals(expectedPatch, dmp.patch_toText(patches)); 853 | 854 | // Test null inputs. 855 | try { 856 | dmp.patch_make(null); 857 | assertEquals(Error, null); 858 | } catch (e) { 859 | // Exception expected. 860 | } 861 | } 862 | 863 | function testPatchSplitMax() { 864 | // Assumes that dmp.Match_MaxBits is 32. 865 | var patches = dmp.patch_make('abcdefghijklmnopqrstuvwxyz01234567890', 'XabXcdXefXghXijXklXmnXopXqrXstXuvXwxXyzX01X23X45X67X89X0'); 866 | dmp.patch_splitMax(patches); 867 | assertEquals('@@ -1,32 +1,46 @@\n+X\n ab\n+X\n cd\n+X\n ef\n+X\n gh\n+X\n ij\n+X\n kl\n+X\n mn\n+X\n op\n+X\n qr\n+X\n st\n+X\n uv\n+X\n wx\n+X\n yz\n+X\n 012345\n@@ -25,13 +39,18 @@\n zX01\n+X\n 23\n+X\n 45\n+X\n 67\n+X\n 89\n+X\n 0\n', dmp.patch_toText(patches)); 868 | 869 | patches = dmp.patch_make('abcdef1234567890123456789012345678901234567890123456789012345678901234567890uvwxyz', 'abcdefuvwxyz'); 870 | var oldToText = dmp.patch_toText(patches); 871 | dmp.patch_splitMax(patches); 872 | assertEquals(oldToText, dmp.patch_toText(patches)); 873 | 874 | patches = dmp.patch_make('1234567890123456789012345678901234567890123456789012345678901234567890', 'abc'); 875 | dmp.patch_splitMax(patches); 876 | assertEquals('@@ -1,32 +1,4 @@\n-1234567890123456789012345678\n 9012\n@@ -29,32 +1,4 @@\n-9012345678901234567890123456\n 7890\n@@ -57,14 +1,3 @@\n-78901234567890\n+abc\n', dmp.patch_toText(patches)); 877 | 878 | patches = dmp.patch_make('abcdefghij , h : 0 , t : 1 abcdefghij , h : 0 , t : 1 abcdefghij , h : 0 , t : 1', 'abcdefghij , h : 1 , t : 1 abcdefghij , h : 1 , t : 1 abcdefghij , h : 0 , t : 1'); 879 | dmp.patch_splitMax(patches); 880 | assertEquals('@@ -2,32 +2,32 @@\n bcdefghij , h : \n-0\n+1\n , t : 1 abcdef\n@@ -29,32 +29,32 @@\n bcdefghij , h : \n-0\n+1\n , t : 1 abcdef\n', dmp.patch_toText(patches)); 881 | } 882 | 883 | function testPatchAddPadding() { 884 | // Both edges full. 885 | var patches = dmp.patch_make('', 'test'); 886 | assertEquals('@@ -0,0 +1,4 @@\n+test\n', dmp.patch_toText(patches)); 887 | dmp.patch_addPadding(patches); 888 | assertEquals('@@ -1,8 +1,12 @@\n %01%02%03%04\n+test\n %01%02%03%04\n', dmp.patch_toText(patches)); 889 | 890 | // Both edges partial. 891 | patches = dmp.patch_make('XY', 'XtestY'); 892 | assertEquals('@@ -1,2 +1,6 @@\n X\n+test\n Y\n', dmp.patch_toText(patches)); 893 | dmp.patch_addPadding(patches); 894 | assertEquals('@@ -2,8 +2,12 @@\n %02%03%04X\n+test\n Y%01%02%03\n', dmp.patch_toText(patches)); 895 | 896 | // Both edges none. 897 | patches = dmp.patch_make('XXXXYYYY', 'XXXXtestYYYY'); 898 | assertEquals('@@ -1,8 +1,12 @@\n XXXX\n+test\n YYYY\n', dmp.patch_toText(patches)); 899 | dmp.patch_addPadding(patches); 900 | assertEquals('@@ -5,8 +5,12 @@\n XXXX\n+test\n YYYY\n', dmp.patch_toText(patches)); 901 | } 902 | 903 | function testPatchApply() { 904 | dmp.Match_Distance = 1000; 905 | dmp.Match_Threshold = 0.5; 906 | dmp.Patch_DeleteThreshold = 0.5; 907 | // Null case. 908 | var patches = dmp.patch_make('', ''); 909 | var results = dmp.patch_apply(patches, 'Hello world.'); 910 | assertEquivalent(['Hello world.', []], results); 911 | 912 | // Exact match. 913 | patches = dmp.patch_make('The quick brown fox jumps over the lazy dog.', 'That quick brown fox jumped over a lazy dog.'); 914 | results = dmp.patch_apply(patches, 'The quick brown fox jumps over the lazy dog.'); 915 | assertEquivalent(['That quick brown fox jumped over a lazy dog.', [true, true]], results); 916 | 917 | // Partial match. 918 | results = dmp.patch_apply(patches, 'The quick red rabbit jumps over the tired tiger.'); 919 | assertEquivalent(['That quick red rabbit jumped over a tired tiger.', [true, true]], results); 920 | 921 | // Failed match. 922 | results = dmp.patch_apply(patches, 'I am the very model of a modern major general.'); 923 | assertEquivalent(['I am the very model of a modern major general.', [false, false]], results); 924 | 925 | // Big delete, small change. 926 | patches = dmp.patch_make('x1234567890123456789012345678901234567890123456789012345678901234567890y', 'xabcy'); 927 | results = dmp.patch_apply(patches, 'x123456789012345678901234567890-----++++++++++-----123456789012345678901234567890y'); 928 | assertEquivalent(['xabcy', [true, true]], results); 929 | 930 | // Big delete, big change 1. 931 | patches = dmp.patch_make('x1234567890123456789012345678901234567890123456789012345678901234567890y', 'xabcy'); 932 | results = dmp.patch_apply(patches, 'x12345678901234567890---------------++++++++++---------------12345678901234567890y'); 933 | assertEquivalent(['xabc12345678901234567890---------------++++++++++---------------12345678901234567890y', [false, true]], results); 934 | 935 | // Big delete, big change 2. 936 | dmp.Patch_DeleteThreshold = 0.6; 937 | patches = dmp.patch_make('x1234567890123456789012345678901234567890123456789012345678901234567890y', 'xabcy'); 938 | results = dmp.patch_apply(patches, 'x12345678901234567890---------------++++++++++---------------12345678901234567890y'); 939 | assertEquivalent(['xabcy', [true, true]], results); 940 | dmp.Patch_DeleteThreshold = 0.5; 941 | 942 | // Compensate for failed patch. 943 | dmp.Match_Threshold = 0.0; 944 | dmp.Match_Distance = 0; 945 | patches = dmp.patch_make('abcdefghijklmnopqrstuvwxyz--------------------1234567890', 'abcXXXXXXXXXXdefghijklmnopqrstuvwxyz--------------------1234567YYYYYYYYYY890'); 946 | results = dmp.patch_apply(patches, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ--------------------1234567890'); 947 | assertEquivalent(['ABCDEFGHIJKLMNOPQRSTUVWXYZ--------------------1234567YYYYYYYYYY890', [false, true]], results); 948 | dmp.Match_Threshold = 0.5; 949 | dmp.Match_Distance = 1000; 950 | 951 | // No side effects. 952 | patches = dmp.patch_make('', 'test'); 953 | var patchstr = dmp.patch_toText(patches); 954 | dmp.patch_apply(patches, ''); 955 | assertEquals(patchstr, dmp.patch_toText(patches)); 956 | 957 | // No side effects with major delete. 958 | patches = dmp.patch_make('The quick brown fox jumps over the lazy dog.', 'Woof'); 959 | patchstr = dmp.patch_toText(patches); 960 | dmp.patch_apply(patches, 'The quick brown fox jumps over the lazy dog.'); 961 | assertEquals(patchstr, dmp.patch_toText(patches)); 962 | 963 | // Edge exact match. 964 | patches = dmp.patch_make('', 'test'); 965 | results = dmp.patch_apply(patches, ''); 966 | assertEquivalent(['test', [true]], results); 967 | 968 | // Near edge exact match. 969 | patches = dmp.patch_make('XY', 'XtestY'); 970 | results = dmp.patch_apply(patches, 'XY'); 971 | assertEquivalent(['XtestY', [true]], results); 972 | 973 | // Edge partial match. 974 | patches = dmp.patch_make('y', 'y123'); 975 | results = dmp.patch_apply(patches, 'x'); 976 | assertEquivalent(['x123', [true]], results); 977 | } 978 | 979 | 980 | /** 981 | * End of test harness, now run tests 982 | */ 983 | 984 | // If expected and actual are the identical, print 'Ok', otherwise 'Fail!' 985 | function assertEquals(msg, expected, actual) { 986 | if (typeof actual == 'undefined') { 987 | // msg is optional. 988 | actual = expected; 989 | expected = msg; 990 | msg = undefined; 991 | } 992 | assert.equal(actual, expected, msg); 993 | } 994 | 995 | function assertTrue(msg, actual) { 996 | if (typeof actual == 'undefined') { 997 | // msg is optional. 998 | actual = msg; 999 | assertEquals(true, actual); 1000 | } else { 1001 | assertEquals(msg, true, actual); 1002 | } 1003 | } 1004 | 1005 | function assertFalse(msg, actual) { 1006 | if (typeof actual == 'undefined') { 1007 | // msg is optional. 1008 | actual = msg; 1009 | assertEquals(false, actual); 1010 | } else { 1011 | assertEquals(msg, false, actual); 1012 | } 1013 | } 1014 | 1015 | var tests = [ 1016 | 'testDiffCommonPrefix', 1017 | 'testDiffCommonSuffix', 1018 | 'testDiffCommonOverlap', 1019 | 'testDiffHalfMatch', 1020 | 'testDiffLinesToChars', 1021 | 'testDiffCharsToLines', 1022 | 'testDiffCleanupMerge', 1023 | 'testDiffCleanupSemanticLossless', 1024 | 'testDiffCleanupSemantic', 1025 | 'testDiffCleanupEfficiency', 1026 | 'testDiffPrettyHtml', 1027 | 'testDiffText', 1028 | 'testDiffDelta', 1029 | 'testDiffXIndex', 1030 | 'testDiffLevenshtein', 1031 | 'testDiffBisect', 1032 | 'testDiffMain', 1033 | 1034 | 'testMatchAlphabet', 1035 | 'testMatchBitap', 1036 | 'testMatchMain', 1037 | 1038 | 'testPatchObj', 1039 | 'testPatchFromText', 1040 | 'testPatchToText', 1041 | 'testPatchAddContext', 1042 | 'testPatchMake', 1043 | 'testPatchSplitMax', 1044 | 'testPatchAddPadding', 1045 | 'testPatchApply']; 1046 | 1047 | 1048 | for (var x = 0; x < tests.length; x++) { 1049 | test(tests[x], eval(tests[x])) 1050 | } --------------------------------------------------------------------------------