jfcherng / php-diff
A comprehensive library for generating differences between two strings in multiple formats (unified, side by side HTML etc).
Fund package maintenance!
www.paypal.me/jfcherng/5usd
Installs: 2 990 253
Dependents: 34
Suggesters: 0
Security: 0
Stars: 430
Watchers: 11
Forks: 51
Open Issues: 6
Requires
- php: >=8.1
- jfcherng/php-color-output: ^3
- jfcherng/php-mb-string: ^2
- jfcherng/php-sequence-matcher: ^4.0.2
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3
- phan/phan: ^5
- phpunit/phpunit: ^10
- squizlabs/php_codesniffer: ^3.7
- 7.0.0-alpha.2
- 7.0.0-alpha.1
- v6.x-dev
- 6.16.2
- 6.16.1
- 6.16.0
- 6.15.3
- 6.15.2
- 6.15.1
- 6.15.0
- 6.14.2
- 6.14.1
- 6.14.0
- 6.13.1
- 6.13.0
- 6.12.1
- 6.12.0
- 6.11.6
- 6.11.5
- 6.11.4
- 6.11.3
- 6.11.2
- 6.11.1
- 6.11.0
- 6.10.17
- 6.10.16
- 6.10.15
- 6.10.14
- 6.10.13
- 6.10.12
- 6.10.11
- 6.10.10
- 6.10.9
- 6.10.8
- 6.10.7
- 6.10.6
- 6.10.5
- 6.10.4
- 6.10.3
- 6.10.2
- 6.10.1
- 6.10.0
- 6.9.0
- 6.8.0
- 6.7.7
- 6.7.6
- 6.7.5
- 6.7.4
- 6.7.3
- 6.7.2
- 6.7.1
- 6.7.0
- 6.6.4
- 6.6.3
- 6.6.2
- 6.6.1
- 6.6.0
- 6.5.7
- 6.5.6
- 6.5.5
- 6.5.4
- 6.5.3
- 6.5.2
- 6.5.1
- 6.5.0
- 6.4.7
- 6.4.6
- 6.4.5
- 6.4.4
- 6.4.3
- 6.4.2
- 6.4.1
- 6.4.0
- 6.3.2
- 6.3.1
- 6.3.0
- 6.2.1
- 6.2.0
- 6.1.2
- 6.1.1
- 6.1.0
- 6.0.1
- 6.0.0
- v5.x-dev
- 5.2.2
- 5.2.1
- 5.2.0
- 5.1.1
- 5.1.0
- 5.0.2
- 5.0.1
- 5.0.0
- v4.x-dev
- 4.2.3
- 4.2.2
- 4.2.1
- 4.2.0
- 4.1.9
- 4.1.8
- 4.1.7
- 4.1.5
- 4.1.4
- 4.1.3
- 4.1.2
- 4.1.1
- 4.1.0
- 4.0.1
- 4.0.0
- 3.3.0
- 3.2.6
- 3.2.5
- 3.2.4
- 3.2.3
- 3.2.2
- 3.2.1
- 3.2.0
- 3.1.4
- 3.1.3
- 3.1.2
- 3.1.1
- 3.1.0
- 3.0.0
- 2.2.0
- 2.1.23
- 2.1.22
- 2.1.21
- 2.1.20
- 2.1.19
- 2.1.18
- 2.1.17
- 2.1.16
- 2.1.15
- 2.1.14
- 2.1.13
- 2.1.12
- 2.1.11
- 2.1.10
- 2.1.9
- 2.1.8
- 2.1.7
- 2.1.6
- 2.1.5
- 2.1.4
- 2.1.3
- 2.1.2
- 2.1.1
- 2.1.0
- 2.0.3
- 2.0.2
- 2.0.1
- 2.0.0
- dev-dependabot/composer/symfony/process-5.4.46
- dev-v6-php71
- dev-exp-php81
This package is auto-updated.
Last update: 2024-12-07 06:00:01 UTC
README
A comprehensive library for generating diff between two strings.
Introduction
Generated diff can be rendered in all of the standard formats including:
Text renderers:
- Context
- Json (plain text)
- Unified
HTML renderers:
- Combined
- Inline
- Json (HTML)
- Side by Side
Note that for HTML rendered results, you have to add CSS for a better visualization.
You may modify one from example/diff-table.css
or write your own from zero.
If you are okay with the default CSS, there is \Jfcherng\Diff\DiffHelper::getStyleSheet()
which can be used to get the content of the example/diff-table.css
.
Requirements
Installation
This package is available on Packagist
by the name of jfcherng/php-diff.
composer require jfcherng/php-diff
Example
See files and readme in the example/ directory.
<?php include __DIR__ . '/vendor/autoload.php'; use Jfcherng\Diff\Differ; use Jfcherng\Diff\DiffHelper; use Jfcherng\Diff\Factory\RendererFactory; use Jfcherng\Diff\Renderer\RendererConstant; $oldFile = __DIR__ . '/example/old_file.txt'; $newFile = __DIR__ . '/example/new_file.txt'; $old = 'This is the old string.'; $new = 'And this is the new one.'; // renderer class name: // Text renderers: Context, JsonText, Unified // HTML renderers: Combined, Inline, JsonHtml, SideBySide $rendererName = 'Unified'; // the Diff class options $differOptions = [ // show how many neighbor lines // Differ::CONTEXT_ALL can be used to show the whole file 'context' => 3, // ignore case difference 'ignoreCase' => false, // ignore line ending difference 'ignoreLineEnding' => false, // ignore whitespace difference 'ignoreWhitespace' => false, // if the input sequence is too long, it will just gives up (especially for char-level diff) 'lengthLimit' => 2000, // if truthy, when inputs are identical, the whole inputs will be rendered in the output 'fullContextIfIdentical' => false, ]; // the renderer class options $rendererOptions = [ // how detailed the rendered HTML in-line diff is? (none, line, word, char) 'detailLevel' => 'line', // renderer language: eng, cht, chs, jpn, ... // or an array which has the same keys with a language file // check the "Custom Language" section in the readme for more advanced usage 'language' => 'eng', // show line numbers in HTML renderers 'lineNumbers' => true, // show a separator between different diff hunks in HTML renderers 'separateBlock' => true, // show the (table) header 'showHeader' => true, // the frontend HTML could use CSS "white-space: pre;" to visualize consecutive whitespaces // but if you want to visualize them in the backend with " ", you can set this to true 'spacesToNbsp' => false, // HTML renderer tab width (negative = do not convert into spaces) 'tabSize' => 4, // this option is currently only for the Combined renderer. // it determines whether a replace-type block should be merged or not // depending on the content changed ratio, which values between 0 and 1. 'mergeThreshold' => 0.8, // this option is currently only for the Unified and the Context renderers. // RendererConstant::CLI_COLOR_AUTO = colorize the output if possible (default) // RendererConstant::CLI_COLOR_ENABLE = force to colorize the output // RendererConstant::CLI_COLOR_DISABLE = force not to colorize the output 'cliColorization' => RendererConstant::CLI_COLOR_AUTO, // this option is currently only for the Json renderer. // internally, ops (tags) are all int type but this is not good for human reading. // set this to "true" to convert them into string form before outputting. 'outputTagAsString' => false, // this option is currently only for the Json renderer. // it controls how the output JSON is formatted. // see available options on https://www.php.net/manual/en/function.json-encode.php 'jsonEncodeFlags' => \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE, // this option is currently effective when the "detailLevel" is "word" // characters listed in this array can be used to make diff segments into a whole // for example, making "<del>good</del>-<del>looking</del>" into "<del>good-looking</del>" // this should bring better readability but set this to empty array if you do not want it 'wordGlues' => [' ', '-'], // change this value to a string as the returned diff if the two input strings are identical 'resultForIdenticals' => null, // extra HTML classes added to the DOM of the diff container 'wrapperClasses' => ['diff-wrapper'], ]; // one-line simply compare two files $result = DiffHelper::calculateFiles($oldFile, $newFile, $rendererName, $differOptions, $rendererOptions); // one-line simply compare two strings $result = DiffHelper::calculate($old, $new, $rendererName, $differOptions, $rendererOptions); // or even shorter if you are happy with default options $result = DiffHelper::calculate($old, $new, $rendererName); // custom usage $differ = new Differ(explode("\n", $old), explode("\n", $new), $differOptions); $renderer = RendererFactory::make($rendererName, $rendererOptions); // or your own renderer object $result = $renderer->render($differ); // use the JSON result to render in HTML $jsonResult = DiffHelper::calculate($old, $new, 'Json'); // may store the JSON result in your database $htmlRenderer = RendererFactory::make('Inline', $rendererOptions); $result = $htmlRenderer->renderArray(json_decode($jsonResult, true));
Rendered Results
HTML Diff In-line Detailed Rendering
Renderer: Inline
<?php $rendererOptions = ['detailLevel' => 'line'];
Renderer: Side By Side
<?php $rendererOptions = ['detailLevel' => 'line'];
Renderer: Combined
<?php $rendererOptions = ['detailLevel' => 'word'];
This renderer is suitable for articles and always has no line number information.
Renderer: Unified
About the Unified
diff format: https://en.wikipedia.org/wiki/Diff#Unified_format
@@ -1,3 +1,4 @@ -<p>Hello World!</p> +<div>Hello World!</div> ~~~~~~~~~~~~~~~~~~~ +Let's add a new line here. X @@ -7,6 +8,5 @@ N -Do you know in Chinese, "金槍魚罐頭" means tuna can. +Do you know in Japanese, "魚の缶詰" means fish can. This is just a useless line. G -// remember to delete this line Say hello to my neighbors.
Renderer: Context
About the Context
diff format: https://en.wikipedia.org/wiki/Diff#Context_format
Click to expand
*************** *** 1,3 **** ! <p>Hello World!</p> ~~~~~~~~~~~~~~~~~~~ X --- 1,4 ---- ! <div>Hello World!</div> ~~~~~~~~~~~~~~~~~~~ + Let's add a new line here. X *************** *** 7,12 **** N ! Do you know in Chinese, "金槍魚罐頭" means tuna can. This is just a useless line. G - // remember to delete this line Say hello to my neighbors. --- 8,12 ---- N ! Do you know in Japanese, "魚の缶詰" means fish can. This is just a useless line. G Say hello to my neighbors.
Renderer: Text JSON
This renderer has no detailed diff.
Click to expand
[ [ { "tag": "rep", "old": { "offset": 0, "lines": ["<p>Hello World! Good-looking.</p>"] }, "new": { "offset": 0, "lines": ["<div>Hello World! Bad-tempered.</div>"] } }, { "tag": "eq", "old": { "offset": 1, "lines": ["~~~~~~~~~~~~~~~~~~~"] }, "new": { "offset": 1, "lines": ["~~~~~~~~~~~~~~~~~~~"] } }, { "tag": "ins", "old": { "offset": 2, "lines": [] }, "new": { "offset": 2, "lines": ["Let's add a new line here."] } }, { "tag": "eq", "old": { "offset": 2, "lines": ["X"] }, "new": { "offset": 3, "lines": ["X"] } } ], [ { "tag": "eq", "old": { "offset": 6, "lines": ["N"] }, "new": { "offset": 7, "lines": ["N"] } }, { "tag": "rep", "old": { "offset": 7, "lines": ["Do you know in Chinese, \"金槍魚罐頭\" means tuna can."] }, "new": { "offset": 8, "lines": ["Do you know in Japanese, \"魚の缶詰\" means fish can."] } }, { "tag": "eq", "old": { "offset": 8, "lines": ["\t \tTab visualization test.", "G"] }, "new": { "offset": 9, "lines": ["\t \tTab visualization test.", "G"] } }, { "tag": "del", "old": { "offset": 10, "lines": ["// remember to delete this line"] }, "new": { "offset": 11, "lines": [] } }, { "tag": "eq", "old": { "offset": 11, "lines": ["Say hello to my neighbors."] }, "new": { "offset": 11, "lines": ["Say hello to my neighbors."] } } ], [ { "tag": "eq", "old": { "offset": 14, "lines": ["B"] }, "new": { "offset": 14, "lines": ["B"] } }, { "tag": "rep", "old": { "offset": 15, "lines": ["Donec rutrum."] }, "new": { "offset": 15, "lines": ["Donec rutrum test.", "There is a new inserted line."] } }, { "tag": "eq", "old": { "offset": 16, "lines": ["C"] }, "new": { "offset": 17, "lines": ["C"] } }, { "tag": "rep", "old": { "offset": 17, "lines": ["Sed dictum lorem ipsum."] }, "new": { "offset": 18, "lines": ["Sed dolor lorem ipsum hendrerit."] } }, { "tag": "eq", "old": { "offset": 18, "lines": [""] }, "new": { "offset": 19, "lines": [""] } } ] ]
Renderer: HTML JSON
For a "tag": "rep" (8)
block, this renderer has HTML-style detailed diff.
If you don't need those detailed diff, consider using the JsonText
renderer.
Click to expand
[ [ { "tag": "rep", "old": { "offset": 0, "lines": ["<<del>p>Hello World! Good-looking.</p</del>>"] }, "new": { "offset": 0, "lines": ["<<ins>div>Hello World! Bad-tempered.</div</ins>>"] } }, { "tag": "eq", "old": { "offset": 1, "lines": ["~~~~~~~~~~~~~~~~~~~"] }, "new": { "offset": 1, "lines": ["~~~~~~~~~~~~~~~~~~~"] } }, { "tag": "ins", "old": { "offset": 2, "lines": [""] }, "new": { "offset": 2, "lines": ["Let's add a new line here."] } }, { "tag": "eq", "old": { "offset": 2, "lines": ["X"] }, "new": { "offset": 3, "lines": ["X"] } } ], [ { "tag": "eq", "old": { "offset": 6, "lines": ["N"] }, "new": { "offset": 7, "lines": ["N"] } }, { "tag": "rep", "old": { "offset": 7, "lines": ["Do you know in <del>Chinese, \"金槍魚罐頭\" means tuna</del> can."] }, "new": { "offset": 8, "lines": ["Do you know in <ins>Japanese, \"魚の缶詰\" means fish</ins> can."] } }, { "tag": "eq", "old": { "offset": 8, "lines": ["\t \tTab visualization test.", "G"] }, "new": { "offset": 9, "lines": ["\t \tTab visualization test.", "G"] } }, { "tag": "del", "old": { "offset": 10, "lines": ["// remember to delete this line"] }, "new": { "offset": 11, "lines": [""] } }, { "tag": "eq", "old": { "offset": 11, "lines": ["Say hello to my neighbors."] }, "new": { "offset": 11, "lines": ["Say hello to my neighbors."] } } ], [ { "tag": "eq", "old": { "offset": 14, "lines": ["B"] }, "new": { "offset": 14, "lines": ["B"] } }, { "tag": "rep", "old": { "offset": 15, "lines": ["Donec rutrum."] }, "new": { "offset": 15, "lines": ["Donec rutrum test.", "There is a new inserted line."] } }, { "tag": "eq", "old": { "offset": 16, "lines": ["C"] }, "new": { "offset": 17, "lines": ["C"] } }, { "tag": "rep", "old": { "offset": 17, "lines": ["Sed d<del>ictum lorem ipsum</del>."] }, "new": { "offset": 18, "lines": ["Sed d<ins>olor lorem ipsum hendrerit</ins>."] } }, { "tag": "eq", "old": { "offset": 18, "lines": [""] }, "new": { "offset": 19, "lines": [""] } } ] ]
Custom Language
Override an Existing Language
If you just want to override some translations of an existing language...
$rendererOptions = [ 'language' => [ // use English as the base language 'eng', // your custom overrides [ // use "Diff" as the new value of the "differences" key 'differences' => 'Diff', ], // maybe more overrides if you somehow need them... ], ]
Acknowledgment
This package is built on the top of chrisboulton/php-diff initially. But the original repository looks like no longer maintained. Here have been quite lots of rewrites and new features since then, hence I re-started this as a new package for better visibility.