Initial commit

This commit is contained in:
2018-04-02 08:07:38 +02:00
commit 7330c1ed3e
2054 changed files with 405203 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
FineDiff
========
Originally written by Raymond Hill ([https://github.com/gorhill/PHP-FineDiff](https://github.com/gorhill/PHP-FineDiff)) FineDiff has been tweaked to bring it up to date with the modern world. That means documented, nicely formatted, tested code that can be easily extended.
[![Build Status](https://travis-ci.org/cogpowered/FineDiff.png?branch=master)](https://travis-ci.org/cogpowered/FineDiff)
Installation
------------
**Composer**
The preferred way of using FineDiff is through [Composer](http://getcomposer.org).
Add the following to your composer.json file:
```json
{
"require": {
"cogpowered/finediff": "0.3.*"
}
}
```
Upgrading
---------
**0.3.x** introduces a backwards incompatible version, so if you have stored opcodes do not upgrade!
`0.3.x` fixes a double encoding issue that generates a longer opcode than is needed.
Usage
-----
**Render HTML**
Render as HTML the difference between two strings:
```php
$diff = new cogpowered\FineDiff\Diff;
echo $diff->render('string one', 'string two');
```
This would then output:
```html
string <ins>tw</ins>o<del>ne</del>
```
You could change the granularity to `cogpowered\FineDiff\Granularity\Word` so the output is:
```html
string <del>one</del><ins>two</ins>
```
You do this by passing it into the Diff constructor:
```php
$granularity = new cogpowered\FineDiff\Granularity\Word;
$diff = new cogpowered\FineDiff\Diff($granularity);
```
**Grab opcode instructions**
Opcode instructions are what tell FineDiff how to change one string into another.
```php
$diff = new cogpowered\FineDiff\Diff;
echo $diff->getOpcodes('string one', 'string two');
```
This would then output:
```html
c7d3i3:two
```
Render text using the opcodes:
```php
$render = new cogpowered\FineDiff\Render\Text;
echo $render->process('string one', 'c7d3i3:two');
```
Would output:
```html
string two
```
Same with HTML:
```php
$render = new cogpowered\FineDiff\Render\Html;
echo $render->process('string one', 'c7d3i3:two');
```
License
-------
Copyright (c) 2011 Raymond Hill (http://raymondhill.net/blog/?p=441)
Copyright (c) 2013 Rob Crowe (http://cogpowered.com)
Licensed under The MIT License
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,27 @@
{
"name": "cogpowered/finediff",
"description": "PHP implementation of a Fine granularity Diff engine",
"homepage": "https://github.com/cogpowered/FineDiff",
"license": "MIT",
"keywords": ["finediff", "diff", "text", "string", "opcode"],
"authors": [
{
"name": "Raymond Hill"
},
{
"name": "Rob Crowe",
"email": "rob@cogpowered.com"
}
],
"type": "library",
"require": {
"php": ">=5.3.0"
},
"require-dev": {
"mockery/mockery": "*",
"phpunit/phpunit": "*"
},
"autoload": {
"psr-0": { "cogpowered\\FineDiff": "src/" }
}
}

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit
colors="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
stopOnError="false"
stopOnFailure="false"
stopOnIncomplete="false"
bootstrap="vendor/autoload.php">
<testsuites>
<testsuite name="Delimiters">
<directory suffix=".php">tests/Delimiters</directory>
</testsuite>
<testsuite name="Diff">
<directory suffix=".php">tests/Diff</directory>
</testsuite>
<testsuite name="Granularity">
<directory suffix=".php">tests/Granularity</directory>
</testsuite>
<testsuite name="Parser">
<directory suffix=".php">tests/Parser</directory>
</testsuite>
<testsuite name="Render">
<directory suffix=".php">tests/Render</directory>
</testsuite>
<testsuite name="Usage">
<directory suffix=".php">tests/Usage</directory>
</testsuite>
</testsuites>
<filter>
<blacklist>
<directory suffix=".php">PEAR_INSTALL_DIR</directory>
<directory suffix=".php">PHP_LIBDIR</directory>
<directory suffix=".php">vendor</directory>
</blacklist>
</filter>
</phpunit>

View File

@@ -0,0 +1,37 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff;
/**
* Used by classes implementing cogpowered\FineDiff\Granularity\GranularityInterface.
*
* Class is used more like an Enum type; the class can not be instantiated.
*/
abstract class Delimiters
{
const PARAGRAPH = "\n\r";
const SENTENCE = ".\n\r";
const WORD = " \t.\n\r";
const CHARACTER = "";
/**
* Do not allow this class to be instantiated.
*/
private function __construct() {}
}

View File

@@ -0,0 +1,164 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff;
use cogpowered\FineDiff\Granularity\GranularityInterface;
use cogpowered\FineDiff\Render\RendererInterface;
use cogpowered\FineDiff\Parser\ParserInterface;
use cogpowered\FineDiff\Granularity\Character;
use cogpowered\FineDiff\Render\Html;
use cogpowered\FineDiff\Parser\Parser;
/**
* Diff class.
*/
class Diff
{
/**
* @var cogpowered\FineDiff\Granularity\GranularityInterface
*/
protected $granularity;
/**
* @var cogpowered\FineDiff\Render\RendererInterface
*/
protected $renderer;
/**
* @var cogpowered\FineDiff\Parser\ParserInterface
*/
protected $parser;
/**
* Instantiate a new instance of Diff.
*
* @param cogpowered\FineDiff\Granularity\GranularityInterface $granularity Level of diff.
* @param cogpowered\FineDiff\Render\RenderInterface $renderer Diff renderer.
* @param cogpowered\FineDiff\Parser\ParserInterface $parser Parser used to generate opcodes.
*
* @throws cogpowered\FineDiff\Exceptions\GranularityCountException
* @throws cogpowered\FineDiff\Exceptions\OperationException
*/
public function __construct(GranularityInterface $granularity = null, RendererInterface $renderer = null, ParserInterface $parser = null)
{
// Set some sensible defaults
// Set the granularity of the diff
$this->granularity = ($granularity !== null) ? $granularity : new Character;
// Set the renderer to use when calling Diff::render
$this->renderer = ($renderer !== null) ? $renderer : new Html;
// Set the diff parser
$this->parser = ($parser !== null) ? $parser : new Parser($this->granularity);
}
/**
* Returns the granularity object used by the parser.
*
* @return @cogpowered\FineDiff\Granularity\GranularityInterface
*/
public function getGranularity()
{
return $this->parser->getGranularity();
}
/**
* Set the granularity level of the parser.
*
* @param cogpowered\FineDiff\Granularity\GranularityInterface $granularity
* @return void
*/
public function setGranularity(GranularityInterface $granularity)
{
$this->parser->setGranularity($granularity);
}
/**
* Get the render.
*
* @return cogpowered\FineDiff\Render\RendererInterface
*/
public function getRenderer()
{
return $this->renderer;
}
/**
* Set the renderer.
*
* @param cogpowered\FineDiff\Render\RendererInterface $renderer
* @return void
*/
public function setRenderer(RendererInterface $renderer)
{
$this->renderer = $renderer;
}
/**
* Get the parser responsible for generating the diff/opcodes.
*
* @return cogpowered\FineDiff\Parser\ParserInterface
*/
public function getParser()
{
return $this->parser;
}
/**
* Set the parser.
*
* @param cogpowered\FineDiff\Parser\ParserInterface $parser
* @return void
*/
public function setParser(ParserInterface $parser)
{
$this->parser = $parser;
}
/**
* Gets the diff / opcodes between two strings.
*
* Returns the opcode diff which can be used for example, to
* to generate a HTML report of the differences.
*
* @return cogpowered\FineDiff\Parser\Opcodes
*/
public function getOpcodes($from_text, $to_text)
{
return $this->parser->parse($from_text, $to_text);
}
/**
* Render the difference between two strings.
*
* By default will return the difference as HTML.
*
* @param string $from_text
* @param string $to_text
* @return string
*/
public function render($from_text, $to_text)
{
// First we need the opcodes
$opcodes = $this->getOpcodes($from_text, $to_text);
return $this->renderer->process($from_text, $opcodes);
}
}

View File

@@ -0,0 +1,24 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Exceptions;
/**
* A granularity must have at least one thing to match against. Thrown when this isn't the case.
*/
class GranularityCountException extends \Exception {}

View File

@@ -0,0 +1,24 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Exceptions;
/**
* Thrown when trying to set an opcode that doesn't implement cogpowered\FineDiff\Parser\Operations\OperationInterface.
*/
class OperationException extends \Exception {}

View File

@@ -0,0 +1,34 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Granularity;
use cogpowered\FineDiff\Delimiters;
/**
* Character level granularity.
*/
class Character extends Granularity
{
protected $delimiters = array(
Delimiters::PARAGRAPH,
Delimiters::SENTENCE,
Delimiters::WORD,
Delimiters::CHARACTER,
);
}

View File

@@ -0,0 +1,92 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Granularity;
/**
* Granularities should extend this class.
*/
abstract class Granularity implements GranularityInterface, \ArrayAccess, \Countable
{
/**
* @var array Extending granularities should override this.
*/
protected $delimiters = array();
/**
* @inheritdoc
*/
public function offsetExists($offset)
{
return isset($this->delimiters[$offset]);
}
/**
* @inheritdoc
*/
public function offsetGet($offset)
{
return isset($this->delimiters[$offset]) ? $this->delimiters[$offset] : null;
}
/**
* @inheritdoc
*/
public function offsetSet($offset, $value)
{
if (is_null($offset)) {
$this->delimiters[] = $value;
} else {
$this->delimiters[$offset] = $value;
}
}
/**
* @inheritdoc
*/
public function offsetUnset($offset)
{
unset($this->delimiters[$offset]);
}
/**
* Return the number of delimiters this granularity contains.
*
* @return int
*/
public function count()
{
return count($this->delimiters);
}
/**
* @inheritdoc
*/
public function getDelimiters()
{
return $this->delimiters;
}
/**
* @inheritdoc
*/
public function setDelimiters(array $delimiters)
{
$this->delimiters = $delimiters;
}
}

View File

@@ -0,0 +1,42 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Granularity;
interface GranularityInterface
{
public function offsetExists($offset);
public function offsetGet($offset);
public function offsetSet($offset, $value);
public function offsetUnset($offset);
/**
* Get the delimiters that make up the granularity.
*
* @return array
*/
public function getDelimiters();
/**
* Set the delimiters that make up the granularity.
*
* @param array $delimiters
* @return void
*/
public function setDelimiters(array $delimiters);
}

View File

@@ -0,0 +1,31 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Granularity;
use cogpowered\FineDiff\Delimiters;
/**
* Paragraph level granularity.
*/
class Paragraph extends Granularity
{
protected $delimiters = array(
Delimiters::PARAGRAPH,
);
}

View File

@@ -0,0 +1,32 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Granularity;
use cogpowered\FineDiff\Delimiters;
/**
* Sentence level granularity.
*/
class Sentence extends Granularity
{
protected $delimiters = array(
Delimiters::PARAGRAPH,
Delimiters::SENTENCE,
);
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Granularity;
use cogpowered\FineDiff\Delimiters;
/**
* Word level granularity.
*/
class Word extends Granularity
{
protected $delimiters = array(
Delimiters::PARAGRAPH,
Delimiters::SENTENCE,
Delimiters::WORD,
);
}

View File

@@ -0,0 +1,74 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Parser;
use cogpowered\FineDiff\Exceptions\OperationException;
/**
* Holds all the opcodes returned by the parser.
*/
class Opcodes implements OpcodesInterface
{
/**
* @var array Individual opcodes.
*/
protected $opcodes = array();
/**
* @inheritdoc
*/
public function getOpcodes()
{
return $this->opcodes;
}
/**
* @inheritdoc
*/
public function setOpcodes(array $opcodes)
{
$this->opcodes = array();
// Ensure that all elements of the array
// are of the correct type
foreach ($opcodes as $opcode) {
if (!is_a($opcode, 'cogpowered\FineDiff\Parser\Operations\OperationInterface')) {
throw new OperationException('Invalid opcode object');
}
$this->opcodes[] = $opcode->getOpcode();
}
}
/**
* @inheritdoc
*/
public function generate()
{
return implode('', $this->opcodes);
}
/**
* @inheritdoc
*/
public function __toString()
{
return $this->generate();
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Parser;
interface OpcodesInterface
{
/**
* Get the opcodes.
*
* @return array
*/
public function getOpcodes();
/**
* Set the opcodes for this parse.
*
* @param array $opcodes Elements must be an instance of cogpowered\FineDiff\Parser\Operations\OperationInterface.
* @throws cogpowered\FineDiff\Exceptions\OperationException
* @return void
*/
public function setOpcodes(array $opcodes);
/**
* Return the opcodes in a format that can then be rendered.
*
* @return string
*/
public function generate();
/**
* When object is cast to a string returns opcodes as string.
*
* @see Opcodes::generate
* @return string
*/
public function __toString();
}

View File

@@ -0,0 +1,74 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Parser\Operations;
/**
* Generates the opcode for a copy operation.
*/
class Copy implements OperationInterface
{
/**
* Set the initial length.
*
* @param int $len Length of string.
*/
public function __construct($len)
{
$this->len = $len;
}
/**
* @inheritdoc
*/
public function getFromLen()
{
return $this->len;
}
/**
* @inheritdoc
*/
public function getToLen()
{
return $this->len;
}
/**
* @inheritdoc
*/
public function getOpcode()
{
if ($this->len === 1) {
return 'c';
}
return "c{$this->len}";
}
/**
* Increase the length of the string.
*
* @param int $size Amount to increase the string length by.
* @return int New length
*/
public function increase($size)
{
return $this->len += $size;
}
}

View File

@@ -0,0 +1,63 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Parser\Operations;
/**
* Generates the opcode for a delete operation.
*/
class Delete implements OperationInterface
{
/**
* Set the initial length.
*
* @param int $len Length of string.
*/
public function __construct($len)
{
$this->fromLen = $len;
}
/**
* @inheritdoc
*/
public function getFromLen()
{
return $this->fromLen;
}
/**
* @inheritdoc
*/
public function getToLen()
{
return 0;
}
/**
* @inheritdoc
*/
public function getOpcode()
{
if ($this->fromLen === 1) {
return 'd';
}
return "d{$this->fromLen}";
}
}

View File

@@ -0,0 +1,73 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Parser\Operations;
/**
* Generates the opcode for a copy operation.
*/
class Insert implements OperationInterface
{
/**
* Sets the text that the operation is working with.
*
* @param string $text
*/
public function __construct($text)
{
$this->text = $text;
}
/**
* @inheritdoc
*/
public function getFromLen()
{
return 0;
}
/**
* @inheritdoc
*/
public function getToLen()
{
return strlen($this->text);
}
/**
* @inheritdoc
*/
public function getText()
{
return $this->text;
}
/**
* @inheritdoc
*/
public function getOpcode()
{
$to_len = strlen($this->text);
if ( $to_len === 1 ) {
return "i:{$this->text}";
}
return "i{$to_len}:{$this->text}";
}
}

View File

@@ -0,0 +1,37 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Parser\Operations;
interface OperationInterface
{
/**
* @return int
*/
public function getFromLen();
/**
* @return int
*/
public function getToLen();
/**
* @return string Opcode for this operation.
*/
public function getOpcode();
}

View File

@@ -0,0 +1,78 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Parser\Operations;
class Replace implements OperationInterface
{
/**
* @param int $fromLen
* @param string $text
*/
public function __construct($fromLen, $text)
{
$this->fromLen = $fromLen;
$this->text = $text;
}
/**
* @inheritdoc
*/
public function getFromLen()
{
return $this->fromLen;
}
/**
* @inheritdoc
*/
public function getToLen()
{
return strlen($this->text);
}
/**
* Get the text the operation is working with.
*
* @return string
*/
public function getText()
{
return $this->text;
}
/**
* @inheritdoc
*/
public function getOpcode()
{
if ($this->fromLen === 1) {
$del_opcode = 'd';
} else {
$del_opcode = "d{$this->fromLen}";
}
$to_len = strlen($this->text);
if ($to_len === 1) {
return "{$del_opcode}i:{$this->text}";
}
return "{$del_opcode}i{$to_len}:{$this->text}";
}
}

View File

@@ -0,0 +1,477 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Parser;
use cogpowered\FineDiff\Granularity\GranularityInterface;
use cogpowered\FineDiff\Exceptions\GranularityCountException;
use cogpowered\FineDiff\Parser\Operations\Copy;
use cogpowered\FineDiff\Parser\Operations\Delete;
use cogpowered\FineDiff\Parser\Operations\Insert;
use cogpowered\FineDiff\Parser\Operations\Replace;
/**
* Generates a set of instructions to convert one string to another.
*/
class Parser implements ParserInterface
{
/**
* @var cogpowered\FineDiff\GranularityInterface
*/
protected $granularity;
/**
* @var cogpowered\FineDiff\Parser\OpcodesInterface
*/
protected $opcodes;
/**
* @var string Text we are comparing against.
*/
protected $from_text;
/**
* @var int Position of the $from_text we are at.
*/
protected $from_offset = 0;
/**
* @var cogpowered\FineDiff\Operations\OperationInterface
*/
protected $last_edit;
/**
* @var int Current position in the granularity array.
*/
protected $stackpointer = 0;
/**
* @var array Holds the individual opcodes as the diff takes place.
*/
protected $edits = array();
/**
* @inheritdoc
*/
public function __construct(GranularityInterface $granularity)
{
$this->granularity = $granularity;
// Set default opcodes generator
$this->opcodes = new Opcodes;
}
/**
* @inheritdoc
*/
public function getGranularity()
{
return $this->granularity;
}
/**
* @inheritdoc
*/
public function setGranularity(GranularityInterface $granularity)
{
$this->granularity = $granularity;
}
/**
* @inheritdoc
*/
public function getOpcodes()
{
return $this->opcodes;
}
/**
* @inheritdoc
*/
public function setOpcodes(OpcodesInterface $opcodes)
{
$this->opcodes = $opcodes;
}
/**
* @inheritdoc
*/
public function parse($from_text, $to_text)
{
// Ensure the granularity contains some delimiters
if (count($this->granularity) === 0) {
throw new GranularityCountException('Granularity contains no delimiters');
}
// Reset internal parser properties
$this->from_text = $from_text;
$this->from_offset = 0;
$this->last_edit = null;
$this->stackpointer = 0;
$this->edits = array();
// Parse the two string
$this->process($from_text, $to_text);
// Return processed diff
$this->opcodes->setOpcodes($this->edits);
return $this->opcodes;
}
/**
* Actually kicks off the processing. Recursive function.
*
* @param string $from_text
* @param string $to_text
* @return void
*/
protected function process($from_text, $to_text)
{
// Lets get parsing
$delimiters = $this->granularity[$this->stackpointer++];
$has_next_stage = $this->stackpointer < count($this->granularity);
// Actually perform diff
$diff = $this->diff($from_text, $to_text, $delimiters);
$diff = (is_array($diff)) ? $diff : array();
foreach ($diff as $fragment) {
// increase granularity
if ($fragment instanceof Replace && $has_next_stage) {
$this->process(
substr($this->from_text, $this->from_offset, $fragment->getFromLen()),
$fragment->getText()
);
}
// fuse copy ops whenever possible
elseif ($fragment instanceof Copy && $this->last_edit instanceof Copy) {
$this->edits[count($this->edits)-1]->increase($fragment->getFromLen());
$this->from_offset += $fragment->getFromLen();
}
else {
/* $fragment instanceof Copy */
/* $fragment instanceof Delete */
/* $fragment instanceof Insert */
$this->edits[] = $this->last_edit = $fragment;
$this->from_offset += $fragment->getFromLen();
}
}
$this->stackpointer--;
}
/**
* Core parsing function.
*
* @param string $from_text
* @param string $to_text
* @param string $delimiters Delimiter to use for this parse.
* @return array
*/
protected function diff($from_text, $to_text, $delimiters)
{
// Empty delimiter means character-level diffing.
// In such case, use code path optimized for character-level diffing.
if (empty($delimiters)) {
return $this->charDiff($from_text, $to_text);
}
$result = array();
// fragment-level diffing
$from_text_len = strlen($from_text);
$to_text_len = strlen($to_text);
$from_fragments = $this->extractFragments($from_text, $delimiters);
$to_fragments = $this->extractFragments($to_text, $delimiters);
$jobs = array(array(0, $from_text_len, 0, $to_text_len));
$cached_array_keys = array();
while ($job = array_pop($jobs)) {
// get the segments which must be diff'ed
list($from_segment_start, $from_segment_end, $to_segment_start, $to_segment_end) = $job;
// catch easy cases first
$from_segment_length = $from_segment_end - $from_segment_start;
$to_segment_length = $to_segment_end - $to_segment_start;
if (!$from_segment_length || !$to_segment_length ) {
if ( $from_segment_length ) {
$result[$from_segment_start * 4] = new Delete($from_segment_length);
} else if ( $to_segment_length ) {
$result[$from_segment_start * 4 + 1] = new Insert(substr($to_text, $to_segment_start, $to_segment_length));
}
continue;
}
// find longest copy operation for the current segments
$best_copy_length = 0;
$from_base_fragment_index = $from_segment_start;
$cached_array_keys_for_current_segment = array();
while ( $from_base_fragment_index < $from_segment_end ) {
$from_base_fragment = $from_fragments[$from_base_fragment_index];
$from_base_fragment_length = strlen($from_base_fragment);
// performance boost: cache array keys
if (!isset($cached_array_keys_for_current_segment[$from_base_fragment])) {
if ( !isset($cached_array_keys[$from_base_fragment]) ) {
$to_all_fragment_indices = $cached_array_keys[$from_base_fragment] = array_keys($to_fragments, $from_base_fragment, true);
}
else {
$to_all_fragment_indices = $cached_array_keys[$from_base_fragment];
}
// get only indices which falls within current segment
if ($to_segment_start > 0 || $to_segment_end < $to_text_len) {
$to_fragment_indices = array();
foreach ($to_all_fragment_indices as $to_fragment_index) {
if ($to_fragment_index < $to_segment_start) {
continue;
}
if ($to_fragment_index >= $to_segment_end) {
break;
}
$to_fragment_indices[] = $to_fragment_index;
}
$cached_array_keys_for_current_segment[$from_base_fragment] = $to_fragment_indices;
} else {
$to_fragment_indices = $to_all_fragment_indices;
}
} else {
$to_fragment_indices = $cached_array_keys_for_current_segment[$from_base_fragment];
}
// iterate through collected indices
foreach ($to_fragment_indices as $to_base_fragment_index) {
$fragment_index_offset = $from_base_fragment_length;
// iterate until no more match
for (;;) {
$fragment_from_index = $from_base_fragment_index + $fragment_index_offset;
if ($fragment_from_index >= $from_segment_end) {
break;
}
$fragment_to_index = $to_base_fragment_index + $fragment_index_offset;
if ($fragment_to_index >= $to_segment_end) {
break;
}
if ($from_fragments[$fragment_from_index] !== $to_fragments[$fragment_to_index]) {
break;
}
$fragment_length = strlen($from_fragments[$fragment_from_index]);
$fragment_index_offset += $fragment_length;
}
if ($fragment_index_offset > $best_copy_length) {
$best_copy_length = $fragment_index_offset;
$best_from_start = $from_base_fragment_index;
$best_to_start = $to_base_fragment_index;
}
}
$from_base_fragment_index += strlen($from_base_fragment);
// If match is larger than half segment size, no point trying to find better
// TODO: Really?
if ($best_copy_length >= $from_segment_length / 2) {
break;
}
// no point to keep looking if what is left is less than
// current best match
if ( $from_base_fragment_index + $best_copy_length >= $from_segment_end ) {
break;
}
}
if ($best_copy_length) {
$jobs[] = array($from_segment_start, $best_from_start, $to_segment_start, $best_to_start);
$result[$best_from_start * 4 + 2] = new Copy($best_copy_length);
$jobs[] = array($best_from_start + $best_copy_length, $from_segment_end, $best_to_start + $best_copy_length, $to_segment_end);
} else {
$result[$from_segment_start * 4 ] = new Replace($from_segment_length, substr($to_text, $to_segment_start, $to_segment_length));
}
}
ksort($result, SORT_NUMERIC);
return array_values($result);
}
/**
* Same as Parser::diff but tuned for character level granularity.
*
* @param string $from_text
* @param string $to_text
* @return array
*/
protected function charDiff($from_text, $to_text)
{
$result = array();
$jobs = array(array(0, strlen($from_text), 0, strlen($to_text)));
while ($job = array_pop($jobs)) {
// get the segments which must be diff'ed
list($from_segment_start, $from_segment_end, $to_segment_start, $to_segment_end) = $job;
$from_segment_len = $from_segment_end - $from_segment_start;
$to_segment_len = $to_segment_end - $to_segment_start;
// catch easy cases first
if (!$from_segment_len || !$to_segment_len) {
if ($from_segment_len) {
$result[$from_segment_start * 4 + 0] = new Delete($from_segment_len);
} else if ( $to_segment_len ) {
$result[$from_segment_start * 4 + 1] = new Insert(substr($to_text, $to_segment_start, $to_segment_len));
}
continue;
}
if ($from_segment_len >= $to_segment_len) {
$copy_len = $to_segment_len;
while ($copy_len) {
$to_copy_start = $to_segment_start;
$to_copy_start_max = $to_segment_end - $copy_len;
while ($to_copy_start <= $to_copy_start_max) {
$from_copy_start = strpos(substr($from_text, $from_segment_start, $from_segment_len), substr($to_text, $to_copy_start, $copy_len));
if ($from_copy_start !== false) {
$from_copy_start += $from_segment_start;
break 2;
}
$to_copy_start++;
}
$copy_len--;
}
} else {
$copy_len = $from_segment_len;
while ($copy_len) {
$from_copy_start = $from_segment_start;
$from_copy_start_max = $from_segment_end - $copy_len;
while ($from_copy_start <= $from_copy_start_max) {
$to_copy_start = strpos(substr($to_text, $to_segment_start, $to_segment_len), substr($from_text, $from_copy_start, $copy_len));
if ($to_copy_start !== false) {
$to_copy_start += $to_segment_start;
break 2;
}
$from_copy_start++;
}
$copy_len--;
}
}
// match found
if ( $copy_len ) {
$jobs[] = array($from_segment_start, $from_copy_start, $to_segment_start, $to_copy_start);
$result[$from_copy_start * 4 + 2] = new Copy($copy_len);
$jobs[] = array($from_copy_start + $copy_len, $from_segment_end, $to_copy_start + $copy_len, $to_segment_end);
}
// no match, so delete all, insert all
else {
$result[$from_segment_start * 4] = new Replace($from_segment_len, substr($to_text, $to_segment_start, $to_segment_len));
}
}
ksort($result, SORT_NUMERIC);
return array_values($result);
}
/**
* Efficiently fragment the text into an array according to specified delimiters.
*
* No delimiters means fragment into single character. The array indices are the offset of the fragments into
* the input string. A sentinel empty fragment is always added at the end.
* Careful: No check is performed as to the validity of the delimiters.
*
* @param string $text
* @param string $delimiters
* @param array
*/
protected function extractFragments($text, $delimiters)
{
// special case: split into characters
if (empty($delimiters)) {
$chars = str_split($text, 1);
$chars[strlen($text)] = '';
return $chars;
}
$fragments = array();
$start = 0;
$end = 0;
for (;;) {
$end += strcspn($text, $delimiters, $end);
$end += strspn($text, $delimiters, $end);
if ($end === $start) {
break;
}
$fragments[$start] = substr($text, $start, $end - $start);
$start = $end;
}
$fragments[$start] = '';
return $fragments;
}
}

View File

@@ -0,0 +1,83 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Parser;
use cogpowered\FineDiff\Granularity\GranularityInterface;
interface ParserInterface
{
/**
* Creates an instance.
*
* @param cogpowered\FineDiff\Granularity\GranularityInterface
*/
public function __construct(GranularityInterface $granularity);
/**
* Granularity the parser is working with.
*
* Default is cogpowered\FineDiff\Granularity\Character.
*
* @see cogpowered\FineDiff\Granularity\Character
* @see cogpowered\FineDiff\Granularity\Word
* @see cogpowered\FineDiff\Granularity\Sentence
* @see cogpowered\FineDiff\Granularity\Paragraph
*
* @return cogpowered\FineDiff\Granularity\GranularityInterface
*/
public function getGranularity();
/**
* Set the granularity that the parser is working with.
*
* @see cogpowered\FineDiff\Granularity\Character
* @see cogpowered\FineDiff\Granularity\Word
* @see cogpowered\FineDiff\Granularity\Sentence
* @see cogpowered\FineDiff\Granularity\Paragraph
*
* @param cogpowered\FineDiff\Granularity\GranularityInterface
* @return void
*/
public function setGranularity(GranularityInterface $granularity);
/**
* Get the opcodes object that is used to store all the opcodes.
*
* @return cogpowered\FineDiff\Parser\OpcodesInterface
*/
public function getOpcodes();
/**
* Set the opcodes object used to store all the opcodes for this parse.
*
* @param cogpowered\FineDiff\Parser\OpcodesInterface $opcodes.
* @return void
*/
public function setOpcodes(OpcodesInterface $opcodes);
/**
* Generates the opcodes needed to transform one string to another.
*
* @param string $from_text
* @param string $to_text
* @throws cogpowered\FineDiff\Exceptions\GranularityCountException
* @return cogpowered\FineDiff\Parser\OpcodesInterface
*/
public function parse($from_text, $to_text);
}

View File

@@ -0,0 +1,45 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Render;
use cogpowered\FineDiff\Parser\OpcodeInterface;
class Html extends Renderer
{
public function callback($opcode, $from, $from_offset, $from_len)
{
if ($opcode === 'c') {
$html = htmlentities(substr($from, $from_offset, $from_len));
} else if ($opcode === 'd') {
$deletion = substr($from, $from_offset, $from_len);
if (strcspn($deletion, " \n\r") === 0) {
$deletion = str_replace(array("\n","\r"), array('\n','\r'), $deletion);
}
$html = '<del>'.htmlentities($deletion).'</del>';
} else /* if ( $opcode === 'i' ) */ {
$html = '<ins>'.htmlentities(substr($from, $from_offset, $from_len)).'</ins>';
}
return $html;
}
}

View File

@@ -0,0 +1,81 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Render;
use cogpowered\FineDiff\Parser\OpcodesInterface;
use InvalidArgumentException;
abstract class Renderer implements RendererInterface
{
/**
* Covert text based on the provided opcodes.
*
* @param string $from_text
* @param string|\cogpowered\FineDiff\Parser\OpcodesInterface $opcodes
*
* @return string
*/
public function process($from_text, $opcodes)
{
// Validate opcodes
if (!is_string($opcodes) && !($opcodes instanceof OpcodesInterface)) {
throw new InvalidArgumentException('Invalid opcodes type');
} else {
$opcodes = ($opcodes instanceof OpcodesInterface) ? $opcodes->generate() : $opcodes;
}
// Holds the generated string that is returned
$output = '';
$opcodes_len = strlen($opcodes);
$from_offset = 0;
$opcodes_offset = 0;
while ($opcodes_offset < $opcodes_len) {
$opcode = substr($opcodes, $opcodes_offset, 1);
$opcodes_offset++;
$n = intval(substr($opcodes, $opcodes_offset));
if ($n) {
$opcodes_offset += strlen(strval($n));
} else {
$n = 1;
}
if ($opcode === 'c') {
// copy n characters from source
$data = $this->callback('c', $from_text, $from_offset, $n);
$from_offset += $n;
} else if ($opcode === 'd') {
// delete n characters from source
$data = $this->callback('d', $from_text, $from_offset, $n);
$from_offset += $n;
} else /* if ( $opcode === 'i' ) */ {
// insert n characters from opcodes
$data = $this->callback('i', $opcodes, $opcodes_offset + 1, $n);
$opcodes_offset += 1 + $n;
}
$output .= $data;
}
return $output;
}
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Render;
interface RendererInterface
{
public function process($from_text, $opcode);
public function callback($opcode, $from, $from_offset, $from_len);
}

View File

@@ -0,0 +1,31 @@
<?php
/**
* FINE granularity DIFF
*
* Computes a set of instructions to convert the content of
* one string into another.
*
* Originally created by Raymond Hill (https://github.com/gorhill/PHP-FineDiff), brought up
* to date by Cog Powered (https://github.com/cogpowered/FineDiff).
*
* @copyright Copyright 2011 (c) Raymond Hill (http://raymondhill.net/blog/?p=441)
* @copyright Copyright 2013 (c) Robert Crowe (http://cogpowered.com)
* @link https://github.com/cogpowered/FineDiff
* @version 0.0.1
* @license MIT License (http://www.opensource.org/licenses/mit-license.php)
*/
namespace cogpowered\FineDiff\Render;
class Text extends Renderer
{
public function callback($opcode, $from, $from_offset, $from_len)
{
if ($opcode === 'c' || $opcode === 'i') {
return substr($from, $from_offset, $from_len);
}
return '';
}
}