diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:23:16 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-14 13:23:16 +0000 |
commit | 3e97c51418e6d27e9a81906f347fcb7c78e27d4f (patch) | |
tree | ee596ce1bc9840661386f96f9b8d1f919a106317 /vendor/gipfl/diff | |
parent | Initial commit. (diff) | |
download | icingaweb2-module-incubator-3e97c51418e6d27e9a81906f347fcb7c78e27d4f.tar.xz icingaweb2-module-incubator-3e97c51418e6d27e9a81906f347fcb7c78e27d4f.zip |
Adding upstream version 0.20.0.upstream/0.20.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'vendor/gipfl/diff')
-rw-r--r-- | vendor/gipfl/diff/LICENSE | 21 | ||||
-rw-r--r-- | vendor/gipfl/diff/composer.json | 25 | ||||
-rw-r--r-- | vendor/gipfl/diff/public/css/diff.less | 133 | ||||
-rw-r--r-- | vendor/gipfl/diff/src/HtmlRenderer/InlineDiff.php | 10 | ||||
-rw-r--r-- | vendor/gipfl/diff/src/HtmlRenderer/SideBySideDiff.php | 10 | ||||
-rw-r--r-- | vendor/gipfl/diff/src/PhpDiff.php | 147 | ||||
-rw-r--r-- | vendor/gipfl/diff/src/PhpDiff/ArrayHelper.php | 54 | ||||
-rw-r--r-- | vendor/gipfl/diff/src/PhpDiff/OpCodeHelper.php | 144 | ||||
-rw-r--r-- | vendor/gipfl/diff/src/PhpDiff/Ratio.php | 139 | ||||
-rw-r--r-- | vendor/gipfl/diff/src/PhpDiff/Renderer/AbstractRenderer.php | 44 | ||||
-rw-r--r-- | vendor/gipfl/diff/src/PhpDiff/Renderer/Html/ArrayRenderer.php | 207 | ||||
-rw-r--r-- | vendor/gipfl/diff/src/PhpDiff/Renderer/Html/Inline.php | 104 | ||||
-rw-r--r-- | vendor/gipfl/diff/src/PhpDiff/Renderer/Html/SideBySide.php | 121 | ||||
-rw-r--r-- | vendor/gipfl/diff/src/PhpDiff/Renderer/Text/Context.php | 98 | ||||
-rw-r--r-- | vendor/gipfl/diff/src/PhpDiff/Renderer/Text/Unified.php | 52 | ||||
-rw-r--r-- | vendor/gipfl/diff/src/PhpDiff/SequenceMatcher.php | 438 |
16 files changed, 1747 insertions, 0 deletions
diff --git a/vendor/gipfl/diff/LICENSE b/vendor/gipfl/diff/LICENSE new file mode 100644 index 0000000..dd88e09 --- /dev/null +++ b/vendor/gipfl/diff/LICENSE @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2018 Thomas Gelf + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/gipfl/diff/composer.json b/vendor/gipfl/diff/composer.json new file mode 100644 index 0000000..05c12a7 --- /dev/null +++ b/vendor/gipfl/diff/composer.json @@ -0,0 +1,25 @@ +{ + "name": "gipfl/diff", + "description": "php-diff wrapper supporting ipl/html", + "type": "library", + "license": "MIT", + "authors": [ + { + "name": "Thomas Gelf", + "email": "thomas@gelf.net" + } + ], + "config": { + "sort-packages": true + }, + "autoload": { + "psr-4": { + "gipfl\\Diff\\": "src" + } + }, + "require": { + "php": ">=5.6.0", + "ext-mbstring": "*", + "ipl/html": ">=0.2" + } +} diff --git a/vendor/gipfl/diff/public/css/diff.less b/vendor/gipfl/diff/public/css/diff.less new file mode 100644 index 0000000..1dc2ef6 --- /dev/null +++ b/vendor/gipfl/diff/public/css/diff.less @@ -0,0 +1,133 @@ +@color-diff-ins: @color-ok; +@color-diff-del: @color-critical; +@color-diff-changed-old: fade(@color-critical, 30%); +@color-diff-changed-new: fade(@color-ok, 30%); +@color-diff-green-shade-light: #dfd; +@color-diff-pale-green: #9e9; +@color-diff-text-on-diff: #051030; +@color-change-replace-del: #e99; + +.Differences { + width: 100%; + table-layout: fixed; + empty-cells: show; +} + +.Differences thead { + display: none; +} + +.Differences thead th { + text-align: left; + padding-left: 4 / 14 * 16em; +} + +.Differences tbody th { + text-align: right; + width: 4em; + padding: 1px 2px; + border-right: 1px solid @gray-light; + background: @gray-lightest; + font-weight: normal; + vertical-align: top; +} + +.Differences tbody td { + color: @text-color; + width: 50%; + .preformatted(); + word-break: break-all; +} + +.DifferencesSideBySide { + ins, del { + text-decoration: none; + } + + .ChangeInsert { + td.Left { + background: @gray-lighter; + } + td.Right { + background: @color-diff-changed-new; + color: @color-diff-text-on-diff; + } + } + + .ChangeDelete { + td.Left { + background: @color-diff-changed-old; + color: @color-diff-text-on-diff; + } + td.Right { + background: @gray-lighter; + } + } + + .ChangeReplace { + td.Left { + background: @color-diff-changed-old; + color: @color-diff-text-on-diff; + del { + background: @color-diff-del; + } + } + + td.Right { + background: @color-diff-changed-new; + color: @color-diff-text-on-diff; + ins { + background: @color-diff-ins; + } + } + + } +} + +.Differences .Skipped { + background: @gray-lightest; +} + +.DifferencesInline .ChangeReplace .Left, +.DifferencesInline .ChangeDelete .Left { + background: @color-diff-changed-old; +} + +.DifferencesInline .ChangeReplace .Right, +.DifferencesInline .ChangeInsert .Right { + background: @color-diff-green-shade-light; +} + +.DifferencesInline .ChangeReplace ins { + background: @color-diff-pale-green; +} + +.DifferencesInline .ChangeReplace del { + background: @color-change-replace-del; +} + +.DifferencesInline { + tr td:last-child { + width: 90%; + } + tr th:first-child { + width: 5%; + } + tr th:nth-child(2) { + width: 5%; + } + ins, del { + text-decoration: none; + } +} + +#layout.compact-layout, #layout.default-layout { + &.twocols table.Differences { + th { + font-size: 0.75em; + } + td { + font-size: 0.916em; + } + } +} diff --git a/vendor/gipfl/diff/src/HtmlRenderer/InlineDiff.php b/vendor/gipfl/diff/src/HtmlRenderer/InlineDiff.php new file mode 100644 index 0000000..5df8bc0 --- /dev/null +++ b/vendor/gipfl/diff/src/HtmlRenderer/InlineDiff.php @@ -0,0 +1,10 @@ +<?php + +namespace gipfl\Diff\HtmlRenderer; + +use gipfl\Diff\PhpDiff\Renderer\Html\Inline; +use ipl\Html\ValidHtml; + +class InlineDiff extends Inline implements ValidHtml +{ +} diff --git a/vendor/gipfl/diff/src/HtmlRenderer/SideBySideDiff.php b/vendor/gipfl/diff/src/HtmlRenderer/SideBySideDiff.php new file mode 100644 index 0000000..e2ac5b2 --- /dev/null +++ b/vendor/gipfl/diff/src/HtmlRenderer/SideBySideDiff.php @@ -0,0 +1,10 @@ +<?php + +namespace gipfl\Diff\HtmlRenderer; + +use gipfl\Diff\PhpDiff\Renderer\Html\SideBySide; +use ipl\Html\ValidHtml; + +class SideBySideDiff extends SideBySide implements ValidHtml +{ +} diff --git a/vendor/gipfl/diff/src/PhpDiff.php b/vendor/gipfl/diff/src/PhpDiff.php new file mode 100644 index 0000000..da2cb1f --- /dev/null +++ b/vendor/gipfl/diff/src/PhpDiff.php @@ -0,0 +1,147 @@ +<?php + +namespace gipfl\Diff; + +use gipfl\Diff\PhpDiff\OpCodeHelper; +use gipfl\Diff\PhpDiff\SequenceMatcher; + +class PhpDiff +{ + /** @var array The "old" sequence to use as the basis for the comparison */ + private $left; + + /** @var array The "new" sequence to generate the changes for */ + private $right; + + /** @var array contains the generated opcodes for the differences between the two items */ + private $groupedCodes; + + /** + * @var array Associative array of the default options available for the diff class and their default value. + */ + private $defaultOptions = [ + 'context' => 3, + 'ignoreNewLines' => false, + 'ignoreWhitespace' => false, + 'ignoreCase' => false + ]; + + /** + * @var array Array of the options that have been applied for generating the diff. + */ + private $options; + + /** + * $left and $right can be strings, arrays of lines, null or any object that + * can be casted to a string + * + * @param mixed $left Left hand (old) side of the comparison + * @param mixed $right Right hand (new) side of the comparison + * @param array $options see $defaultOptions for possible settings + */ + public function __construct($left, $right, array $options = []) + { + $this->setLeftLines($this->wantArray($left)); + $this->setRightLines($this->wantArray($right)); + $this->options = array_merge($this->defaultOptions, $options); + } + + /** + * Get a range of lines from $start to $end from the first comparison string + * and return them as an array. If no values are supplied, the entire string + * is returned. It's also possible to specify just one line to return only + * that line. + * + * @param int $start The starting number. + * @param int $end The ending number. If not supplied, only the item in $start will be returned. + * @return array Array of all of the lines between the specified range. + */ + public function getLeft($start = 0, $end = null) + { + if ($start === 0 && $end === null) { + return $this->left; + } + + if ($end === null) { + $length = 1; + } else { + $length = $end - $start; + } + + return array_slice($this->left, $start, $length); + } + + /** + * Get a range of lines from $start to $end from the second comparison string + * and return them as an array. If no values are supplied, the entire string + * is returned. It's also possible to specify just one line to return only + * that line. + * + * @param int $start The starting number. + * @param int $end The ending number. If not supplied, only the item in $start will be returned. + * @return array Array of all of the lines between the specified range. + */ + public function getRight($start = 0, $end = null) + { + if ($start === 0 && $end === null) { + return $this->right; + } + + if ($end === null) { + $length = 1; + } else { + $length = $end - $start; + } + + return array_slice($this->right, $start, $length); + } + + /** + * Generate a list of the compiled and grouped opcodes for the differences between the + * two strings. Generally called by the renderer, this class instantiates the sequence + * matcher and performs the actual diff generation and return an array of the opcodes + * for it. Once generated, the results are cached in the diff class instance. + * + * @return array Array of the grouped opcodes for the generated diff. + */ + public function getGroupedOpcodes() + { + if ($this->groupedCodes === null) { + $this->groupedCodes = $this->fetchGroupedOpCodes(); + } + + return $this->groupedCodes; + } + + protected function fetchGroupedOpCodes() + { + $matcher = new SequenceMatcher($this->left, $this->right, null, $this->options); + return OpCodeHelper::getGroupedOpcodes( + $matcher->getOpcodes(), + $this->options['context'] + ); + } + + protected function wantArray($value) + { + if (empty($value)) { + return []; + } + if (! is_array($value)) { + return explode("\n", (string) $value); + } + + return $value; + } + + protected function setLeftLines(array $lines) + { + $this->left = $lines; + } + + protected function setRightLines(array $lines) + { + $this->right = $lines; + } + +} diff --git a/vendor/gipfl/diff/src/PhpDiff/ArrayHelper.php b/vendor/gipfl/diff/src/PhpDiff/ArrayHelper.php new file mode 100644 index 0000000..2c57c8d --- /dev/null +++ b/vendor/gipfl/diff/src/PhpDiff/ArrayHelper.php @@ -0,0 +1,54 @@ +<?php + +namespace gipfl\Diff\PhpDiff; + +abstract class ArrayHelper +{ + /** + * Helper function that provides the ability to return the value for a key + * in an array of it exists, or if it doesn't then return a default value. + * Essentially cleaner than doing a series of if(isset()) {} else {} calls. + * + * @param array $array The array to search. + * @param string $key The key to check that exists. + * @param mixed $default The value to return as the default value if the key doesn't exist. + * @return mixed The value from the array if the key exists or otherwise the default. + */ + public static function getPropertyOrDefault($array, $key, $default) + { + if (isset($array[$key])) { + return $array[$key]; + } + + return $default; + } + + /** + * Sort an array by the nested arrays it contains. Helper function for getMatchingBlocks + * + * @param array $a First array to compare. + * @param array $b Second array to compare. + * @return int -1, 0 or 1, as expected by the usort function. + */ + public static function tupleSort($a, $b) + { + $max = max(count($a), count($b)); + for ($i = 0; $i < $max; ++$i) { + if ($a[$i] < $b[$i]) { + return -1; + } + if ($a[$i] > $b[$i]) { + return 1; + } + } + + if (count($a) === count($b)) { + return 0; + } + if (count($a) < count($b)) { + return -1; + } + + return 1; + } +} diff --git a/vendor/gipfl/diff/src/PhpDiff/OpCodeHelper.php b/vendor/gipfl/diff/src/PhpDiff/OpCodeHelper.php new file mode 100644 index 0000000..7b12b1e --- /dev/null +++ b/vendor/gipfl/diff/src/PhpDiff/OpCodeHelper.php @@ -0,0 +1,144 @@ +<?php + +namespace gipfl\Diff\PhpDiff; + +use function count; +use function max; +use function min; + +abstract class OpCodeHelper +{ + /** + * Return a list of all of the opcodes for the differences between the + * two strings. + * + * The nested array returned contains an array describing the opcode + * which includes: + * 0 - The type of tag (as described below) for the opcode. + * 1 - The beginning line in the first sequence. + * 2 - The end line in the first sequence. + * 3 - The beginning line in the second sequence. + * 4 - The end line in the second sequence. + * + * The different types of tags include: + * replace - The string from $i1 to $i2 in $a should be replaced by + * the string in $b from $j1 to $j2. + * delete - The string in $a from $i1 to $j2 should be deleted. + * insert - The string in $b from $j1 to $j2 should be inserted at + * $i1 in $a. + * equal - The two strings with the specified ranges are equal. + * + * @param array $blocks + * @return array Array of the opcodes describing the differences between the strings. + */ + public static function calculateOpCodes(array $blocks) + { + $lastLeftEnd = 0; + $lastRightEnd = 0; + $opCodes = []; + + foreach ($blocks as list($beginLeft, $beginRight, $cntLines)) { + $tag = null; + if ($lastLeftEnd < $beginLeft) { + if ($lastRightEnd < $beginRight) { + $tag = 'replace'; + } else { + $tag = 'delete'; + } + } elseif ($lastRightEnd < $beginRight) { + $tag = 'insert'; + } + + if ($tag) { + $opCodes[] = [$tag, $lastLeftEnd, $beginLeft, $lastRightEnd, $beginRight]; + } + + $lastLeftEnd = $beginLeft + $cntLines; + $lastRightEnd = $beginRight + $cntLines; + + if ($cntLines) { + $opCodes[] = ['equal', $beginLeft, $lastLeftEnd, $beginRight, $lastRightEnd]; + } + } + + return $opCodes; + } + + /** + * Return a series of nested arrays containing different groups of generated + * opcodes for the differences between the strings with up to $context lines + * of surrounding content. + * + * Essentially what happens here is any big equal blocks of strings are stripped + * out, the smaller subsets of changes are then arranged in to their groups. + * This means that the sequence matcher and diffs do not need to include the full + * content of the different files but can still provide context as to where the + * changes are. + * + * @param array $opCodes + * @param int $context The number of lines of context to provide around the groups. + * @return array Nested array of all of the grouped opcodes. + */ + public static function getGroupedOpcodes(array $opCodes, $context = 3) + { + if (empty($opCodes)) { + $opCodes = [ + ['equal', 0, 1, 0, 1] + ]; + } + + if ($opCodes[0][0] === 'equal') { + $opCodes[0] = [ + $opCodes[0][0], + max($opCodes[0][1], $opCodes[0][2] - $context), + $opCodes[0][2], + max($opCodes[0][3], $opCodes[0][4] - $context), + $opCodes[0][4] + ]; + } + + $lastItem = count($opCodes) - 1; + if ($opCodes[$lastItem][0] === 'equal') { + list($tag, $beginLeft, $endLeft, $beginRight, $endRight) = $opCodes[$lastItem]; + $opCodes[$lastItem] = [ + $tag, + $beginLeft, + min($endLeft, $beginLeft + $context), + $beginRight, + min($endRight, $beginRight + $context) + ]; + } + /* + public $type; + public $beginLeft; + public $endLeft; + public $beginRight; + public $endRight; + */ + $maxRange = $context * 2; + $groups = []; + $group = []; + foreach ($opCodes as list($tag, $beginLeft, $endLeft, $beginRight, $endRight)) { + if ($tag === 'equal' && $endLeft - $beginLeft > $maxRange) { + $group[] = [ + $tag, + $beginLeft, + min($endLeft, $beginLeft + $context), + $beginRight, + min($endRight, $beginRight + $context) + ]; + $groups[] = $group; + $group = []; + $beginLeft = max($beginLeft, $endLeft - $context); + $beginRight = max($beginRight, $endRight - $context); + } + $group[] = [$tag, $beginLeft, $endLeft, $beginRight, $endRight]; + } + + if (!empty($group) && !(count($group) === 1 && $group[0][0] === 'equal')) { + $groups[] = $group; + } + + return $groups; + } +} diff --git a/vendor/gipfl/diff/src/PhpDiff/Ratio.php b/vendor/gipfl/diff/src/PhpDiff/Ratio.php new file mode 100644 index 0000000..4db1460 --- /dev/null +++ b/vendor/gipfl/diff/src/PhpDiff/Ratio.php @@ -0,0 +1,139 @@ +<?php + +namespace gipfl\Diff\PhpDiff; + +use function count; + +class Ratio +{ + /** + * @var SequenceMatcher + */ + private $matcher; + + /** @var float */ + private $ratio; + + /** @var array */ + private $a; + + /** @var array */ + private $b; + + /** @var array */ + private $fullBCount; + + public function __construct(SequenceMatcher $matcher) + { + $this->matcher = $matcher; + $this->a = $matcher->getLeftSequence(); + $this->b = $matcher->getRightSequence(); + } + + /** + * Return a measure of the similarity between the two sequences. + * This will be a float value between 0 and 1. + * + * Out of all of the ratio calculation functions, this is the most + * expensive to call if getMatchingBlocks or getOpCodes is yet to be + * called. The other calculation methods (quickRatio and realquickRatio) + * can be used to perform quicker calculations but may be less accurate. + * + * The ratio is calculated as (2 * number of matches) / total number of + * elements in both sequences. + * + * @return float The calculated ratio. + */ + public function getRatio() + { + if ($this->ratio === null) { + $matcher = $this->matcher; + $matches = array_reduce($matcher->getMatchingBlocks(), [$this, 'ratioReduce'], 0); + $this->ratio = $this->calculateRatio( + $matches, + count($this->a) + count($this->b) + ); + } + + return $this->ratio; + } + + /** + * Helper function to calculate the number of matches for Ratio(). + * + * @param int $sum The running total for the number of matches. + * @param array $triple Array containing the matching block triple to add to the running total. + * @return int The new running total for the number of matches. + */ + private function ratioReduce($sum, $triple) + { + return $sum + ($triple[count($triple) - 1]); + } + + /** + * Quickly return an upper bound ratio for the similarity of the strings. + * This is quicker to compute than Ratio(). + * + * @return float The calculated ratio. + */ + private function quickRatio() + { + $aLength = count($this->a); + $bLength = count($this->b); + if ($this->fullBCount === null) { + $this->fullBCount = []; + for ($i = 0; $i < $bLength; ++$i) { + $char = $this->b[$i]; + $this->fullBCount[$char] = ArrayHelper::getPropertyOrDefault($this->fullBCount, $char, 0) + 1; + } + } + + $avail = array(); + $matches = 0; + for ($i = 0; $i < $aLength; ++$i) { + $char = $this->a[$i]; + if (isset($avail[$char])) { + $numb = $avail[$char]; + } else { + $numb = ArrayHelper::getPropertyOrDefault($this->fullBCount, $char, 0); + } + $avail[$char] = $numb - 1; + if ($numb > 0) { + ++$matches; + } + } + + $this->calculateRatio($matches, $aLength + $bLength); + } + + /** + * Return an upper bound ratio really quickly for the similarity of the strings. + * This is quicker to compute than Ratio() and quickRatio(). + * + * @return float The calculated ratio. + */ + private function realquickRatio() + { + $aLength = count($this->a); + $bLength = count($this->b); + + return $this->calculateRatio(min($aLength, $bLength), $aLength + $bLength); + } + + /** + * Helper function for calculating the ratio to measure similarity for the strings. + * The ratio is defined as being 2 * (number of matches / total length) + * + * @param int $matches The number of matches in the two strings. + * @param int $length The length of the two strings. + * @return float The calculated ratio. + */ + private function calculateRatio($matches, $length = 0) + { + if ($length) { + return 2 * ($matches / $length); + } + + return 1; + } +} diff --git a/vendor/gipfl/diff/src/PhpDiff/Renderer/AbstractRenderer.php b/vendor/gipfl/diff/src/PhpDiff/Renderer/AbstractRenderer.php new file mode 100644 index 0000000..fc93d8d --- /dev/null +++ b/vendor/gipfl/diff/src/PhpDiff/Renderer/AbstractRenderer.php @@ -0,0 +1,44 @@ +<?php + +namespace gipfl\Diff\PhpDiff\Renderer; + +use gipfl\Diff\PhpDiff; + +/** + * Abstract class for diff renderers in PHP DiffLib. + */ +abstract class AbstractRenderer +{ + /** @var PhpDiff */ + public $diff; + + /** @var array default options that apply to this renderer */ + protected $defaultOptions = []; + + /** @var array merged (user applied and default) options for the renderer */ + protected $options = []; + + /** + * @param PhpDiff $diff + * @param array $options Optionally, an array of the options for the renderer. + */ + public function __construct(PhpDiff $diff, array $options = []) + { + $this->diff = $diff; + $this->setOptions($options); + } + + /** + * Set the options of the renderer to those supplied in the passed in array. + * Options are merged with the default to ensure that there aren't any missing + * options. + * + * @param array $options + */ + public function setOptions(array $options) + { + $this->options = array_merge($this->defaultOptions, $options); + } + + abstract public function render(); +} diff --git a/vendor/gipfl/diff/src/PhpDiff/Renderer/Html/ArrayRenderer.php b/vendor/gipfl/diff/src/PhpDiff/Renderer/Html/ArrayRenderer.php new file mode 100644 index 0000000..57f6cb4 --- /dev/null +++ b/vendor/gipfl/diff/src/PhpDiff/Renderer/Html/ArrayRenderer.php @@ -0,0 +1,207 @@ +<?php + +namespace gipfl\Diff\PhpDiff\Renderer\Html; + +use gipfl\Diff\PhpDiff\Renderer\AbstractRenderer; + +/** + * Base renderer for rendering HTML based diffs for PHP DiffLib. + */ +class ArrayRenderer extends AbstractRenderer +{ + /** @var array default options */ + protected $defaultOptions = [ + 'tabSize' => 4 + ]; + + /** + * Render and return an array structure suitable for generating HTML + * based differences. Generally called by subclasses that generate a + * HTML based diff and return an array of the changes to show in the diff. + * + * @return array An array of the generated chances, suitable for presentation in HTML. + */ + public function render() + { + // As we'll be modifying a & b to include our change markers, + // we need to get the contents and store them here. That way + // we're not going to destroy the original data + $a = $this->diff->getLeft(); + $b = $this->diff->getRight(); + + $changes = []; + foreach ($this->diff->getGroupedOpcodes() as $group) { + $changes[] = $this->renderOpcodesGroup($group, $a, $b); + } + return $changes; + } + + protected function insertLineMarkers($line, $start, $end) + { + $last = $end + mb_strlen($line); + + return mb_substr($line, 0, $start) + . "\0" + . mb_substr($line, $start, $last - $start) + . "\1" + . mb_substr($line, $last); + } + + /** + * @param $group + * @param array $a + * @param array $b + * @return array + */ + protected function renderOpcodesGroup($group, array $a, array $b) + { + $blocks = []; + $lastTag = null; + $lastBlock = 0; + foreach ($group as list($tag, $i1, $i2, $j1, $j2)) { + if ($tag === 'replace' && $i2 - $i1 === $j2 - $j1) { + for ($i = 0; $i < ($i2 - $i1); ++$i) { + $fromLine = $a[$i1 + $i]; + $toLine = $b[$j1 + $i]; + + list($start, $end) = $this->getChangeExtent($fromLine, $toLine); + if ($start !== 0 || $end !== 0) { + $a[$i1 + $i] = $this->insertLineMarkers($fromLine, $start, $end); + $b[$j1 + $i] = $this->insertLineMarkers($toLine, $start, $end); + } + } + } + + if ($tag !== $lastTag) { + $blocks[] = [ + 'tag' => $tag, + 'base' => [ + 'offset' => $i1, + 'lines' => [] + ], + 'changed' => [ + 'offset' => $j1, + 'lines' => [] + ] + ]; + $lastBlock = count($blocks) - 1; + } + + $lastTag = $tag; + + if ($tag === 'equal') { + $lines = array_slice($a, $i1, ($i2 - $i1)); + $blocks[$lastBlock]['base']['lines'] += $this->formatLines($lines); + $lines = array_slice($b, $j1, ($j2 - $j1)); + $blocks[$lastBlock]['changed']['lines'] += $this->formatLines($lines); + } else { + if ($tag === 'replace' || $tag === 'delete') { + $lines = array_slice($a, $i1, ($i2 - $i1)); + $lines = $this->formatLines($lines); + $lines = str_replace(array("\0", "\1"), array('<del>', '</del>'), $lines); + $blocks[$lastBlock]['base']['lines'] += $lines; + } + + if ($tag === 'replace' || $tag === 'insert') { + $lines = array_slice($b, $j1, ($j2 - $j1)); + $lines = $this->formatLines($lines); + $lines = str_replace(array("\0", "\1"), array('<ins>', '</ins>'), $lines); + $blocks[$lastBlock]['changed']['lines'] += $lines; + } + } + } + + return $blocks; + } + + /** + * Given two strings, determine where the changes in the two strings + * begin, and where the changes in the two strings end. + * + * @param string $fromLine The first string. + * @param string $toLine The second string. + * @return array Array containing the starting position (0 by default) and the ending position (-1 by default) + */ + private function getChangeExtent($fromLine, $toLine) + { + $start = 0; + $limit = min(strlen($fromLine), strlen($toLine)); + while ($start < $limit && $fromLine[$start] === $toLine[$start]) { + ++$start; + } + $end = -1; + $limit -= $start; + /** @noinspection SubStrUsedAsArrayAccessInspection $end is negative, array index needs PHP >= 7 */ + while (-$end <= $limit && substr($fromLine, $end, 1) === substr($toLine, $end, 1)) { + --$end; + } + return [ + $start, + $end + 1 + ]; + } + + /** + * Format a series of lines suitable for output in a HTML rendered diff. + * This involves replacing tab characters with spaces, making the HTML safe + * for output, ensuring that double spaces are replaced with etc. + * + * @param array $lines lines to format. + * @return array formatted lines. + */ + protected function formatLines($lines) + { + $lines = array_map([$this, 'ExpandTabs'], $lines); + $lines = array_map([$this, 'HtmlSafe'], $lines); + foreach ($lines as &$line) { + $line = preg_replace_callback('# ( +)|^ #', [$this, 'fixSpaces'], $line); + } + return $lines; + } + + /** + * Replace a string containing spaces with a HTML representation using . + * + * @param string[] $matches preg matches. + * @return string HTML representation of the string. + */ + private function fixSpaces(array $matches) + { + $count = 0; + + if (count($matches) > 1) { + $spaces = $matches[1]; + $count = strlen($spaces); + } + + if ($count === 0) { + return ''; + } + + $div = floor($count / 2); + $mod = $count % 2; + return str_repeat(' ', $div).str_repeat(' ', $mod); + } + + /** + * Replace tabs in a single line with a number of spaces as defined by the tabSize option. + * + * @param string $line The containing tabs to convert. + * @return string The line with the tabs converted to spaces. + */ + private function expandTabs($line) + { + return str_replace("\t", str_repeat(' ', $this->options['tabSize']), $line); + } + + /** + * Make a string containing HTML safe for output on a page. + * + * @param string $string The string. + * @return string The string with the HTML characters replaced by entities. + */ + private function htmlSafe($string) + { + return htmlspecialchars($string, ENT_NOQUOTES, 'UTF-8'); + } +} diff --git a/vendor/gipfl/diff/src/PhpDiff/Renderer/Html/Inline.php b/vendor/gipfl/diff/src/PhpDiff/Renderer/Html/Inline.php new file mode 100644 index 0000000..6587de1 --- /dev/null +++ b/vendor/gipfl/diff/src/PhpDiff/Renderer/Html/Inline.php @@ -0,0 +1,104 @@ +<?php + +namespace gipfl\Diff\PhpDiff\Renderer\Html; + +/** + * Inline HTML diff generator for PHP DiffLib. + */ +class Inline extends ArrayRenderer +{ + /** + * Render a and return diff with changes between the two sequences + * displayed inline (under each other) + * + * @return string The generated inline diff. + */ + public function render() + { + $changes = parent::render(); + $html = ''; + if (empty($changes)) { + return $html; + } + + $html .= '<table class="Differences DifferencesInline">'; + $html .= '<thead>'; + $html .= '<tr>'; + $html .= '<th>Old</th>'; + $html .= '<th>New</th>'; + $html .= '<th>Differences</th>'; + $html .= '</tr>'; + $html .= '</thead>'; + foreach ($changes as $i => $blocks) { + // If this is a separate block, we're condensing code so output ..., + // indicating a significant portion of the code has been collapsed as + // it is the same + if ($i > 0) { + $html .= '<tbody class="Skipped">'; + $html .= '<th>…</th>'; + $html .= '<th>…</th>'; + $html .= '<td> </td>'; + $html .= '</tbody>'; + } + + foreach ($blocks as $change) { + $html .= '<tbody class="Change'.ucfirst($change['tag']).'">'; + // Equal changes should be shown on both sides of the diff + if ($change['tag'] === 'equal') { + foreach ($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $toLine = $change['changed']['offset'] + $no + 1; + $html .= '<tr>'; + $html .= '<th>' . $fromLine . '</th>'; + $html .= '<th>' . $toLine . '</th>'; + $html .= '<td class="Left">' . $line . '</td>'; + $html .= '</tr>'; + } + } elseif ($change['tag'] === 'insert') { + // Added lines only on the right side + foreach ($change['changed']['lines'] as $no => $line) { + $toLine = $change['changed']['offset'] + $no + 1; + $html .= '<tr>'; + $html .= '<th> </th>'; + $html .= '<th>' . $toLine . '</th>'; + $html .= '<td class="Right"><ins>' . $line . '</ins> </td>'; + $html .= '</tr>'; + } + } elseif ($change['tag'] === 'delete') { + // Show deleted lines only on the left side + foreach ($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $html .= '<tr>'; + $html .= '<th>' . $fromLine . '</th>'; + $html .= '<th> </th>'; + $html .= '<td class="Left"><del>' . $line . '</del> </td>'; + $html .= '</tr>'; + } + } elseif ($change['tag'] === 'replace') { + // Show modified lines on both sides + foreach ($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $html .= '<tr>'; + $html .= '<th>' . $fromLine . '</th>'; + $html .= '<th> </th>'; + $html .= '<td class="Left"><span>' . $line . '</span></td>'; + $html .= '</tr>'; + } + + foreach ($change['changed']['lines'] as $no => $line) { + $toLine = $change['changed']['offset'] + $no + 1; + $html .= '<tr>'; + $html .= '<th> </th>'; + $html .= '<th>' . $toLine . '</th>'; + $html .= '<td class="Right"><span>' . $line . '</span></td>'; + $html .= '</tr>'; + } + } + $html .= '</tbody>'; + } + } + $html .= '</table>'; + + return $html; + } +} diff --git a/vendor/gipfl/diff/src/PhpDiff/Renderer/Html/SideBySide.php b/vendor/gipfl/diff/src/PhpDiff/Renderer/Html/SideBySide.php new file mode 100644 index 0000000..2d16e08 --- /dev/null +++ b/vendor/gipfl/diff/src/PhpDiff/Renderer/Html/SideBySide.php @@ -0,0 +1,121 @@ +<?php + +namespace gipfl\Diff\PhpDiff\Renderer\Html; + +/** + * Side by Side HTML diff generator for PHP DiffLib. + */ +class SideBySide extends ArrayRenderer +{ + /** + * Render a and return diff with changes between the two sequences + * displayed side by side. + * + * @return string The generated side by side diff. + */ + public function render() + { + $changes = parent::render(); + + $html = ''; + if (empty($changes)) { + return $html; + } + + $html .= '<table class="Differences DifferencesSideBySide">'; + $html .= '<thead>'; + $html .= '<tr>'; + $html .= '<th colspan="2">Old Version</th>'; + $html .= '<th colspan="2">New Version</th>'; + $html .= '</tr>'; + $html .= '</thead>'; + foreach ($changes as $i => $blocks) { + if ($i > 0) { + $html .= '<tbody class="Skipped">'; + $html .= '<th>…</th><td> </td>'; + $html .= '<th>…</th><td> </td>'; + $html .= '</tbody>'; + } + + foreach ($blocks as $change) { + $html .= '<tbody class="Change'.ucfirst($change['tag']).'">'; + // Equal changes should be shown on both sides of the diff + if ($change['tag'] === 'equal') { + foreach ($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $toLine = $change['changed']['offset'] + $no + 1; + $html .= '<tr>'; + $html .= '<th>' . $fromLine . '</th>'; + $html .= '<td class="Left"><span>' . $line . '</span> </td>'; + $html .= '<th>' . $toLine . '</th>'; + $html .= '<td class="Right"><span>' . $line . '</span> </td>'; + $html .= '</tr>'; + } + } elseif ($change['tag'] === 'insert') { + // Added lines only on the right side + foreach ($change['changed']['lines'] as $no => $line) { + $toLine = $change['changed']['offset'] + $no + 1; + $html .= '<tr>'; + $html .= '<th> </th>'; + $html .= '<td class="Left"> </td>'; + $html .= '<th>' . $toLine . '</th>'; + $html .= '<td class="Right"><ins>' . $line . '</ins> </td>'; + $html .= '</tr>'; + } + } elseif ($change['tag'] === 'delete') { + // Show deleted lines only on the left side + foreach ($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $html .= '<tr>'; + $html .= '<th>' . $fromLine . '</th>'; + $html .= '<td class="Left"><del>' . $line . '</del> </td>'; + $html .= '<th> </th>'; + $html .= '<td class="Right"> </td>'; + $html .= '</tr>'; + } + } elseif ($change['tag'] === 'replace') { + // Show modified lines on both sides + if (count($change['base']['lines']) >= count($change['changed']['lines'])) { + foreach ($change['base']['lines'] as $no => $line) { + $fromLine = $change['base']['offset'] + $no + 1; + $html .= '<tr>'; + $html .= '<th>'.$fromLine.'</th>'; + $html .= '<td class="Left"><span>' . $line . '</span> </td>'; + if (!isset($change['changed']['lines'][$no])) { + $toLine = ' '; + $changedLine = ' '; + } else { + $toLine = $change['base']['offset'] + $no + 1; + $changedLine = '<span>'.$change['changed']['lines'][$no].'</span>'; + } + $html .= '<th>' . $toLine . '</th>'; + $html .= '<td class="Right">' . $changedLine . '</td>'; + $html .= '</tr>'; + } + } else { + foreach ($change['changed']['lines'] as $no => $changedLine) { + if (!isset($change['base']['lines'][$no])) { + $fromLine = ' '; + $line = ' '; + } else { + $fromLine = $change['base']['offset'] + $no + 1; + $line = '<span>' . $change['base']['lines'][$no] . '</span>'; + } + $html .= '<tr>'; + $html .= '<th>' . $fromLine . '</th>'; + $html .= '<td class="Left"><span>' . $line . '</span> </td>'; + $toLine = $change['changed']['offset'] + $no + 1; + $html .= '<th>' . $toLine . '</th>'; + $html .= '<td class="Right">' . $changedLine . '</td>'; + $html .= '</tr>'; + } + } + } + $html .= '</tbody>'; + } + } + $html .= '</table>'; + + return $html; + } +} diff --git a/vendor/gipfl/diff/src/PhpDiff/Renderer/Text/Context.php b/vendor/gipfl/diff/src/PhpDiff/Renderer/Text/Context.php new file mode 100644 index 0000000..b8d9cad --- /dev/null +++ b/vendor/gipfl/diff/src/PhpDiff/Renderer/Text/Context.php @@ -0,0 +1,98 @@ +<?php + +namespace gipfl\Diff\PhpDiff\Renderer\Text; + +use gipfl\Diff\PhpDiff\Renderer\AbstractRenderer; + +/** + * Context diff generator for PHP DiffLib. + */ +class Context extends AbstractRenderer +{ + /** + * @var array Array of the different opcode tags and how they map to the context diff equivalent. + */ + private $tagMap = [ + 'insert' => '+', + 'delete' => '-', + 'replace' => '!', + 'equal' => ' ' + ]; + + /** + * Render and return a context formatted (old school!) diff file. + * + * @return string The generated context diff. + */ + public function render() + { + $diff = ''; + $opCodes = $this->diff->getGroupedOpcodes(); + foreach ($opCodes as $group) { + $diff .= "***************\n"; + $lastItem = count($group)-1; + $i1 = $group[0][1]; + $i2 = $group[$lastItem][2]; + $j1 = $group[0][3]; + $j2 = $group[$lastItem][4]; + + if ($i2 - $i1 >= 2) { + $diff .= '*** '.($group[0][1] + 1).','.$i2." ****\n"; + } else { + $diff .= '*** '.$i2." ****\n"; + } + + if ($j2 - $j1 >= 2) { + $separator = '--- '.($j1 + 1).','.$j2." ----\n"; + } else { + $separator = '--- '.$j2." ----\n"; + } + + $hasVisible = false; + foreach ($group as $code) { + if ($code[0] === 'replace' || $code[0] === 'delete') { + $hasVisible = true; + break; + } + } + + if ($hasVisible) { + foreach ($group as $code) { + list($tag, $i1, $i2, $j1, $j2) = $code; + if ($tag === 'insert') { + continue; + } + $diff .= $this->tagMap[$tag] + . ' ' + . implode("\n" . $this->tagMap[$tag] . ' ', $this->diff->getLeft($i1, $i2)) + . "\n"; + } + } + + $hasVisible = false; + foreach ($group as $code) { + if ($code[0] === 'replace' || $code[0] === 'insert') { + $hasVisible = true; + break; + } + } + + $diff .= $separator; + + if ($hasVisible) { + foreach ($group as $code) { + list($tag, $i1, $i2, $j1, $j2) = $code; + if ($tag === 'delete') { + continue; + } + $diff .= $this->tagMap[$tag] + . ' ' + . implode("\n" . $this->tagMap[$tag] . ' ', $this->diff->getRight($j1, $j2)) + . "\n"; + } + } + } + + return $diff; + } +} diff --git a/vendor/gipfl/diff/src/PhpDiff/Renderer/Text/Unified.php b/vendor/gipfl/diff/src/PhpDiff/Renderer/Text/Unified.php new file mode 100644 index 0000000..afeb96d --- /dev/null +++ b/vendor/gipfl/diff/src/PhpDiff/Renderer/Text/Unified.php @@ -0,0 +1,52 @@ +<?php + +namespace gipfl\Diff\PhpDiff\Renderer\Text; + +use gipfl\Diff\PhpDiff\Renderer\AbstractRenderer; + +/** + * Unified diff generator for PHP DiffLib. + */ +class Unified extends AbstractRenderer +{ + /** + * Render and return a unified diff. + * + * @return string The unified diff. + */ + public function render() + { + $diff = ''; + $opCodes = $this->diff->getGroupedOpcodes(); + foreach ($opCodes as $group) { + $lastItem = count($group)-1; + $i1 = $group[0][1]; + $i2 = $group[$lastItem][2]; + $j1 = $group[0][3]; + $j2 = $group[$lastItem][4]; + + if ($i1 === 0 && $i2 === 0) { + $i1 = -1; + $i2 = -1; + } + + $diff .= '@@ -' . ($i1 + 1) . ',' . ($i2 - $i1) . ' +' . ($j1 + 1) . ',' . ($j2 - $j1) . " @@\n"; + foreach ($group as $code) { + list($tag, $i1, $i2, $j1, $j2) = $code; + if ($tag === 'equal') { + $diff .= ' ' . implode("\n ", $this->diff->getLeft($i1, $i2))."\n"; + } else { + if ($tag === 'replace' || $tag === 'delete') { + $diff .= '-' . implode("\n-", $this->diff->getLeft($i1, $i2))."\n"; + } + + if ($tag === 'replace' || $tag === 'insert') { + $diff .= '+' . implode("\n+", $this->diff->getRight($j1, $j2))."\n"; + } + } + } + } + + return $diff; + } +} diff --git a/vendor/gipfl/diff/src/PhpDiff/SequenceMatcher.php b/vendor/gipfl/diff/src/PhpDiff/SequenceMatcher.php new file mode 100644 index 0000000..a185762 --- /dev/null +++ b/vendor/gipfl/diff/src/PhpDiff/SequenceMatcher.php @@ -0,0 +1,438 @@ +<?php + +namespace gipfl\Diff\PhpDiff; + +use function array_merge; +use function count; +use function is_array; +use function str_replace; +use function str_split; +use function strtolower; + +/** + * Sequence matcher for Diff + */ +class SequenceMatcher +{ + /** + * Either a string or an array containing a callback function to determine + * if a line is "junk" or not + * + * @var string|array + */ + private $junkCallback; + + /** + * @var array The first sequence to compare against. + */ + private $left = []; + + /** + * @var array The second sequence. + */ + private $right = []; + + /** + * @var array Characters that are considered junk from the second sequence. Characters are the array key. + */ + private $junkCharacters = []; + + /** + * @var array Array of indices that do not contain junk elements. + */ + private $b2j = []; + + private $options = []; + + private $defaultOptions = [ + 'ignoreNewLines' => false, + 'ignoreWhitespace' => false, + 'ignoreCase' => false + ]; + + /** @var array|null */ + private $matchingBlocks; + + /** @var array|null */ + private $opCodes; + + /** + * The constructor. With the sequences being passed, they'll be set for the + * sequence matcher and it will perform a basic cleanup & calculate junk + * elements. + * + * @param string|array $left A string or array containing the lines to compare against. + * @param string|array $right A string or array containing the lines to compare. + * @param string|array $junkCallback Either an array or string that references a callback + * function (if there is one) to determine 'junk' characters. + * @param array $options + */ + public function __construct($left, $right, $junkCallback = null, $options = []) + { + $this->junkCallback = $junkCallback; + $this->setOptions($options); + $this->setSequences($left, $right); + } + + public function setOptions($options) + { + $this->options = array_merge($this->defaultOptions, $options); + } + + /** + * Set the first and second sequences to use with the sequence matcher. + * + * @param string|array $left A string or array containing the lines to compare against. + * @param string|array $right A string or array containing the lines to compare. + */ + public function setSequences($left, $right) + { + $this->setLeftSequence($left); + $this->setRightSequence($right); + } + + /** + * Set the first sequence and reset any internal caches to indicate that + * when calling the calculation methods, we need to recalculate them. + * + * @param string|array $sequence The sequence to set as the first sequence. + */ + protected function setLeftSequence($sequence) + { + if (!is_array($sequence)) { + $sequence = str_split($sequence); + } + if ($sequence === $this->left) { + return; + } + + $this->resetCalculation(); + $this->left = $sequence; + } + + /** + * Set the second sequence ($b) and reset any internal caches to indicate that + * when calling the calculation methods, we need to recalculate them. + * + * @param string|array $sequence The sequence to set as the second sequence. + */ + protected function setRightSequence($sequence) + { + if (!is_array($sequence)) { + $sequence = str_split($sequence); + } + if ($sequence === $this->right) { + return; + } + + $this->resetCalculation(); + $this->right = $sequence; + $this->generateRightChain(); + } + + protected function resetCalculation() + { + $this->matchingBlocks = null; + $this->opCodes = null; + } + + /** + * @return array + */ + public function getLeftSequence() + { + return $this->left; + } + + /** + * @return array + */ + public function getRightSequence() + { + return $this->right; + } + + /** + * Generate the internal arrays containing the list of junk and non-junk + * characters for the second ($b) sequence. + */ + private function generateRightChain() + { + $length = count($this->right); + $this->b2j = []; + $popularDict = []; + + foreach ($this->right as $i => $char) { + if (isset($this->b2j[$char])) { + if ($length >= 200 && count($this->b2j[$char]) * 100 > $length) { + $popularDict[$char] = 1; + unset($this->b2j[$char]); + } else { + $this->b2j[$char][] = $i; + } + } else { + $this->b2j[$char] = [$i]; + } + } + + // Remove leftovers + foreach (array_keys($popularDict) as $char) { + unset($this->b2j[$char]); + } + + $this->junkCharacters = []; + if (is_callable($this->junkCallback)) { + foreach (array_keys($popularDict) as $char) { + if (call_user_func($this->junkCallback, $char)) { + $this->junkCharacters[$char] = 1; + unset($popularDict[$char]); + } + } + + foreach (array_keys($this->b2j) as $char) { + if (call_user_func($this->junkCallback, $char)) { + $this->junkCharacters[$char] = 1; + unset($this->b2j[$char]); + } + } + } + } + + /** + * Checks if a particular character is in the junk dictionary + * for the list of junk characters. + * + * @param $b + * @return boolean whether the character is considered junk + */ + private function isBJunk($b) + { + if (isset($this->junkCharacters[$b])) { + return true; + } + + return false; + } + + /** + * Find the longest matching block in the two sequences, as defined by the + * lower and upper constraints for each sequence. (for the first sequence, + * $alo - $ahi and for the second sequence, $blo - $bhi) + * + * Essentially, of all of the maximal matching blocks, return the one that + * starts earliest in $a, and all of those maximal matching blocks that + * start earliest in $a, return the one that starts earliest in $b. + * + * If the junk callback is defined, do the above but with the restriction + * that the junk element appears in the block. Extend it as far as possible + * by matching only junk elements in both $a and $b. + * + * @param int $beginLeft The lower constraint for the first sequence. + * @param int $endLeft The upper constraint for the first sequence. + * @param int $beginRight The lower constraint for the second sequence. + * @param int $endRight The upper constraint for the second sequence. + * @return array Array containing the longest match that includes the starting + * position in $a, start in $b and the length/size. + */ + public function findLongestMatch($beginLeft, $endLeft, $beginRight, $endRight) + { + $left = $this->left; + $right = $this->right; + + $bestBeginLeft = $beginLeft; + $bestBeginRight = $beginRight; + $bestSize = 0; + + $j2Len = []; + $nothing = []; + + for ($currentLeft = $beginLeft; $currentLeft < $endLeft; ++$currentLeft) { + $newJ2Len = []; + $junkList = ArrayHelper::getPropertyOrDefault($this->b2j, $left[$currentLeft], $nothing); + foreach ($junkList as $junk) { + if ($junk < $beginRight) { + continue; + } + if ($junk >= $endRight) { + break; + } + + $k = ArrayHelper::getPropertyOrDefault($j2Len, $junk -1, 0) + 1; + $newJ2Len[$junk] = $k; + if ($k > $bestSize) { + $bestBeginLeft = $currentLeft - $k + 1; + $bestBeginRight = $junk - $k + 1; + $bestSize = $k; + } + } + + $j2Len = $newJ2Len; + } + + while ($bestBeginLeft > $beginLeft + && $bestBeginRight > $beginRight + && !$this->isBJunk($right[$bestBeginRight - 1]) + && !$this->linesAreDifferent($bestBeginLeft - 1, $bestBeginRight - 1) + ) { + --$bestBeginLeft; + --$bestBeginRight; + ++$bestSize; + } + + while ($bestBeginLeft + $bestSize < $endLeft && ($bestBeginRight + $bestSize) < $endRight + && !$this->isBJunk($right[$bestBeginRight + $bestSize]) + && !$this->linesAreDifferent($bestBeginLeft + $bestSize, $bestBeginRight + $bestSize) + ) { + ++$bestSize; + } + + while ($bestBeginLeft > $beginLeft + && $bestBeginRight > $beginRight + && $this->isBJunk($right[$bestBeginRight - 1]) + && !$this->linesAreDifferent($bestBeginLeft - 1, $bestBeginRight - 1) + ) { + --$bestBeginLeft; + --$bestBeginRight; + ++$bestSize; + } + + while ($bestBeginLeft + $bestSize < $endLeft + && $bestBeginRight + $bestSize < $endRight + && $this->isBJunk($right[$bestBeginRight + $bestSize]) + && !$this->linesAreDifferent($bestBeginLeft + $bestSize, $bestBeginRight + $bestSize) + ) { + ++$bestSize; + } + + return [$bestBeginLeft, $bestBeginRight, $bestSize]; + } + + /** + * Check if the two lines at the given indexes are different or not. + * + * @param int $leftIndex Line number to check against in a. + * @param int $rightIndex Line number to check against in b. + * @return boolean True if the lines are different and false if not. + */ + public function linesAreDifferent($leftIndex, $rightIndex) + { + $leftLine = $this->left[$leftIndex]; + $rightLine = $this->right[$rightIndex]; + + if ($this->options['ignoreWhitespace']) { + $replace = ["\t", ' ']; + $leftLine = str_replace($replace, '', $leftLine); + $rightLine = str_replace($replace, '', $rightLine); + } + + if ($this->options['ignoreCase']) { + $leftLine = strtolower($leftLine); + $rightLine = strtolower($rightLine); + } + + return $leftLine !== $rightLine; + } + + /** + * Return a nested set of arrays for all of the matching sub-sequences + * in the strings $a and $b. + * + * Each block contains the lower constraint of the block in $a, the lower + * constraint of the block in $b and finally the number of lines that the + * block continues for. + * + * @return array Nested array of the matching blocks, as described by the function. + */ + public function getMatchingBlocks() + { + if ($this->matchingBlocks === null) { + $this->matchingBlocks = $this->calculateMatchingBlocks(); + } + + return $this->matchingBlocks; + } + + public function calculateMatchingBlocks() + { + $leftLength = count($this->left); + $rightLength = count($this->right); + + $queue = [ + [0, $leftLength, 0, $rightLength] + ]; + + $matchingBlocks = []; + while (!empty($queue)) { + list($leftBegin, $leftEnd, $rightBegin, $rightEnd) = array_pop($queue); + $block = $this->findLongestMatch($leftBegin, $leftEnd, $rightBegin, $rightEnd); + list($bestBeginLeft, $bestBeginRight, $bestSize) = $block; + if ($bestSize) { + $matchingBlocks[] = $block; + if ($leftBegin < $bestBeginLeft && $rightBegin < $bestBeginRight) { + $queue[] = [ + $leftBegin, + $bestBeginLeft, + $rightBegin, + $bestBeginRight + ]; + } + + if ($bestBeginLeft + $bestSize < $leftEnd && $bestBeginRight + $bestSize < $rightEnd) { + $queue[] = [ + $bestBeginLeft + $bestSize, + $leftEnd, + $bestBeginRight + $bestSize, + $rightEnd + ]; + } + } + } + + usort($matchingBlocks, [ArrayHelper::class, 'tupleSort']); + + return static::getNonAdjacentBlocks($matchingBlocks, $leftLength, $rightLength); + } + + public function getOpcodes() + { + if ($this->opCodes === null) { + $this->opCodes = OpCodeHelper::calculateOpCodes($this->getMatchingBlocks()); + } + + return $this->opCodes; + } + + /** + * @param array $matchingBlocks + * @param $leftLength + * @param $rightLength + * @return array + */ + protected static function getNonAdjacentBlocks(array $matchingBlocks, $leftLength, $rightLength) + { + $newLeft = 0; + $newRight = 0; + $newCnt = 0; + $nonAdjacent = []; + foreach ($matchingBlocks as list($beginLeft, $beginRight, $cntLines)) { + if ($newLeft + $newCnt === $beginLeft && $newRight + $newCnt === $beginRight) { + $newCnt += $cntLines; + } else { + if ($newCnt) { + $nonAdjacent[] = [$newLeft, $newRight, $newCnt]; + } + + $newLeft = $beginLeft; + $newRight = $beginRight; + $newCnt = $cntLines; + } + } + + if ($newCnt) { + $nonAdjacent[] = [$newLeft, $newRight, $newCnt]; + } + + $nonAdjacent[] = [$leftLength, $rightLength, 0]; + return $nonAdjacent; + } +} |