Search by

phil / text-diff

phil

Differences between two texts: Myers' algorithm over words, lines, paragraphs or HTML that keeps its tags whole, and block by block over UJO documents, as HTML with ins and del or as a list of changes

0.1.0-alpha 2026-11-11 04:05 UTC

This package is auto-updated.

Last update: 2026-11-11 06:17:19 UTC


README

What changed between two texts, word by word, paragraph by paragraph, or block by block — shown in HTML or listed.

phil/text-diff compares two texts and gives the shortest set of deletions and insertions that turns the old one into the new one. It cuts texts into words, lines or paragraphs; it reads HTML without ever cutting a tag, so that the difference of two pages is itself a well-formed page; and it compares two documents in UJO (Universal JSON Output) — the block document format read by phil/ujo — block by block, by block identifier, then field by field inside each block that changed. Each difference comes out as HTML with <ins> and <del>, or as a structured list of changes with their offsets.

It is for any PHP application that keeps versions of what people write and must show what moved between two of them: the revision history of an article, a page or a wiki, the review of a proposed edit, the segments of a translation whose source changed, the audit of a document before it is published again. phil/content-bundle keeps the versions of its contents; this library is how an application shows their differences.

It is a plain PHP library whose only requirements are ext-mbstring and phil/ujo. The comparison is Eugene W. Myers' O(ND) difference algorithm, run after the common start and end of both texts are set aside, and bounded: past a number of edits — a thousand by default — the rest is shown as replaced whole, which keeps time and memory in check on texts rewritten from end to end. The output is deterministic.

The design is small: a Tokenizer cuts a text into tokens that give it back byte for byte; Myers finds the script; a Diff holds its Operations and lists its Changes; HtmlFormatter writes the HTML. For documents, DocumentDiffer pairs the blocks of each level by identifier into BlockChanges with their FieldChanges, gathered in a DocumentDiff that DocumentFormatter renders through phil/ujo. TextDiff puts them together.

Status

Part State
Myers' algorithm with common start and end set aside and a bound of edits Implemented and tested
Words, lines and paragraphs; HTML with tags, comments and character references whole Implemented and tested
Scripts written without spaces (Han, kana, Thai, Lao, Khmer, Myanmar) compared character by character Implemented and tested
White space between two changes joined to them; deletions before insertions in each change Implemented and tested
HTML of a difference: plain text escaped, HTML keeping the structure of the new text Implemented and tested
Change list: insertions, deletions and replacements with their offsets in code points Implemented and tested
UJO documents: blocks paired by identifier at every level, kept, changed, moved, added or removed; values compared by path Implemented and tested
HTML of a document difference, each block rendered by phil/ujo and marked Implemented and tested
Lists of values other than blocks aligned by content rather than by position Not started: they are compared by index

Documentation

Document What it covers
Installation Requirements and installing.
Comparing texts TextDiff, the tokenizers, operations, changes and the bound of edits.
HTML output HtmlFormatter: plain text and HTML, what is marked and what is kept, styling.
Comparing documents UJO documents block by block: pairing, moves, field paths, nested blocks, HTML.
Versions of contents Showing the differences between two versions kept by phil/content-bundle.
Exceptions TextDiffException and its two exceptions.
Extension points The ports, arguments and data an application chooses or replaces.
Testing The library's tests and mutation bar, and how to test code that shows differences.
Reference Every type of the library, with its role.

Quick start

composer require phil/text-diff
use Phil\TextDiff\TextDiff;

$differ = new TextDiff();

echo $differ->toHtml($differ->words('The quick brown fox', 'The slow brown dog'));
// The <del>quick</del><ins>slow</ins> brown <del>fox</del><ins>dog</ins>

echo $differ->toHtml($differ->html('<p>Hello <b>big</b> world</p>', '<p>Hello <i>small</i> world!</p>'));
// <p>Hello <del>big</del><i><ins>small</ins></i> world<ins>!</ins></p>

foreach ($differ->paragraphs($before, $after)->changes() as $change) {
    printf("%s at %d: %s -> %s\n", $change->kind->value, $change->oldOffset, $change->old, $change->new);
}
use Phil\Ujo\Ujo;

$ujo = new Ujo();
$diff = $differ->documents($ujo->decode($oldJson), $ujo->decode($newJson));

echo $differ->documentToHtml($diff);

Requirements

PHP 8.5 or later, ext-mbstring and phil/ujo.

Contributing

Contributions are welcome on the PHIL monorepo: read the contribution guide and the code of conduct. Report a vulnerability privately, as the security policy explains, never in a public issue.

License

MIT, © Philippe Gamache, doing business as Le Gnome Archiviste; a Parler Haut Interagir Librement project. See LICENSE.