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() {} } ?>