Page MenuHomePhabricator

Annotation.php

Authored By
bzimport
Nov 21 2014, 6:56 PM
Size
8 KB
Referenced Files
None
Subscribers
None

Annotation.php

<?php
/**
* Defines Annotation object to implement annotate/blame functionality
*
* An Annotation is an array of ExtendedStrings. Both Annotation and
* ExtendedStrings must act like regular strings, but their sets of
* functionality vary. An ExtendedString must be able to be chopped up without
* damaging information. An Annotation must be able to have segments of text
* deleted, added or modified while keeping track of all the ExtendedStrings,
* chopping up them if necessary. RevisionInfo is a parameter object that
* contains the interesting information that regular strings don't have.
*/
/**
* A regular string that has RevisionInfo. Automatically normalizes newlines.
*/
class ExtendedString
{
var $string;
var $revision_info;
function ExtendedString($string, $revision_info) {
$this->string = str_replace( "\r\n", "\n", $string );
$this->revision_info = $revision_info;
}
function newFromArray($array, $revision_info) {
if (!is_array($array)) return null;
return new ExtendedString(implode($array, ''), $revision_info);
}
function strlen() {
return strlen($this->string);
}
function substr($start, $length = null) {
if ($length === null) {
$string = substr($this->string, $start);
} else {
$string = substr($this->string, $start, $length);
}
return new ExtendedString($string, $this->revision_info);
}
function append($string) {
$this->string .= $string;
}
function splitAt($position) {
return array(
new ExtendedString( substr( $this->string, 0, $position ),
$this->revision_info ),
new ExtendedString( substr( $this->string, $position ),
$this->revision_info )
);
}
}
/**
* This string is used to show that something was deleted here
*/
class PhantomString extends ExtendedString
{
var $phantom = true;
function PhantomString($revision_info) {
$this->ExtendedString('', $revision_info);
}
function strlen() {return 0;}
function substr() {return new PhantomString($this->revision_info);}
function append() {}
function splitAt() {return array();}
}
/**
* Indicates something was deleted here in the specified revision.
*/
class DeletionMark
{
var $revision_info;
}
/**
* Specifies extra authorship information, such as the user and the ID of it
*/
class RevisionInfo
{
var $id;
var $user;
var $user_text;
var $timestamp;
var $minor_edit;
function RevisionInfo($id, $user, $user_text, $timestamp, $minor_edit) {
$this->id = $id;
$this->user = $user;
$this->user_text = $user_text;
$this->timestamp = $timestamp;
$this->minor_edit = $minor_edit;
}
}
class UnknownRevisionInfo
{
var $unknown = true;
function UnknownRevisionInfo() {}
}
/**
* Represents a string, but keeps track of RevisionInfo. Has diffs applied to
* it.
*/
class Annotation
{
var $strings;
var $ac = 0;
var $sc = 0;
function Annotation($strings = array(), $positions = array(0, 0)) {
$this->strings = $strings; //expects an array of ExtendedString objects
list($this->ac, $this->sc) = $positions;
}
function copy($distance, $extended_string = null) {
//scroll ahead specified distance
//loop iterates each time we advance one element in the array
for(
// i represents the string, as we scroll ahead, we chop off bits of
// the string until nothing is left
$i = $distance;
// every iteration, we check that there is stil is string left, and
// do a sanity check, making sure that our entry exists
$i > 0 && isset($this->strings[$this->ac]);
// an iteration represents scrolling forward in the array, resetting
// the string counter
$this->ac++, $this->sc = 0
) {
$length = $this->strings[$this->ac]->strlen() - $this->sc;
if ($length > $i) {
// we have to break it up, since the distance can't get
// all the way across
$new_strings = $this->strings[$this->ac]->splitAt($i);
array_splice($this->strings, $this->ac, 1, $new_strings);
}
$i -= $length;
}
//fix extra possible addition to extended_string
if ($extended_string !== null &&
$distance != $extended_string->strlen()) {
$extra = $extended_string->substr($distance);
$extra = $extra->string;
$this->strings[$this->ac - 1]->append($extra);
}
}
function add($extended_string) {
//scroll past all phantom strings before inserting
while (isset($this->strings[$this->ac]->phantom)) $this->ac++;
array_splice(
$this->strings,
$this->ac++,
0,
array($extended_string)
);
}
function delete($distance, $revision_info) {
// loop is similar to structure as copy, it just has different meat
for (
$i = $distance;
$i > 0 && isset($this->strings[$this->ac]);
//ac does not get reset because we just shifted the array
$this->sc = 0
) {
// sc expected to be 0, too lazy to write defense code
$length = $this->strings[$this->ac]->strlen();
if ($i < $length) {
$array = array();
if ($revision_info !== false) {
$array[] = new PhantomString($revision_info);
}
$array[] = $this->strings[$this->ac]->substr($i);
array_splice($this->strings, $this->ac, 1, $array);
} else {
array_splice($this->strings, $this->ac, 1);
}
$i -= $length;
}
if ($i == 0 && $revision_info !== false) {
//ahh, it finished perfectly, so no phantom was added
$this->add(new PhantomString($revision_info));
}
}
function change($distance, $extended_string) {
$this->delete($distance, false);
$this->add($extended_string);
}
function newFromRevisions($strings) {
require_once('DifferenceEngine.php');
$annotation = new Annotation();
$num_diffs = count($strings) - 1;
for($i = 0; $i < $num_diffs; $i++) {
$annotation->reset();
if ($i == 0) {
//this is the first iteration, load up annotation
$annotation->strings[] = $strings[$i];
}
//get the diff
$ota =explode("\n",str_replace("\r\n","\n",$strings[$i]->string));
$nta =explode("\n",str_replace("\r\n","\n",$strings[$i+1]->string));
$diff = new WordLevelDiff($ota, $nta);
$edits = $diff->edits;
$info = $strings[$i+1]->revision_info;
foreach ($edits as $edit) {
$orig = ExtendedString::newFromArray($edit->orig, $info);
$closing = ExtendedString::newFromArray($edit->closing, $info);
switch ($edit->type) {
case 'copy':
$annotation->copy($orig->strlen(), $closing);
break;
case 'add':
$annotation->add($closing);
break;
case 'delete':
$annotation->delete($orig->strlen());
break;
case 'change':
$annotation->change($orig->strlen(), $closing);
break;
default:
exit;
}
}
}
$annotation->reset();
return $annotation;
}
function reset() {$this->ac = $this->sc = 0;}
function clump() {}
}
?>

File Metadata

Mime Type
text/x-php
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1206
Default Alt Text
Annotation.php (8 KB)

Event Timeline