diff --git a/example/dark-theme.css b/example/dark-theme.css
index 77669d2..175b075 100644
--- a/example/dark-theme.css
+++ b/example/dark-theme.css
@@ -6,8 +6,8 @@ body {
}
pre {
- width: 100%;
overflow: auto;
+ width: 100%;
}
a, a:visited {
@@ -19,35 +19,35 @@ a, a:visited {
*/
.Differences {
- width: 100%;
border-collapse: collapse;
border-spacing: 0;
empty-cells: show;
+ width: 100%;
}
.Differences thead th {
- text-align: left;
- border-bottom: 1px solid #000000;
background: #AAAAAA;
+ border-bottom: 1px solid #000000;
color: #000000;
padding: 4px;
+ text-align: left;
}
.Differences tbody th {
- text-align: right;
background: #AAAAAA;
+ border-right: 1px solid #000000;
color: #272822;
- width: 4em;
+ font-size: 13px;
padding: 1px 2px;
- border-right: 1px solid #000000;
+ text-align: right;
vertical-align: top;
- font-size: 13px;
+ width: 4em;
}
.Differences td {
- padding: 1px 2px;
font-family: Consolas, monospace;
font-size: 13px;
+ padding: 1px 2px;
}
.Differences .Skipped {
@@ -65,7 +65,7 @@ a, a:visited {
* HTML Side by Side Diff
*/
.DifferencesSideBySide .ChangeInsert td.Left {
- background: #008000;
+ background: #DDFFDD;
}
.DifferencesSideBySide .ChangeInsert td.Right {
@@ -83,7 +83,7 @@ a, a:visited {
}
.DifferencesSideBySide .ChangeReplace .Left {
- background: #FFEE99;
+ background: #FFDD88;
color: #272822;
}
@@ -114,10 +114,12 @@ a, a:visited {
.DifferencesUnified .ChangeReplace .Right,
.DifferencesUnified .ChangeInsert .Right {
background: #DDFFDD;
+ color: #272822;
}
.DifferencesUnified .ChangeReplace ins {
background: #008000;
+ color: #272822;
}
.DifferencesUnified .ChangeReplace del {
@@ -128,13 +130,16 @@ a, a:visited {
/*
* HTML Merged Diff
*/
-.DifferencesMerged .ChangeReplace .Left,
+.DifferencesMerged td.ChangeReplace {
+ background: #FFDD88;
+ color: #272822;
+}
+
.DifferencesMerged .ChangeDelete {
background: #FFDDDD;
color: #272822;
}
-.DifferencesMerged .ChangeReplace .Right,
.DifferencesMerged .ChangeInsert {
background: #DDFFDD;
color: #272822;
@@ -153,3 +158,7 @@ a, a:visited {
.DifferencesMerged th.ChangeDelete {
background-image: linear-gradient(-45deg, #AAAAAA 0%, #EE9999 100%);
}
+
+.DifferencesMerged th.ChangeReplace {
+ background-image: linear-gradient(-45deg, #CCCCCC 0%, #FFDD88 100%);
+}
diff --git a/example/styles.css b/example/styles.css
index bbf2ba7..5dd6abe 100644
--- a/example/styles.css
+++ b/example/styles.css
@@ -6,8 +6,8 @@ body {
}
pre {
- width: 100%;
overflow: auto;
+ width: 100%;
}
/*
@@ -15,34 +15,35 @@ pre {
*/
.Differences {
- width: 100%;
border-collapse: collapse;
border-spacing: 0;
empty-cells: show;
+ width: 100%;
}
.Differences thead th {
- text-align: left;
- border-bottom: 1px solid #000000;
background: #AAAAAA;
+ border-bottom: 1px solid #000000;
color: #000000;
padding: 4px;
+ text-align: left;
}
.Differences tbody th {
- text-align: right;
background: #CCCCCC;
- width: 4em;
- padding: 1px 2px;
border-right: 1px solid #000000;
- vertical-align: top;
font-size: 13px;
+ padding: 1px 2px;
+ text-align: right;
+ vertical-align: top;
+ width: 4em;
}
.Differences td {
- padding: 1px 2px;
- font-family: Consolas, monospace;
- font-size: 13px;
+ font-family: Consolas, monospace;
+ font-size: 13px;
+ padding: 1px 2px;
+ vertical-align: top;
}
.Differences .Skipped {
@@ -111,12 +112,14 @@ pre {
/*
* HTML Merged Diff
*/
-.DifferencesMerged .ChangeReplace .Left,
+.DifferencesMerged td.ChangeReplace {
+ background: #FFDD88;
+}
+
.DifferencesMerged .ChangeDelete {
background: #FFDDDD;
}
-.DifferencesMerged .ChangeReplace .Right,
.DifferencesMerged .ChangeInsert {
background: #DDFFDD;
}
@@ -132,3 +135,7 @@ pre {
.DifferencesMerged th.ChangeDelete {
background-image: linear-gradient(-45deg, #CCCCCC 0%, #EE9999 100%);
}
+
+.DifferencesMerged th.ChangeReplace {
+ background-image: linear-gradient(-45deg, #CCCCCC 0%, #FFDD88 100%);
+}
diff --git a/lib/jblond/Diff/Renderer/Html/Merged.php b/lib/jblond/Diff/Renderer/Html/Merged.php
index ab75577..3ccfd04 100644
--- a/lib/jblond/Diff/Renderer/Html/Merged.php
+++ b/lib/jblond/Diff/Renderer/Html/Merged.php
@@ -112,7 +112,7 @@ public function generateSkippedLines(): string
return <<
-
+
… |
HTML;
@@ -125,18 +125,19 @@ public function generateSkippedLines(): string
*/
public function generateLinesEqual(array $changes): string
{
- $html = '';
- $headerClass = '';
+ $html = '';
foreach ($changes['base']['lines'] as $lineNo => $line) {
- $fromLine = $changes['base']['offset'] + $lineNo + 1 + $this->lineOffset;
+ $fromLine = $changes['base']['offset'] + $lineNo + 1 + $this->lineOffset;
+ $headerClass = '';
+
if (!$lineNo && $this->lastDeleted !== null) {
$headerClass = 'ChangeDelete';
}
$html .= <<
-
+
$line |
HTML;
@@ -153,19 +154,19 @@ public function generateLinesEqual(array $changes): string
*/
public function generateLinesInsert(array $changes): string
{
- $html = '';
- $headerClass = '';
+ $html = '';
foreach ($changes['changed']['lines'] as $lineNo => $line) {
$this->lineOffset++;
- $toLine = $changes['base']['offset'] + $this->lineOffset;
+ $toLine = $changes['base']['offset'] + $this->lineOffset;
+ $headerClass = '';
if (!$lineNo && $this->lastDeleted !== null) {
$headerClass = 'ChangeDelete';
}
$html .= <<
-
+
$line |
HTML;
@@ -185,7 +186,7 @@ public function generateLinesDelete(array $changes): string
{
$this->lineOffset -= count($changes['base']['lines']);
- $title = "Lines deleted at {$this->options['title2']}:\n";
+ $title = "Lines of {$this->options['title1']} deleted at {$this->options['title2']}:\n";
foreach ($changes['base']['lines'] as $lineNo => $line) {
$fromLine = $changes['base']['offset'] + $lineNo + 1;
@@ -196,7 +197,7 @@ public function generateLinesDelete(array $changes): string
TEXT;
}
- $this->lastDeleted = $title;
+ $this->lastDeleted = htmlentities($title);
return '';
}
@@ -208,41 +209,68 @@ public function generateLinesDelete(array $changes): string
*/
public function generateLinesReplace(array $changes): string
{
- $html = '';
- $headerClass = '';
-
- foreach ($changes['base']['lines'] as $lineNo => $line) {
- $fromLine = $changes['base']['offset'] + $lineNo + 1 + $this->lineOffset;
- if (!$lineNo && $this->lastDeleted !== null) {
- $headerClass = 'ChangeDelete';
+ $html = '';
+ $baseLineCount = count($changes['base']['lines']);
+ $changedLineCount = count($changes['changed']['lines']);
+
+ if (count($changes['base']['lines']) == $changedLineCount) {
+ // Lines of Version 1 are modified at version 2.
+ foreach ($changes['base']['lines'] as $lineNo => $line) {
+ $fromLine = $changes['base']['offset'] + $lineNo + 1 + $this->lineOffset;
+
+ // Capture line-parts which are added to the same line at version 2.
+ $addedParts = [];
+ preg_match_all('/\x0.*?\x1/', $changes['changed']['lines'][$lineNo], $addedParts, PREG_PATTERN_ORDER);
+ array_unshift($addedParts[0], '');
+
+ // Inline Replacement:
+ // Concatenate line-parts which are removed at version2 with line-parts which are added at version 2.
+ $line = preg_replace_callback(
+ '/\x0.*?\x1/',
+ function ($removedParts) use ($addedParts) {
+ $addedPart = str_replace(["\0", "\1"], $this->options['insertMarkers'], next($addedParts[0]));
+ $removedPart = str_replace(["\0", "\1"], $this->options['deleteMarkers'], $removedParts[0]);
+
+ return "$removedPart$addedPart";
+ },
+ $line
+ );
+
+ $html .= <<
+ $fromLine |
+ $line |
+
+HTML;
}
- // Capture added parts.
- $addedParts = [];
- preg_match_all('/\x0.*?\x1/', $changes['changed']['lines'][$lineNo], $addedParts, PREG_PATTERN_ORDER);
- array_unshift($addedParts[0], '');
+ return $html;
+ }
- // Concatenate removed parts with added parts.
- $line = preg_replace_callback(
- '/\x0.*?\x1/',
- function ($removedParts) use ($addedParts) {
- $addedPart = str_replace(["\0", "\1"], $this->options['insertMarkers'], next($addedParts[0]));
- $removedPart = str_replace(["\0", "\1"], $this->options['deleteMarkers'], $removedParts[0]);
+ // More or less lines at version 2. Block of version 1 is replaced by block of version 2.
+ $title = '';
- return "$removedPart$addedPart";
- },
- $line
- );
+ foreach ($changes['changed']['lines'] as $lineNo => $line) {
+ $toLine = $changes['changed']['offset'] + $lineNo + 1;
- $html .= <<options['title1']}:\n";
+ foreach ($changes['base']['lines'] as $baseLineNo => $baseLine) {
+ $title .= $changes['base']['offset'] + $baseLineNo + 1 . ": $baseLine\n";
+ }
+ }
+
+ $title = htmlentities($title);
+ $html .= <<
-
- $line |
+ $toLine |
+ $line |
HTML;
- $this->lastDeleted = null;
}
+ $this->lineOffset = $this->lineOffset + $changedLineCount - $baseLineCount;
+
return $html;
}
diff --git a/tests/resources/htmlMerged.txt b/tests/resources/htmlMerged.txt
index 0e261b7..666263d 100644
--- a/tests/resources/htmlMerged.txt
+++ b/tests/resources/htmlMerged.txt
@@ -13,7 +13,7 @@
3 |
<meta http-equiv="Content-type" content="text/html; charset=utf-8"/> |
- 4 |
+ 4 |
<title>Hello WorldYou!</title> |
5 |
@@ -25,18 +25,18 @@
7 |
<h1>This is demo content to show features of the php-diff package.</h1> |
- 8 |
<h2>This line is the same for both versions.</h2> |
- 9 |
+ 9 |
<h2>this line has inlineThis line has differences between both versions.</h2> |
10 |
<h2>This line is the same for both versions.</h2> |
- 11 |
+ 11 |
<h2>This line also has inlInLine differences between both versions.</h2> |
12 |
@@ -54,13 +54,13 @@
16 |
It's also compatible with multibyte characters (like Chinese and emoji) as shown below: |
- 17 |
+ 17 |
另外我覺得那個評價鑑的白色櫃子有點沒有必要欸。外觀我就不說了 ,怎麼連空間都那麼狹隘。不過倒是從這個地方看出所謂的“改革” |
- 18 |
+ 18 |
Do you know what "金槍魚罐頭魚の缶詰" means in Chinese? |
- 19 |
+ 19 |
🍏🍎🍎🍏🙂 |
20 |
@@ -84,13 +84,13 @@
27 |
<p>Just some lines to demonstrate the collapsing of a block of lines which are the same in both versions.</p> |
- 28 |
+ 28 |
<h2>This line also has inline differences between both versions. It's the whitespace in front.</h2> |
29 |
<h2>This line is the same for both versions.</h2> |
- 30 |
+ 30 |
<h2>This line also has inline differences between both versions.!</h2> |
31 |