From cc3e050304f2c88696ea23dc31ac6b7b5f11f080 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 23 Apr 2025 21:27:47 +0200 Subject: [PATCH] Add compare function between snipsContents --- src/Controller/SnipContentController.php | 41 +++++++ src/Service/SnipContent/DiffTypeEnum.php | 15 +++ src/Service/SnipContent/MyersDiff.php | 101 ++++++++++++++---- .../SnipContent/SnipContentService.php | 3 +- templates/content/compare.html.twig | 38 +++++++ templates/snip/single.html.twig | 3 + 6 files changed, 179 insertions(+), 22 deletions(-) create mode 100644 src/Controller/SnipContentController.php create mode 100644 src/Service/SnipContent/DiffTypeEnum.php create mode 100644 templates/content/compare.html.twig diff --git a/src/Controller/SnipContentController.php b/src/Controller/SnipContentController.php new file mode 100644 index 0000000..af57b9c --- /dev/null +++ b/src/Controller/SnipContentController.php @@ -0,0 +1,41 @@ +denyAccessUnlessGranted(SnipVoter::VIEW, $to->getSnip()); + + if ($from === null) { + $from = $to->getParent(); + } + + $diff = MyersDiff::buildDiffLines( + $this->contentService->rebuildText($from), + $this->contentService->rebuildText($to), + ); + + dump($diff); + + return $this->render('content/compare.html.twig', [ + 'snip' => $to->getSnip(), + 'diff' => $diff, + ]); + } +} \ No newline at end of file diff --git a/src/Service/SnipContent/DiffTypeEnum.php b/src/Service/SnipContent/DiffTypeEnum.php new file mode 100644 index 0000000..cfc1abc --- /dev/null +++ b/src/Service/SnipContent/DiffTypeEnum.php @@ -0,0 +1,15 @@ +value === $diffType; + } +} diff --git a/src/Service/SnipContent/MyersDiff.php b/src/Service/SnipContent/MyersDiff.php index b7342f6..155d804 100644 --- a/src/Service/SnipContent/MyersDiff.php +++ b/src/Service/SnipContent/MyersDiff.php @@ -52,7 +52,7 @@ class MyersDiff $x++; $count++; } - $solution[] = ['D', $count]; + $solution[] = [DiffTypeEnum::DELETE->value, $count]; } // Insertions @@ -63,10 +63,10 @@ class MyersDiff $y++; } $solutionKey = count($solution) - 1; - if ($solutionKey >= 0 && $solution[$solutionKey][0] === 'I') { + if ($solutionKey >= 0 && DiffTypeEnum::INSERT->is($solution[$solutionKey][0])) { $solution[$solutionKey][1] = array_merge($solution[$solutionKey][1], $values); } else { - $solution[] = ['I', $values]; + $solution[] = [DiffTypeEnum::INSERT->value, $values]; } } @@ -78,7 +78,7 @@ class MyersDiff $count++; } if ($count > 0) { - $solution[] = ['K', $count]; + $solution[] = [DiffTypeEnum::KEEP->value, $count]; } } @@ -88,16 +88,24 @@ class MyersDiff /** * Calculate the shortest edit sequence to convert $x into $y. * - * @param string $textFrom - tokens (characters, words or lines) - * @param string $textTo - tokens (characters, words or lines) - * @param ?callable $compare - comparison function for tokens. Signature is compare($x, $y):bool. If null, === is used. + * @param string|array $textFrom - tokens (characters, words or lines) + * @param string|array $textTo - tokens (characters, words or lines) + * @param ?callable $compare - comparison function for tokens. Signature is compare($x, $y):bool. If null, === is used. * * @return array[] - pairs of token and edit (-1 for delete, 0 for keep, +1 for insert) */ - public static function calculate(string $textFrom, string $textTo, ?callable $compare = null): array + public static function calculate(string|array $textFrom, string|array $textTo, ?callable $compare = null): array { - $a = self::explode($textFrom); - $b = self::explode($textTo); + if (is_string($textFrom)) { + $a = self::explode($textFrom); + } else { + $a = $textFrom; + } + if (is_string($textTo)) { + $b = self::explode($textTo); + } else { + $b = $textTo; + } if ($compare === null) { $compare = function ($x, $y) { @@ -144,22 +152,75 @@ class MyersDiff $x = 0; foreach ($diff as [$op, $data]) { - if ($op === 'K') { - for ($i = 0; $i < $data; $i++) { - $b[] = $a[$x++]; - } - } elseif ($op === 'D') { - $x += $data; // skip deleted - } elseif ($op === 'I') { - foreach ($data as $v) { - $b[] = $v; - } + switch ($op) { + case DiffTypeEnum::KEEP->value: + for ($i = 0; $i < $data; $i++) { + $b[] = $a[$x++]; + } + break; + case DiffTypeEnum::DELETE->value: + $x += $data; // skip deleted + break; + case DiffTypeEnum::INSERT->value: + foreach ($data as $v) { + $b[] = $v; + } + break; + default: + throw new \InvalidArgumentException('Invalid diff operation'); } } return self::implode($b); } + public static function buildDiffLines(string $textFrom, string $textTo): array + { + $a = self::explode($textFrom); + $b = self::explode($textTo); + $diff = MyersDiff::calculate($a, $b); + + $lines = []; + $x = 0; + foreach ($diff as [$op, $data]) { + switch ($op) { + case DiffTypeEnum::KEEP->value: + for ($i = 0; $i < $data; $i++) { + $lines[] = [ + 'type' => 'keep', + 'from' => $a[$x], + 'to' => $a[$x], + ]; + $x++; + } + break; + case DiffTypeEnum::DELETE->value: + for ($i = 0; $i < $data; $i++) { + $lines[] = [ + 'type' => 'delete', + 'from' => $a[$x], + 'to' => '', + ]; + $x++; + } + break; + case DiffTypeEnum::INSERT->value: + foreach ($data as $v) { + $lines[] = [ + 'type' => 'insert', + 'from' => '', + 'to' => $v, + ]; + } + break; + default: + throw new \InvalidArgumentException('Invalid diff operation'); + } + } + + return $lines; + } + private static function explode(string $text): array { return explode(self::NEWLINE, $text); diff --git a/src/Service/SnipContent/SnipContentService.php b/src/Service/SnipContent/SnipContentService.php index 09b4ba6..a615684 100644 --- a/src/Service/SnipContent/SnipContentService.php +++ b/src/Service/SnipContent/SnipContentService.php @@ -41,8 +41,7 @@ readonly class SnipContentService public function getActiveText(Snip $snip): string { - $contentRepo = $this->em->getRepository(SnipContent::class); - return $this->rebuildText($contentRepo->find($snip->getActiveVersion())); + return $this->rebuildText($snip->getActiveVersion()); } public function rebuildText(?SnipContent $snipContent): string diff --git a/templates/content/compare.html.twig b/templates/content/compare.html.twig new file mode 100644 index 0000000..ac00bfd --- /dev/null +++ b/templates/content/compare.html.twig @@ -0,0 +1,38 @@ +{% extends 'base/single.column.html.twig' %} + +{% set title = 'Snip compare ' ~ snip %} + +{% block body %} + + Back + +

+ + + + + + + + + + {% for line in diff %} + + + + + + {% endfor %} + +
LineOldNew
+ {{ line.type }} + + {% if line.from %} + {{ line.from }} + {% endif %} + + {% if line.to %} + {{ line.to }} + {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/templates/snip/single.html.twig b/templates/snip/single.html.twig index 50595c8..e246951 100644 --- a/templates/snip/single.html.twig +++ b/templates/snip/single.html.twig @@ -26,6 +26,9 @@ Raw + + Compare +