summaryrefslogtreecommitdiffstats
path: root/vendor/gipfl/diff
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:23:16 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-14 13:23:16 +0000
commit3e97c51418e6d27e9a81906f347fcb7c78e27d4f (patch)
treeee596ce1bc9840661386f96f9b8d1f919a106317 /vendor/gipfl/diff
parentInitial commit. (diff)
downloadicingaweb2-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/LICENSE21
-rw-r--r--vendor/gipfl/diff/composer.json25
-rw-r--r--vendor/gipfl/diff/public/css/diff.less133
-rw-r--r--vendor/gipfl/diff/src/HtmlRenderer/InlineDiff.php10
-rw-r--r--vendor/gipfl/diff/src/HtmlRenderer/SideBySideDiff.php10
-rw-r--r--vendor/gipfl/diff/src/PhpDiff.php147
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/ArrayHelper.php54
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/OpCodeHelper.php144
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/Ratio.php139
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/Renderer/AbstractRenderer.php44
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/Renderer/Html/ArrayRenderer.php207
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/Renderer/Html/Inline.php104
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/Renderer/Html/SideBySide.php121
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/Renderer/Text/Context.php98
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/Renderer/Text/Unified.php52
-rw-r--r--vendor/gipfl/diff/src/PhpDiff/SequenceMatcher.php438
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 &nbsp; 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 &nbsp;.
+ *
+ * @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('&nbsp; ', $div).str_repeat('&nbsp;', $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>&hellip;</th>';
+ $html .= '<th>&hellip;</th>';
+ $html .= '<td>&nbsp;</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>&nbsp;</th>';
+ $html .= '<th>' . $toLine . '</th>';
+ $html .= '<td class="Right"><ins>' . $line . '</ins>&nbsp;</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>&nbsp;</th>';
+ $html .= '<td class="Left"><del>' . $line . '</del>&nbsp;</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>&nbsp;</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>&nbsp;</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>&hellip;</th><td>&nbsp;</td>';
+ $html .= '<th>&hellip;</th><td>&nbsp;</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>&nbsp;</td>';
+ $html .= '<th>' . $toLine . '</th>';
+ $html .= '<td class="Right"><span>' . $line . '</span>&nbsp;</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>&nbsp;</th>';
+ $html .= '<td class="Left">&nbsp;</td>';
+ $html .= '<th>' . $toLine . '</th>';
+ $html .= '<td class="Right"><ins>' . $line . '</ins>&nbsp;</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>&nbsp;</td>';
+ $html .= '<th>&nbsp;</th>';
+ $html .= '<td class="Right">&nbsp;</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>&nbsp;</td>';
+ if (!isset($change['changed']['lines'][$no])) {
+ $toLine = '&nbsp;';
+ $changedLine = '&nbsp;';
+ } 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 = '&nbsp;';
+ $line = '&nbsp;';
+ } 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>&nbsp;</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;
+ }
+}