Skip to content

Commit 7748252

Browse files
committed
Code Optimization, cleanup, refactoring and commenting.
1 parent 7f87ce3 commit 7748252

File tree

1 file changed

+170
-122
lines changed

1 file changed

+170
-122
lines changed

lib/jblond/Diff/Renderer/Html/HtmlArray.php

+170-122
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@
1111
*
1212
* PHP version 7.2 or greater
1313
*
14-
* @package jblond\Diff\Renderer\Html
15-
* @author Chris Boulton <[email protected]>
14+
* @package jblond\Diff\Renderer\Html
15+
* @author Chris Boulton <[email protected]>
1616
* @copyright (c) 2009 Chris Boulton
17-
* @license New BSD License http://www.opensource.org/licenses/bsd-license.php
18-
* @version 1.15
19-
* @link https://github.com/JBlond/php-diff
17+
* @license New BSD License http://www.opensource.org/licenses/bsd-license.php
18+
* @version 1.15
19+
* @link https://github.com/JBlond/php-diff
2020
*/
2121
class HtmlArray extends RendererAbstract
2222
{
@@ -33,13 +33,19 @@ class HtmlArray extends RendererAbstract
3333
'title_b' => 'New Version',
3434
];
3535

36+
/**
37+
* @var string The last operation which was recorded in the array which contains the changes, used by the renderer.
38+
* @see HtmlArray::appendChangesArray()
39+
*/
40+
private $lastTag;
41+
3642
/**
3743
* Generate a string representation of changes between the "old and "new" sequences.
3844
*
3945
* This method is called by the renderers which extends this class.
4046
*
41-
* @param array $changes Contains the op-codes about the differences between "old and "new".
42-
* @param SideBySide|Inline $htmlRenderer Renderer which extends this class.
47+
* @param array $changes Contains the op-codes about the differences between "old and "new".
48+
* @param object $htmlRenderer Renderer which extends this class.
4349
*
4450
* @return string HTML representation of the differences.
4551
*/
@@ -101,70 +107,68 @@ public function renderHtml(array $changes, object $htmlRenderer): string
101107
*/
102108
public function render()
103109
{
104-
// "old" & "new" are copied so change markers can be added without modifying the original sequences.
105-
$old = $this->diff->getOld();
106-
$new = $this->diff->getNew();
110+
// The old and New texts are copied so change markers can be added without modifying the original sequences.
111+
$oldText = $this->diff->getOld();
112+
$newText = $this->diff->getNew();
107113

108114
$changes = [];
109-
$opCodes = $this->diff->getGroupedOpcodes();
110115

111-
foreach ($opCodes as $group) {
112-
$blocks = [];
113-
$lastTag = null;
114-
$lastBlock = 0;
116+
foreach ($this->diff->getGroupedOpcodes() as $group) {
117+
$blocks = [];
118+
$this->lastTag = null;
119+
115120
foreach ($group as $code) {
116-
list($tag, $i1, $i2, $j1, $j2) = $code;
117-
118-
if ($tag == 'replace' && $i2 - $i1 == $j2 - $j1) {
119-
for ($i = 0; $i < ($i2 - $i1); ++$i) {
120-
$fromLine = $old[$i1 + $i];
121-
$toLine = $new[$j1 + $i];
122-
123-
list($start, $end) = $this->getChangeExtent($fromLine, $toLine);
124-
if ($start != 0 || $end != 0) {
125-
$realEnd = mb_strlen($fromLine) + $end;
126-
$fromLine = mb_substr($fromLine, 0, $start) . "\0" .
127-
mb_substr($fromLine, $start, $realEnd - $start) . "\1" .
128-
mb_substr($fromLine, $realEnd);
129-
130-
$realEnd = mb_strlen($toLine) + $end;
131-
$toLine = mb_substr($toLine, 0, $start) . "\0" .
132-
mb_substr($toLine, $start, $realEnd - $start) . "\1" .
133-
mb_substr($toLine, $realEnd);
134-
135-
$old[$i1 + $i] = $fromLine;
136-
$new[$j1 + $i] = $toLine;
137-
}
138-
}
121+
list($tag, $startOld, $endOld, $startNew, $endNew) = $code;
122+
/**
123+
* $code is an array describing a op-code which includes:
124+
* 0 - The type of tag (as described below) for the op code.
125+
* 1 - The beginning line in the first sequence.
126+
* 2 - The end line in the first sequence.
127+
* 3 - The beginning line in the second sequence.
128+
* 4 - The end line in the second sequence.
129+
*
130+
* The different types of tags include:
131+
* replace - The string from $startOld to $endOld in $oldText should be replaced by
132+
* the string in $newText from $startNew to $endNew.
133+
* delete - The string in $oldText from $startOld to $endNew should be deleted.
134+
* insert - The string in $newText from $startNew to $endNew should be inserted at $startOld in
135+
* $oldText.
136+
* equal - The two strings with the specified ranges are equal.
137+
*/
138+
139+
$blockSizeOld = $endOld - $startOld;
140+
$blockSizeNew = $endNew - $startNew;
141+
142+
if (($tag == 'replace') && ($blockSizeOld == $blockSizeNew)) {
143+
// Inline differences between old and new block.
144+
$this->markInlineChange($oldText, $newText, $startOld, $endOld, $startNew);
139145
}
140146

141-
if ($tag != $lastTag) {
142-
$blocks[] = $this->getDefaultArray($tag, $i1, $j1);
143-
$lastBlock = count($blocks) - 1;
144-
}
147+
$lastBlock = $this->appendChangesArray($blocks, $tag, $startOld, $startNew);
145148

146-
$lastTag = $tag;
149+
// Extract the block from both the old and new text and format each line.
150+
$oldBlock = $this->formatLines(array_slice($oldText, $startOld, $blockSizeOld));
151+
$newBlock = $this->formatLines(array_slice($newText, $startNew, $blockSizeNew));
147152

148153
if ($tag == 'equal') {
149-
$lines = array_slice($old, $i1, ($i2 - $i1));
150-
$blocks[$lastBlock]['base']['lines'] += $this->formatLines($lines);
151-
152-
$lines = array_slice($new, $j1, ($j2 - $j1));
153-
$blocks[$lastBlock]['changed']['lines'] += $this->formatLines($lines);
154-
} else {
155-
if ($tag == 'replace' || $tag == 'delete') {
156-
$lines = array_slice($old, $i1, ($i2 - $i1));
157-
$lines = $this->formatLines($lines);
158-
$lines = str_replace(array("\0", "\1"), array('<del>', '</del>'), $lines);
159-
$blocks[$lastBlock]['base']['lines'] += $lines;
160-
}
161-
162-
if ($tag == 'replace' || $tag == 'insert') {
163-
$lines = array_slice($new, $j1, ($j2 - $j1));
164-
$lines = $this->formatLines($lines);
165-
$lines = str_replace(array("\0", "\1"), array('<ins>', '</ins>'), $lines);
166-
$blocks[$lastBlock]['changed']['lines'] += $lines;
167-
}
154+
// Old block equals New block
155+
$blocks[$lastBlock]['base']['lines'] += $oldBlock;
156+
$blocks[$lastBlock]['changed']['lines'] += $newBlock;
157+
continue;
158+
}
159+
160+
if ($tag == 'replace' || $tag == 'delete') {
161+
// Inline differences or old block doesn't exist in the new text.
162+
// Replace the markers, which where added above, by HTML delete tags.
163+
$oldBlock = str_replace(["\0", "\1"], ['<del>', '</del>'], $oldBlock);
164+
$blocks[$lastBlock]['base']['lines'] += $oldBlock;
165+
}
166+
167+
if ($tag == 'replace' || $tag == 'insert') {
168+
// Inline differences or the new block doesn't exist in the old text.
169+
// Replace the markers, which where added above, by HTML insert tags.
170+
$newBlock = str_replace(["\0", "\1"], ['<ins>', '</ins>'], $newBlock);
171+
$blocks[$lastBlock]['changed']['lines'] += $newBlock;
168172
}
169173
}
170174
$changes[] = $blocks;
@@ -174,32 +178,89 @@ public function render()
174178
}
175179

176180
/**
177-
* Determine where changes in two strings begin and where they end.
181+
* Add markers around inline changes between old and new text.
178182
*
179-
* This returns an array.
180-
* The first value defines the first (starting at 0) character from start of the old string which is different.
181-
* The second value defines the last character from end of the old string which is different.
183+
* Each line of the old and new text is evaluated.
184+
* When a line of old differs from the same line of new, a marker is inserted into both lines, just before the first
185+
* different character. A second marker is added just behind the last character which differs from each other.
186+
*
187+
* E.g.
188+
* <pre>
189+
* 1234567
190+
* OLd => "abcdefg" Start marker inserted at position 3
191+
* New => "ab123fg" End marker inserted at position 6
192+
* </pre>
193+
*
194+
* @param array $oldText Collection of lines of old text.
195+
* @param array $newText Collection of lines of new text.
196+
* @param int $startOld First line of the block in old to replace.
197+
* @param int $endOld last line of the block in old to replace.
198+
* @param int $startNew First line of the block in new to replace.
199+
*/
200+
private function markInlineChange(array &$oldText, array &$newText, $startOld, $endOld, $startNew)
201+
{
202+
for ($i = 0; $i < ($endOld - $startOld); ++$i) {
203+
// Check each line in the block for differences.
204+
$oldString = $oldText[$startOld + $i];
205+
$newString = $newText[$startNew + $i];
206+
207+
// Determine the start and end position of the line difference.
208+
list($start, $end) = $this->getInlineChange($oldString, $newString);
209+
if ($start != 0 || $end != 0) {
210+
// Changes between the lines exist.
211+
// Add markers around the changed character sequence in the old string.
212+
$sequenceEnd = mb_strlen($oldString) + $end;
213+
$oldString =
214+
mb_substr($oldString, 0, $start) . "\0" .
215+
mb_substr($oldString, $start, $sequenceEnd - $start) . "\1" .
216+
mb_substr($oldString, $sequenceEnd);
217+
218+
// Add markers around the changed character sequence in the new string.
219+
$sequenceEnd = mb_strlen($newString) + $end;
220+
$newString =
221+
mb_substr($newString, 0, $start) . "\0" .
222+
mb_substr($newString, $start, $sequenceEnd - $start) . "\1" .
223+
mb_substr($newString, $sequenceEnd);
224+
225+
// Overwrite the strings in the old and new text so the changed lines include the markers.
226+
$oldText[$startOld + $i] = $oldString;
227+
$newText[$startNew + $i] = $newString;
228+
}
229+
}
230+
}
231+
232+
/**
233+
* Determine where changes between two strings begin and where they end.
234+
*
235+
* This returns a two elements array.
236+
* The first element defines the first (starting at 0) character from the start of the old string which is
237+
* different.
238+
* The second element defines the last (starting at -0) character from the end of the old string which is different.
182239
*
183240
*
184241
* @param string $oldString The first string to compare.
185242
* @param string $newString The second string to compare.
186243
*
187244
* @return array Array containing the starting position (0 by default) and the ending position (-1 by default)
188245
*/
189-
private function getChangeExtent(string $oldString, string $newString): array
246+
private function getInlineChange(string $oldString, string $newString): array
190247
{
191248
$start = 0;
192249
$limit = min(mb_strlen($oldString), mb_strlen($newString));
193250

194-
// Find first difference.
251+
// Find the position of the first character which is different between old and new.
252+
// Starts at the begin of the strings.
253+
// Stops at the end of the shortest string.
195254
while ($start < $limit && mb_substr($oldString, $start, 1) == mb_substr($newString, $start, 1)) {
196255
++$start;
197256
}
198257

199-
$end = -1;
200-
$limit = $limit - $start;
258+
$end = -1;
259+
$limit = $limit - $start;
201260

202-
// Find last difference.
261+
// Find the position of the last character which is different between old and new.
262+
// Starts at the end of the shortest string.
263+
// Stops just before the last different character.
203264
while (-$end <= $limit && mb_substr($oldString, $end, 1) == mb_substr($newString, $end, 1)) {
204265
--$end;
205266
}
@@ -210,6 +271,41 @@ private function getChangeExtent(string $oldString, string $newString): array
210271
];
211272
}
212273

274+
/**
275+
* Helper function that will fill the changes-array for the renderer with default values.
276+
* Every time a operation changes (specified by $tag) , a new element will be appended to this array.
277+
*
278+
* The index of the last element of the array is always returned.
279+
*
280+
* @param array $blocks The array which keeps the changes for the HTML renderer.
281+
* @param string $tag Kind of difference.
282+
* @param integer $lineInOld Start of block in "old".
283+
* @param integer $lineInNew Start of block in "new".
284+
*
285+
* @return int The index of the last element.
286+
*/
287+
private function appendChangesArray(array &$blocks, string $tag, int $lineInOld, int $lineInNew): int
288+
{
289+
if ($tag == $this->lastTag) {
290+
return count($blocks) - 1;
291+
}
292+
293+
$blocks[] = [
294+
'tag' => $tag,
295+
'base' => [
296+
'offset' => $lineInOld,
297+
'lines' => []
298+
],
299+
'changed' => [
300+
'offset' => $lineInNew,
301+
'lines' => []
302+
]
303+
];
304+
305+
$this->lastTag = $tag;
306+
return count($blocks) - 1;
307+
}
308+
213309
/**
214310
* Format a series of strings which are suitable for output in a HTML rendered diff.
215311
*
@@ -226,7 +322,7 @@ protected function formatLines(array $strings): array
226322
// Replace tabs with spaces.
227323
$strings = array_map(
228324
function ($item) {
229-
return $this->expandTabs($item);
325+
return str_replace("\t", str_repeat(' ', $this->options['tabSize']), $item);
230326
},
231327
$strings
232328
);
@@ -235,12 +331,12 @@ function ($item) {
235331
// Convert special characters to HTML entities
236332
$strings = array_map(
237333
function ($item) {
238-
return $this->htmlSafe($item);
334+
return htmlspecialchars($item, ENT_NOQUOTES, 'UTF-8');
239335
},
240336
$strings
241337
);
242338

243-
// Replace leading spaces of a line with HTML enities.
339+
// Replace leading spaces of a line with HTML entities.
244340
foreach ($strings as &$line) {
245341
$line = preg_replace_callback(
246342
'/(^[ \0\1]*)/',
@@ -254,52 +350,4 @@ function ($matches) {
254350

255351
return $strings;
256352
}
257-
258-
/**
259-
* Replace tabs in a string with an amount of spaces as defined by the tabSize option of this class.
260-
*
261-
* @param string $line The string which contains tabs to convert.
262-
*
263-
* @return string The line with the tabs converted to spaces.
264-
*/
265-
private function expandTabs(string $line): string
266-
{
267-
return str_replace("\t", str_repeat(' ', $this->options['tabSize']), $line);
268-
}
269-
270-
/**
271-
* Make a string HTML safe for output on a page.
272-
*
273-
* @param string $string The string to make safe.
274-
*
275-
* @return string The string with the HTML characters replaced by entities.
276-
*/
277-
private function htmlSafe(string $string): string
278-
{
279-
return htmlspecialchars($string, ENT_NOQUOTES, 'UTF-8');
280-
}
281-
282-
/**
283-
* Helper function that provides an array for the renderer with default values for the changes to render.
284-
*
285-
* @param string $tag Kind of difference.
286-
* @param integer $lineInOld Start of block in "old".
287-
* @param integer $lineInNew Start of block in "new".
288-
*
289-
* @return array
290-
*/
291-
private function getDefaultArray(string $tag, int $lineInOld, int $lineInNew): array
292-
{
293-
return [
294-
'tag' => $tag,
295-
'base' => [
296-
'offset' => $lineInOld,
297-
'lines' => []
298-
],
299-
'changed' => [
300-
'offset' => $lineInNew,
301-
'lines' => []
302-
]
303-
];
304-
}
305353
}

0 commit comments

Comments
 (0)