43 changed files with 2781 additions and 275 deletions
@ -1,21 +0,0 @@ |
|||||||
<?php |
|
||||||
/** |
|
||||||
* @link http://www.yiiframework.com/ |
|
||||||
* @copyright Copyright (c) 2008 Yii Software LLC |
|
||||||
* @license http://www.yiiframework.com/license/ |
|
||||||
*/ |
|
||||||
|
|
||||||
namespace yii\gii; |
|
||||||
|
|
||||||
/** |
|
||||||
* @author Qiang Xue <qiang.xue@gmail.com> |
|
||||||
* @since 2.0 |
|
||||||
*/ |
|
||||||
class ActiveField extends \yii\widgets\ActiveField |
|
||||||
{ |
|
||||||
public function sticky() |
|
||||||
{ |
|
||||||
$this->options['class'] .= ' sticky'; |
|
||||||
return $this; |
|
||||||
} |
|
||||||
} |
|
@ -1,24 +1,85 @@ |
|||||||
yii.gii = (function ($) { |
yii.gii = (function ($) { |
||||||
return { |
var isActive = $('.default-view').length > 0; |
||||||
init: function () { |
|
||||||
$('.hint-block').each(function() { |
var initHintBlocks = function () { |
||||||
var $hint = $(this); |
$('.hint-block').each(function () { |
||||||
$hint.parent().find('input,select,textarea').popover({ |
var $hint = $(this); |
||||||
html: true, |
$hint.parent().find('input,select,textarea').popover({ |
||||||
trigger: 'focus', |
html: true, |
||||||
placement: 'right', |
trigger: 'focus', |
||||||
content: $hint.html() |
placement: 'right', |
||||||
}); |
content: $hint.html() |
||||||
}); |
}); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
var $checkAll = $('#check-all'); |
var initStickyInputs = function () { |
||||||
$checkAll.click(function() { |
$('.sticky:not(.error) input,select,textarea').each(function () { |
||||||
$('.code-files .check input').prop('checked', this.checked); |
var value; |
||||||
|
if (this.tagName == 'SELECT') { |
||||||
|
value = this.options[this.selectedIndex].text; |
||||||
|
} else if (this.tagName == 'TEXTAREA') { |
||||||
|
value = $(this).html(); |
||||||
|
} else { |
||||||
|
value = $(this).val(); |
||||||
|
} |
||||||
|
if (value == '') { |
||||||
|
value = '[empty]'; |
||||||
|
} |
||||||
|
$(this).before('<div class="sticky-value">' + value + '</div>').hide(); |
||||||
|
}); |
||||||
|
$('.sticky-value').on('click', function() { |
||||||
|
$(this).hide(); |
||||||
|
$(this).next().show().get(0).focus(); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
var initPreviewDiffLinks = function () { |
||||||
|
$('.preview-code,.diff-code').on('click', function () { |
||||||
|
var $modal = $('#preview-modal'); |
||||||
|
var $link = $(this); |
||||||
|
$modal.find('.modal-title').text($link.data('title')); |
||||||
|
$modal.find('.modal-body').html('Loading ...'); |
||||||
|
$modal.modal('show'); |
||||||
|
$.ajax({ |
||||||
|
type: 'POST', |
||||||
|
cache: false, |
||||||
|
url: $link.prop('href'), |
||||||
|
data: $('.default-view form').serializeArray(), |
||||||
|
success: function (data) { |
||||||
|
$modal.find('.modal-body').html(data); |
||||||
|
$modal.find('.content').css('max-height', ($(window).height() - 200) + 'px'); |
||||||
|
}, |
||||||
|
error: function (XMLHttpRequest, textStatus, errorThrown) { |
||||||
|
$modal.find('.modal-body').html('<div class="error">' + XMLHttpRequest.responseText + '</div>') |
||||||
|
} |
||||||
}); |
}); |
||||||
$('.code-files .check input').click(function() { |
return false; |
||||||
$checkAll.prop('checked', !$('.code-files .check input:not(:checked)').length); |
}); |
||||||
|
}; |
||||||
|
|
||||||
|
var initConfirmationCheckboxes = function () { |
||||||
|
var $checkAll = $('#check-all'); |
||||||
|
$checkAll.click(function () { |
||||||
|
$('.default-view-files table .check input').prop('checked', this.checked); |
||||||
|
}); |
||||||
|
$('.default-view-files table .check input').click(function () { |
||||||
|
$checkAll.prop('checked', !$('.default-view-files table .check input:not(:checked)').length); |
||||||
|
}); |
||||||
|
$checkAll.prop('checked', !$('.default-view-files table .check input:not(:checked)').length); |
||||||
|
}; |
||||||
|
|
||||||
|
return { |
||||||
|
init: function () { |
||||||
|
initHintBlocks(); |
||||||
|
initStickyInputs(); |
||||||
|
initPreviewDiffLinks(); |
||||||
|
initConfirmationCheckboxes(); |
||||||
|
|
||||||
|
$('.default-view .form-group input,select,textarea').change(function(){ |
||||||
|
$('.default-view-results,.default-view-files').hide(); |
||||||
|
$('.default-view button[name="generate"]').hide(); |
||||||
}); |
}); |
||||||
$checkAll.prop('checked', !$('.code-files .check input:not(:checked)').length); |
|
||||||
} |
} |
||||||
}; |
}; |
||||||
})(jQuery); |
})(jQuery); |
||||||
|
@ -0,0 +1,40 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace yii\gii\components; |
||||||
|
|
||||||
|
use yii\gii\Generator; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com> |
||||||
|
* @since 2.0 |
||||||
|
*/ |
||||||
|
class ActiveField extends \yii\widgets\ActiveField |
||||||
|
{ |
||||||
|
/** |
||||||
|
* @var Generator |
||||||
|
*/ |
||||||
|
public $model; |
||||||
|
|
||||||
|
public function init() |
||||||
|
{ |
||||||
|
$stickyAttributes = $this->model->stickyAttributes(); |
||||||
|
if (in_array($this->attribute, $stickyAttributes)) { |
||||||
|
$this->sticky(); |
||||||
|
} |
||||||
|
$hints = $this->model->hints(); |
||||||
|
if (isset($hints[$this->attribute])) { |
||||||
|
$this->hint($hints[$this->attribute]); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function sticky() |
||||||
|
{ |
||||||
|
$this->options['class'] .= ' sticky'; |
||||||
|
return $this; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @link http://www.yiiframework.com/ |
||||||
|
* @copyright Copyright (c) 2008 Yii Software LLC |
||||||
|
* @license http://www.yiiframework.com/license/ |
||||||
|
*/ |
||||||
|
|
||||||
|
namespace yii\gii\components; |
||||||
|
|
||||||
|
use Yii; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Qiang Xue <qiang.xue@gmail.com> |
||||||
|
* @since 2.0 |
||||||
|
*/ |
||||||
|
class TextDiff |
||||||
|
{ |
||||||
|
public static function compare($lines1, $lines2) |
||||||
|
{ |
||||||
|
Yii::setAlias('@Horde', '@yii/gii/lib/Horde'); |
||||||
|
if (is_string($lines1)) { |
||||||
|
$lines1 = explode("\n", $lines1); |
||||||
|
} |
||||||
|
if (is_string($lines2)) { |
||||||
|
$lines2 = explode("\n", $lines2); |
||||||
|
} |
||||||
|
$diff = new \Horde_Text_Diff('auto', array($lines1, $lines2)); |
||||||
|
$renderer = new \Horde_Text_Diff_Renderer_Inline(); |
||||||
|
return $renderer->render($diff); |
||||||
|
} |
||||||
|
} |
@ -1,8 +1,24 @@ |
|||||||
<?php |
<?php |
||||||
/** |
/** |
||||||
* Created by JetBrains PhpStorm. |
* This is the template for generating a controller class file. |
||||||
* User: qiang |
* |
||||||
* Date: 8/15/13 |
* @var yii\base\View $this |
||||||
* Time: 4:45 PM |
* @var yii\gii\generators\controller\Generator $generator |
||||||
* To change this template use File | Settings | File Templates. |
|
||||||
*/ |
*/ |
||||||
|
?> |
||||||
|
<?php echo "<?php\n"; ?> |
||||||
|
|
||||||
|
<?php if (!empty($generator->ns)): ?> |
||||||
|
namespace <?php echo $generator->ns; ?>;
|
||||||
|
<?php endif; ?> |
||||||
|
|
||||||
|
class <?php echo $generator->controllerClass; ?> extends <?php echo '\\' . ltrim($generator->baseClass, '\\') . "\n"; ?> |
||||||
|
{ |
||||||
|
<?php foreach($generator->getActionIDs() as $action): ?> |
||||||
|
public function action<?php echo ucfirst($action); ?>()
|
||||||
|
{ |
||||||
|
return $this->render('<?php echo $action; ?>');
|
||||||
|
} |
||||||
|
|
||||||
|
<?php endforeach; ?> |
||||||
|
} |
||||||
|
@ -1,8 +1,21 @@ |
|||||||
<?php |
<?php |
||||||
/** |
/** |
||||||
* Created by JetBrains PhpStorm. |
* This is the template for generating an action view file. |
||||||
* User: qiang |
* |
||||||
* Date: 8/15/13 |
* @var yii\base\View $this |
||||||
* Time: 4:46 PM |
* @var yii\gii\generators\controller\Generator $generator |
||||||
* To change this template use File | Settings | File Templates. |
* @var string $action the action ID |
||||||
*/ |
*/ |
||||||
|
?> |
||||||
|
<?php echo "<?php\n"; ?> |
||||||
|
/** |
||||||
|
* @var yii\base\View $this |
||||||
|
*/ |
||||||
|
<?php echo "?>"; ?> |
||||||
|
|
||||||
|
<h1><?php echo $action; ?></h1>
|
||||||
|
|
||||||
|
<p> |
||||||
|
You may change the content of this page by modifying |
||||||
|
the file <code><?php echo '<?php'; ?> echo __FILE__; ?></code>.
|
||||||
|
</p> |
||||||
|
@ -0,0 +1,233 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* General API for generating and formatting diffs - the differences between |
||||||
|
* two sequences of strings. |
||||||
|
* |
||||||
|
* The original PHP version of this code was written by Geoffrey T. Dairiki |
||||||
|
* <dairiki@dairiki.org>, and is used/adapted with his permission. |
||||||
|
* |
||||||
|
* Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org> |
||||||
|
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you did |
||||||
|
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @package Text_Diff |
||||||
|
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
||||||
|
*/ |
||||||
|
class Horde_Text_Diff |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Array of changes. |
||||||
|
* |
||||||
|
* @var array |
||||||
|
*/ |
||||||
|
protected $_edits; |
||||||
|
|
||||||
|
/** |
||||||
|
* Computes diffs between sequences of strings. |
||||||
|
* |
||||||
|
* @param string $engine Name of the diffing engine to use. 'auto' |
||||||
|
* will automatically select the best. |
||||||
|
* @param array $params Parameters to pass to the diffing engine. |
||||||
|
* Normally an array of two arrays, each |
||||||
|
* containing the lines from a file. |
||||||
|
*/ |
||||||
|
public function __construct($engine, $params) |
||||||
|
{ |
||||||
|
if ($engine == 'auto') { |
||||||
|
$engine = extension_loaded('xdiff') ? 'Xdiff' : 'Native'; |
||||||
|
} else { |
||||||
|
$engine = ucfirst(basename($engine)); |
||||||
|
} |
||||||
|
|
||||||
|
$class = 'Horde_Text_Diff_Engine_' . $engine; |
||||||
|
$diff_engine = new $class(); |
||||||
|
|
||||||
|
$this->_edits = call_user_func_array(array($diff_engine, 'diff'), $params); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the array of differences. |
||||||
|
*/ |
||||||
|
public function getDiff() |
||||||
|
{ |
||||||
|
return $this->_edits; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* returns the number of new (added) lines in a given diff. |
||||||
|
* |
||||||
|
* @return integer The number of new lines |
||||||
|
*/ |
||||||
|
public function countAddedLines() |
||||||
|
{ |
||||||
|
$count = 0; |
||||||
|
foreach ($this->_edits as $edit) { |
||||||
|
if ($edit instanceof Horde_Text_Diff_Op_Add || |
||||||
|
$edit instanceof Horde_Text_Diff_Op_Change) { |
||||||
|
$count += $edit->nfinal(); |
||||||
|
} |
||||||
|
} |
||||||
|
return $count; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the number of deleted (removed) lines in a given diff. |
||||||
|
* |
||||||
|
* @return integer The number of deleted lines |
||||||
|
*/ |
||||||
|
public function countDeletedLines() |
||||||
|
{ |
||||||
|
$count = 0; |
||||||
|
foreach ($this->_edits as $edit) { |
||||||
|
if ($edit instanceof Horde_Text_Diff_Op_Delete || |
||||||
|
$edit instanceof Horde_Text_Diff_Op_Change) { |
||||||
|
$count += $edit->norig(); |
||||||
|
} |
||||||
|
} |
||||||
|
return $count; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Computes a reversed diff. |
||||||
|
* |
||||||
|
* Example: |
||||||
|
* <code> |
||||||
|
* $diff = new Horde_Text_Diff($lines1, $lines2); |
||||||
|
* $rev = $diff->reverse(); |
||||||
|
* </code> |
||||||
|
* |
||||||
|
* @return Horde_Text_Diff A Diff object representing the inverse of the |
||||||
|
* original diff. Note that we purposely don't return a |
||||||
|
* reference here, since this essentially is a clone() |
||||||
|
* method. |
||||||
|
*/ |
||||||
|
public function reverse() |
||||||
|
{ |
||||||
|
if (version_compare(zend_version(), '2', '>')) { |
||||||
|
$rev = clone($this); |
||||||
|
} else { |
||||||
|
$rev = $this; |
||||||
|
} |
||||||
|
$rev->_edits = array(); |
||||||
|
foreach ($this->_edits as $edit) { |
||||||
|
$rev->_edits[] = $edit->reverse(); |
||||||
|
} |
||||||
|
return $rev; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks for an empty diff. |
||||||
|
* |
||||||
|
* @return boolean True if two sequences were identical. |
||||||
|
*/ |
||||||
|
public function isEmpty() |
||||||
|
{ |
||||||
|
foreach ($this->_edits as $edit) { |
||||||
|
if (!($edit instanceof Horde_Text_Diff_Op_Copy)) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Computes the length of the Longest Common Subsequence (LCS). |
||||||
|
* |
||||||
|
* This is mostly for diagnostic purposes. |
||||||
|
* |
||||||
|
* @return integer The length of the LCS. |
||||||
|
*/ |
||||||
|
public function lcs() |
||||||
|
{ |
||||||
|
$lcs = 0; |
||||||
|
foreach ($this->_edits as $edit) { |
||||||
|
if ($edit instanceof Horde_Text_Diff_Op_Copy) { |
||||||
|
$lcs += count($edit->orig); |
||||||
|
} |
||||||
|
} |
||||||
|
return $lcs; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the original set of lines. |
||||||
|
* |
||||||
|
* This reconstructs the $from_lines parameter passed to the constructor. |
||||||
|
* |
||||||
|
* @return array The original sequence of strings. |
||||||
|
*/ |
||||||
|
public function getOriginal() |
||||||
|
{ |
||||||
|
$lines = array(); |
||||||
|
foreach ($this->_edits as $edit) { |
||||||
|
if ($edit->orig) { |
||||||
|
array_splice($lines, count($lines), 0, $edit->orig); |
||||||
|
} |
||||||
|
} |
||||||
|
return $lines; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Gets the final set of lines. |
||||||
|
* |
||||||
|
* This reconstructs the $to_lines parameter passed to the constructor. |
||||||
|
* |
||||||
|
* @return array The sequence of strings. |
||||||
|
*/ |
||||||
|
public function getFinal() |
||||||
|
{ |
||||||
|
$lines = array(); |
||||||
|
foreach ($this->_edits as $edit) { |
||||||
|
if ($edit->final) { |
||||||
|
array_splice($lines, count($lines), 0, $edit->final); |
||||||
|
} |
||||||
|
} |
||||||
|
return $lines; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Removes trailing newlines from a line of text. This is meant to be used |
||||||
|
* with array_walk(). |
||||||
|
* |
||||||
|
* @param string $line The line to trim. |
||||||
|
* @param integer $key The index of the line in the array. Not used. |
||||||
|
*/ |
||||||
|
static public function trimNewlines(&$line, $key) |
||||||
|
{ |
||||||
|
$line = str_replace(array("\n", "\r"), '', $line); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Checks a diff for validity. |
||||||
|
* |
||||||
|
* This is here only for debugging purposes. |
||||||
|
*/ |
||||||
|
protected function _check($from_lines, $to_lines) |
||||||
|
{ |
||||||
|
if (serialize($from_lines) != serialize($this->getOriginal())) { |
||||||
|
trigger_error("Reconstructed original doesn't match", E_USER_ERROR); |
||||||
|
} |
||||||
|
if (serialize($to_lines) != serialize($this->getFinal())) { |
||||||
|
trigger_error("Reconstructed final doesn't match", E_USER_ERROR); |
||||||
|
} |
||||||
|
|
||||||
|
$rev = $this->reverse(); |
||||||
|
if (serialize($to_lines) != serialize($rev->getOriginal())) { |
||||||
|
trigger_error("Reversed original doesn't match", E_USER_ERROR); |
||||||
|
} |
||||||
|
if (serialize($from_lines) != serialize($rev->getFinal())) { |
||||||
|
trigger_error("Reversed final doesn't match", E_USER_ERROR); |
||||||
|
} |
||||||
|
|
||||||
|
$prevtype = null; |
||||||
|
foreach ($this->_edits as $edit) { |
||||||
|
if ($prevtype == get_class($edit)) { |
||||||
|
trigger_error("Edit sequence is non-optimal", E_USER_ERROR); |
||||||
|
} |
||||||
|
$prevtype = get_class($edit); |
||||||
|
} |
||||||
|
|
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,435 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* Class used internally by Horde_Text_Diff to actually compute the diffs. |
||||||
|
* |
||||||
|
* This class is implemented using native PHP code. |
||||||
|
* |
||||||
|
* The algorithm used here is mostly lifted from the perl module |
||||||
|
* Algorithm::Diff (version 1.06) by Ned Konz, which is available at: |
||||||
|
* http://www.perl.com/CPAN/authors/id/N/NE/NEDKONZ/Algorithm-Diff-1.06.zip |
||||||
|
* |
||||||
|
* More ideas are taken from: http://www.ics.uci.edu/~eppstein/161/960229.html |
||||||
|
* |
||||||
|
* Some ideas (and a bit of code) are taken from analyze.c, of GNU |
||||||
|
* diffutils-2.7, which can be found at: |
||||||
|
* ftp://gnudist.gnu.org/pub/gnu/diffutils/diffutils-2.7.tar.gz |
||||||
|
* |
||||||
|
* Some ideas (subdivision by NCHUNKS > 2, and some optimizations) are from |
||||||
|
* Geoffrey T. Dairiki <dairiki@dairiki.org>. The original PHP version of this |
||||||
|
* code was written by him, and is used/adapted with his permission. |
||||||
|
* |
||||||
|
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you did |
||||||
|
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
||||||
|
* @package Text_Diff |
||||||
|
*/ |
||||||
|
class Horde_Text_Diff_Engine_Native |
||||||
|
{ |
||||||
|
public function diff($from_lines, $to_lines) |
||||||
|
{ |
||||||
|
array_walk($from_lines, array('Horde_Text_Diff', 'trimNewlines')); |
||||||
|
array_walk($to_lines, array('Horde_Text_Diff', 'trimNewlines')); |
||||||
|
|
||||||
|
$n_from = count($from_lines); |
||||||
|
$n_to = count($to_lines); |
||||||
|
|
||||||
|
$this->xchanged = $this->ychanged = array(); |
||||||
|
$this->xv = $this->yv = array(); |
||||||
|
$this->xind = $this->yind = array(); |
||||||
|
unset($this->seq); |
||||||
|
unset($this->in_seq); |
||||||
|
unset($this->lcs); |
||||||
|
|
||||||
|
// Skip leading common lines. |
||||||
|
for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) { |
||||||
|
if ($from_lines[$skip] !== $to_lines[$skip]) { |
||||||
|
break; |
||||||
|
} |
||||||
|
$this->xchanged[$skip] = $this->ychanged[$skip] = false; |
||||||
|
} |
||||||
|
|
||||||
|
// Skip trailing common lines. |
||||||
|
$xi = $n_from; $yi = $n_to; |
||||||
|
for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) { |
||||||
|
if ($from_lines[$xi] !== $to_lines[$yi]) { |
||||||
|
break; |
||||||
|
} |
||||||
|
$this->xchanged[$xi] = $this->ychanged[$yi] = false; |
||||||
|
} |
||||||
|
|
||||||
|
// Ignore lines which do not exist in both files. |
||||||
|
for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { |
||||||
|
$xhash[$from_lines[$xi]] = 1; |
||||||
|
} |
||||||
|
for ($yi = $skip; $yi < $n_to - $endskip; $yi++) { |
||||||
|
$line = $to_lines[$yi]; |
||||||
|
if (($this->ychanged[$yi] = empty($xhash[$line]))) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
$yhash[$line] = 1; |
||||||
|
$this->yv[] = $line; |
||||||
|
$this->yind[] = $yi; |
||||||
|
} |
||||||
|
for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { |
||||||
|
$line = $from_lines[$xi]; |
||||||
|
if (($this->xchanged[$xi] = empty($yhash[$line]))) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
$this->xv[] = $line; |
||||||
|
$this->xind[] = $xi; |
||||||
|
} |
||||||
|
|
||||||
|
// Find the LCS. |
||||||
|
$this->_compareseq(0, count($this->xv), 0, count($this->yv)); |
||||||
|
|
||||||
|
// Merge edits when possible. |
||||||
|
$this->_shiftBoundaries($from_lines, $this->xchanged, $this->ychanged); |
||||||
|
$this->_shiftBoundaries($to_lines, $this->ychanged, $this->xchanged); |
||||||
|
|
||||||
|
// Compute the edit operations. |
||||||
|
$edits = array(); |
||||||
|
$xi = $yi = 0; |
||||||
|
while ($xi < $n_from || $yi < $n_to) { |
||||||
|
assert($yi < $n_to || $this->xchanged[$xi]); |
||||||
|
assert($xi < $n_from || $this->ychanged[$yi]); |
||||||
|
|
||||||
|
// Skip matching "snake". |
||||||
|
$copy = array(); |
||||||
|
while ($xi < $n_from && $yi < $n_to |
||||||
|
&& !$this->xchanged[$xi] && !$this->ychanged[$yi]) { |
||||||
|
$copy[] = $from_lines[$xi++]; |
||||||
|
++$yi; |
||||||
|
} |
||||||
|
if ($copy) { |
||||||
|
$edits[] = new Horde_Text_Diff_Op_Copy($copy); |
||||||
|
} |
||||||
|
|
||||||
|
// Find deletes & adds. |
||||||
|
$delete = array(); |
||||||
|
while ($xi < $n_from && $this->xchanged[$xi]) { |
||||||
|
$delete[] = $from_lines[$xi++]; |
||||||
|
} |
||||||
|
|
||||||
|
$add = array(); |
||||||
|
while ($yi < $n_to && $this->ychanged[$yi]) { |
||||||
|
$add[] = $to_lines[$yi++]; |
||||||
|
} |
||||||
|
|
||||||
|
if ($delete && $add) { |
||||||
|
$edits[] = new Horde_Text_Diff_Op_Change($delete, $add); |
||||||
|
} elseif ($delete) { |
||||||
|
$edits[] = new Horde_Text_Diff_Op_Delete($delete); |
||||||
|
} elseif ($add) { |
||||||
|
$edits[] = new Horde_Text_Diff_Op_Add($add); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $edits; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Divides the Largest Common Subsequence (LCS) of the sequences (XOFF, |
||||||
|
* XLIM) and (YOFF, YLIM) into NCHUNKS approximately equally sized |
||||||
|
* segments. |
||||||
|
* |
||||||
|
* Returns (LCS, PTS). LCS is the length of the LCS. PTS is an array of |
||||||
|
* NCHUNKS+1 (X, Y) indexes giving the diving points between sub |
||||||
|
* sequences. The first sub-sequence is contained in (X0, X1), (Y0, Y1), |
||||||
|
* the second in (X1, X2), (Y1, Y2) and so on. Note that (X0, Y0) == |
||||||
|
* (XOFF, YOFF) and (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM). |
||||||
|
* |
||||||
|
* This public function assumes that the first lines of the specified portions of |
||||||
|
* the two files do not match, and likewise that the last lines do not |
||||||
|
* match. The caller must trim matching lines from the beginning and end |
||||||
|
* of the portions it is going to specify. |
||||||
|
*/ |
||||||
|
protected function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) |
||||||
|
{ |
||||||
|
$flip = false; |
||||||
|
|
||||||
|
if ($xlim - $xoff > $ylim - $yoff) { |
||||||
|
/* Things seems faster (I'm not sure I understand why) when the |
||||||
|
* shortest sequence is in X. */ |
||||||
|
$flip = true; |
||||||
|
list ($xoff, $xlim, $yoff, $ylim) |
||||||
|
= array($yoff, $ylim, $xoff, $xlim); |
||||||
|
} |
||||||
|
|
||||||
|
if ($flip) { |
||||||
|
for ($i = $ylim - 1; $i >= $yoff; $i--) { |
||||||
|
$ymatches[$this->xv[$i]][] = $i; |
||||||
|
} |
||||||
|
} else { |
||||||
|
for ($i = $ylim - 1; $i >= $yoff; $i--) { |
||||||
|
$ymatches[$this->yv[$i]][] = $i; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$this->lcs = 0; |
||||||
|
$this->seq[0]= $yoff - 1; |
||||||
|
$this->in_seq = array(); |
||||||
|
$ymids[0] = array(); |
||||||
|
|
||||||
|
$numer = $xlim - $xoff + $nchunks - 1; |
||||||
|
$x = $xoff; |
||||||
|
for ($chunk = 0; $chunk < $nchunks; $chunk++) { |
||||||
|
if ($chunk > 0) { |
||||||
|
for ($i = 0; $i <= $this->lcs; $i++) { |
||||||
|
$ymids[$i][$chunk - 1] = $this->seq[$i]; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $chunk) / $nchunks); |
||||||
|
for (; $x < $x1; $x++) { |
||||||
|
$line = $flip ? $this->yv[$x] : $this->xv[$x]; |
||||||
|
if (empty($ymatches[$line])) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
$matches = $ymatches[$line]; |
||||||
|
reset($matches); |
||||||
|
while (list(, $y) = each($matches)) { |
||||||
|
if (empty($this->in_seq[$y])) { |
||||||
|
$k = $this->_lcsPos($y); |
||||||
|
assert($k > 0); |
||||||
|
$ymids[$k] = $ymids[$k - 1]; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
while (list(, $y) = each($matches)) { |
||||||
|
if ($y > $this->seq[$k - 1]) { |
||||||
|
assert($y <= $this->seq[$k]); |
||||||
|
/* Optimization: this is a common case: next match is |
||||||
|
* just replacing previous match. */ |
||||||
|
$this->in_seq[$this->seq[$k]] = false; |
||||||
|
$this->seq[$k] = $y; |
||||||
|
$this->in_seq[$y] = 1; |
||||||
|
} elseif (empty($this->in_seq[$y])) { |
||||||
|
$k = $this->_lcsPos($y); |
||||||
|
assert($k > 0); |
||||||
|
$ymids[$k] = $ymids[$k - 1]; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
$seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff); |
||||||
|
$ymid = $ymids[$this->lcs]; |
||||||
|
for ($n = 0; $n < $nchunks - 1; $n++) { |
||||||
|
$x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); |
||||||
|
$y1 = $ymid[$n] + 1; |
||||||
|
$seps[] = $flip ? array($y1, $x1) : array($x1, $y1); |
||||||
|
} |
||||||
|
$seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim); |
||||||
|
|
||||||
|
return array($this->lcs, $seps); |
||||||
|
} |
||||||
|
|
||||||
|
protected function _lcsPos($ypos) |
||||||
|
{ |
||||||
|
$end = $this->lcs; |
||||||
|
if ($end == 0 || $ypos > $this->seq[$end]) { |
||||||
|
$this->seq[++$this->lcs] = $ypos; |
||||||
|
$this->in_seq[$ypos] = 1; |
||||||
|
return $this->lcs; |
||||||
|
} |
||||||
|
|
||||||
|
$beg = 1; |
||||||
|
while ($beg < $end) { |
||||||
|
$mid = (int)(($beg + $end) / 2); |
||||||
|
if ($ypos > $this->seq[$mid]) { |
||||||
|
$beg = $mid + 1; |
||||||
|
} else { |
||||||
|
$end = $mid; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
assert($ypos != $this->seq[$end]); |
||||||
|
|
||||||
|
$this->in_seq[$this->seq[$end]] = false; |
||||||
|
$this->seq[$end] = $ypos; |
||||||
|
$this->in_seq[$ypos] = 1; |
||||||
|
return $end; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Finds LCS of two sequences. |
||||||
|
* |
||||||
|
* The results are recorded in the vectors $this->{x,y}changed[], by |
||||||
|
* storing a 1 in the element for each line that is an insertion or |
||||||
|
* deletion (ie. is not in the LCS). |
||||||
|
* |
||||||
|
* The subsequence of file 0 is (XOFF, XLIM) and likewise for file 1. |
||||||
|
* |
||||||
|
* Note that XLIM, YLIM are exclusive bounds. All line numbers are |
||||||
|
* origin-0 and discarded lines are not counted. |
||||||
|
*/ |
||||||
|
protected function _compareseq ($xoff, $xlim, $yoff, $ylim) |
||||||
|
{ |
||||||
|
/* Slide down the bottom initial diagonal. */ |
||||||
|
while ($xoff < $xlim && $yoff < $ylim |
||||||
|
&& $this->xv[$xoff] == $this->yv[$yoff]) { |
||||||
|
++$xoff; |
||||||
|
++$yoff; |
||||||
|
} |
||||||
|
|
||||||
|
/* Slide up the top initial diagonal. */ |
||||||
|
while ($xlim > $xoff && $ylim > $yoff |
||||||
|
&& $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) { |
||||||
|
--$xlim; |
||||||
|
--$ylim; |
||||||
|
} |
||||||
|
|
||||||
|
if ($xoff == $xlim || $yoff == $ylim) { |
||||||
|
$lcs = 0; |
||||||
|
} else { |
||||||
|
/* This is ad hoc but seems to work well. $nchunks = |
||||||
|
* sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); $nchunks = |
||||||
|
* max(2,min(8,(int)$nchunks)); */ |
||||||
|
$nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; |
||||||
|
list($lcs, $seps) |
||||||
|
= $this->_diag($xoff, $xlim, $yoff, $ylim, $nchunks); |
||||||
|
} |
||||||
|
|
||||||
|
if ($lcs == 0) { |
||||||
|
/* X and Y sequences have no common subsequence: mark all |
||||||
|
* changed. */ |
||||||
|
while ($yoff < $ylim) { |
||||||
|
$this->ychanged[$this->yind[$yoff++]] = 1; |
||||||
|
} |
||||||
|
while ($xoff < $xlim) { |
||||||
|
$this->xchanged[$this->xind[$xoff++]] = 1; |
||||||
|
} |
||||||
|
} else { |
||||||
|
/* Use the partitions to split this problem into subproblems. */ |
||||||
|
reset($seps); |
||||||
|
$pt1 = $seps[0]; |
||||||
|
while ($pt2 = next($seps)) { |
||||||
|
$this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]); |
||||||
|
$pt1 = $pt2; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Adjusts inserts/deletes of identical lines to join changes as much as |
||||||
|
* possible. |
||||||
|
* |
||||||
|
* We do something when a run of changed lines include a line at one end |
||||||
|
* and has an excluded, identical line at the other. We are free to |
||||||
|
* choose which identical line is included. `compareseq' usually chooses |
||||||
|
* the one at the beginning, but usually it is cleaner to consider the |
||||||
|
* following identical line to be the "change". |
||||||
|
* |
||||||
|
* This is extracted verbatim from analyze.c (GNU diffutils-2.7). |
||||||
|
*/ |
||||||
|
protected function _shiftBoundaries($lines, &$changed, $other_changed) |
||||||
|
{ |
||||||
|
$i = 0; |
||||||
|
$j = 0; |
||||||
|
|
||||||
|
assert('count($lines) == count($changed)'); |
||||||
|
$len = count($lines); |
||||||
|
$other_len = count($other_changed); |
||||||
|
|
||||||
|
while (1) { |
||||||
|
/* Scan forward to find the beginning of another run of |
||||||
|
* changes. Also keep track of the corresponding point in the |
||||||
|
* other file. |
||||||
|
* |
||||||
|
* Throughout this code, $i and $j are adjusted together so that |
||||||
|
* the first $i elements of $changed and the first $j elements of |
||||||
|
* $other_changed both contain the same number of zeros (unchanged |
||||||
|
* lines). |
||||||
|
* |
||||||
|
* Furthermore, $j is always kept so that $j == $other_len or |
||||||
|
* $other_changed[$j] == false. */ |
||||||
|
while ($j < $other_len && $other_changed[$j]) { |
||||||
|
$j++; |
||||||
|
} |
||||||
|
|
||||||
|
while ($i < $len && ! $changed[$i]) { |
||||||
|
assert('$j < $other_len && ! $other_changed[$j]'); |
||||||
|
$i++; $j++; |
||||||
|
while ($j < $other_len && $other_changed[$j]) { |
||||||
|
$j++; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if ($i == $len) { |
||||||
|
break; |
||||||
|
} |
||||||
|
|
||||||
|
$start = $i; |
||||||
|
|
||||||
|
/* Find the end of this run of changes. */ |
||||||
|
while (++$i < $len && $changed[$i]) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
|
||||||
|
do { |
||||||
|
/* Record the length of this run of changes, so that we can |
||||||
|
* later determine whether the run has grown. */ |
||||||
|
$runlength = $i - $start; |
||||||
|
|
||||||
|
/* Move the changed region back, so long as the previous |
||||||
|
* unchanged line matches the last changed one. This merges |
||||||
|
* with previous changed regions. */ |
||||||
|
while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) { |
||||||
|
$changed[--$start] = 1; |
||||||
|
$changed[--$i] = false; |
||||||
|
while ($start > 0 && $changed[$start - 1]) { |
||||||
|
$start--; |
||||||
|
} |
||||||
|
assert('$j > 0'); |
||||||
|
while ($other_changed[--$j]) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
assert('$j >= 0 && !$other_changed[$j]'); |
||||||
|
} |
||||||
|
|
||||||
|
/* Set CORRESPONDING to the end of the changed run, at the |
||||||
|
* last point where it corresponds to a changed run in the |
||||||
|
* other file. CORRESPONDING == LEN means no such point has |
||||||
|
* been found. */ |
||||||
|
$corresponding = $j < $other_len ? $i : $len; |
||||||
|
|
||||||
|
/* Move the changed region forward, so long as the first |
||||||
|
* changed line matches the following unchanged one. This |
||||||
|
* merges with following changed regions. Do this second, so |
||||||
|
* that if there are no merges, the changed region is moved |
||||||
|
* forward as far as possible. */ |
||||||
|
while ($i < $len && $lines[$start] == $lines[$i]) { |
||||||
|
$changed[$start++] = false; |
||||||
|
$changed[$i++] = 1; |
||||||
|
while ($i < $len && $changed[$i]) { |
||||||
|
$i++; |
||||||
|
} |
||||||
|
|
||||||
|
assert('$j < $other_len && ! $other_changed[$j]'); |
||||||
|
$j++; |
||||||
|
if ($j < $other_len && $other_changed[$j]) { |
||||||
|
$corresponding = $i; |
||||||
|
while ($j < $other_len && $other_changed[$j]) { |
||||||
|
$j++; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} while ($runlength != $i - $start); |
||||||
|
|
||||||
|
/* If possible, move the fully-merged run of changes back to a |
||||||
|
* corresponding run in the other file. */ |
||||||
|
while ($corresponding < $i) { |
||||||
|
$changed[--$start] = 1; |
||||||
|
$changed[--$i] = 0; |
||||||
|
assert('$j > 0'); |
||||||
|
while ($other_changed[--$j]) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
assert('$j >= 0 && !$other_changed[$j]'); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,158 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* Class used internally by Diff to actually compute the diffs. |
||||||
|
* |
||||||
|
* This class uses the Unix `diff` program via shell_exec to compute the |
||||||
|
* differences between the two input arrays. |
||||||
|
* |
||||||
|
* Copyright 2007-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you did |
||||||
|
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @author Milian Wolff <mail@milianw.de> |
||||||
|
* @package Text_Diff |
||||||
|
*/ |
||||||
|
class Horde_Text_Diff_Engine_Shell |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Path to the diff executable |
||||||
|
* |
||||||
|
* @var string |
||||||
|
*/ |
||||||
|
protected $_diffCommand = 'diff'; |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the array of differences. |
||||||
|
* |
||||||
|
* @param array $from_lines lines of text from old file |
||||||
|
* @param array $to_lines lines of text from new file |
||||||
|
* |
||||||
|
* @return array all changes made (array with Horde_Text_Diff_Op_* objects) |
||||||
|
*/ |
||||||
|
public function diff($from_lines, $to_lines) |
||||||
|
{ |
||||||
|
array_walk($from_lines, array('Horde_Text_Diff', 'trimNewlines')); |
||||||
|
array_walk($to_lines, array('Horde_Text_Diff', 'trimNewlines')); |
||||||
|
|
||||||
|
// Execute gnu diff or similar to get a standard diff file. |
||||||
|
$from_file = Horde_Util::getTempFile('Horde_Text_Diff'); |
||||||
|
$to_file = Horde_Util::getTempFile('Horde_Text_Diff'); |
||||||
|
$fp = fopen($from_file, 'w'); |
||||||
|
fwrite($fp, implode("\n", $from_lines)); |
||||||
|
fclose($fp); |
||||||
|
$fp = fopen($to_file, 'w'); |
||||||
|
fwrite($fp, implode("\n", $to_lines)); |
||||||
|
fclose($fp); |
||||||
|
$diff = shell_exec($this->_diffCommand . ' ' . $from_file . ' ' . $to_file); |
||||||
|
unlink($from_file); |
||||||
|
unlink($to_file); |
||||||
|
|
||||||
|
if (is_null($diff)) { |
||||||
|
// No changes were made |
||||||
|
return array(new Horde_Text_Diff_Op_Copy($from_lines)); |
||||||
|
} |
||||||
|
|
||||||
|
$from_line_no = 1; |
||||||
|
$to_line_no = 1; |
||||||
|
$edits = array(); |
||||||
|
|
||||||
|
// Get changed lines by parsing something like: |
||||||
|
// 0a1,2 |
||||||
|
// 1,2c4,6 |
||||||
|
// 1,5d6 |
||||||
|
preg_match_all('#^(\d+)(?:,(\d+))?([adc])(\d+)(?:,(\d+))?$#m', $diff, |
||||||
|
$matches, PREG_SET_ORDER); |
||||||
|
|
||||||
|
foreach ($matches as $match) { |
||||||
|
if (!isset($match[5])) { |
||||||
|
// This paren is not set every time (see regex). |
||||||
|
$match[5] = false; |
||||||
|
} |
||||||
|
|
||||||
|
if ($match[3] == 'a') { |
||||||
|
$from_line_no--; |
||||||
|
} |
||||||
|
|
||||||
|
if ($match[3] == 'd') { |
||||||
|
$to_line_no--; |
||||||
|
} |
||||||
|
|
||||||
|
if ($from_line_no < $match[1] || $to_line_no < $match[4]) { |
||||||
|
// copied lines |
||||||
|
assert('$match[1] - $from_line_no == $match[4] - $to_line_no'); |
||||||
|
array_push($edits, |
||||||
|
new Horde_Text_Diff_Op_Copy( |
||||||
|
$this->_getLines($from_lines, $from_line_no, $match[1] - 1), |
||||||
|
$this->_getLines($to_lines, $to_line_no, $match[4] - 1))); |
||||||
|
} |
||||||
|
|
||||||
|
switch ($match[3]) { |
||||||
|
case 'd': |
||||||
|
// deleted lines |
||||||
|
array_push($edits, |
||||||
|
new Horde_Text_Diff_Op_Delete( |
||||||
|
$this->_getLines($from_lines, $from_line_no, $match[2]))); |
||||||
|
$to_line_no++; |
||||||
|
break; |
||||||
|
|
||||||
|
case 'c': |
||||||
|
// changed lines |
||||||
|
array_push($edits, |
||||||
|
new Horde_Text_Diff_Op_Change( |
||||||
|
$this->_getLines($from_lines, $from_line_no, $match[2]), |
||||||
|
$this->_getLines($to_lines, $to_line_no, $match[5]))); |
||||||
|
break; |
||||||
|
|
||||||
|
case 'a': |
||||||
|
// added lines |
||||||
|
array_push($edits, |
||||||
|
new Horde_Text_Diff_Op_Add( |
||||||
|
$this->_getLines($to_lines, $to_line_no, $match[5]))); |
||||||
|
$from_line_no++; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (!empty($from_lines)) { |
||||||
|
// Some lines might still be pending. Add them as copied |
||||||
|
array_push($edits, |
||||||
|
new Horde_Text_Diff_Op_Copy( |
||||||
|
$this->_getLines($from_lines, $from_line_no, |
||||||
|
$from_line_no + count($from_lines) - 1), |
||||||
|
$this->_getLines($to_lines, $to_line_no, |
||||||
|
$to_line_no + count($to_lines) - 1))); |
||||||
|
} |
||||||
|
|
||||||
|
return $edits; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Get lines from either the old or new text |
||||||
|
* |
||||||
|
* @access private |
||||||
|
* |
||||||
|
* @param array &$text_lines Either $from_lines or $to_lines |
||||||
|
* @param int &$line_no Current line number |
||||||
|
* @param int $end Optional end line, when we want to chop more |
||||||
|
* than one line. |
||||||
|
* |
||||||
|
* @return array The chopped lines |
||||||
|
*/ |
||||||
|
protected function _getLines(&$text_lines, &$line_no, $end = false) |
||||||
|
{ |
||||||
|
if (!empty($end)) { |
||||||
|
$lines = array(); |
||||||
|
// We can shift even more |
||||||
|
while ($line_no <= $end) { |
||||||
|
array_push($lines, array_shift($text_lines)); |
||||||
|
$line_no++; |
||||||
|
} |
||||||
|
} else { |
||||||
|
$lines = array(array_shift($text_lines)); |
||||||
|
$line_no++; |
||||||
|
} |
||||||
|
|
||||||
|
return $lines; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,247 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* Parses unified or context diffs output from eg. the diff utility. |
||||||
|
* |
||||||
|
* Example: |
||||||
|
* <code> |
||||||
|
* $patch = file_get_contents('example.patch'); |
||||||
|
* $diff = new Horde_Text_Diff('string', array($patch)); |
||||||
|
* $renderer = new Horde_Text_Diff_Renderer_inline(); |
||||||
|
* echo $renderer->render($diff); |
||||||
|
* </code> |
||||||
|
* |
||||||
|
* Copyright 2005 Örjan Persson <o@42mm.org> |
||||||
|
* Copyright 2005-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you did |
||||||
|
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @author Örjan Persson <o@42mm.org> |
||||||
|
* @package Text_Diff |
||||||
|
*/ |
||||||
|
class Horde_Text_Diff_Engine_String |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Parses a unified or context diff. |
||||||
|
* |
||||||
|
* First param contains the whole diff and the second can be used to force |
||||||
|
* a specific diff type. If the second parameter is 'autodetect', the |
||||||
|
* diff will be examined to find out which type of diff this is. |
||||||
|
* |
||||||
|
* @param string $diff The diff content. |
||||||
|
* @param string $mode The diff mode of the content in $diff. One of |
||||||
|
* 'context', 'unified', or 'autodetect'. |
||||||
|
* |
||||||
|
* @return array List of all diff operations. |
||||||
|
* @throws Horde_Text_Diff_Exception |
||||||
|
*/ |
||||||
|
public function diff($diff, $mode = 'autodetect') |
||||||
|
{ |
||||||
|
// Detect line breaks. |
||||||
|
$lnbr = "\n"; |
||||||
|
if (strpos($diff, "\r\n") !== false) { |
||||||
|
$lnbr = "\r\n"; |
||||||
|
} elseif (strpos($diff, "\r") !== false) { |
||||||
|
$lnbr = "\r"; |
||||||
|
} |
||||||
|
|
||||||
|
// Make sure we have a line break at the EOF. |
||||||
|
if (substr($diff, -strlen($lnbr)) != $lnbr) { |
||||||
|
$diff .= $lnbr; |
||||||
|
} |
||||||
|
|
||||||
|
if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') { |
||||||
|
throw new Horde_Text_Diff_Exception('Type of diff is unsupported'); |
||||||
|
} |
||||||
|
|
||||||
|
if ($mode == 'autodetect') { |
||||||
|
$context = strpos($diff, '***'); |
||||||
|
$unified = strpos($diff, '---'); |
||||||
|
if ($context === $unified) { |
||||||
|
throw new Horde_Text_Diff_Exception('Type of diff could not be detected'); |
||||||
|
} elseif ($context === false || $unified === false) { |
||||||
|
$mode = $context !== false ? 'context' : 'unified'; |
||||||
|
} else { |
||||||
|
$mode = $context < $unified ? 'context' : 'unified'; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Split by new line and remove the diff header, if there is one. |
||||||
|
$diff = explode($lnbr, $diff); |
||||||
|
if (($mode == 'context' && strpos($diff[0], '***') === 0) || |
||||||
|
($mode == 'unified' && strpos($diff[0], '---') === 0)) { |
||||||
|
array_shift($diff); |
||||||
|
array_shift($diff); |
||||||
|
} |
||||||
|
|
||||||
|
if ($mode == 'context') { |
||||||
|
return $this->parseContextDiff($diff); |
||||||
|
} else { |
||||||
|
return $this->parseUnifiedDiff($diff); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Parses an array containing the unified diff. |
||||||
|
* |
||||||
|
* @param array $diff Array of lines. |
||||||
|
* |
||||||
|
* @return array List of all diff operations. |
||||||
|
*/ |
||||||
|
public function parseUnifiedDiff($diff) |
||||||
|
{ |
||||||
|
$edits = array(); |
||||||
|
$end = count($diff) - 1; |
||||||
|
for ($i = 0; $i < $end;) { |
||||||
|
$diff1 = array(); |
||||||
|
switch (substr($diff[$i], 0, 1)) { |
||||||
|
case ' ': |
||||||
|
do { |
||||||
|
$diff1[] = substr($diff[$i], 1); |
||||||
|
} while (++$i < $end && substr($diff[$i], 0, 1) == ' '); |
||||||
|
$edits[] = new Horde_Text_Diff_Op_Copy($diff1); |
||||||
|
break; |
||||||
|
|
||||||
|
case '+': |
||||||
|
// get all new lines |
||||||
|
do { |
||||||
|
$diff1[] = substr($diff[$i], 1); |
||||||
|
} while (++$i < $end && substr($diff[$i], 0, 1) == '+'); |
||||||
|
$edits[] = new Horde_Text_Diff_Op_Add($diff1); |
||||||
|
break; |
||||||
|
|
||||||
|
case '-': |
||||||
|
// get changed or removed lines |
||||||
|
$diff2 = array(); |
||||||
|
do { |
||||||
|
$diff1[] = substr($diff[$i], 1); |
||||||
|
} while (++$i < $end && substr($diff[$i], 0, 1) == '-'); |
||||||
|
|
||||||
|
while ($i < $end && substr($diff[$i], 0, 1) == '+') { |
||||||
|
$diff2[] = substr($diff[$i++], 1); |
||||||
|
} |
||||||
|
if (count($diff2) == 0) { |
||||||
|
$edits[] = new Horde_Text_Diff_Op_Delete($diff1); |
||||||
|
} else { |
||||||
|
$edits[] = new Horde_Text_Diff_Op_Change($diff1, $diff2); |
||||||
|
} |
||||||
|
break; |
||||||
|
|
||||||
|
default: |
||||||
|
$i++; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $edits; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Parses an array containing the context diff. |
||||||
|
* |
||||||
|
* @param array $diff Array of lines. |
||||||
|
* |
||||||
|
* @return array List of all diff operations. |
||||||
|
*/ |
||||||
|
public function parseContextDiff(&$diff) |
||||||
|
{ |
||||||
|
$edits = array(); |
||||||
|
$i = $max_i = $j = $max_j = 0; |
||||||
|
$end = count($diff) - 1; |
||||||
|
while ($i < $end && $j < $end) { |
||||||
|
while ($i >= $max_i && $j >= $max_j) { |
||||||
|
// Find the boundaries of the diff output of the two files |
||||||
|
for ($i = $j; |
||||||
|
$i < $end && substr($diff[$i], 0, 3) == '***'; |
||||||
|
$i++); |
||||||
|
for ($max_i = $i; |
||||||
|
$max_i < $end && substr($diff[$max_i], 0, 3) != '---'; |
||||||
|
$max_i++); |
||||||
|
for ($j = $max_i; |
||||||
|
$j < $end && substr($diff[$j], 0, 3) == '---'; |
||||||
|
$j++); |
||||||
|
for ($max_j = $j; |
||||||
|
$max_j < $end && substr($diff[$max_j], 0, 3) != '***'; |
||||||
|
$max_j++); |
||||||
|
} |
||||||
|
|
||||||
|
// find what hasn't been changed |
||||||
|
$array = array(); |
||||||
|
while ($i < $max_i && |
||||||
|
$j < $max_j && |
||||||
|
strcmp($diff[$i], $diff[$j]) == 0) { |
||||||
|
$array[] = substr($diff[$i], 2); |
||||||
|
$i++; |
||||||
|
$j++; |
||||||
|
} |
||||||
|
|
||||||
|
while ($i < $max_i && ($max_j-$j) <= 1) { |
||||||
|
if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') { |
||||||
|
break; |
||||||
|
} |
||||||
|
$array[] = substr($diff[$i++], 2); |
||||||
|
} |
||||||
|
|
||||||
|
while ($j < $max_j && ($max_i-$i) <= 1) { |
||||||
|
if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') { |
||||||
|
break; |
||||||
|
} |
||||||
|
$array[] = substr($diff[$j++], 2); |
||||||
|
} |
||||||
|
if (count($array) > 0) { |
||||||
|
$edits[] = new Horde_Text_Diff_Op_Copy($array); |
||||||
|
} |
||||||
|
|
||||||
|
if ($i < $max_i) { |
||||||
|
$diff1 = array(); |
||||||
|
switch (substr($diff[$i], 0, 1)) { |
||||||
|
case '!': |
||||||
|
$diff2 = array(); |
||||||
|
do { |
||||||
|
$diff1[] = substr($diff[$i], 2); |
||||||
|
if ($j < $max_j && substr($diff[$j], 0, 1) == '!') { |
||||||
|
$diff2[] = substr($diff[$j++], 2); |
||||||
|
} |
||||||
|
} while (++$i < $max_i && substr($diff[$i], 0, 1) == '!'); |
||||||
|
$edits[] = new Horde_Text_Diff_Op_Change($diff1, $diff2); |
||||||
|
break; |
||||||
|
|
||||||
|
case '+': |
||||||
|
do { |
||||||
|
$diff1[] = substr($diff[$i], 2); |
||||||
|
} while (++$i < $max_i && substr($diff[$i], 0, 1) == '+'); |
||||||
|
$edits[] = new Horde_Text_Diff_Op_Add($diff1); |
||||||
|
break; |
||||||
|
|
||||||
|
case '-': |
||||||
|
do { |
||||||
|
$diff1[] = substr($diff[$i], 2); |
||||||
|
} while (++$i < $max_i && substr($diff[$i], 0, 1) == '-'); |
||||||
|
$edits[] = new Horde_Text_Diff_Op_Delete($diff1); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if ($j < $max_j) { |
||||||
|
$diff2 = array(); |
||||||
|
switch (substr($diff[$j], 0, 1)) { |
||||||
|
case '+': |
||||||
|
do { |
||||||
|
$diff2[] = substr($diff[$j++], 2); |
||||||
|
} while ($j < $max_j && substr($diff[$j], 0, 1) == '+'); |
||||||
|
$edits[] = new Horde_Text_Diff_Op_Add($diff2); |
||||||
|
break; |
||||||
|
|
||||||
|
case '-': |
||||||
|
do { |
||||||
|
$diff2[] = substr($diff[$j++], 2); |
||||||
|
} while ($j < $max_j && substr($diff[$j], 0, 1) == '-'); |
||||||
|
$edits[] = new Horde_Text_Diff_Op_Delete($diff2); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $edits; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,67 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* Class used internally by Diff to actually compute the diffs. |
||||||
|
* |
||||||
|
* This class uses the xdiff PECL package (http://pecl.php.net/package/xdiff) |
||||||
|
* to compute the differences between the two input arrays. |
||||||
|
* |
||||||
|
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you did |
||||||
|
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @author Jon Parise <jon@horde.org> |
||||||
|
* @package Text_Diff |
||||||
|
*/ |
||||||
|
class Horde_Text_Diff_Engine_Xdiff |
||||||
|
{ |
||||||
|
/** |
||||||
|
*/ |
||||||
|
public function diff($from_lines, $to_lines) |
||||||
|
{ |
||||||
|
if (!extension_loaded('xdiff')) { |
||||||
|
throw new Horde_Text_Diff_Exception('The xdiff extension is required for this diff engine'); |
||||||
|
} |
||||||
|
|
||||||
|
array_walk($from_lines, array('Horde_Text_Diff', 'trimNewlines')); |
||||||
|
array_walk($to_lines, array('Horde_Text_Diff', 'trimNewlines')); |
||||||
|
|
||||||
|
/* Convert the two input arrays into strings for xdiff processing. */ |
||||||
|
$from_string = implode("\n", $from_lines); |
||||||
|
$to_string = implode("\n", $to_lines); |
||||||
|
|
||||||
|
/* Diff the two strings and convert the result to an array. */ |
||||||
|
$diff = xdiff_string_diff($from_string, $to_string, count($to_lines)); |
||||||
|
$diff = explode("\n", $diff); |
||||||
|
|
||||||
|
/* Walk through the diff one line at a time. We build the $edits |
||||||
|
* array of diff operations by reading the first character of the |
||||||
|
* xdiff output (which is in the "unified diff" format). |
||||||
|
* |
||||||
|
* Note that we don't have enough information to detect "changed" |
||||||
|
* lines using this approach, so we can't add Horde_Text_Diff_Op_Changed |
||||||
|
* instances to the $edits array. The result is still perfectly |
||||||
|
* valid, albeit a little less descriptive and efficient. */ |
||||||
|
$edits = array(); |
||||||
|
foreach ($diff as $line) { |
||||||
|
if (!strlen($line)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
switch ($line[0]) { |
||||||
|
case ' ': |
||||||
|
$edits[] = new Horde_Text_Diff_Op_Copy(array(substr($line, 1))); |
||||||
|
break; |
||||||
|
|
||||||
|
case '+': |
||||||
|
$edits[] = new Horde_Text_Diff_Op_Add(array(substr($line, 1))); |
||||||
|
break; |
||||||
|
|
||||||
|
case '-': |
||||||
|
$edits[] = new Horde_Text_Diff_Op_Delete(array(substr($line, 1))); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $edits; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,17 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* Exception handler for the Text_Diff package. |
||||||
|
* |
||||||
|
* Copyright 2011-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you |
||||||
|
* did not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @author Jan Schneider <jan@horde.org> |
||||||
|
* @category Horde |
||||||
|
* @license http://www.horde.org/licenses/lgpl21 LGPL 2.1 |
||||||
|
* @package Text_Diff |
||||||
|
*/ |
||||||
|
class Horde_Text_Diff_Exception extends Horde_Exception_Wrapped |
||||||
|
{ |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* Copyright 2007-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you did |
||||||
|
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @package Text_Diff |
||||||
|
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
||||||
|
*/ |
||||||
|
class Horde_Text_Diff_Mapped extends Horde_Text_Diff |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Computes a diff between sequences of strings. |
||||||
|
* |
||||||
|
* This can be used to compute things like case-insensitve diffs, or diffs |
||||||
|
* which ignore changes in white-space. |
||||||
|
* |
||||||
|
* @param array $from_lines An array of strings. |
||||||
|
* @param array $to_lines An array of strings. |
||||||
|
* @param array $mapped_from_lines This array should have the same size |
||||||
|
* number of elements as $from_lines. The |
||||||
|
* elements in $mapped_from_lines and |
||||||
|
* $mapped_to_lines are what is actually |
||||||
|
* compared when computing the diff. |
||||||
|
* @param array $mapped_to_lines This array should have the same number |
||||||
|
* of elements as $to_lines. |
||||||
|
*/ |
||||||
|
public function __construct($from_lines, $to_lines, |
||||||
|
$mapped_from_lines, $mapped_to_lines) |
||||||
|
{ |
||||||
|
assert(count($from_lines) == count($mapped_from_lines)); |
||||||
|
assert(count($to_lines) == count($mapped_to_lines)); |
||||||
|
|
||||||
|
parent::__construct($mapped_from_lines, $mapped_to_lines); |
||||||
|
|
||||||
|
$xi = $yi = 0; |
||||||
|
for ($i = 0; $i < count($this->_edits); $i++) { |
||||||
|
$orig = &$this->_edits[$i]->orig; |
||||||
|
if (is_array($orig)) { |
||||||
|
$orig = array_slice($from_lines, $xi, count($orig)); |
||||||
|
$xi += count($orig); |
||||||
|
} |
||||||
|
|
||||||
|
$final = &$this->_edits[$i]->final; |
||||||
|
if (is_array($final)) { |
||||||
|
$final = array_slice($to_lines, $yi, count($final)); |
||||||
|
$yi += count($final); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* The original PHP version of this code was written by Geoffrey T. Dairiki |
||||||
|
* <dairiki@dairiki.org>, and is used/adapted with his permission. |
||||||
|
* |
||||||
|
* Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org> |
||||||
|
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you did |
||||||
|
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @package Text_Diff |
||||||
|
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
||||||
|
*/ |
||||||
|
class Horde_Text_Diff_Op_Add extends Horde_Text_Diff_Op_Base |
||||||
|
{ |
||||||
|
public function __construct($lines) |
||||||
|
{ |
||||||
|
$this->final = $lines; |
||||||
|
$this->orig = false; |
||||||
|
} |
||||||
|
|
||||||
|
public function reverse() |
||||||
|
{ |
||||||
|
return new Horde_Text_Diff_Op_Delete($this->final); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,31 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* The original PHP version of this code was written by Geoffrey T. Dairiki |
||||||
|
* <dairiki@dairiki.org>, and is used/adapted with his permission. |
||||||
|
* |
||||||
|
* Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org> |
||||||
|
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you did |
||||||
|
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @package Text_Diff |
||||||
|
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
||||||
|
*/ |
||||||
|
abstract class Horde_Text_Diff_Op_Base |
||||||
|
{ |
||||||
|
public $orig; |
||||||
|
public $final; |
||||||
|
|
||||||
|
abstract public function reverse(); |
||||||
|
|
||||||
|
public function norig() |
||||||
|
{ |
||||||
|
return $this->orig ? count($this->orig) : 0; |
||||||
|
} |
||||||
|
|
||||||
|
public function nfinal() |
||||||
|
{ |
||||||
|
return $this->final ? count($this->final) : 0; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* The original PHP version of this code was written by Geoffrey T. Dairiki |
||||||
|
* <dairiki@dairiki.org>, and is used/adapted with his permission. |
||||||
|
* |
||||||
|
* Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org> |
||||||
|
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you did |
||||||
|
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @package Text_Diff |
||||||
|
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
||||||
|
*/ |
||||||
|
class Horde_Text_Diff_Op_Change extends Horde_Text_Diff_Op_Base |
||||||
|
{ |
||||||
|
public function __construct($orig, $final) |
||||||
|
{ |
||||||
|
$this->orig = $orig; |
||||||
|
$this->final = $final; |
||||||
|
} |
||||||
|
|
||||||
|
public function reverse() |
||||||
|
{ |
||||||
|
return new Horde_Text_Diff_Op_Change($this->final, $this->orig); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,30 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* The original PHP version of this code was written by Geoffrey T. Dairiki |
||||||
|
* <dairiki@dairiki.org>, and is used/adapted with his permission. |
||||||
|
* |
||||||
|
* Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org> |
||||||
|
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you did |
||||||
|
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @package Text_Diff |
||||||
|
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
||||||
|
*/ |
||||||
|
class Horde_Text_Diff_Op_Copy extends Horde_Text_Diff_Op_Base |
||||||
|
{ |
||||||
|
public function __construct($orig, $final = false) |
||||||
|
{ |
||||||
|
if (!is_array($final)) { |
||||||
|
$final = $orig; |
||||||
|
} |
||||||
|
$this->orig = $orig; |
||||||
|
$this->final = $final; |
||||||
|
} |
||||||
|
|
||||||
|
public function reverse() |
||||||
|
{ |
||||||
|
return new Horde_Text_Diff_Op_Copy($this->final, $this->orig); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,27 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* The original PHP version of this code was written by Geoffrey T. Dairiki |
||||||
|
* <dairiki@dairiki.org>, and is used/adapted with his permission. |
||||||
|
* |
||||||
|
* Copyright 2004 Geoffrey T. Dairiki <dairiki@dairiki.org> |
||||||
|
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you did |
||||||
|
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @package Text_Diff |
||||||
|
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
||||||
|
*/ |
||||||
|
class Horde_Text_Diff_Op_Delete extends Horde_Text_Diff_Op_Base |
||||||
|
{ |
||||||
|
public function __construct($lines) |
||||||
|
{ |
||||||
|
$this->orig = $lines; |
||||||
|
$this->final = false; |
||||||
|
} |
||||||
|
|
||||||
|
public function reverse() |
||||||
|
{ |
||||||
|
return new Horde_Text_Diff_Op_Add($this->orig); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,234 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* A class to render Diffs in different formats. |
||||||
|
* |
||||||
|
* This class renders the diff in classic diff format. It is intended that |
||||||
|
* this class be customized via inheritance, to obtain fancier outputs. |
||||||
|
* |
||||||
|
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you did |
||||||
|
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @package Text_Diff |
||||||
|
*/ |
||||||
|
class Horde_Text_Diff_Renderer |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Number of leading context "lines" to preserve. |
||||||
|
* |
||||||
|
* This should be left at zero for this class, but subclasses may want to |
||||||
|
* set this to other values. |
||||||
|
*/ |
||||||
|
protected $_leading_context_lines = 0; |
||||||
|
|
||||||
|
/** |
||||||
|
* Number of trailing context "lines" to preserve. |
||||||
|
* |
||||||
|
* This should be left at zero for this class, but subclasses may want to |
||||||
|
* set this to other values. |
||||||
|
*/ |
||||||
|
protected $_trailing_context_lines = 0; |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor. |
||||||
|
*/ |
||||||
|
public function __construct($params = array()) |
||||||
|
{ |
||||||
|
foreach ($params as $param => $value) { |
||||||
|
$v = '_' . $param; |
||||||
|
if (isset($this->$v)) { |
||||||
|
$this->$v = $value; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Get any renderer parameters. |
||||||
|
* |
||||||
|
* @return array All parameters of this renderer object. |
||||||
|
*/ |
||||||
|
public function getParams() |
||||||
|
{ |
||||||
|
$params = array(); |
||||||
|
foreach (get_object_vars($this) as $k => $v) { |
||||||
|
if ($k[0] == '_') { |
||||||
|
$params[substr($k, 1)] = $v; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $params; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Renders a diff. |
||||||
|
* |
||||||
|
* @param Horde_Text_Diff $diff A Horde_Text_Diff object. |
||||||
|
* |
||||||
|
* @return string The formatted output. |
||||||
|
*/ |
||||||
|
public function render($diff) |
||||||
|
{ |
||||||
|
$xi = $yi = 1; |
||||||
|
$block = false; |
||||||
|
$context = array(); |
||||||
|
|
||||||
|
$nlead = $this->_leading_context_lines; |
||||||
|
$ntrail = $this->_trailing_context_lines; |
||||||
|
|
||||||
|
$output = $this->_startDiff(); |
||||||
|
|
||||||
|
$diffs = $diff->getDiff(); |
||||||
|
foreach ($diffs as $i => $edit) { |
||||||
|
/* If these are unchanged (copied) lines, and we want to keep |
||||||
|
* leading or trailing context lines, extract them from the copy |
||||||
|
* block. */ |
||||||
|
if ($edit instanceof Horde_Text_Diff_Op_Copy) { |
||||||
|
/* Do we have any diff blocks yet? */ |
||||||
|
if (is_array($block)) { |
||||||
|
/* How many lines to keep as context from the copy |
||||||
|
* block. */ |
||||||
|
$keep = $i == count($diffs) - 1 ? $ntrail : $nlead + $ntrail; |
||||||
|
if (count($edit->orig) <= $keep) { |
||||||
|
/* We have less lines in the block than we want for |
||||||
|
* context => keep the whole block. */ |
||||||
|
$block[] = $edit; |
||||||
|
} else { |
||||||
|
if ($ntrail) { |
||||||
|
/* Create a new block with as many lines as we need |
||||||
|
* for the trailing context. */ |
||||||
|
$context = array_slice($edit->orig, 0, $ntrail); |
||||||
|
$block[] = new Horde_Text_Diff_Op_Copy($context); |
||||||
|
} |
||||||
|
/* @todo */ |
||||||
|
$output .= $this->_block($x0, $ntrail + $xi - $x0, |
||||||
|
$y0, $ntrail + $yi - $y0, |
||||||
|
$block); |
||||||
|
$block = false; |
||||||
|
} |
||||||
|
} |
||||||
|
/* Keep the copy block as the context for the next block. */ |
||||||
|
$context = $edit->orig; |
||||||
|
} else { |
||||||
|
/* Don't we have any diff blocks yet? */ |
||||||
|
if (!is_array($block)) { |
||||||
|
/* Extract context lines from the preceding copy block. */ |
||||||
|
$context = array_slice($context, count($context) - $nlead); |
||||||
|
$x0 = $xi - count($context); |
||||||
|
$y0 = $yi - count($context); |
||||||
|
$block = array(); |
||||||
|
if ($context) { |
||||||
|
$block[] = new Horde_Text_Diff_Op_Copy($context); |
||||||
|
} |
||||||
|
} |
||||||
|
$block[] = $edit; |
||||||
|
} |
||||||
|
|
||||||
|
if ($edit->orig) { |
||||||
|
$xi += count($edit->orig); |
||||||
|
} |
||||||
|
if ($edit->final) { |
||||||
|
$yi += count($edit->final); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if (is_array($block)) { |
||||||
|
$output .= $this->_block($x0, $xi - $x0, |
||||||
|
$y0, $yi - $y0, |
||||||
|
$block); |
||||||
|
} |
||||||
|
|
||||||
|
return $output . $this->_endDiff(); |
||||||
|
} |
||||||
|
|
||||||
|
protected function _block($xbeg, $xlen, $ybeg, $ylen, &$edits) |
||||||
|
{ |
||||||
|
$output = $this->_startBlock($this->_blockHeader($xbeg, $xlen, $ybeg, $ylen)); |
||||||
|
|
||||||
|
foreach ($edits as $edit) { |
||||||
|
switch (get_class($edit)) { |
||||||
|
case 'Horde_Text_Diff_Op_Copy': |
||||||
|
$output .= $this->_context($edit->orig); |
||||||
|
break; |
||||||
|
|
||||||
|
case 'Horde_Text_Diff_Op_Add': |
||||||
|
$output .= $this->_added($edit->final); |
||||||
|
break; |
||||||
|
|
||||||
|
case 'Horde_Text_Diff_Op_Delete': |
||||||
|
$output .= $this->_deleted($edit->orig); |
||||||
|
break; |
||||||
|
|
||||||
|
case 'Horde_Text_Diff_Op_Change': |
||||||
|
$output .= $this->_changed($edit->orig, $edit->final); |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $output . $this->_endBlock(); |
||||||
|
} |
||||||
|
|
||||||
|
protected function _startDiff() |
||||||
|
{ |
||||||
|
return ''; |
||||||
|
} |
||||||
|
|
||||||
|
protected function _endDiff() |
||||||
|
{ |
||||||
|
return ''; |
||||||
|
} |
||||||
|
|
||||||
|
protected function _blockHeader($xbeg, $xlen, $ybeg, $ylen) |
||||||
|
{ |
||||||
|
if ($xlen > 1) { |
||||||
|
$xbeg .= ',' . ($xbeg + $xlen - 1); |
||||||
|
} |
||||||
|
if ($ylen > 1) { |
||||||
|
$ybeg .= ',' . ($ybeg + $ylen - 1); |
||||||
|
} |
||||||
|
|
||||||
|
// this matches the GNU Diff behaviour |
||||||
|
if ($xlen && !$ylen) { |
||||||
|
$ybeg--; |
||||||
|
} elseif (!$xlen) { |
||||||
|
$xbeg--; |
||||||
|
} |
||||||
|
|
||||||
|
return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg; |
||||||
|
} |
||||||
|
|
||||||
|
protected function _startBlock($header) |
||||||
|
{ |
||||||
|
return $header . "\n"; |
||||||
|
} |
||||||
|
|
||||||
|
protected function _endBlock() |
||||||
|
{ |
||||||
|
return ''; |
||||||
|
} |
||||||
|
|
||||||
|
protected function _lines($lines, $prefix = ' ') |
||||||
|
{ |
||||||
|
return $prefix . implode("\n$prefix", $lines) . "\n"; |
||||||
|
} |
||||||
|
|
||||||
|
protected function _context($lines) |
||||||
|
{ |
||||||
|
return $this->_lines($lines, ' '); |
||||||
|
} |
||||||
|
|
||||||
|
protected function _added($lines) |
||||||
|
{ |
||||||
|
return $this->_lines($lines, '> '); |
||||||
|
} |
||||||
|
|
||||||
|
protected function _deleted($lines) |
||||||
|
{ |
||||||
|
return $this->_lines($lines, '< '); |
||||||
|
} |
||||||
|
|
||||||
|
protected function _changed($orig, $final) |
||||||
|
{ |
||||||
|
return $this->_deleted($orig) . "---\n" . $this->_added($final); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,68 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* "Context" diff renderer. |
||||||
|
* |
||||||
|
* This class renders the diff in classic "context diff" format. |
||||||
|
* |
||||||
|
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you did |
||||||
|
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @package Text_Diff |
||||||
|
*/ |
||||||
|
class Horde_Text_Diff_Renderer_Context extends Horde_Text_Diff_Renderer |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Number of leading context "lines" to preserve. |
||||||
|
*/ |
||||||
|
protected $_leading_context_lines = 4; |
||||||
|
|
||||||
|
/** |
||||||
|
* Number of trailing context "lines" to preserve. |
||||||
|
*/ |
||||||
|
protected $_trailing_context_lines = 4; |
||||||
|
|
||||||
|
protected $_second_block = ''; |
||||||
|
|
||||||
|
protected function _blockHeader($xbeg, $xlen, $ybeg, $ylen) |
||||||
|
{ |
||||||
|
if ($xlen != 1) { |
||||||
|
$xbeg .= ',' . $xlen; |
||||||
|
} |
||||||
|
if ($ylen != 1) { |
||||||
|
$ybeg .= ',' . $ylen; |
||||||
|
} |
||||||
|
$this->_second_block = "--- $ybeg ----\n"; |
||||||
|
return "***************\n*** $xbeg ****"; |
||||||
|
} |
||||||
|
|
||||||
|
protected function _endBlock() |
||||||
|
{ |
||||||
|
return $this->_second_block; |
||||||
|
} |
||||||
|
|
||||||
|
protected function _context($lines) |
||||||
|
{ |
||||||
|
$this->_second_block .= $this->_lines($lines, ' '); |
||||||
|
return $this->_lines($lines, ' '); |
||||||
|
} |
||||||
|
|
||||||
|
protected function _added($lines) |
||||||
|
{ |
||||||
|
$this->_second_block .= $this->_lines($lines, '+ '); |
||||||
|
return ''; |
||||||
|
} |
||||||
|
|
||||||
|
protected function _deleted($lines) |
||||||
|
{ |
||||||
|
return $this->_lines($lines, '- '); |
||||||
|
} |
||||||
|
|
||||||
|
protected function _changed($orig, $final) |
||||||
|
{ |
||||||
|
$this->_second_block .= $this->_lines($final, '! '); |
||||||
|
return $this->_lines($orig, '! '); |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,193 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* "Inline" diff renderer. |
||||||
|
* |
||||||
|
* This class renders diffs in the Wiki-style "inline" format. |
||||||
|
* |
||||||
|
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you did |
||||||
|
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @author Ciprian Popovici |
||||||
|
* @package Text_Diff |
||||||
|
*/ |
||||||
|
class Horde_Text_Diff_Renderer_Inline extends Horde_Text_Diff_Renderer |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Number of leading context "lines" to preserve. |
||||||
|
* |
||||||
|
* @var integer |
||||||
|
*/ |
||||||
|
protected $_leading_context_lines = 10000; |
||||||
|
|
||||||
|
/** |
||||||
|
* Number of trailing context "lines" to preserve. |
||||||
|
* |
||||||
|
* @var integer |
||||||
|
*/ |
||||||
|
protected $_trailing_context_lines = 10000; |
||||||
|
|
||||||
|
/** |
||||||
|
* Prefix for inserted text. |
||||||
|
* |
||||||
|
* @var string |
||||||
|
*/ |
||||||
|
protected $_ins_prefix = '<ins>'; |
||||||
|
|
||||||
|
/** |
||||||
|
* Suffix for inserted text. |
||||||
|
* |
||||||
|
* @var string |
||||||
|
*/ |
||||||
|
protected $_ins_suffix = '</ins>'; |
||||||
|
|
||||||
|
/** |
||||||
|
* Prefix for deleted text. |
||||||
|
* |
||||||
|
* @var string |
||||||
|
*/ |
||||||
|
protected $_del_prefix = '<del>'; |
||||||
|
|
||||||
|
/** |
||||||
|
* Suffix for deleted text. |
||||||
|
* |
||||||
|
* @var string |
||||||
|
*/ |
||||||
|
protected $_del_suffix = '</del>'; |
||||||
|
|
||||||
|
/** |
||||||
|
* Header for each change block. |
||||||
|
* |
||||||
|
* @var string |
||||||
|
*/ |
||||||
|
protected $_block_header = ''; |
||||||
|
|
||||||
|
/** |
||||||
|
* Whether to split down to character-level. |
||||||
|
* |
||||||
|
* @var boolean |
||||||
|
*/ |
||||||
|
protected $_split_characters = false; |
||||||
|
|
||||||
|
/** |
||||||
|
* What are we currently splitting on? Used to recurse to show word-level |
||||||
|
* or character-level changes. |
||||||
|
* |
||||||
|
* @var string |
||||||
|
*/ |
||||||
|
protected $_split_level = 'lines'; |
||||||
|
|
||||||
|
protected function _blockHeader($xbeg, $xlen, $ybeg, $ylen) |
||||||
|
{ |
||||||
|
return $this->_block_header; |
||||||
|
} |
||||||
|
|
||||||
|
protected function _startBlock($header) |
||||||
|
{ |
||||||
|
return $header; |
||||||
|
} |
||||||
|
|
||||||
|
protected function _lines($lines, $prefix = ' ', $encode = true) |
||||||
|
{ |
||||||
|
if ($encode) { |
||||||
|
array_walk($lines, array(&$this, '_encode')); |
||||||
|
} |
||||||
|
|
||||||
|
if ($this->_split_level == 'lines') { |
||||||
|
return implode("\n", $lines) . "\n"; |
||||||
|
} else { |
||||||
|
return implode('', $lines); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected function _added($lines) |
||||||
|
{ |
||||||
|
array_walk($lines, array(&$this, '_encode')); |
||||||
|
$lines[0] = $this->_ins_prefix . $lines[0]; |
||||||
|
$lines[count($lines) - 1] .= $this->_ins_suffix; |
||||||
|
return $this->_lines($lines, ' ', false); |
||||||
|
} |
||||||
|
|
||||||
|
protected function _deleted($lines, $words = false) |
||||||
|
{ |
||||||
|
array_walk($lines, array(&$this, '_encode')); |
||||||
|
$lines[0] = $this->_del_prefix . $lines[0]; |
||||||
|
$lines[count($lines) - 1] .= $this->_del_suffix; |
||||||
|
return $this->_lines($lines, ' ', false); |
||||||
|
} |
||||||
|
|
||||||
|
protected function _changed($orig, $final) |
||||||
|
{ |
||||||
|
/* If we've already split on characters, just display. */ |
||||||
|
if ($this->_split_level == 'characters') { |
||||||
|
return $this->_deleted($orig) |
||||||
|
. $this->_added($final); |
||||||
|
} |
||||||
|
|
||||||
|
/* If we've already split on words, just display. */ |
||||||
|
if ($this->_split_level == 'words') { |
||||||
|
$prefix = ''; |
||||||
|
while ($orig[0] !== false && $final[0] !== false && |
||||||
|
substr($orig[0], 0, 1) == ' ' && |
||||||
|
substr($final[0], 0, 1) == ' ') { |
||||||
|
$prefix .= substr($orig[0], 0, 1); |
||||||
|
$orig[0] = substr($orig[0], 1); |
||||||
|
$final[0] = substr($final[0], 1); |
||||||
|
} |
||||||
|
return $prefix . $this->_deleted($orig) . $this->_added($final); |
||||||
|
} |
||||||
|
|
||||||
|
$text1 = implode("\n", $orig); |
||||||
|
$text2 = implode("\n", $final); |
||||||
|
|
||||||
|
/* Non-printing newline marker. */ |
||||||
|
$nl = "\0"; |
||||||
|
|
||||||
|
if ($this->_split_characters) { |
||||||
|
$diff = new Horde_Text_Diff('native', |
||||||
|
array(preg_split('//', $text1), |
||||||
|
preg_split('//', $text2))); |
||||||
|
} else { |
||||||
|
/* We want to split on word boundaries, but we need to preserve |
||||||
|
* whitespace as well. Therefore we split on words, but include |
||||||
|
* all blocks of whitespace in the wordlist. */ |
||||||
|
$diff = new Horde_Text_Diff('native', |
||||||
|
array($this->_splitOnWords($text1, $nl), |
||||||
|
$this->_splitOnWords($text2, $nl))); |
||||||
|
} |
||||||
|
|
||||||
|
/* Get the diff in inline format. */ |
||||||
|
$renderer = new Horde_Text_Diff_Renderer_inline |
||||||
|
(array_merge($this->getParams(), |
||||||
|
array('split_level' => $this->_split_characters ? 'characters' : 'words'))); |
||||||
|
|
||||||
|
/* Run the diff and get the output. */ |
||||||
|
return str_replace($nl, "\n", $renderer->render($diff)) . "\n"; |
||||||
|
} |
||||||
|
|
||||||
|
protected function _splitOnWords($string, $newlineEscape = "\n") |
||||||
|
{ |
||||||
|
// Ignore \0; otherwise the while loop will never finish. |
||||||
|
$string = str_replace("\0", '', $string); |
||||||
|
|
||||||
|
$words = array(); |
||||||
|
$length = strlen($string); |
||||||
|
$pos = 0; |
||||||
|
|
||||||
|
while ($pos < $length) { |
||||||
|
// Eat a word with any preceding whitespace. |
||||||
|
$spaces = strspn(substr($string, $pos), " \n"); |
||||||
|
$nextpos = strcspn(substr($string, $pos + $spaces), " \n"); |
||||||
|
$words[] = str_replace("\n", $newlineEscape, substr($string, $pos, $spaces + $nextpos)); |
||||||
|
$pos += $spaces + $nextpos; |
||||||
|
} |
||||||
|
|
||||||
|
return $words; |
||||||
|
} |
||||||
|
|
||||||
|
protected function _encode(&$string) |
||||||
|
{ |
||||||
|
$string = htmlspecialchars($string); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,57 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* "Unified" diff renderer. |
||||||
|
* |
||||||
|
* This class renders the diff in classic "unified diff" format. |
||||||
|
* |
||||||
|
* Copyright 2004-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you did |
||||||
|
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @author Ciprian Popovici |
||||||
|
* @package Text_Diff |
||||||
|
*/ |
||||||
|
class Horde_Text_Diff_Renderer_Unified extends Horde_Text_Diff_Renderer |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Number of leading context "lines" to preserve. |
||||||
|
*/ |
||||||
|
protected $_leading_context_lines = 4; |
||||||
|
|
||||||
|
/** |
||||||
|
* Number of trailing context "lines" to preserve. |
||||||
|
*/ |
||||||
|
protected $_trailing_context_lines = 4; |
||||||
|
|
||||||
|
protected function _blockHeader($xbeg, $xlen, $ybeg, $ylen) |
||||||
|
{ |
||||||
|
if ($xlen != 1) { |
||||||
|
$xbeg .= ',' . $xlen; |
||||||
|
} |
||||||
|
if ($ylen != 1) { |
||||||
|
$ybeg .= ',' . $ylen; |
||||||
|
} |
||||||
|
return "@@ -$xbeg +$ybeg @@"; |
||||||
|
} |
||||||
|
|
||||||
|
protected function _context($lines) |
||||||
|
{ |
||||||
|
return $this->_lines($lines, ' '); |
||||||
|
} |
||||||
|
|
||||||
|
protected function _added($lines) |
||||||
|
{ |
||||||
|
return $this->_lines($lines, '+'); |
||||||
|
} |
||||||
|
|
||||||
|
protected function _deleted($lines) |
||||||
|
{ |
||||||
|
return $this->_lines($lines, '-'); |
||||||
|
} |
||||||
|
|
||||||
|
protected function _changed($orig, $final) |
||||||
|
{ |
||||||
|
return $this->_deleted($orig) . $this->_added($final); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,143 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* A class for computing three way merges. |
||||||
|
* |
||||||
|
* Copyright 2007-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you did |
||||||
|
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @package Text_Diff |
||||||
|
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
||||||
|
*/ |
||||||
|
class Horde_Text_Diff_ThreeWay |
||||||
|
{ |
||||||
|
/** |
||||||
|
* Array of changes. |
||||||
|
* |
||||||
|
* @var array |
||||||
|
*/ |
||||||
|
protected $_edits; |
||||||
|
|
||||||
|
/** |
||||||
|
* Conflict counter. |
||||||
|
* |
||||||
|
* @var integer |
||||||
|
*/ |
||||||
|
protected $_conflictingBlocks = 0; |
||||||
|
|
||||||
|
/** |
||||||
|
* Computes diff between 3 sequences of strings. |
||||||
|
* |
||||||
|
* @param array $orig The original lines to use. |
||||||
|
* @param array $final1 The first version to compare to. |
||||||
|
* @param array $final2 The second version to compare to. |
||||||
|
*/ |
||||||
|
public function __construct($orig, $final1, $final2) |
||||||
|
{ |
||||||
|
if (extension_loaded('xdiff')) { |
||||||
|
$engine = new Horde_Text_Diff_Engine_Xdiff(); |
||||||
|
} else { |
||||||
|
$engine = new Horde_Text_Diff_Engine_Native(); |
||||||
|
} |
||||||
|
|
||||||
|
$this->_edits = $this->_diff3($engine->diff($orig, $final1), |
||||||
|
$engine->diff($orig, $final2)); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
*/ |
||||||
|
public function mergedOutput($label1 = false, $label2 = false) |
||||||
|
{ |
||||||
|
$lines = array(); |
||||||
|
foreach ($this->_edits as $edit) { |
||||||
|
if ($edit->isConflict()) { |
||||||
|
/* FIXME: this should probably be moved somewhere else. */ |
||||||
|
$lines = array_merge($lines, |
||||||
|
array('<<<<<<<' . ($label1 ? ' ' . $label1 : '')), |
||||||
|
$edit->final1, |
||||||
|
array("======="), |
||||||
|
$edit->final2, |
||||||
|
array('>>>>>>>' . ($label2 ? ' ' . $label2 : ''))); |
||||||
|
$this->_conflictingBlocks++; |
||||||
|
} else { |
||||||
|
$lines = array_merge($lines, $edit->merged()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $lines; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
*/ |
||||||
|
protected function _diff3($edits1, $edits2) |
||||||
|
{ |
||||||
|
$edits = array(); |
||||||
|
$bb = new Horde_Text_Diff_ThreeWay_BlockBuilder(); |
||||||
|
|
||||||
|
$e1 = current($edits1); |
||||||
|
$e2 = current($edits2); |
||||||
|
while ($e1 || $e2) { |
||||||
|
if ($e1 && $e2 && |
||||||
|
$e1 instanceof Horde_Text_Diff_Op_Copy && |
||||||
|
$e2 instanceof Horde_Text_Diff_Op_Copy) { |
||||||
|
/* We have copy blocks from both diffs. This is the (only) |
||||||
|
* time we want to emit a diff3 copy block. Flush current |
||||||
|
* diff3 diff block, if any. */ |
||||||
|
if ($edit = $bb->finish()) { |
||||||
|
$edits[] = $edit; |
||||||
|
} |
||||||
|
|
||||||
|
$ncopy = min($e1->norig(), $e2->norig()); |
||||||
|
assert($ncopy > 0); |
||||||
|
$edits[] = new Horde_Text_Diff_ThreeWay_Op_Copy(array_slice($e1->orig, 0, $ncopy)); |
||||||
|
|
||||||
|
if ($e1->norig() > $ncopy) { |
||||||
|
array_splice($e1->orig, 0, $ncopy); |
||||||
|
array_splice($e1->final, 0, $ncopy); |
||||||
|
} else { |
||||||
|
$e1 = next($edits1); |
||||||
|
} |
||||||
|
|
||||||
|
if ($e2->norig() > $ncopy) { |
||||||
|
array_splice($e2->orig, 0, $ncopy); |
||||||
|
array_splice($e2->final, 0, $ncopy); |
||||||
|
} else { |
||||||
|
$e2 = next($edits2); |
||||||
|
} |
||||||
|
} else { |
||||||
|
if ($e1 && $e2) { |
||||||
|
if ($e1->orig && $e2->orig) { |
||||||
|
$norig = min($e1->norig(), $e2->norig()); |
||||||
|
$orig = array_splice($e1->orig, 0, $norig); |
||||||
|
array_splice($e2->orig, 0, $norig); |
||||||
|
$bb->input($orig); |
||||||
|
} |
||||||
|
|
||||||
|
if ($e1 instanceof Horde_Text_Diff_Op_Copy) { |
||||||
|
$bb->out1(array_splice($e1->final, 0, $norig)); |
||||||
|
} |
||||||
|
|
||||||
|
if ($e2 instanceof Horde_Text_Diff_Op_Copy) { |
||||||
|
$bb->out2(array_splice($e2->final, 0, $norig)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if ($e1 && ! $e1->orig) { |
||||||
|
$bb->out1($e1->final); |
||||||
|
$e1 = next($edits1); |
||||||
|
} |
||||||
|
if ($e2 && ! $e2->orig) { |
||||||
|
$bb->out2($e2->final); |
||||||
|
$e2 = next($edits2); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if ($edit = $bb->finish()) { |
||||||
|
$edits[] = $edit; |
||||||
|
} |
||||||
|
|
||||||
|
return $edits; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,64 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* Copyright 2007-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you did |
||||||
|
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @package Text_Diff |
||||||
|
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
||||||
|
*/ |
||||||
|
class Horde_Text_Diff_ThreeWay_BlockBuilder |
||||||
|
{ |
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
$this->_init(); |
||||||
|
} |
||||||
|
|
||||||
|
public function input($lines) |
||||||
|
{ |
||||||
|
if ($lines) { |
||||||
|
$this->_append($this->orig, $lines); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function out1($lines) |
||||||
|
{ |
||||||
|
if ($lines) { |
||||||
|
$this->_append($this->final1, $lines); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function out2($lines) |
||||||
|
{ |
||||||
|
if ($lines) { |
||||||
|
$this->_append($this->final2, $lines); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public function isEmpty() |
||||||
|
{ |
||||||
|
return !$this->orig && !$this->final1 && !$this->final2; |
||||||
|
} |
||||||
|
|
||||||
|
public function finish() |
||||||
|
{ |
||||||
|
if ($this->isEmpty()) { |
||||||
|
return false; |
||||||
|
} else { |
||||||
|
$edit = new Horde_Text_Diff_ThreeWay_Op_Base($this->orig, $this->final1, $this->final2); |
||||||
|
$this->_init(); |
||||||
|
return $edit; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected function _init() |
||||||
|
{ |
||||||
|
$this->orig = $this->final1 = $this->final2 = array(); |
||||||
|
} |
||||||
|
|
||||||
|
protected function _append(&$array, $lines) |
||||||
|
{ |
||||||
|
array_splice($array, sizeof($array), 0, $lines); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,41 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* Copyright 2007-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you did |
||||||
|
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @package Text_Diff |
||||||
|
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
||||||
|
*/ |
||||||
|
class Horde_Text_Diff_ThreeWay_Op_Base |
||||||
|
{ |
||||||
|
public function __construct($orig = false, $final1 = false, $final2 = false) |
||||||
|
{ |
||||||
|
$this->orig = $orig ? $orig : array(); |
||||||
|
$this->final1 = $final1 ? $final1 : array(); |
||||||
|
$this->final2 = $final2 ? $final2 : array(); |
||||||
|
} |
||||||
|
|
||||||
|
public function merged() |
||||||
|
{ |
||||||
|
if (!isset($this->_merged)) { |
||||||
|
if ($this->final1 === $this->final2) { |
||||||
|
$this->_merged = &$this->final1; |
||||||
|
} elseif ($this->final1 === $this->orig) { |
||||||
|
$this->_merged = &$this->final2; |
||||||
|
} elseif ($this->final2 === $this->orig) { |
||||||
|
$this->_merged = &$this->final1; |
||||||
|
} else { |
||||||
|
$this->_merged = false; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return $this->_merged; |
||||||
|
} |
||||||
|
|
||||||
|
public function isConflict() |
||||||
|
{ |
||||||
|
return $this->merged() === false; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* Copyright 2007-2012 Horde LLC (http://www.horde.org/) |
||||||
|
* |
||||||
|
* See the enclosed file COPYING for license information (LGPL). If you did |
||||||
|
* not receive this file, see http://www.horde.org/licenses/lgpl21. |
||||||
|
* |
||||||
|
* @package Text_Diff |
||||||
|
* @author Geoffrey T. Dairiki <dairiki@dairiki.org> |
||||||
|
*/ |
||||||
|
class Horde_Text_Diff_ThreeWay_Op_Copy extends Horde_Text_Diff_ThreeWay_Op_Base |
||||||
|
{ |
||||||
|
public function __construct($lines = false) |
||||||
|
{ |
||||||
|
$this->orig = $lines ? $lines : array(); |
||||||
|
$this->final1 = &$this->orig; |
||||||
|
$this->final2 = &$this->orig; |
||||||
|
} |
||||||
|
|
||||||
|
public function merged() |
||||||
|
{ |
||||||
|
return $this->orig; |
||||||
|
} |
||||||
|
|
||||||
|
public function isConflict() |
||||||
|
{ |
||||||
|
return false; |
||||||
|
} |
||||||
|
} |
@ -1,65 +0,0 @@ |
|||||||
<?php |
|
||||||
|
|
||||||
use yii\gii\Generator; |
|
||||||
use yii\helpers\Html; |
|
||||||
use yii\gii\CodeFile; |
|
||||||
|
|
||||||
/** |
|
||||||
* @var $this \yii\base\View |
|
||||||
* @var $generator \yii\gii\Generator |
|
||||||
* @var CodeFile[] $files |
|
||||||
* @var array $answers |
|
||||||
*/ |
|
||||||
?> |
|
||||||
<table class="table table-bordered table-striped table-condensed code-files"> |
|
||||||
<thead> |
|
||||||
<tr> |
|
||||||
<th class="file">Code File</th> |
|
||||||
<th class="action">Action</th> |
|
||||||
<th> |
|
||||||
<?php |
|
||||||
$count = 0; |
|
||||||
foreach ($files as $file) { |
|
||||||
if ($file->operation !== CodeFile::OP_SKIP) { |
|
||||||
$count++; |
|
||||||
} |
|
||||||
} |
|
||||||
if ($count > 1) { |
|
||||||
echo '<input type="checkbox" id="check-all">'; |
|
||||||
} |
|
||||||
?> |
|
||||||
</th> |
|
||||||
</tr> |
|
||||||
</thead> |
|
||||||
<tbody> |
|
||||||
<?php foreach ($files as $i => $file): ?> |
|
||||||
<tr class="<?php echo $file->operation; ?>">
|
|
||||||
<td class="file"> |
|
||||||
<?php echo Html::a(Html::encode($file->getRelativePath()), array('code', 'file' => $i), array('class' => 'view-code', 'rel' => $file->path)); ?> |
|
||||||
<?php if ($file->operation === CodeFile::OP_OVERWRITE): ?> |
|
||||||
<?php echo Html::a('diff', array('diff', 'file' => $i), array('class' => 'view-code label label-warning', 'rel' => $file->path)); ?> |
|
||||||
<?php endif; ?> |
|
||||||
</td> |
|
||||||
<td class="action"> |
|
||||||
<?php |
|
||||||
if ($file->operation === CodeFile::OP_SKIP) { |
|
||||||
echo 'unchanged'; |
|
||||||
} else { |
|
||||||
echo $file->operation; |
|
||||||
} |
|
||||||
?> |
|
||||||
</td> |
|
||||||
<td class="check"> |
|
||||||
<?php |
|
||||||
if ($file->operation === CodeFile::OP_SKIP) { |
|
||||||
echo ' '; |
|
||||||
} else { |
|
||||||
$key = md5($file->path); |
|
||||||
echo Html::checkBox("answers[$key]", isset($answers) ? isset($answers[$key]) : ($file->operation === CodeFile::OP_NEW)); |
|
||||||
} |
|
||||||
?> |
|
||||||
</td> |
|
||||||
</tr> |
|
||||||
<?php endforeach; ?> |
|
||||||
</tbody> |
|
||||||
</table> |
|
@ -0,0 +1,17 @@ |
|||||||
|
<?php |
||||||
|
/** |
||||||
|
* @var yii\base\View $this |
||||||
|
* @var mixed $diff |
||||||
|
*/ |
||||||
|
?> |
||||||
|
<div class="default-diff"> |
||||||
|
<?php if ($diff === false): ?> |
||||||
|
<div class="alert alert-danger">Diff is not supported for this file type.</div> |
||||||
|
<?php elseif (empty($diff)): ?> |
||||||
|
<div class="alert alert-success">Identical.</div> |
||||||
|
<?php else: ?> |
||||||
|
<div class="content"> |
||||||
|
<pre><?php echo $diff; ?></pre>
|
||||||
|
</div> |
||||||
|
<?php endif; ?> |
||||||
|
</div> |
@ -0,0 +1,79 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
use yii\gii\Generator; |
||||||
|
use yii\helpers\Html; |
||||||
|
use yii\gii\CodeFile; |
||||||
|
|
||||||
|
/** |
||||||
|
* @var $this \yii\base\View |
||||||
|
* @var $generator \yii\gii\Generator |
||||||
|
* @var CodeFile[] $files |
||||||
|
* @var array $answers |
||||||
|
*/ |
||||||
|
?> |
||||||
|
<div class="default-view-files"> |
||||||
|
<p>Click on the above <code>Generate</code> button to generate the files selected below:</p> |
||||||
|
|
||||||
|
<table class="table table-bordered table-striped table-condensed"> |
||||||
|
<thead> |
||||||
|
<tr> |
||||||
|
<th class="file">Code File</th> |
||||||
|
<th class="action">Action</th> |
||||||
|
<th> |
||||||
|
<?php |
||||||
|
foreach ($files as $file) { |
||||||
|
if ($file->operation !== CodeFile::OP_SKIP) { |
||||||
|
echo '<input type="checkbox" id="check-all">'; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
?> |
||||||
|
</th> |
||||||
|
</tr> |
||||||
|
</thead> |
||||||
|
<tbody> |
||||||
|
<?php foreach ($files as $file): ?> |
||||||
|
<tr class="<?php echo $file->operation; ?>">
|
||||||
|
<td class="file"> |
||||||
|
<?php echo Html::a(Html::encode($file->getRelativePath()), array('preview', 'file' => $file->id), array('class' => 'preview-code', 'data-title' => $file->getRelativePath())); ?> |
||||||
|
<?php if ($file->operation === CodeFile::OP_OVERWRITE): ?> |
||||||
|
<?php echo Html::a('diff', array('diff', 'file' => $file->id), array('class' => 'diff-code label label-warning', 'data-title' => $file->getRelativePath())); ?> |
||||||
|
<?php endif; ?> |
||||||
|
</td> |
||||||
|
<td class="action"> |
||||||
|
<?php |
||||||
|
if ($file->operation === CodeFile::OP_SKIP) { |
||||||
|
echo 'unchanged'; |
||||||
|
} else { |
||||||
|
echo $file->operation; |
||||||
|
} |
||||||
|
?> |
||||||
|
</td> |
||||||
|
<td class="check"> |
||||||
|
<?php |
||||||
|
if ($file->operation === CodeFile::OP_SKIP) { |
||||||
|
echo ' '; |
||||||
|
} else { |
||||||
|
echo Html::checkBox("answers[{$file->id}]", isset($answers) ? isset($answers[$file->id]) : ($file->operation === CodeFile::OP_NEW)); |
||||||
|
} |
||||||
|
?> |
||||||
|
</td> |
||||||
|
</tr> |
||||||
|
<?php endforeach; ?> |
||||||
|
</tbody> |
||||||
|
</table> |
||||||
|
|
||||||
|
<div class="modal fade" id="preview-modal"> |
||||||
|
<div class="modal-dialog"> |
||||||
|
<div class="modal-content"> |
||||||
|
<div class="modal-header"> |
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button> |
||||||
|
<h4 class="modal-title">Modal title</h4> |
||||||
|
</div> |
||||||
|
<div class="modal-body"> |
||||||
|
<p>Please wait ...</p> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
@ -0,0 +1,22 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
use yii\gii\Generator; |
||||||
|
use yii\gii\CodeFile; |
||||||
|
|
||||||
|
/** |
||||||
|
* @var yii\base\View $this |
||||||
|
* @var yii\gii\Generator $generator |
||||||
|
* @var string $results |
||||||
|
* @var boolean $hasError |
||||||
|
*/ |
||||||
|
?> |
||||||
|
<div class="default-view-results"> |
||||||
|
<?php |
||||||
|
if ($hasError) { |
||||||
|
echo '<div class="alert alert-danger">There was something wrong when generating the code. Please check the following messages.</div>'; |
||||||
|
} else { |
||||||
|
echo '<div class="alert alert-success">' . $generator->successMessage() . '</div>'; |
||||||
|
} |
||||||
|
?> |
||||||
|
<pre><?php echo $results; ?></pre>
|
||||||
|
</div> |
Loading…
Reference in new issue