<?php
# -- BEGIN LICENSE BLOCK ---------------------------------------
#
# This file is part of Clearbricks.
#
# Copyright (c) 2003-2011 Olivier Meunier & Association Dotclear
# Licensed under the GPL version 2.0 license.
# See LICENSE file or
# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
#
# -- END LICENSE BLOCK -----------------------------------------
/**
* TIDY diff
*
* A TIDY diff representation
*
* @package Clearbricks
* @subpackage Diff
* @author Thomas Bouron
*/
class tidyDiff
{
/** @var array Chunks array */
protected $__data = array();
private $up_range = '/^@@ -([\d]+),([\d]+) \+([\d]+),([\d]+) @@/m';
private $up_ctx = '/^ (.*)$/';
private $up_ins = '/^\+(.*)$/';
private $up_del = '/^-(.*)$/';
/**
* Constructor
*
* Creates a diff representation from unified diff.
*
* @param string $udiff Unified diff
* @param array $inline_changes Find inline changes
*/
public function __construct($udiff,$inline_changes = false)
{
diff::uniCheck($udiff);
preg_match_all($this->up_range,$udiff,$context);
$chunks = preg_split($this->up_range,$udiff,-1,PREG_SPLIT_NO_EMPTY);
foreach ($chunks as $k => $chunk)
{
$tidy_chunk = new tidyDiffChunk();
$tidy_chunk->setRange(
(int) $context[1][$k],
(int) $context[2][$k],
(int) $context[3][$k],
(int) $context[4][$k]
);
$old_line = (int) $context[1][$k];
$new_line = (int) $context[3][$k];
foreach (explode("\n",$chunk) as $line)
{
# context
if (preg_match($this->up_ctx,$line,$m)) {
$tidy_chunk->addLine('context',array($old_line,$new_line),$m[1]);
$old_line++;
$new_line++;
}
# insertion
if (preg_match($this->up_ins,$line,$m)) {
$tidy_chunk->addLine('insert',array($old_line,$new_line),$m[1]);
$new_line++;
}
# deletion
if (preg_match($this->up_del,$line,$m)) {
$tidy_chunk->addLine('delete',array($old_line,$new_line),$m[1]);
$old_line++;
}
}
if ($inline_changes) {
$tidy_chunk->findInsideChanges();
}
array_push($this->__data,$tidy_chunk);
}
}
/**
* All chunks
*
* Returns all chunks defined.
*
* @return array
*/
public function getChunks()
{
return $this->__data;
}
}
/**
* TIDY diff chunk
*
* A diff chunk representation. Used by a TIDY diff.
*
* @package Clearbricks
* @subpackage Diff
*/
class tidyDiffChunk
{
/** @var array Chunk information array */
protected $__info;
/** @var array Chunk data array */
protected $__data;
/**
* Constructor
*
* Creates and initializes a chunk representation for a TIDY diff.
*/
public function __construct()
{
$this->__info = array(
'context' => 0,
'delete' => 0,
'insert' => 0,
'range' => array(
'start' => array(),
'end' => array()
)
);
$this->__data = array();
}
/**
* Set chunk range
*
* Sets chunk range in TIDY chunk object.
*
* @param integer $line_start Old start line number
* @param integer $offest_start Old offset number
* @param integer $line_end new start line number
* @param integer $offset_end New offset number
*/
public function setRange($line_start,$offest_start,$line_end,$offset_end)
{
if (is_int($line_start) && is_int($offest_start) && is_int($line_end) && is_int($offset_end))
{
$this->__info['range']['start'] = array($line_start,$offest_start);
$this->__info['range']['end'] = array($line_end,$offset_end);
}
}
/**
* Add line
*
* Adds TIDY line object for TIDY chunk object.
*
* @param string $type Tine type
* @param array $lines Line number for old and new context
* @param string $content Line content
*/
public function addLine($type,$lines,$content)
{
$tidy_line = new tidyDiffLine($type,$lines,$content);
if (!is_null($tidy_line)) {
array_push($this->__data,$tidy_line);
$this->__info[$type]++;
}
}
/**
* All lines
*
* Returns all lines defined.
*
* @return array
*/
public function getLines()
{
return $this->__data;
}
/**
* Chunk information
*
* Returns chunk information according to the given name, null otherwise.
*
* @param string $n Info name
* @return string
*/
public function getInfo($n)
{
return array_key_exists($n,$this->__info) ? $this->__info[$n] : null;
}
/**
* Find changes
*
* Finds changes inside lines for each groups of diff lines. Wraps changes
* by string \0 and \1
*/
public function findInsideChanges()
{
$groups = $this->getGroups();
foreach ($groups as $group) {
$middle = count($group) / 2;
for ($i = 0; $i < $middle; $i++) {
$from = $group[$i];
$to = $group[$i + $middle];
$threshold = $this->getChangeExtent($from->content,$to->content);
if ($threshold['start'] != 0 || $threshold['end'] != 0)
{
$start = $threshold['start'];
$end = $threshold['end'] + strlen($from->content);
$offset = $end - $start;
$from->overwrite(
substr($from->content,0,$start).'\0'.
substr($from->content,$start,$offset).'\1'.
substr($from->content,$end,strlen($from->content))
);
$end = $threshold['end'] + strlen($to->content);
$offset = $end - $start;
$to->overwrite(
substr($to->content,0,$start).'\0'.
substr($to->content,$start,$offset).'\1'.
substr($to->content,$end,strlen($to->content))
);
}
}
}
}
private function getGroups()
{
$res = $group = array();
$allowed_types = array('delete','insert');
$delete = $insert = 0;
foreach ($this->__data as $k => $line) {
if (in_array($line->type,$allowed_types)) {
array_push($group,$line);
${$line->type}++;
} else {
if ($delete === $insert && count($group) > 0) {
array_push($res,$group);
}
$delete = $insert = 0;
$group = array();
}
}
if ($delete === $insert && count($group) > 0) {
array_push($res,$group);
}
return $res;
}
private function getChangeExtent($str1,$str2)
{
$start = 0;
$limit = min(strlen($str1), strlen($str2));
while ($start < $limit and $str1[$start] === $str2[$start]) {
$start++;
}
$end = -1;
$limit = $limit - $start;
while (-$end <= $limit && $str1[strlen($str1) + $end] === $str2[strlen($str2) + $end]) {
$end--;
}
return array('start' => $start,'end' => $end +1);
}
}
/**
* TIDY diff line
*
* A diff line representation. Used by a TIDY chunk.
*
* @package Clearbricks
* @subpackage Diff
*/
class tidyDiffLine
{
/** @var string Line type */
protected $type;
/** @var array Line number for old and new context */
protected $lines;
/** @var string Line content */
protected $content;
/**
* Constructor
*
* Creates a line representation for a tidy chunk. Returns a new object if
* all parameters are fine, null otherwise.
*
* @param string $type Tine type
* @param array $lines Line number for old and new context
* @param string $content Line content
* @return object
*/
public function __construct($type,$lines,$content)
{
$allowed_type = array('context','delete','insert');
if (in_array($type,$allowed_type) && is_array($lines) && is_string($content)) {
$this->type = $type;
$this->lines = $lines;
$this->content = $content;
return $this;
}
return null;
}
/**
* Magic get
*
* Returns field content according to the given name, null otherwise.
*
* @param string $n Field name
* @return string
*/
public function __get($n)
{
return isset($n,$this) ? $this->{$n} : null;
}
/**
* Overwrite
*
* Overwrites content for the current line.
*
* @param string $content Line content
*/
public function overwrite($content)
{
if (is_string($content)) {
$this->content = $content;
}
}
}
?>