Qiang Xue
12 years ago
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 ($) { |
||||
return { |
||||
init: function () { |
||||
$('.hint-block').each(function() { |
||||
var $hint = $(this); |
||||
$hint.parent().find('input,select,textarea').popover({ |
||||
html: true, |
||||
trigger: 'focus', |
||||
placement: 'right', |
||||
content: $hint.html() |
||||
}); |
||||
var isActive = $('.default-view').length > 0; |
||||
|
||||
var initHintBlocks = function () { |
||||
$('.hint-block').each(function () { |
||||
var $hint = $(this); |
||||
$hint.parent().find('input,select,textarea').popover({ |
||||
html: true, |
||||
trigger: 'focus', |
||||
placement: 'right', |
||||
content: $hint.html() |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
var $checkAll = $('#check-all'); |
||||
$checkAll.click(function() { |
||||
$('.code-files .check input').prop('checked', this.checked); |
||||
var initStickyInputs = function () { |
||||
$('.sticky:not(.error) input,select,textarea').each(function () { |
||||
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() { |
||||
$checkAll.prop('checked', !$('.code-files .check input:not(:checked)').length); |
||||
return false; |
||||
}); |
||||
}; |
||||
|
||||
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); |
||||
|
@ -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 |
||||
/** |
||||
* Created by JetBrains PhpStorm. |
||||
* User: qiang |
||||
* Date: 8/15/13 |
||||
* Time: 4:45 PM |
||||
* To change this template use File | Settings | File Templates. |
||||
* This is the template for generating a controller class file. |
||||
* |
||||
* @var yii\base\View $this |
||||
* @var yii\gii\generators\controller\Generator $generator |
||||
*/ |
||||
?> |
||||
<?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 |
||||
/** |
||||
* Created by JetBrains PhpStorm. |
||||
* User: qiang |
||||
* Date: 8/15/13 |
||||
* Time: 4:46 PM |
||||
* To change this template use File | Settings | File Templates. |
||||
* This is the template for generating an action view file. |
||||
* |
||||
* @var yii\base\View $this |
||||
* @var yii\gii\generators\controller\Generator $generator |
||||
* @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