comparison vendor/sebastian/diff/src/Differ.php @ 12:7a779792577d

Update Drupal core to v8.4.5 (via Composer)
author Chris Cannam
date Fri, 23 Feb 2018 15:52:07 +0000
parents 4c8ae668cc8c
children 1fec387a4317
comparison
equal deleted inserted replaced
11:bfffd8d7479a 12:7a779792577d
1 <?php 1 <?php
2 /* 2 /*
3 * This file is part of the Diff package. 3 * This file is part of sebastian/diff.
4 * 4 *
5 * (c) Sebastian Bergmann <sebastian@phpunit.de> 5 * (c) Sebastian Bergmann <sebastian@phpunit.de>
6 * 6 *
7 * For the full copyright and license information, please view the LICENSE 7 * For the full copyright and license information, please view the LICENSE
8 * file that was distributed with this source code. 8 * file that was distributed with this source code.
29 */ 29 */
30 private $showNonDiffLines; 30 private $showNonDiffLines;
31 31
32 /** 32 /**
33 * @param string $header 33 * @param string $header
34 * @param bool $showNonDiffLines
34 */ 35 */
35 public function __construct($header = "--- Original\n+++ New\n", $showNonDiffLines = true) 36 public function __construct($header = "--- Original\n+++ New\n", $showNonDiffLines = true)
36 { 37 {
37 $this->header = $header; 38 $this->header = $header;
38 $this->showNonDiffLines = $showNonDiffLines; 39 $this->showNonDiffLines = $showNonDiffLines;
47 * 48 *
48 * @return string 49 * @return string
49 */ 50 */
50 public function diff($from, $to, LongestCommonSubsequence $lcs = null) 51 public function diff($from, $to, LongestCommonSubsequence $lcs = null)
51 { 52 {
52 if (!is_array($from) && !is_string($from)) { 53 $from = $this->validateDiffInput($from);
53 $from = (string) $from; 54 $to = $this->validateDiffInput($to);
54 } 55 $diff = $this->diffToArray($from, $to, $lcs);
55 56 $old = $this->checkIfDiffInOld($diff);
56 if (!is_array($to) && !is_string($to)) { 57 $start = isset($old[0]) ? $old[0] : 0;
57 $to = (string) $to; 58 $end = \count($diff);
58 } 59
59 60 if ($tmp = \array_search($end, $old)) {
60 $buffer = $this->header; 61 $end = $tmp;
61 $diff = $this->diffToArray($from, $to, $lcs); 62 }
62 63
64 return $this->getBuffer($diff, $old, $start, $end);
65 }
66
67 /**
68 * Casts variable to string if it is not a string or array.
69 *
70 * @param mixed $input
71 *
72 * @return string
73 */
74 private function validateDiffInput($input)
75 {
76 if (!\is_array($input) && !\is_string($input)) {
77 return (string) $input;
78 }
79
80 return $input;
81 }
82
83 /**
84 * Takes input of the diff array and returns the old array.
85 * Iterates through diff line by line,
86 *
87 * @param array $diff
88 *
89 * @return array
90 */
91 private function checkIfDiffInOld(array $diff)
92 {
63 $inOld = false; 93 $inOld = false;
64 $i = 0; 94 $i = 0;
65 $old = array(); 95 $old = array();
66 96
67 foreach ($diff as $line) { 97 foreach ($diff as $line) {
68 if ($line[1] === 0 /* OLD */) { 98 if ($line[1] === 0 /* OLD */) {
69 if ($inOld === false) { 99 if ($inOld === false) {
70 $inOld = $i; 100 $inOld = $i;
71 } 101 }
72 } elseif ($inOld !== false) { 102 } elseif ($inOld !== false) {
73 if (($i - $inOld) > 5) { 103 if (($i - $inOld) > 5) {
78 } 108 }
79 109
80 ++$i; 110 ++$i;
81 } 111 }
82 112
83 $start = isset($old[0]) ? $old[0] : 0; 113 return $old;
84 $end = count($diff); 114 }
85 115
86 if ($tmp = array_search($end, $old)) { 116 /**
87 $end = $tmp; 117 * Generates buffer in string format, returning the patch.
88 } 118 *
89 119 * @param array $diff
90 $newChunk = true; 120 * @param array $old
121 * @param int $start
122 * @param int $end
123 *
124 * @return string
125 */
126 private function getBuffer(array $diff, array $old, $start, $end)
127 {
128 $buffer = $this->header;
129
130 if (!isset($old[$start])) {
131 $buffer = $this->getDiffBufferElementNew($diff, $buffer, $start);
132 ++$start;
133 }
91 134
92 for ($i = $start; $i < $end; $i++) { 135 for ($i = $start; $i < $end; $i++) {
93 if (isset($old[$i])) { 136 if (isset($old[$i])) {
94 $buffer .= "\n"; 137 $i = $old[$i];
95 $newChunk = true; 138 $buffer = $this->getDiffBufferElementNew($diff, $buffer, $i);
96 $i = $old[$i]; 139 } else {
97 } 140 $buffer = $this->getDiffBufferElement($diff, $buffer, $i);
98
99 if ($newChunk) {
100 if ($this->showNonDiffLines === true) {
101 $buffer .= "@@ @@\n";
102 }
103 $newChunk = false;
104 }
105
106 if ($diff[$i][1] === 1 /* ADDED */) {
107 $buffer .= '+' . $diff[$i][0] . "\n";
108 } elseif ($diff[$i][1] === 2 /* REMOVED */) {
109 $buffer .= '-' . $diff[$i][0] . "\n";
110 } elseif ($this->showNonDiffLines === true) {
111 $buffer .= ' ' . $diff[$i][0] . "\n";
112 } 141 }
113 } 142 }
114 143
115 return $buffer; 144 return $buffer;
116 } 145 }
117 146
118 /** 147 /**
148 * Gets individual buffer element.
149 *
150 * @param array $diff
151 * @param string $buffer
152 * @param int $diffIndex
153 *
154 * @return string
155 */
156 private function getDiffBufferElement(array $diff, $buffer, $diffIndex)
157 {
158 if ($diff[$diffIndex][1] === 1 /* ADDED */) {
159 $buffer .= '+' . $diff[$diffIndex][0] . "\n";
160 } elseif ($diff[$diffIndex][1] === 2 /* REMOVED */) {
161 $buffer .= '-' . $diff[$diffIndex][0] . "\n";
162 } elseif ($this->showNonDiffLines === true) {
163 $buffer .= ' ' . $diff[$diffIndex][0] . "\n";
164 }
165
166 return $buffer;
167 }
168
169 /**
170 * Gets individual buffer element with opening.
171 *
172 * @param array $diff
173 * @param string $buffer
174 * @param int $diffIndex
175 *
176 * @return string
177 */
178 private function getDiffBufferElementNew(array $diff, $buffer, $diffIndex)
179 {
180 if ($this->showNonDiffLines === true) {
181 $buffer .= "@@ @@\n";
182 }
183
184 return $this->getDiffBufferElement($diff, $buffer, $diffIndex);
185 }
186
187 /**
119 * Returns the diff between two arrays or strings as array. 188 * Returns the diff between two arrays or strings as array.
120 * 189 *
121 * Each array element contains two elements: 190 * Each array element contains two elements:
122 * - [0] => string $token 191 * - [0] => mixed $token
123 * - [1] => 2|1|0 192 * - [1] => 2|1|0
124 * 193 *
125 * - 2: REMOVED: $token was removed from $from 194 * - 2: REMOVED: $token was removed from $from
126 * - 1: ADDED: $token was added to $from 195 * - 1: ADDED: $token was added to $from
127 * - 0: OLD: $token is not changed in $to 196 * - 0: OLD: $token is not changed in $to
132 * 201 *
133 * @return array 202 * @return array
134 */ 203 */
135 public function diffToArray($from, $to, LongestCommonSubsequence $lcs = null) 204 public function diffToArray($from, $to, LongestCommonSubsequence $lcs = null)
136 { 205 {
137 preg_match_all('(\r\n|\r|\n)', $from, $fromMatches); 206 if (\is_string($from)) {
138 preg_match_all('(\r\n|\r|\n)', $to, $toMatches); 207 $fromMatches = $this->getNewLineMatches($from);
139 208 $from = $this->splitStringByLines($from);
140 if (is_string($from)) { 209 } elseif (\is_array($from)) {
141 $from = preg_split('(\r\n|\r|\n)', $from); 210 $fromMatches = array();
142 } 211 } else {
143 212 throw new \InvalidArgumentException('"from" must be an array or string.');
144 if (is_string($to)) { 213 }
145 $to = preg_split('(\r\n|\r|\n)', $to); 214
146 } 215 if (\is_string($to)) {
147 216 $toMatches = $this->getNewLineMatches($to);
148 $start = array(); 217 $to = $this->splitStringByLines($to);
149 $end = array(); 218 } elseif (\is_array($to)) {
150 $fromLength = count($from); 219 $toMatches = array();
151 $toLength = count($to); 220 } else {
152 $length = min($fromLength, $toLength); 221 throw new \InvalidArgumentException('"to" must be an array or string.');
153 222 }
154 for ($i = 0; $i < $length; ++$i) { 223
155 if ($from[$i] === $to[$i]) { 224 list($from, $to, $start, $end) = self::getArrayDiffParted($from, $to);
156 $start[] = $from[$i];
157 unset($from[$i], $to[$i]);
158 } else {
159 break;
160 }
161 }
162
163 $length -= $i;
164
165 for ($i = 1; $i < $length; ++$i) {
166 if ($from[$fromLength - $i] === $to[$toLength - $i]) {
167 array_unshift($end, $from[$fromLength - $i]);
168 unset($from[$fromLength - $i], $to[$toLength - $i]);
169 } else {
170 break;
171 }
172 }
173 225
174 if ($lcs === null) { 226 if ($lcs === null) {
175 $lcs = $this->selectLcsImplementation($from, $to); 227 $lcs = $this->selectLcsImplementation($from, $to);
176 } 228 }
177 229
178 $common = $lcs->calculate(array_values($from), array_values($to)); 230 $common = $lcs->calculate(\array_values($from), \array_values($to));
179 $diff = array(); 231 $diff = array();
180 232
181 if (isset($fromMatches[0]) && $toMatches[0] && 233 if ($this->detectUnmatchedLineEndings($fromMatches, $toMatches)) {
182 count($fromMatches[0]) === count($toMatches[0]) &&
183 $fromMatches[0] !== $toMatches[0]) {
184 $diff[] = array( 234 $diff[] = array(
185 '#Warning: Strings contain different line endings!', 0 235 '#Warning: Strings contain different line endings!',
236 0
186 ); 237 );
187 } 238 }
188 239
189 foreach ($start as $token) { 240 foreach ($start as $token) {
190 $diff[] = array($token, 0 /* OLD */); 241 $diff[] = array($token, 0 /* OLD */);
191 } 242 }
192 243
193 reset($from); 244 \reset($from);
194 reset($to); 245 \reset($to);
195 246
196 foreach ($common as $token) { 247 foreach ($common as $token) {
197 while ((($fromToken = reset($from)) !== $token)) { 248 while (($fromToken = \reset($from)) !== $token) {
198 $diff[] = array(array_shift($from), 2 /* REMOVED */); 249 $diff[] = array(\array_shift($from), 2 /* REMOVED */);
199 } 250 }
200 251
201 while ((($toToken = reset($to)) !== $token)) { 252 while (($toToken = \reset($to)) !== $token) {
202 $diff[] = array(array_shift($to), 1 /* ADDED */); 253 $diff[] = array(\array_shift($to), 1 /* ADDED */);
203 } 254 }
204 255
205 $diff[] = array($token, 0 /* OLD */); 256 $diff[] = array($token, 0 /* OLD */);
206 257
207 array_shift($from); 258 \array_shift($from);
208 array_shift($to); 259 \array_shift($to);
209 } 260 }
210 261
211 while (($token = array_shift($from)) !== null) { 262 while (($token = \array_shift($from)) !== null) {
212 $diff[] = array($token, 2 /* REMOVED */); 263 $diff[] = array($token, 2 /* REMOVED */);
213 } 264 }
214 265
215 while (($token = array_shift($to)) !== null) { 266 while (($token = \array_shift($to)) !== null) {
216 $diff[] = array($token, 1 /* ADDED */); 267 $diff[] = array($token, 1 /* ADDED */);
217 } 268 }
218 269
219 foreach ($end as $token) { 270 foreach ($end as $token) {
220 $diff[] = array($token, 0 /* OLD */); 271 $diff[] = array($token, 0 /* OLD */);
221 } 272 }
222 273
223 return $diff; 274 return $diff;
275 }
276
277 /**
278 * Get new strings denoting new lines from a given string.
279 *
280 * @param string $string
281 *
282 * @return array
283 */
284 private function getNewLineMatches($string)
285 {
286 \preg_match_all('(\r\n|\r|\n)', $string, $stringMatches);
287
288 return $stringMatches;
289 }
290
291 /**
292 * Checks if input is string, if so it will split it line-by-line.
293 *
294 * @param string $input
295 *
296 * @return array
297 */
298 private function splitStringByLines($input)
299 {
300 return \preg_split('(\r\n|\r|\n)', $input);
224 } 301 }
225 302
226 /** 303 /**
227 * @param array $from 304 * @param array $from
228 * @param array $to 305 * @param array $to
248 * Calculates the estimated memory footprint for the DP-based method. 325 * Calculates the estimated memory footprint for the DP-based method.
249 * 326 *
250 * @param array $from 327 * @param array $from
251 * @param array $to 328 * @param array $to
252 * 329 *
253 * @return int 330 * @return int|float
254 */ 331 */
255 private function calculateEstimatedFootprint(array $from, array $to) 332 private function calculateEstimatedFootprint(array $from, array $to)
256 { 333 {
257 $itemSize = PHP_INT_SIZE == 4 ? 76 : 144; 334 $itemSize = PHP_INT_SIZE === 4 ? 76 : 144;
258 335
259 return $itemSize * pow(min(count($from), count($to)), 2); 336 return $itemSize * \pow(\min(\count($from), \count($to)), 2);
337 }
338
339 /**
340 * Returns true if line ends don't match on fromMatches and toMatches.
341 *
342 * @param array $fromMatches
343 * @param array $toMatches
344 *
345 * @return bool
346 */
347 private function detectUnmatchedLineEndings(array $fromMatches, array $toMatches)
348 {
349 return isset($fromMatches[0], $toMatches[0]) &&
350 \count($fromMatches[0]) === \count($toMatches[0]) &&
351 $fromMatches[0] !== $toMatches[0];
352 }
353
354 /**
355 * @param array $from
356 * @param array $to
357 *
358 * @return array
359 */
360 private static function getArrayDiffParted(array &$from, array &$to)
361 {
362 $start = array();
363 $end = array();
364
365 \reset($to);
366
367 foreach ($from as $k => $v) {
368 $toK = \key($to);
369
370 if ($toK === $k && $v === $to[$k]) {
371 $start[$k] = $v;
372
373 unset($from[$k], $to[$k]);
374 } else {
375 break;
376 }
377 }
378
379 \end($from);
380 \end($to);
381
382 do {
383 $fromK = \key($from);
384 $toK = \key($to);
385
386 if (null === $fromK || null === $toK || \current($from) !== \current($to)) {
387 break;
388 }
389
390 \prev($from);
391 \prev($to);
392
393 $end = array($fromK => $from[$fromK]) + $end;
394 unset($from[$fromK], $to[$toK]);
395 } while (true);
396
397 return array($from, $to, $start, $end);
260 } 398 }
261 } 399 }