diff --git a/library/Director/ConfigDiff.php b/library/Director/ConfigDiff.php
index 08adde08..c1bb8192 100644
--- a/library/Director/ConfigDiff.php
+++ b/library/Director/ConfigDiff.php
@@ -2,7 +2,11 @@
namespace Icinga\Module\Director;
-use FineDiff;
+use Diff;
+use Diff_Renderer_Html_Inline;
+use Diff_Renderer_Html_SideBySide;
+use Diff_Renderer_Text_Context;
+use Diff_Renderer_Text_Unified;
use Icinga\Application\Benchmark;
class ConfigDiff
@@ -16,24 +20,49 @@ class ConfigDiff
protected function __construct($a, $b)
{
- $this->a = $a;
- $this->b = $b;
- require_once dirname(__DIR__) . '/vendor/PHP-FineDiff/finediff.php';
+ require_once dirname(__DIR__) . '/vendor/php-diff/lib/Diff.php';
- // Trying character granularity first...
- $granularity = FineDiff::$characterGranularity;
- $this->diff = new FineDiff($a, $b, $granularity);
- if (count($this->diff->getOps()) > 4) {
- // ...fall back to word granularity if too many differences
- // (available granularities: character, word, sentence, paragraph
- $granularity = FineDiff::$wordGranularity;
- $this->diff = new FineDiff($a, $b, $granularity);
- }
+ $this->a = explode("\n", (string) $a);
+ $this->b = explode("\n", (string) $b);
+
+ $options = array(
+ // 'ignoreWhitespace' => true,
+ // 'ignoreCase' => true,
+ );
+ $this->diff = new Diff($this->a, $this->b, $options);
}
public function renderHtml()
{
- return $this->diff->renderDiffToHTML();
+ return $this->renderHtmlSideBySide();
+ }
+
+ public function renderHtmlSideBySide()
+ {
+ require_once dirname(__DIR__) . '/vendor/php-diff/lib/Diff/Renderer/Html/SideBySide.php';
+ $renderer = new Diff_Renderer_Html_SideBySide;
+ return $this->diff->Render($renderer);
+ }
+
+ public function renderHtmlInline()
+ {
+ require_once dirname(__DIR__) . '/vendor/php-diff/lib/Diff/Renderer/Html/Inline.php';
+ $renderer = new Diff_Renderer_Html_Inline;
+ return $this->diff->Render($renderer);
+ }
+
+ public function renderTextContext()
+ {
+ require_once dirname(__DIR__) . '/vendor/php-diff/lib/Diff/Renderer/Text/Context.php';
+ $renderer = new Diff_Renderer_Text_Context;
+ return $this->diff->Render($renderer);
+ }
+
+ public function renderTextUnified()
+ {
+ require_once dirname(__DIR__) . '/vendor/php-diff/lib/Diff/Renderer/Text/Context.php';
+ $renderer = new Diff_Renderer_Text_Context;
+ return $this->diff->Render($renderer);
}
public function __toString()
diff --git a/library/vendor/PHP-FineDiff/finediff.php b/library/vendor/PHP-FineDiff/finediff.php
deleted file mode 100644
index 920365ae..00000000
--- a/library/vendor/PHP-FineDiff/finediff.php
+++ /dev/null
@@ -1,688 +0,0 @@
-copy->insert
-* command (swap) for when the inserted segment is exactly the same
-* as the deleted one, and with only a copy operation in between.
-* TODO: How often this case occurs? Is it worth it? Can only
-* be done as a postprocessing method (->optimize()?)
-*/
-abstract class FineDiffOp {
- abstract public function getFromLen();
- abstract public function getToLen();
- abstract public function getOpcode();
- }
-
-class FineDiffDeleteOp extends FineDiffOp {
- public function __construct($len) {
- $this->fromLen = $len;
- }
- public function getFromLen() {
- return $this->fromLen;
- }
- public function getToLen() {
- return 0;
- }
- public function getOpcode() {
- if ( $this->fromLen === 1 ) {
- return 'd';
- }
- return "d{$this->fromLen}";
- }
- }
-
-class FineDiffInsertOp extends FineDiffOp {
- public function __construct($text) {
- $this->text = $text;
- }
- public function getFromLen() {
- return 0;
- }
- public function getToLen() {
- return strlen($this->text);
- }
- public function getText() {
- return $this->text;
- }
- public function getOpcode() {
- $to_len = strlen($this->text);
- if ( $to_len === 1 ) {
- return "i:{$this->text}";
- }
- return "i{$to_len}:{$this->text}";
- }
- }
-
-class FineDiffReplaceOp extends FineDiffOp {
- public function __construct($fromLen, $text) {
- $this->fromLen = $fromLen;
- $this->text = $text;
- }
- public function getFromLen() {
- return $this->fromLen;
- }
- public function getToLen() {
- return strlen($this->text);
- }
- public function getText() {
- return $this->text;
- }
- 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}";
- }
- }
-
-class FineDiffCopyOp extends FineDiffOp {
- public function __construct($len) {
- $this->len = $len;
- }
- public function getFromLen() {
- return $this->len;
- }
- public function getToLen() {
- return $this->len;
- }
- public function getOpcode() {
- if ( $this->len === 1 ) {
- return 'c';
- }
- return "c{$this->len}";
- }
- public function increase($size) {
- return $this->len += $size;
- }
- }
-
-/**
-* FineDiff ops
-*
-* Collection of ops
-*/
-class FineDiffOps {
- public function appendOpcode($opcode, $from, $from_offset, $from_len) {
- if ( $opcode === 'c' ) {
- $edits[] = new FineDiffCopyOp($from_len);
- }
- else if ( $opcode === 'd' ) {
- $edits[] = new FineDiffDeleteOp($from_len);
- }
- else /* if ( $opcode === 'i' ) */ {
- $edits[] = new FineDiffInsertOp(substr($from, $from_offset, $from_len));
- }
- }
- public $edits = array();
- }
-
-/**
-* FineDiff class
-*
-* TODO: Document
-*
-*/
-class FineDiff {
-
- /**------------------------------------------------------------------------
- *
- * Public section
- *
- */
-
- /**
- * Constructor
- * ...
- * The $granularityStack allows FineDiff to be configurable so that
- * a particular stack tailored to the specific content of a document can
- * be passed.
- */
- public function __construct($from_text = '', $to_text = '', $granularityStack = null) {
- // setup stack for generic text documents by default
- $this->granularityStack = $granularityStack ? $granularityStack : FineDiff::$characterGranularity;
- $this->edits = array();
- $this->from_text = $from_text;
- $this->doDiff($from_text, $to_text);
- }
-
- public function getOps() {
- return $this->edits;
- }
-
- public function getOpcodes() {
- $opcodes = array();
- foreach ( $this->edits as $edit ) {
- $opcodes[] = $edit->getOpcode();
- }
- return implode('', $opcodes);
- }
-
- public function renderDiffToHTML() {
- $in_offset = 0;
- ob_start();
- foreach ( $this->edits as $edit ) {
- $n = $edit->getFromLen();
- if ( $edit instanceof FineDiffCopyOp ) {
- FineDiff::renderDiffToHTMLFromOpcode('c', $this->from_text, $in_offset, $n);
- }
- else if ( $edit instanceof FineDiffDeleteOp ) {
- FineDiff::renderDiffToHTMLFromOpcode('d', $this->from_text, $in_offset, $n);
- }
- else if ( $edit instanceof FineDiffInsertOp ) {
- FineDiff::renderDiffToHTMLFromOpcode('i', $edit->getText(), 0, $edit->getToLen());
- }
- else /* if ( $edit instanceof FineDiffReplaceOp ) */ {
- FineDiff::renderDiffToHTMLFromOpcode('d', $this->from_text, $in_offset, $n);
- FineDiff::renderDiffToHTMLFromOpcode('i', $edit->getText(), 0, $edit->getToLen());
- }
- $in_offset += $n;
- }
- return ob_get_clean();
- }
-
- /**------------------------------------------------------------------------
- * Return an opcodes string describing the diff between a "From" and a
- * "To" string
- */
- public static function getDiffOpcodes($from, $to, $granularities = null) {
- $diff = new FineDiff($from, $to, $granularities);
- return $diff->getOpcodes();
- }
-
- /**------------------------------------------------------------------------
- * Return an iterable collection of diff ops from an opcodes string
- */
- public static function getDiffOpsFromOpcodes($opcodes) {
- $diffops = new FineDiffOps();
- FineDiff::renderFromOpcodes(null, $opcodes, array($diffops,'appendOpcode'));
- return $diffops->edits;
- }
-
- /**------------------------------------------------------------------------
- * Re-create the "To" string from the "From" string and an "Opcodes" string
- */
- public static function renderToTextFromOpcodes($from, $opcodes) {
- ob_start();
- FineDiff::renderFromOpcodes($from, $opcodes, array('FineDiff','renderToTextFromOpcode'));
- return ob_get_clean();
- }
-
- /**------------------------------------------------------------------------
- * Render the diff to an HTML string
- */
- public static function renderDiffToHTMLFromOpcodes($from, $opcodes) {
- ob_start();
- FineDiff::renderFromOpcodes($from, $opcodes, array('FineDiff','renderDiffToHTMLFromOpcode'));
- return ob_get_clean();
- }
-
- /**------------------------------------------------------------------------
- * Generic opcodes parser, user must supply callback for handling
- * single opcode
- */
- public static function renderFromOpcodes($from, $opcodes, $callback) {
- if ( !is_callable($callback) ) {
- return;
- }
- $opcodes_len = strlen($opcodes);
- $from_offset = $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
- call_user_func($callback, 'c', $from, $from_offset, $n, '');
- $from_offset += $n;
- }
- else if ( $opcode === 'd' ) { // delete n characters from source
- call_user_func($callback, 'd', $from, $from_offset, $n, '');
- $from_offset += $n;
- }
- else /* if ( $opcode === 'i' ) */ { // insert n characters from opcodes
- call_user_func($callback, 'i', $opcodes, $opcodes_offset + 1, $n);
- $opcodes_offset += 1 + $n;
- }
- }
- }
-
- /**
- * Stock granularity stacks and delimiters
- */
-
- const paragraphDelimiters = "\n\r";
- public static $paragraphGranularity = array(
- FineDiff::paragraphDelimiters
- );
- const sentenceDelimiters = ".\n\r";
- public static $sentenceGranularity = array(
- FineDiff::paragraphDelimiters,
- FineDiff::sentenceDelimiters
- );
- const wordDelimiters = " \t.\n\r";
- public static $wordGranularity = array(
- FineDiff::paragraphDelimiters,
- FineDiff::sentenceDelimiters,
- FineDiff::wordDelimiters
- );
- const characterDelimiters = "";
- public static $characterGranularity = array(
- FineDiff::paragraphDelimiters,
- FineDiff::sentenceDelimiters,
- FineDiff::wordDelimiters,
- FineDiff::characterDelimiters
- );
-
- public static $textStack = array(
- ".",
- " \t.\n\r",
- ""
- );
-
- /**------------------------------------------------------------------------
- *
- * Private section
- *
- */
-
- /**
- * Entry point to compute the diff.
- */
- private function doDiff($from_text, $to_text) {
- $this->last_edit = false;
- $this->stackpointer = 0;
- $this->from_text = $from_text;
- $this->from_offset = 0;
- // can't diff without at least one granularity specifier
- if ( empty($this->granularityStack) ) {
- return;
- }
- $this->_processGranularity($from_text, $to_text);
- }
-
- /**
- * This is the recursive function which is responsible for
- * handling/increasing granularity.
- *
- * Incrementally increasing the granularity is key to compute the
- * overall diff in a very efficient way.
- */
- private function _processGranularity($from_segment, $to_segment) {
- $delimiters = $this->granularityStack[$this->stackpointer++];
- $has_next_stage = $this->stackpointer < count($this->granularityStack);
- foreach ( FineDiff::doFragmentDiff($from_segment, $to_segment, $delimiters) as $fragment_edit ) {
- // increase granularity
- if ( $fragment_edit instanceof FineDiffReplaceOp && $has_next_stage ) {
- $this->_processGranularity(
- substr($this->from_text, $this->from_offset, $fragment_edit->getFromLen()),
- $fragment_edit->getText()
- );
- }
- // fuse copy ops whenever possible
- else if ( $fragment_edit instanceof FineDiffCopyOp && $this->last_edit instanceof FineDiffCopyOp ) {
- $this->edits[count($this->edits)-1]->increase($fragment_edit->getFromLen());
- $this->from_offset += $fragment_edit->getFromLen();
- }
- else {
- /* $fragment_edit instanceof FineDiffCopyOp */
- /* $fragment_edit instanceof FineDiffDeleteOp */
- /* $fragment_edit instanceof FineDiffInsertOp */
- $this->edits[] = $this->last_edit = $fragment_edit;
- $this->from_offset += $fragment_edit->getFromLen();
- }
- }
- $this->stackpointer--;
- }
-
- /**
- * This is the core algorithm which actually perform the diff itself,
- * fragmenting the strings as per specified delimiters.
- *
- * This function is naturally recursive, however for performance purpose
- * a local job queue is used instead of outright recursivity.
- */
- private static function doFragmentDiff($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 FineDiff::doCharDiff($from_text, $to_text);
- }
-
- $result = array();
-
- // fragment-level diffing
- $from_text_len = strlen($from_text);
- $to_text_len = strlen($to_text);
- $from_fragments = FineDiff::extractFragments($from_text, $delimiters);
- $to_fragments = FineDiff::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 FineDiffDeleteOp($from_segment_length);
- }
- else if ( $to_segment_length ) {
- $result[$from_segment_start * 4 + 1] = new FineDiffInsertOp(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 FineDiffCopyOp($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 FineDiffReplaceOp($from_segment_length, substr($to_text, $to_segment_start, $to_segment_length));
- }
- }
-
- ksort($result, SORT_NUMERIC);
- return array_values($result);
- }
-
- /**
- * Perform a character-level diff.
- *
- * The algorithm is quite similar to doFragmentDiff(), except that
- * the code path is optimized for character-level diff -- strpos() is
- * used to find out the longest common subequence of characters.
- *
- * We try to find a match using the longest possible subsequence, which
- * is at most the length of the shortest of the two strings, then incrementally
- * reduce the size until a match is found.
- *
- * I still need to study more the performance of this function. It
- * appears that for long strings, the generic doFragmentDiff() is more
- * performant. For word-sized strings, doCharDiff() is somewhat more
- * performant.
- */
- private static function doCharDiff($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 FineDiffDeleteOp($from_segment_len);
- }
- else if ( $to_segment_len ) {
- $result[$from_segment_start * 4 + 1] = new FineDiffInsertOp(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 FineDiffCopyOp($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 FineDiffReplaceOp($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.
- */
- private static 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 = $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;
- }
-
- /**
- * Stock opcode renderers
- */
- private static function renderToTextFromOpcode($opcode, $from, $from_offset, $from_len) {
- if ( $opcode === 'c' || $opcode === 'i' ) {
- echo substr($from, $from_offset, $from_len);
- }
- }
-
- private static function renderDiffToHTMLFromOpcode($opcode, $from, $from_offset, $from_len) {
- if ( $opcode === 'c' ) {
- echo 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);
- }
- echo '', htmlentities($deletion), '';
- }
- else /* if ( $opcode === 'i' ) */ {
- echo '', htmlentities(substr($from, $from_offset, $from_len)), '';
- }
- }
- }
-
diff --git a/library/vendor/php-diff/README.md b/library/vendor/php-diff/README.md
new file mode 100644
index 00000000..0110b5c5
--- /dev/null
+++ b/library/vendor/php-diff/README.md
@@ -0,0 +1,65 @@
+PHP Diff Class
+--------------
+
+Introduction
+------------
+A comprehensive library for generating differences between
+two hashable objects (strings or arrays). Generated differences can be
+rendered in all of the standard formats including:
+ * Unified
+ * Context
+ * Inline HTML
+ * Side by Side HTML
+
+The logic behind the core of the diff engine (ie, the sequence matcher)
+is primarily based on the Python difflib package. The reason for doing
+so is primarily because of its high degree of accuracy.
+
+Example Use
+-----------
+A quick usage example can be found in the example/ directory and under
+example.php.
+
+More complete documentation will be available shortly.
+
+Merge files using jQuery
+------------------------
+Xiphe has build a jQuery plugin with that you can merge the compared
+files. Have a look at [jQuery-Merge-for-php-diff](https://github.com/Xiphe/jQuery-Merge-for-php-diff).
+
+Todo
+----
+ * Ability to ignore blank line changes
+ * 3 way diff support
+ * Performance optimizations
+
+License (BSD License)
+---------------------
+Copyright (c) 2009 Chris Boulton
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ - Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ - Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+ - Neither the name of the Chris Boulton nor the names of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+```
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGE.
+```
diff --git a/library/vendor/php-diff/SOURCE b/library/vendor/php-diff/SOURCE
new file mode 100644
index 00000000..c083b23c
--- /dev/null
+++ b/library/vendor/php-diff/SOURCE
@@ -0,0 +1,9 @@
+git clone https://github.com/chrisboulton/php-diff.git
+# Last used commit:
+cd php-diff
+git checkout f4db229d7ae8dffa0a4f90e1adbec9bf22c93d99
+rm -rf .git
+rm -rf .gitignore
+rm -rf composer.json
+rm -rf example
+cd ..
diff --git a/library/vendor/php-diff/lib/Diff.php b/library/vendor/php-diff/lib/Diff.php
new file mode 100644
index 00000000..d1eb9da0
--- /dev/null
+++ b/library/vendor/php-diff/lib/Diff.php
@@ -0,0 +1,179 @@
+
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the Chris Boulton nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package Diff
+ * @author Chris Boulton
+ * @copyright (c) 2009 Chris Boulton
+ * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
+ * @version 1.1
+ * @link http://github.com/chrisboulton/php-diff
+ */
+
+class Diff
+{
+ /**
+ * @var array The "old" sequence to use as the basis for the comparison.
+ */
+ private $a = null;
+
+ /**
+ * @var array The "new" sequence to generate the changes for.
+ */
+ private $b = null;
+
+ /**
+ * @var array Array containing the generated opcodes for the differences between the two items.
+ */
+ private $groupedCodes = null;
+
+ /**
+ * @var array Associative array of the default options available for the diff class and their default value.
+ */
+ private $defaultOptions = array(
+ 'context' => 3,
+ 'ignoreNewLines' => false,
+ 'ignoreWhitespace' => false,
+ 'ignoreCase' => false
+ );
+
+ /**
+ * @var array Array of the options that have been applied for generating the diff.
+ */
+ private $options = array();
+
+ /**
+ * The constructor.
+ *
+ * @param array $a Array containing the lines of the first string to compare.
+ * @param array $b Array containing the lines for the second string to compare.
+ */
+ public function __construct($a, $b, $options=array())
+ {
+ $this->a = $a;
+ $this->b = $b;
+
+ if (is_array($options))
+ $this->options = array_merge($this->defaultOptions, $options);
+ else
+ $this->options = $this->defaultOptions;
+ }
+
+ /**
+ * Render a diff using the supplied rendering class and return it.
+ *
+ * @param object $renderer An instance of the rendering object to use for generating the diff.
+ * @return mixed The generated diff. Exact return value depends on the rendered.
+ */
+ public function render(Diff_Renderer_Abstract $renderer)
+ {
+ $renderer->diff = $this;
+ return $renderer->render();
+ }
+
+ /**
+ * Get a range of lines from $start to $end from the first comparison string
+ * and return them as an array. If no values are supplied, the entire string
+ * is returned. It's also possible to specify just one line to return only
+ * that line.
+ *
+ * @param int $start The starting number.
+ * @param int $end The ending number. If not supplied, only the item in $start will be returned.
+ * @return array Array of all of the lines between the specified range.
+ */
+ public function getA($start=0, $end=null)
+ {
+ if($start == 0 && $end === null) {
+ return $this->a;
+ }
+
+ if($end === null) {
+ $length = 1;
+ }
+ else {
+ $length = $end - $start;
+ }
+
+ return array_slice($this->a, $start, $length);
+
+ }
+
+ /**
+ * Get a range of lines from $start to $end from the second comparison string
+ * and return them as an array. If no values are supplied, the entire string
+ * is returned. It's also possible to specify just one line to return only
+ * that line.
+ *
+ * @param int $start The starting number.
+ * @param int $end The ending number. If not supplied, only the item in $start will be returned.
+ * @return array Array of all of the lines between the specified range.
+ */
+ public function getB($start=0, $end=null)
+ {
+ if($start == 0 && $end === null) {
+ return $this->b;
+ }
+
+ if($end === null) {
+ $length = 1;
+ }
+ else {
+ $length = $end - $start;
+ }
+
+ return array_slice($this->b, $start, $length);
+ }
+
+ /**
+ * Generate a list of the compiled and grouped opcodes for the differences between the
+ * two strings. Generally called by the renderer, this class instantiates the sequence
+ * matcher and performs the actual diff generation and return an array of the opcodes
+ * for it. Once generated, the results are cached in the diff class instance.
+ *
+ * @return array Array of the grouped opcodes for the generated diff.
+ */
+ public function getGroupedOpcodes()
+ {
+ if(!is_null($this->groupedCodes)) {
+ return $this->groupedCodes;
+ }
+
+ require_once dirname(__FILE__).'/Diff/SequenceMatcher.php';
+ $sequenceMatcher = new Diff_SequenceMatcher($this->a, $this->b, null, $this->options);
+ $this->groupedCodes = $sequenceMatcher->getGroupedOpcodes($this->options['context']);
+ return $this->groupedCodes;
+ }
+}
\ No newline at end of file
diff --git a/library/vendor/php-diff/lib/Diff/Renderer/Abstract.php b/library/vendor/php-diff/lib/Diff/Renderer/Abstract.php
new file mode 100644
index 00000000..f63c3e7f
--- /dev/null
+++ b/library/vendor/php-diff/lib/Diff/Renderer/Abstract.php
@@ -0,0 +1,82 @@
+
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the Chris Boulton nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package DiffLib
+ * @author Chris Boulton
+ * @copyright (c) 2009 Chris Boulton
+ * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
+ * @version 1.1
+ * @link http://github.com/chrisboulton/php-diff
+ */
+
+abstract class Diff_Renderer_Abstract
+{
+ /**
+ * @var object Instance of the diff class that this renderer is generating the rendered diff for.
+ */
+ public $diff;
+
+ /**
+ * @var array Array of the default options that apply to this renderer.
+ */
+ protected $defaultOptions = array();
+
+ /**
+ * @var array Array containing the user applied and merged default options for the renderer.
+ */
+ protected $options = array();
+
+ /**
+ * The constructor. Instantiates the rendering engine and if options are passed,
+ * sets the options for the renderer.
+ *
+ * @param array $options Optionally, an array of the options for the renderer.
+ */
+ public function __construct(array $options = array())
+ {
+ $this->setOptions($options);
+ }
+
+ /**
+ * Set the options of the renderer to those supplied in the passed in array.
+ * Options are merged with the default to ensure that there aren't any missing
+ * options.
+ *
+ * @param array $options Array of options to set.
+ */
+ public function setOptions(array $options)
+ {
+ $this->options = array_merge($this->defaultOptions, $options);
+ }
+}
\ No newline at end of file
diff --git a/library/vendor/php-diff/lib/Diff/Renderer/Html/Array.php b/library/vendor/php-diff/lib/Diff/Renderer/Html/Array.php
new file mode 100644
index 00000000..b012fb6b
--- /dev/null
+++ b/library/vendor/php-diff/lib/Diff/Renderer/Html/Array.php
@@ -0,0 +1,224 @@
+
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the Chris Boulton nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package DiffLib
+ * @author Chris Boulton
+ * @copyright (c) 2009 Chris Boulton
+ * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
+ * @version 1.1
+ * @link http://github.com/chrisboulton/php-diff
+ */
+
+require_once dirname(__FILE__).'/../Abstract.php';
+
+class Diff_Renderer_Html_Array extends Diff_Renderer_Abstract
+{
+ /**
+ * @var array Array of the default options that apply to this renderer.
+ */
+ protected $defaultOptions = array(
+ 'tabSize' => 4
+ );
+
+ /**
+ * Render and return an array structure suitable for generating HTML
+ * based differences. Generally called by subclasses that generate a
+ * HTML based diff and return an array of the changes to show in the diff.
+ *
+ * @return array An array of the generated chances, suitable for presentation in HTML.
+ */
+ public function render()
+ {
+ // As we'll be modifying a & b to include our change markers,
+ // we need to get the contents and store them here. That way
+ // we're not going to destroy the original data
+ $a = $this->diff->getA();
+ $b = $this->diff->getB();
+
+ $changes = array();
+ $opCodes = $this->diff->getGroupedOpcodes();
+ foreach($opCodes as $group) {
+ $blocks = array();
+ $lastTag = null;
+ $lastBlock = 0;
+ foreach($group as $code) {
+ list($tag, $i1, $i2, $j1, $j2) = $code;
+
+ if($tag == 'replace' && $i2 - $i1 == $j2 - $j1) {
+ for($i = 0; $i < ($i2 - $i1); ++$i) {
+ $fromLine = $a[$i1 + $i];
+ $toLine = $b[$j1 + $i];
+
+ list($start, $end) = $this->getChangeExtent($fromLine, $toLine);
+ if($start != 0 || $end != 0) {
+ $last = $end + strlen($fromLine);
+ $fromLine = substr_replace($fromLine, "\0", $start, 0);
+ $fromLine = substr_replace($fromLine, "\1", $last + 1, 0);
+ $last = $end + strlen($toLine);
+ $toLine = substr_replace($toLine, "\0", $start, 0);
+ $toLine = substr_replace($toLine, "\1", $last + 1, 0);
+ $a[$i1 + $i] = $fromLine;
+ $b[$j1 + $i] = $toLine;
+ }
+ }
+ }
+
+ if($tag != $lastTag) {
+ $blocks[] = array(
+ 'tag' => $tag,
+ 'base' => array(
+ 'offset' => $i1,
+ 'lines' => array()
+ ),
+ 'changed' => array(
+ 'offset' => $j1,
+ 'lines' => array()
+ )
+ );
+ $lastBlock = count($blocks)-1;
+ }
+
+ $lastTag = $tag;
+
+ if($tag == 'equal') {
+ $lines = array_slice($a, $i1, ($i2 - $i1));
+ $blocks[$lastBlock]['base']['lines'] += $this->formatLines($lines);
+ $lines = array_slice($b, $j1, ($j2 - $j1));
+ $blocks[$lastBlock]['changed']['lines'] += $this->formatLines($lines);
+ }
+ else {
+ if($tag == 'replace' || $tag == 'delete') {
+ $lines = array_slice($a, $i1, ($i2 - $i1));
+ $lines = $this->formatLines($lines);
+ $lines = str_replace(array("\0", "\1"), array('', ''), $lines);
+ $blocks[$lastBlock]['base']['lines'] += $lines;
+ }
+
+ if($tag == 'replace' || $tag == 'insert') {
+ $lines = array_slice($b, $j1, ($j2 - $j1));
+ $lines = $this->formatLines($lines);
+ $lines = str_replace(array("\0", "\1"), array('', ''), $lines);
+ $blocks[$lastBlock]['changed']['lines'] += $lines;
+ }
+ }
+ }
+ $changes[] = $blocks;
+ }
+ return $changes;
+ }
+
+ /**
+ * Given two strings, determine where the changes in the two strings
+ * begin, and where the changes in the two strings end.
+ *
+ * @param string $fromLine The first string.
+ * @param string $toLine The second string.
+ * @return array Array containing the starting position (0 by default) and the ending position (-1 by default)
+ */
+ private function getChangeExtent($fromLine, $toLine)
+ {
+ $start = 0;
+ $limit = min(strlen($fromLine), strlen($toLine));
+ while($start < $limit && $fromLine{$start} == $toLine{$start}) {
+ ++$start;
+ }
+ $end = -1;
+ $limit = $limit - $start;
+ while(-$end <= $limit && substr($fromLine, $end, 1) == substr($toLine, $end, 1)) {
+ --$end;
+ }
+ return array(
+ $start,
+ $end + 1
+ );
+ }
+
+ /**
+ * Format a series of lines suitable for output in a HTML rendered diff.
+ * This involves replacing tab characters with spaces, making the HTML safe
+ * for output, ensuring that double spaces are replaced with etc.
+ *
+ * @param array $lines Array of lines to format.
+ * @return array Array of the formatted lines.
+ */
+ protected function formatLines($lines)
+ {
+ $lines = array_map(array($this, 'ExpandTabs'), $lines);
+ $lines = array_map(array($this, 'HtmlSafe'), $lines);
+ foreach($lines as &$line) {
+ $line = preg_replace('# ( +)|^ #e', "\$this->fixSpaces('\\1')", $line);
+ }
+ return $lines;
+ }
+
+ /**
+ * Replace a string containing spaces with a HTML representation using .
+ *
+ * @param string $spaces The string of spaces.
+ * @return string The HTML representation of the string.
+ */
+ function fixSpaces($spaces='')
+ {
+ $count = strlen($spaces);
+ if($count == 0) {
+ return '';
+ }
+
+ $div = floor($count / 2);
+ $mod = $count % 2;
+ return str_repeat(' ', $div).str_repeat(' ', $mod);
+ }
+
+ /**
+ * Replace tabs in a single line with a number of spaces as defined by the tabSize option.
+ *
+ * @param string $line The containing tabs to convert.
+ * @return string The line with the tabs converted to spaces.
+ */
+ private function expandTabs($line)
+ {
+ return str_replace("\t", str_repeat(' ', $this->options['tabSize']), $line);
+ }
+
+ /**
+ * Make a string containing HTML safe for output on a page.
+ *
+ * @param string $string The string.
+ * @return string The string with the HTML characters replaced by entities.
+ */
+ private function htmlSafe($string)
+ {
+ return htmlspecialchars($string, ENT_NOQUOTES, 'UTF-8');
+ }
+}
diff --git a/library/vendor/php-diff/lib/Diff/Renderer/Html/Inline.php b/library/vendor/php-diff/lib/Diff/Renderer/Html/Inline.php
new file mode 100644
index 00000000..60e8005a
--- /dev/null
+++ b/library/vendor/php-diff/lib/Diff/Renderer/Html/Inline.php
@@ -0,0 +1,143 @@
+
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the Chris Boulton nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package DiffLib
+ * @author Chris Boulton
+ * @copyright (c) 2009 Chris Boulton
+ * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
+ * @version 1.1
+ * @link http://github.com/chrisboulton/php-diff
+ */
+
+require_once dirname(__FILE__).'/Array.php';
+
+class Diff_Renderer_Html_Inline extends Diff_Renderer_Html_Array
+{
+ /**
+ * Render a and return diff with changes between the two sequences
+ * displayed inline (under each other)
+ *
+ * @return string The generated inline diff.
+ */
+ public function render()
+ {
+ $changes = parent::render();
+ $html = '';
+ if(empty($changes)) {
+ return $html;
+ }
+
+ $html .= '';
+ $html .= '';
+ $html .= '';
+ $html .= 'Old | ';
+ $html .= 'New | ';
+ $html .= 'Differences | ';
+ $html .= '
';
+ $html .= '';
+ foreach($changes as $i => $blocks) {
+ // If this is a separate block, we're condensing code so output ...,
+ // indicating a significant portion of the code has been collapsed as
+ // it is the same
+ if($i > 0) {
+ $html .= '';
+ $html .= '… | ';
+ $html .= '… | ';
+ $html .= ' | ';
+ $html .= '';
+ }
+
+ foreach($blocks as $change) {
+ $html .= '';
+ // Equal changes should be shown on both sides of the diff
+ if($change['tag'] == 'equal') {
+ foreach($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= '';
+ $html .= ''.$fromLine.' | ';
+ $html .= ''.$toLine.' | ';
+ $html .= ''.$line.' | ';
+ $html .= '
';
+ }
+ }
+ // Added lines only on the right side
+ else if($change['tag'] == 'insert') {
+ foreach($change['changed']['lines'] as $no => $line) {
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= '';
+ $html .= ' | ';
+ $html .= ''.$toLine.' | ';
+ $html .= ''.$line.' | ';
+ $html .= '
';
+ }
+ }
+ // Show deleted lines only on the left side
+ else if($change['tag'] == 'delete') {
+ foreach($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $html .= '';
+ $html .= ''.$fromLine.' | ';
+ $html .= ' | ';
+ $html .= ''.$line.' | ';
+ $html .= '
';
+ }
+ }
+ // Show modified lines on both sides
+ else if($change['tag'] == 'replace') {
+ foreach($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $html .= '';
+ $html .= ''.$fromLine.' | ';
+ $html .= ' | ';
+ $html .= ''.$line.' | ';
+ $html .= '
';
+ }
+
+ foreach($change['changed']['lines'] as $no => $line) {
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= '';
+ $html .= ''.$toLine.' | ';
+ $html .= ' | ';
+ $html .= ''.$line.' | ';
+ $html .= '
';
+ }
+ }
+ $html .= '';
+ }
+ }
+ $html .= '
';
+ return $html;
+ }
+}
\ No newline at end of file
diff --git a/library/vendor/php-diff/lib/Diff/Renderer/Html/SideBySide.php b/library/vendor/php-diff/lib/Diff/Renderer/Html/SideBySide.php
new file mode 100644
index 00000000..307af1c3
--- /dev/null
+++ b/library/vendor/php-diff/lib/Diff/Renderer/Html/SideBySide.php
@@ -0,0 +1,163 @@
+
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the Chris Boulton nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package DiffLib
+ * @author Chris Boulton
+ * @copyright (c) 2009 Chris Boulton
+ * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
+ * @version 1.1
+ * @link http://github.com/chrisboulton/php-diff
+ */
+
+require_once dirname(__FILE__).'/Array.php';
+
+class Diff_Renderer_Html_SideBySide extends Diff_Renderer_Html_Array
+{
+ /**
+ * Render a and return diff with changes between the two sequences
+ * displayed side by side.
+ *
+ * @return string The generated side by side diff.
+ */
+ public function render()
+ {
+ $changes = parent::render();
+
+ $html = '';
+ if(empty($changes)) {
+ return $html;
+ }
+
+ $html .= '';
+ $html .= '';
+ $html .= '';
+ $html .= 'Old Version | ';
+ $html .= 'New Version | ';
+ $html .= '
';
+ $html .= '';
+ foreach($changes as $i => $blocks) {
+ if($i > 0) {
+ $html .= '';
+ $html .= '… | | ';
+ $html .= '… | | ';
+ $html .= '';
+ }
+
+ foreach($blocks as $change) {
+ $html .= '';
+ // Equal changes should be shown on both sides of the diff
+ if($change['tag'] == 'equal') {
+ foreach($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= '';
+ $html .= ''.$fromLine.' | ';
+ $html .= ''.$line.' | ';
+ $html .= ''.$toLine.' | ';
+ $html .= ''.$line.' | ';
+ $html .= '
';
+ }
+ }
+ // Added lines only on the right side
+ else if($change['tag'] == 'insert') {
+ foreach($change['changed']['lines'] as $no => $line) {
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= '';
+ $html .= ' | ';
+ $html .= ' | ';
+ $html .= ''.$toLine.' | ';
+ $html .= ''.$line.' | ';
+ $html .= '
';
+ }
+ }
+ // Show deleted lines only on the left side
+ else if($change['tag'] == 'delete') {
+ foreach($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $html .= '';
+ $html .= ''.$fromLine.' | ';
+ $html .= ''.$line.' | ';
+ $html .= ' | ';
+ $html .= ' | ';
+ $html .= '
';
+ }
+ }
+ // Show modified lines on both sides
+ else if($change['tag'] == 'replace') {
+ if(count($change['base']['lines']) >= count($change['changed']['lines'])) {
+ foreach($change['base']['lines'] as $no => $line) {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $html .= '';
+ $html .= ''.$fromLine.' | ';
+ $html .= ''.$line.' | ';
+ if(!isset($change['changed']['lines'][$no])) {
+ $toLine = ' ';
+ $changedLine = ' ';
+ }
+ else {
+ $toLine = $change['base']['offset'] + $no + 1;
+ $changedLine = ''.$change['changed']['lines'][$no].'';
+ }
+ $html .= ''.$toLine.' | ';
+ $html .= ''.$changedLine.' | ';
+ $html .= '
';
+ }
+ }
+ else {
+ foreach($change['changed']['lines'] as $no => $changedLine) {
+ if(!isset($change['base']['lines'][$no])) {
+ $fromLine = ' ';
+ $line = ' ';
+ }
+ else {
+ $fromLine = $change['base']['offset'] + $no + 1;
+ $line = ''.$change['base']['lines'][$no].'';
+ }
+ $html .= '';
+ $html .= ''.$fromLine.' | ';
+ $html .= ''.$line.' | ';
+ $toLine = $change['changed']['offset'] + $no + 1;
+ $html .= ''.$toLine.' | ';
+ $html .= ''.$changedLine.' | ';
+ $html .= '
';
+ }
+ }
+ }
+ $html .= '';
+ }
+ }
+ $html .= '
';
+ return $html;
+ }
+}
\ No newline at end of file
diff --git a/library/vendor/php-diff/lib/Diff/Renderer/Text/Context.php b/library/vendor/php-diff/lib/Diff/Renderer/Text/Context.php
new file mode 100644
index 00000000..1200b01c
--- /dev/null
+++ b/library/vendor/php-diff/lib/Diff/Renderer/Text/Context.php
@@ -0,0 +1,128 @@
+
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the Chris Boulton nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package DiffLib
+ * @author Chris Boulton
+ * @copyright (c) 2009 Chris Boulton
+ * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
+ * @version 1.1
+ * @link http://github.com/chrisboulton/php-diff
+ */
+
+require_once dirname(__FILE__).'/../Abstract.php';
+
+class Diff_Renderer_Text_Context extends Diff_Renderer_Abstract
+{
+ /**
+ * @var array Array of the different opcode tags and how they map to the context diff equivalent.
+ */
+ private $tagMap = array(
+ 'insert' => '+',
+ 'delete' => '-',
+ 'replace' => '!',
+ 'equal' => ' '
+ );
+
+ /**
+ * Render and return a context formatted (old school!) diff file.
+ *
+ * @return string The generated context diff.
+ */
+ public function render()
+ {
+ $diff = '';
+ $opCodes = $this->diff->getGroupedOpcodes();
+ foreach($opCodes as $group) {
+ $diff .= "***************\n";
+ $lastItem = count($group)-1;
+ $i1 = $group[0][1];
+ $i2 = $group[$lastItem][2];
+ $j1 = $group[0][3];
+ $j2 = $group[$lastItem][4];
+
+ if($i2 - $i1 >= 2) {
+ $diff .= '*** '.($group[0][1] + 1).','.$i2." ****\n";
+ }
+ else {
+ $diff .= '*** '.$i2." ****\n";
+ }
+
+ if($j2 - $j1 >= 2) {
+ $separator = '--- '.($j1 + 1).','.$j2." ----\n";
+ }
+ else {
+ $separator = '--- '.$j2." ----\n";
+ }
+
+ $hasVisible = false;
+ foreach($group as $code) {
+ if($code[0] == 'replace' || $code[0] == 'delete') {
+ $hasVisible = true;
+ break;
+ }
+ }
+
+ if($hasVisible) {
+ foreach($group as $code) {
+ list($tag, $i1, $i2, $j1, $j2) = $code;
+ if($tag == 'insert') {
+ continue;
+ }
+ $diff .= $this->tagMap[$tag].' '.implode("\n".$this->tagMap[$tag].' ', $this->diff->GetA($i1, $i2))."\n";
+ }
+ }
+
+ $hasVisible = false;
+ foreach($group as $code) {
+ if($code[0] == 'replace' || $code[0] == 'insert') {
+ $hasVisible = true;
+ break;
+ }
+ }
+
+ $diff .= $separator;
+
+ if($hasVisible) {
+ foreach($group as $code) {
+ list($tag, $i1, $i2, $j1, $j2) = $code;
+ if($tag == 'delete') {
+ continue;
+ }
+ $diff .= $this->tagMap[$tag].' '.implode("\n".$this->tagMap[$tag].' ', $this->diff->GetB($j1, $j2))."\n";
+ }
+ }
+ }
+ return $diff;
+ }
+}
\ No newline at end of file
diff --git a/library/vendor/php-diff/lib/Diff/Renderer/Text/Unified.php b/library/vendor/php-diff/lib/Diff/Renderer/Text/Unified.php
new file mode 100644
index 00000000..e94d951d
--- /dev/null
+++ b/library/vendor/php-diff/lib/Diff/Renderer/Text/Unified.php
@@ -0,0 +1,87 @@
+
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the Chris Boulton nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package DiffLib
+ * @author Chris Boulton
+ * @copyright (c) 2009 Chris Boulton
+ * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
+ * @version 1.1
+ * @link http://github.com/chrisboulton/php-diff
+ */
+
+require_once dirname(__FILE__).'/../Abstract.php';
+
+class Diff_Renderer_Text_Unified extends Diff_Renderer_Abstract
+{
+ /**
+ * Render and return a unified diff.
+ *
+ * @return string The unified diff.
+ */
+ public function render()
+ {
+ $diff = '';
+ $opCodes = $this->diff->getGroupedOpcodes();
+ foreach($opCodes as $group) {
+ $lastItem = count($group)-1;
+ $i1 = $group[0][1];
+ $i2 = $group[$lastItem][2];
+ $j1 = $group[0][3];
+ $j2 = $group[$lastItem][4];
+
+ if($i1 == 0 && $i2 == 0) {
+ $i1 = -1;
+ $i2 = -1;
+ }
+
+ $diff .= '@@ -'.($i1 + 1).','.($i2 - $i1).' +'.($j1 + 1).','.($j2 - $j1)." @@\n";
+ foreach($group as $code) {
+ list($tag, $i1, $i2, $j1, $j2) = $code;
+ if($tag == 'equal') {
+ $diff .= ' '.implode("\n ", $this->diff->GetA($i1, $i2))."\n";
+ }
+ else {
+ if($tag == 'replace' || $tag == 'delete') {
+ $diff .= '-'.implode("\n-", $this->diff->GetA($i1, $i2))."\n";
+ }
+
+ if($tag == 'replace' || $tag == 'insert') {
+ $diff .= '+'.implode("\n+", $this->diff->GetB($j1, $j2))."\n";
+ }
+ }
+ }
+ }
+ return $diff;
+ }
+}
\ No newline at end of file
diff --git a/library/vendor/php-diff/lib/Diff/SequenceMatcher.php b/library/vendor/php-diff/lib/Diff/SequenceMatcher.php
new file mode 100644
index 00000000..e819e810
--- /dev/null
+++ b/library/vendor/php-diff/lib/Diff/SequenceMatcher.php
@@ -0,0 +1,742 @@
+
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * - Neither the name of the Chris Boulton nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * @package Diff
+ * @author Chris Boulton
+ * @copyright (c) 2009 Chris Boulton
+ * @license New BSD License http://www.opensource.org/licenses/bsd-license.php
+ * @version 1.1
+ * @link http://github.com/chrisboulton/php-diff
+ */
+
+class Diff_SequenceMatcher
+{
+ /**
+ * @var string|array Either a string or an array containing a callback function to determine if a line is "junk" or not.
+ */
+ private $junkCallback = null;
+
+ /**
+ * @var array The first sequence to compare against.
+ */
+ private $a = null;
+
+ /**
+ * @var array The second sequence.
+ */
+ private $b = null;
+
+ /**
+ * @var array Array of characters that are considered junk from the second sequence. Characters are the array key.
+ */
+ private $junkDict = array();
+
+ /**
+ * @var array Array of indices that do not contain junk elements.
+ */
+ private $b2j = array();
+
+ private $options = array();
+
+ private $defaultOptions = array(
+ 'ignoreNewLines' => false,
+ 'ignoreWhitespace' => false,
+ 'ignoreCase' => false
+ );
+
+ /**
+ * The constructor. With the sequences being passed, they'll be set for the
+ * sequence matcher and it will perform a basic cleanup & calculate junk
+ * elements.
+ *
+ * @param string|array $a A string or array containing the lines to compare against.
+ * @param string|array $b A string or array containing the lines to compare.
+ * @param string|array $junkCallback Either an array or string that references a callback function (if there is one) to determine 'junk' characters.
+ */
+ public function __construct($a, $b, $junkCallback=null, $options)
+ {
+ $this->a = null;
+ $this->b = null;
+ $this->junkCallback = $junkCallback;
+ $this->setOptions($options);
+ $this->setSequences($a, $b);
+ }
+
+ public function setOptions($options)
+ {
+ $this->options = array_merge($this->defaultOptions, $options);
+ }
+
+ /**
+ * Set the first and second sequences to use with the sequence matcher.
+ *
+ * @param string|array $a A string or array containing the lines to compare against.
+ * @param string|array $b A string or array containing the lines to compare.
+ */
+ public function setSequences($a, $b)
+ {
+ $this->setSeq1($a);
+ $this->setSeq2($b);
+ }
+
+ /**
+ * Set the first sequence ($a) and reset any internal caches to indicate that
+ * when calling the calculation methods, we need to recalculate them.
+ *
+ * @param string|array $a The sequence to set as the first sequence.
+ */
+ public function setSeq1($a)
+ {
+ if(!is_array($a)) {
+ $a = str_split($a);
+ }
+ if($a == $this->a) {
+ return;
+ }
+
+ $this->a= $a;
+ $this->matchingBlocks = null;
+ $this->opCodes = null;
+ }
+
+ /**
+ * Set the second sequence ($b) and reset any internal caches to indicate that
+ * when calling the calculation methods, we need to recalculate them.
+ *
+ * @param string|array $b The sequence to set as the second sequence.
+ */
+ public function setSeq2($b)
+ {
+ if(!is_array($b)) {
+ $b = str_split($b);
+ }
+ if($b == $this->b) {
+ return;
+ }
+
+ $this->b = $b;
+ $this->matchingBlocks = null;
+ $this->opCodes = null;
+ $this->fullBCount = null;
+ $this->chainB();
+ }
+
+ /**
+ * Generate the internal arrays containing the list of junk and non-junk
+ * characters for the second ($b) sequence.
+ */
+ private function chainB()
+ {
+ $length = count ($this->b);
+ $this->b2j = array();
+ $popularDict = array();
+
+ for($i = 0; $i < $length; ++$i) {
+ $char = $this->b[$i];
+ if(isset($this->b2j[$char])) {
+ if($length >= 200 && count($this->b2j[$char]) * 100 > $length) {
+ $popularDict[$char] = 1;
+ unset($this->b2j[$char]);
+ }
+ else {
+ $this->b2j[$char][] = $i;
+ }
+ }
+ else {
+ $this->b2j[$char] = array(
+ $i
+ );
+ }
+ }
+
+ // Remove leftovers
+ foreach(array_keys($popularDict) as $char) {
+ unset($this->b2j[$char]);
+ }
+
+ $this->junkDict = array();
+ if(is_callable($this->junkCallback)) {
+ foreach(array_keys($popularDict) as $char) {
+ if(call_user_func($this->junkCallback, $char)) {
+ $this->junkDict[$char] = 1;
+ unset($popularDict[$char]);
+ }
+ }
+
+ foreach(array_keys($this->b2j) as $char) {
+ if(call_user_func($this->junkCallback, $char)) {
+ $this->junkDict[$char] = 1;
+ unset($this->b2j[$char]);
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks if a particular character is in the junk dictionary
+ * for the list of junk characters.
+ *
+ * @return boolean $b True if the character is considered junk. False if not.
+ */
+ private function isBJunk($b)
+ {
+ if(isset($this->juncDict[$b])) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Find the longest matching block in the two sequences, as defined by the
+ * lower and upper constraints for each sequence. (for the first sequence,
+ * $alo - $ahi and for the second sequence, $blo - $bhi)
+ *
+ * Essentially, of all of the maximal matching blocks, return the one that
+ * startest earliest in $a, and all of those maximal matching blocks that
+ * start earliest in $a, return the one that starts earliest in $b.
+ *
+ * If the junk callback is defined, do the above but with the restriction
+ * that the junk element appears in the block. Extend it as far as possible
+ * by matching only junk elements in both $a and $b.
+ *
+ * @param int $alo The lower constraint for the first sequence.
+ * @param int $ahi The upper constraint for the first sequence.
+ * @param int $blo The lower constraint for the second sequence.
+ * @param int $bhi The upper constraint for the second sequence.
+ * @return array Array containing the longest match that includes the starting position in $a, start in $b and the length/size.
+ */
+ public function findLongestMatch($alo, $ahi, $blo, $bhi)
+ {
+ $a = $this->a;
+ $b = $this->b;
+
+ $bestI = $alo;
+ $bestJ = $blo;
+ $bestSize = 0;
+
+ $j2Len = array();
+ $nothing = array();
+
+ for($i = $alo; $i < $ahi; ++$i) {
+ $newJ2Len = array();
+ $jDict = $this->arrayGetDefault($this->b2j, $a[$i], $nothing);
+ foreach($jDict as $jKey => $j) {
+ if($j < $blo) {
+ continue;
+ }
+ else if($j >= $bhi) {
+ break;
+ }
+
+ $k = $this->arrayGetDefault($j2Len, $j -1, 0) + 1;
+ $newJ2Len[$j] = $k;
+ if($k > $bestSize) {
+ $bestI = $i - $k + 1;
+ $bestJ = $j - $k + 1;
+ $bestSize = $k;
+ }
+ }
+
+ $j2Len = $newJ2Len;
+ }
+
+ while($bestI > $alo && $bestJ > $blo && !$this->isBJunk($b[$bestJ - 1]) &&
+ !$this->linesAreDifferent($bestI - 1, $bestJ - 1)) {
+ --$bestI;
+ --$bestJ;
+ ++$bestSize;
+ }
+
+ while($bestI + $bestSize < $ahi && ($bestJ + $bestSize) < $bhi &&
+ !$this->isBJunk($b[$bestJ + $bestSize]) && !$this->linesAreDifferent($bestI + $bestSize, $bestJ + $bestSize)) {
+ ++$bestSize;
+ }
+
+ while($bestI > $alo && $bestJ > $blo && $this->isBJunk($b[$bestJ - 1]) &&
+ !$this->isLineDifferent($bestI - 1, $bestJ - 1)) {
+ --$bestI;
+ --$bestJ;
+ ++$bestSize;
+ }
+
+ while($bestI + $bestSize < $ahi && $bestJ + $bestSize < $bhi &&
+ $this->isBJunk($b[$bestJ + $bestSize]) && !$this->linesAreDifferent($bestI + $bestSize, $bestJ + $bestSize)) {
+ ++$bestSize;
+ }
+
+ return array(
+ $bestI,
+ $bestJ,
+ $bestSize
+ );
+ }
+
+ /**
+ * Check if the two lines at the given indexes are different or not.
+ *
+ * @param int $aIndex Line number to check against in a.
+ * @param int $bIndex Line number to check against in b.
+ * @return boolean True if the lines are different and false if not.
+ */
+ public function linesAreDifferent($aIndex, $bIndex)
+ {
+ $lineA = $this->a[$aIndex];
+ $lineB = $this->b[$bIndex];
+
+ if($this->options['ignoreWhitespace']) {
+ $replace = array("\t", ' ');
+ $lineA = str_replace($replace, '', $lineA);
+ $lineB = str_replace($replace, '', $lineB);
+ }
+
+ if($this->options['ignoreCase']) {
+ $lineA = strtolower($lineA);
+ $lineB = strtolower($lineB);
+ }
+
+ if($lineA != $lineB) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return a nested set of arrays for all of the matching sub-sequences
+ * in the strings $a and $b.
+ *
+ * Each block contains the lower constraint of the block in $a, the lower
+ * constraint of the block in $b and finally the number of lines that the
+ * block continues for.
+ *
+ * @return array Nested array of the matching blocks, as described by the function.
+ */
+ public function getMatchingBlocks()
+ {
+ if(!empty($this->matchingBlocks)) {
+ return $this->matchingBlocks;
+ }
+
+ $aLength = count($this->a);
+ $bLength = count($this->b);
+
+ $queue = array(
+ array(
+ 0,
+ $aLength,
+ 0,
+ $bLength
+ )
+ );
+
+ $matchingBlocks = array();
+ while(!empty($queue)) {
+ list($alo, $ahi, $blo, $bhi) = array_pop($queue);
+ $x = $this->findLongestMatch($alo, $ahi, $blo, $bhi);
+ list($i, $j, $k) = $x;
+ if($k) {
+ $matchingBlocks[] = $x;
+ if($alo < $i && $blo < $j) {
+ $queue[] = array(
+ $alo,
+ $i,
+ $blo,
+ $j
+ );
+ }
+
+ if($i + $k < $ahi && $j + $k < $bhi) {
+ $queue[] = array(
+ $i + $k,
+ $ahi,
+ $j + $k,
+ $bhi
+ );
+ }
+ }
+ }
+
+ usort($matchingBlocks, array($this, 'tupleSort'));
+
+ $i1 = 0;
+ $j1 = 0;
+ $k1 = 0;
+ $nonAdjacent = array();
+ foreach($matchingBlocks as $block) {
+ list($i2, $j2, $k2) = $block;
+ if($i1 + $k1 == $i2 && $j1 + $k1 == $j2) {
+ $k1 += $k2;
+ }
+ else {
+ if($k1) {
+ $nonAdjacent[] = array(
+ $i1,
+ $j1,
+ $k1
+ );
+ }
+
+ $i1 = $i2;
+ $j1 = $j2;
+ $k1 = $k2;
+ }
+ }
+
+ if($k1) {
+ $nonAdjacent[] = array(
+ $i1,
+ $j1,
+ $k1
+ );
+ }
+
+ $nonAdjacent[] = array(
+ $aLength,
+ $bLength,
+ 0
+ );
+
+ $this->matchingBlocks = $nonAdjacent;
+ return $this->matchingBlocks;
+ }
+
+ /**
+ * Return a list of all of the opcodes for the differences between the
+ * two strings.
+ *
+ * The nested array returned contains an array describing the opcode
+ * which includes:
+ * 0 - The type of tag (as described below) for the opcode.
+ * 1 - The beginning line in the first sequence.
+ * 2 - The end line in the first sequence.
+ * 3 - The beginning line in the second sequence.
+ * 4 - The end line in the second sequence.
+ *
+ * The different types of tags include:
+ * replace - The string from $i1 to $i2 in $a should be replaced by
+ * the string in $b from $j1 to $j2.
+ * delete - The string in $a from $i1 to $j2 should be deleted.
+ * insert - The string in $b from $j1 to $j2 should be inserted at
+ * $i1 in $a.
+ * equal - The two strings with the specified ranges are equal.
+ *
+ * @return array Array of the opcodes describing the differences between the strings.
+ */
+ public function getOpCodes()
+ {
+ if(!empty($this->opCodes)) {
+ return $this->opCodes;
+ }
+
+ $i = 0;
+ $j = 0;
+ $this->opCodes = array();
+
+ $blocks = $this->getMatchingBlocks();
+ foreach($blocks as $block) {
+ list($ai, $bj, $size) = $block;
+ $tag = '';
+ if($i < $ai && $j < $bj) {
+ $tag = 'replace';
+ }
+ else if($i < $ai) {
+ $tag = 'delete';
+ }
+ else if($j < $bj) {
+ $tag = 'insert';
+ }
+
+ if($tag) {
+ $this->opCodes[] = array(
+ $tag,
+ $i,
+ $ai,
+ $j,
+ $bj
+ );
+ }
+
+ $i = $ai + $size;
+ $j = $bj + $size;
+
+ if($size) {
+ $this->opCodes[] = array(
+ 'equal',
+ $ai,
+ $i,
+ $bj,
+ $j
+ );
+ }
+ }
+ return $this->opCodes;
+ }
+
+ /**
+ * Return a series of nested arrays containing different groups of generated
+ * opcodes for the differences between the strings with up to $context lines
+ * of surrounding content.
+ *
+ * Essentially what happens here is any big equal blocks of strings are stripped
+ * out, the smaller subsets of changes are then arranged in to their groups.
+ * This means that the sequence matcher and diffs do not need to include the full
+ * content of the different files but can still provide context as to where the
+ * changes are.
+ *
+ * @param int $context The number of lines of context to provide around the groups.
+ * @return array Nested array of all of the grouped opcodes.
+ */
+ public function getGroupedOpcodes($context=3)
+ {
+ $opCodes = $this->getOpCodes();
+ if(empty($opCodes)) {
+ $opCodes = array(
+ array(
+ 'equal',
+ 0,
+ 1,
+ 0,
+ 1
+ )
+ );
+ }
+
+ if($opCodes[0][0] == 'equal') {
+ $opCodes[0] = array(
+ $opCodes[0][0],
+ max($opCodes[0][1], $opCodes[0][2] - $context),
+ $opCodes[0][2],
+ max($opCodes[0][3], $opCodes[0][4] - $context),
+ $opCodes[0][4]
+ );
+ }
+
+ $lastItem = count($opCodes) - 1;
+ if($opCodes[$lastItem][0] == 'equal') {
+ list($tag, $i1, $i2, $j1, $j2) = $opCodes[$lastItem];
+ $opCodes[$lastItem] = array(
+ $tag,
+ $i1,
+ min($i2, $i1 + $context),
+ $j1,
+ min($j2, $j1 + $context)
+ );
+ }
+
+ $maxRange = $context * 2;
+ $groups = array();
+ $group = array();
+ foreach($opCodes as $code) {
+ list($tag, $i1, $i2, $j1, $j2) = $code;
+ if($tag == 'equal' && $i2 - $i1 > $maxRange) {
+ $group[] = array(
+ $tag,
+ $i1,
+ min($i2, $i1 + $context),
+ $j1,
+ min($j2, $j1 + $context)
+ );
+ $groups[] = $group;
+ $group = array();
+ $i1 = max($i1, $i2 - $context);
+ $j1 = max($j1, $j2 - $context);
+ }
+ $group[] = array(
+ $tag,
+ $i1,
+ $i2,
+ $j1,
+ $j2
+ );
+ }
+
+ if(!empty($group) && !(count($group) == 1 && $group[0][0] == 'equal')) {
+ $groups[] = $group;
+ }
+
+ return $groups;
+ }
+
+ /**
+ * Return a measure of the similarity between the two sequences.
+ * This will be a float value between 0 and 1.
+ *
+ * Out of all of the ratio calculation functions, this is the most
+ * expensive to call if getMatchingBlocks or getOpCodes is yet to be
+ * called. The other calculation methods (quickRatio and realquickRatio)
+ * can be used to perform quicker calculations but may be less accurate.
+ *
+ * The ratio is calculated as (2 * number of matches) / total number of
+ * elements in both sequences.
+ *
+ * @return float The calculated ratio.
+ */
+ public function Ratio()
+ {
+ $matches = array_reduce($this->getMatchingBlocks(), array($this, 'ratioReduce'), 0);
+ return $this->calculateRatio($matches, count ($this->a) + count ($this->b));
+ }
+
+ /**
+ * Helper function to calculate the number of matches for Ratio().
+ *
+ * @param int $sum The running total for the number of matches.
+ * @param array $triple Array containing the matching block triple to add to the running total.
+ * @return int The new running total for the number of matches.
+ */
+ private function ratioReduce($sum, $triple)
+ {
+ return $sum + ($triple[count($triple) - 1]);
+ }
+
+ /**
+ * Quickly return an upper bound ratio for the similarity of the strings.
+ * This is quicker to compute than Ratio().
+ *
+ * @return float The calculated ratio.
+ */
+ private function quickRatio()
+ {
+ if($this->fullBCount === null) {
+ $this->fullBCount = array();
+ $bLength = count ($b);
+ for($i = 0; $i < $bLength; ++$i) {
+ $char = $this->b[$i];
+ $this->fullBCount[$char] = $this->arrayGetDefault($this->fullBCount, $char, 0) + 1;
+ }
+ }
+
+ $avail = array();
+ $matches = 0;
+ $aLength = count ($this->a);
+ for($i = 0; $i < $aLength; ++$i) {
+ $char = $this->a[$i];
+ if(isset($avail[$char])) {
+ $numb = $avail[$char];
+ }
+ else {
+ $numb = $this->arrayGetDefault($this->fullBCount, $char, 0);
+ }
+ $avail[$char] = $numb - 1;
+ if($numb > 0) {
+ ++$matches;
+ }
+ }
+
+ $this->calculateRatio($matches, count ($this->a) + count ($this->b));
+ }
+
+ /**
+ * Return an upper bound ratio really quickly for the similarity of the strings.
+ * This is quicker to compute than Ratio() and quickRatio().
+ *
+ * @return float The calculated ratio.
+ */
+ private function realquickRatio()
+ {
+ $aLength = count ($this->a);
+ $bLength = count ($this->b);
+
+ return $this->calculateRatio(min($aLength, $bLength), $aLength + $bLength);
+ }
+
+ /**
+ * Helper function for calculating the ratio to measure similarity for the strings.
+ * The ratio is defined as being 2 * (number of matches / total length)
+ *
+ * @param int $matches The number of matches in the two strings.
+ * @param int $length The length of the two strings.
+ * @return float The calculated ratio.
+ */
+ private function calculateRatio($matches, $length=0)
+ {
+ if($length) {
+ return 2 * ($matches / $length);
+ }
+ else {
+ return 1;
+ }
+ }
+
+ /**
+ * Helper function that provides the ability to return the value for a key
+ * in an array of it exists, or if it doesn't then return a default value.
+ * Essentially cleaner than doing a series of if(isset()) {} else {} calls.
+ *
+ * @param array $array The array to search.
+ * @param string $key The key to check that exists.
+ * @param mixed $default The value to return as the default value if the key doesn't exist.
+ * @return mixed The value from the array if the key exists or otherwise the default.
+ */
+ private function arrayGetDefault($array, $key, $default)
+ {
+ if(isset($array[$key])) {
+ return $array[$key];
+ }
+ else {
+ return $default;
+ }
+ }
+
+ /**
+ * Sort an array by the nested arrays it contains. Helper function for getMatchingBlocks
+ *
+ * @param array $a First array to compare.
+ * @param array $b Second array to compare.
+ * @return int -1, 0 or 1, as expected by the usort function.
+ */
+ private function tupleSort($a, $b)
+ {
+ $max = max(count($a), count($b));
+ for($i = 0; $i < $max; ++$i) {
+ if($a[$i] < $b[$i]) {
+ return -1;
+ }
+ else if($a[$i] > $b[$i]) {
+ return 1;
+ }
+ }
+
+ if(count($a) == $count($b)) {
+ return 0;
+ }
+ else if(count($a) < count($b)) {
+ return -1;
+ }
+ else {
+ return 1;
+ }
+ }
+}
\ No newline at end of file
diff --git a/public/css/module.less b/public/css/module.less
index da351d10..0b80387d 100644
--- a/public/css/module.less
+++ b/public/css/module.less
@@ -3,6 +3,10 @@ a {
font-weight: normal;
}
+pre {
+ background: none;
+}
+
table.avp th {
font-size: inherit;
}
@@ -427,3 +431,103 @@ div.diff {
.tree a.abstract { background-image: url('../img/director/tree.png'); }
.tree a.object { background-image: url('../img/director/leaf.gif'); }
+/** php-diff **/
+
+.Differences {
+ width: 100%;
+ table-layout: fixed;
+ empty-cells: show;
+}
+
+.Differences thead {
+ display: none;
+}
+
+.Differences thead th {
+ text-align: left;
+ padding-left: 4 / 14 * 16em;
+}
+.Differences tbody th {
+ text-align: right;
+ width: 4em;
+ padding: 1px 2px;
+ border-right: 1px solid @gray-light;
+ background: @gray-lightest;
+ font-weight: normal;
+ vertical-align: top;
+}
+
+.Differences tbody td {
+ width: 50%;
+ .preformatted();
+ word-break: break-all;
+}
+
+@color-diff-ins: #bfb;
+@color-diff-del: #faa;
+@color-diff-changed-old: #fdd;
+@color-diff-changed-new: #efe;
+
+.DifferencesSideBySide {
+ ins, del {
+ text-decoration: none;
+ }
+
+ .ChangeInsert {
+ td.Left {
+ background: @gray-lighter;
+ }
+ td.Right {
+ background: @color-diff-ins;
+ }
+ }
+
+ .ChangeDelete {
+ td.Left {
+ background: @color-diff-del;
+ }
+ td.Right {
+ background: @gray-ligher;
+ }
+ }
+
+ .ChangeReplace {
+ td.Left {
+ background: @color-diff-changed-old;
+ del {
+ background: @color-diff-del;
+ }
+ }
+
+ td.Right {
+ background: @color-diff-changed-new;
+ ins {
+ background: @color-diff-ins;
+ }
+ }
+
+ }
+}
+
+.Differences .Skipped {
+ background: @gray-lightest;
+}
+
+.DifferencesInline .ChangeReplace .Left,
+.DifferencesInline .ChangeDelete .Left {
+ background: #fdd;
+}
+
+.DifferencesInline .ChangeReplace .Right,
+.DifferencesInline .ChangeInsert .Right {
+ background: #dfd;
+}
+
+.DifferencesInline .ChangeReplace ins {
+ background: #9e9;
+}
+
+.DifferencesInline .ChangeReplace del {
+ background: #e99;
+}
+/** END of php-diff **/