Ticket #7760: replace.patch
| File replace.patch, 167.8 KB (added by Simek, 5 years ago) |
|---|
-
atomlib.php
21 21 * @var array 22 22 * @access public 23 23 */ 24 var $links = array(); 25 /** 26 * Stores Categories 27 * @var array 28 * @access public 29 */ 30 var $categories = array(); 24 var $links = array(); 31 25 /** 26 * Stores Categories 27 * @var array 28 * @access public 29 */ 30 var $categories = array(); 31 /** 32 32 * Stores Entries 33 33 * 34 34 * @var array 35 35 * @access public 36 36 */ 37 var $entries = array();37 var $entries = array(); 38 38 } 39 39 40 40 /** … … 48 48 * @var array 49 49 * @access public 50 50 */ 51 var $links = array();52 /**53 * Stores Categories54 * @var array51 var $links = array(); 52 /** 53 * Stores Categories 54 * @var array 55 55 * @access public 56 */57 var $categories = array();56 */ 57 var $categories = array(); 58 58 } 59 59 60 60 /** … … 64 64 */ 65 65 class AtomParser { 66 66 67 var $NS = 'http://www.w3.org/2005/Atom';68 var $ATOM_CONTENT_ELEMENTS = array('content','summary','title','subtitle','rights');69 var $ATOM_SIMPLE_ELEMENTS = array('id','updated','published','draft');67 var $NS = 'http://www.w3.org/2005/Atom'; 68 var $ATOM_CONTENT_ELEMENTS = array('content','summary','title','subtitle','rights'); 69 var $ATOM_SIMPLE_ELEMENTS = array('id','updated','published','draft'); 70 70 71 var $debug = false;71 var $debug = false; 72 72 73 var $depth = 0;74 var $indent = 2;75 var $in_content;76 var $ns_contexts = array();77 var $ns_decls = array();78 var $content_ns_decls = array();79 var $content_ns_contexts = array();80 var $is_xhtml = false;81 var $is_html = false;82 var $is_text = true;83 var $skipped_div = false;73 var $depth = 0; 74 var $indent = 2; 75 var $in_content; 76 var $ns_contexts = array(); 77 var $ns_decls = array(); 78 var $content_ns_decls = array(); 79 var $content_ns_contexts = array(); 80 var $is_xhtml = false; 81 var $is_html = false; 82 var $is_text = true; 83 var $skipped_div = false; 84 84 85 var $FILE = "php://input";85 var $FILE = "php://input"; 86 86 87 var $feed;88 var $current;87 var $feed; 88 var $current; 89 89 90 function AtomParser() {90 function AtomParser() { 91 91 92 $this->feed = new AtomFeed();93 $this->current = null;94 $this->map_attrs_func = create_function('$k,$v', 'return "$k=\"$v\"";');95 $this->map_xmlns_func = create_function('$p,$n', '$xd = "xmlns"; if(strlen($n[0])>0) $xd .= ":{$n[0]}"; return "{$xd}=\"{$n[1]}\"";');96 }92 $this->feed = new AtomFeed(); 93 $this->current = null; 94 $this->map_attrs_func = create_function('$k,$v', 'return "$k=\"$v\"";'); 95 $this->map_xmlns_func = create_function('$p,$n', '$xd = "xmlns"; if(strlen($n[0])>0) $xd .= ":{$n[0]}"; return "{$xd}=\"{$n[1]}\"";'); 96 } 97 97 98 function _p($msg) {99 if($this->debug) {100 print str_repeat(" ", $this->depth * $this->indent) . $msg ."\n";101 }102 }98 function _p($msg) { 99 if($this->debug) { 100 print str_repeat(" ", $this->depth * $this->indent) . $msg ."\n"; 101 } 102 } 103 103 104 function error_handler($log_level, $log_text, $error_file, $error_line) {105 $this->error = $log_text;106 }104 function error_handler($log_level, $log_text, $error_file, $error_line) { 105 $this->error = $log_text; 106 } 107 107 108 function parse() {108 function parse() { 109 109 110 set_error_handler(array(&$this, 'error_handler'));110 set_error_handler(array(&$this, 'error_handler')); 111 111 112 array_unshift($this->ns_contexts, array());112 array_unshift($this->ns_contexts, array()); 113 113 114 $parser = xml_parser_create_ns();115 xml_set_object($parser, $this);116 xml_set_element_handler($parser, "start_element", "end_element");117 xml_parser_set_option($parser,XML_OPTION_CASE_FOLDING,0);118 xml_parser_set_option($parser,XML_OPTION_SKIP_WHITE,0);119 xml_set_character_data_handler($parser, "cdata");120 xml_set_default_handler($parser, "_default");121 xml_set_start_namespace_decl_handler($parser, "start_ns");122 xml_set_end_namespace_decl_handler($parser, "end_ns");114 $parser = xml_parser_create_ns(); 115 xml_set_object($parser, $this); 116 xml_set_element_handler($parser, "start_element", "end_element"); 117 xml_parser_set_option($parser,XML_OPTION_CASE_FOLDING,0); 118 xml_parser_set_option($parser,XML_OPTION_SKIP_WHITE,0); 119 xml_set_character_data_handler($parser, "cdata"); 120 xml_set_default_handler($parser, "_default"); 121 xml_set_start_namespace_decl_handler($parser, "start_ns"); 122 xml_set_end_namespace_decl_handler($parser, "end_ns"); 123 123 124 $this->content = '';124 $this->content = ''; 125 125 126 $ret = true;126 $ret = true; 127 127 128 $fp = fopen($this->FILE, "r");129 while ($data = fread($fp, 4096)) {130 if($this->debug) $this->content .= $data;128 $fp = fopen($this->FILE, "r"); 129 while ($data = fread($fp, 4096)) { 130 if($this->debug) $this->content .= $data; 131 131 132 if(!xml_parse($parser, $data, feof($fp))) {133 trigger_error(sprintf(__('XML error: %s at line %d')."\n",134 xml_error_string(xml_get_error_code($xml_parser)),135 xml_get_current_line_number($xml_parser)));136 $ret = false;137 break;138 }139 }140 fclose($fp);132 if(!xml_parse($parser, $data, feof($fp))) { 133 trigger_error(sprintf(__('XML error: %s at line %d')."\n", 134 xml_error_string(xml_get_error_code($xml_parser)), 135 xml_get_current_line_number($xml_parser))); 136 $ret = false; 137 break; 138 } 139 } 140 fclose($fp); 141 141 142 xml_parser_free($parser);142 xml_parser_free($parser); 143 143 144 restore_error_handler();144 restore_error_handler(); 145 145 146 return $ret;147 }146 return $ret; 147 } 148 148 149 function start_element($parser, $name, $attrs) {149 function start_element($parser, $name, $attrs) { 150 150 151 $tag = array_pop(split(":", $name));151 $tag = array_pop(split(":", $name)); 152 152 153 switch($name) {154 case $this->NS . ':feed':155 $this->current = $this->feed;156 break;157 case $this->NS . ':entry':158 $this->current = new AtomEntry();159 break;160 };153 switch($name) { 154 case $this->NS . ':feed': 155 $this->current = $this->feed; 156 break; 157 case $this->NS . ':entry': 158 $this->current = new AtomEntry(); 159 break; 160 }; 161 161 162 $this->_p("start_element('$name')");163 #$this->_p(print_r($this->ns_contexts,true));164 #$this->_p('current(' . $this->current . ')');162 $this->_p("start_element('$name')"); 163 #$this->_p(print_r($this->ns_contexts,true)); 164 #$this->_p('current(' . $this->current . ')'); 165 165 166 array_unshift($this->ns_contexts, $this->ns_decls);166 array_unshift($this->ns_contexts, $this->ns_decls); 167 167 168 $this->depth++;168 $this->depth++; 169 169 170 if(!empty($this->in_content)) {170 if(!empty($this->in_content)) { 171 171 172 $this->content_ns_decls = array();172 $this->content_ns_decls = array(); 173 173 174 if($this->is_html || $this->is_text)175 trigger_error("Invalid content in element found. Content must not be of type text or html if it contains markup.");174 if($this->is_html || $this->is_text) 175 trigger_error("Invalid content in element found. Content must not be of type text or html if it contains markup."); 176 176 177 $attrs_prefix = array();177 $attrs_prefix = array(); 178 178 179 // resolve prefixes for attributes180 foreach($attrs as $key => $value) {181 $with_prefix = $this->ns_to_prefix($key, true);182 $attrs_prefix[$with_prefix[1]] = $this->xml_escape($value);183 }179 // resolve prefixes for attributes 180 foreach($attrs as $key => $value) { 181 $with_prefix = $this->ns_to_prefix($key, true); 182 $attrs_prefix[$with_prefix[1]] = $this->xml_escape($value); 183 } 184 184 185 $attrs_str = join(' ', array_map($this->map_attrs_func, array_keys($attrs_prefix), array_values($attrs_prefix)));186 if(strlen($attrs_str) > 0) {187 $attrs_str = " " . $attrs_str;188 }185 $attrs_str = join(' ', array_map($this->map_attrs_func, array_keys($attrs_prefix), array_values($attrs_prefix))); 186 if(strlen($attrs_str) > 0) { 187 $attrs_str = " " . $attrs_str; 188 } 189 189 190 $with_prefix = $this->ns_to_prefix($name);190 $with_prefix = $this->ns_to_prefix($name); 191 191 192 if(!$this->is_declared_content_ns($with_prefix[0])) {193 array_push($this->content_ns_decls, $with_prefix[0]);194 }192 if(!$this->is_declared_content_ns($with_prefix[0])) { 193 array_push($this->content_ns_decls, $with_prefix[0]); 194 } 195 195 196 $xmlns_str = '';197 if(count($this->content_ns_decls) > 0) {198 array_unshift($this->content_ns_contexts, $this->content_ns_decls);199 $xmlns_str .= join(' ', array_map($this->map_xmlns_func, array_keys($this->content_ns_contexts[0]), array_values($this->content_ns_contexts[0])));200 if(strlen($xmlns_str) > 0) {201 $xmlns_str = " " . $xmlns_str;202 }203 }196 $xmlns_str = ''; 197 if(count($this->content_ns_decls) > 0) { 198 array_unshift($this->content_ns_contexts, $this->content_ns_decls); 199 $xmlns_str .= join(' ', array_map($this->map_xmlns_func, array_keys($this->content_ns_contexts[0]), array_values($this->content_ns_contexts[0]))); 200 if(strlen($xmlns_str) > 0) { 201 $xmlns_str = " " . $xmlns_str; 202 } 203 } 204 204 205 array_push($this->in_content, array($tag, $this->depth, "<". $with_prefix[1] ."{$xmlns_str}{$attrs_str}" . ">"));205 array_push($this->in_content, array($tag, $this->depth, "<". $with_prefix[1] ."{$xmlns_str}{$attrs_str}" . ">")); 206 206 207 } else if(in_array($tag, $this->ATOM_CONTENT_ELEMENTS) || in_array($tag, $this->ATOM_SIMPLE_ELEMENTS)) {208 $this->in_content = array();209 $this->is_xhtml = $attrs['type'] == 'xhtml';210 $this->is_html = $attrs['type'] == 'html' || $attrs['type'] == 'text/html';211 $this->is_text = !in_array('type',array_keys($attrs)) || $attrs['type'] == 'text';212 $type = $this->is_xhtml ? 'XHTML' : ($this->is_html ? 'HTML' : ($this->is_text ? 'TEXT' : $attrs['type']));207 } else if(in_array($tag, $this->ATOM_CONTENT_ELEMENTS) || in_array($tag, $this->ATOM_SIMPLE_ELEMENTS)) { 208 $this->in_content = array(); 209 $this->is_xhtml = $attrs['type'] == 'xhtml'; 210 $this->is_html = $attrs['type'] == 'html' || $attrs['type'] == 'text/html'; 211 $this->is_text = !in_array('type',array_keys($attrs)) || $attrs['type'] == 'text'; 212 $type = $this->is_xhtml ? 'XHTML' : ($this->is_html ? 'HTML' : ($this->is_text ? 'TEXT' : $attrs['type'])); 213 213 214 if(in_array('src',array_keys($attrs))) {215 $this->current->$tag = $attrs;216 } else {217 array_push($this->in_content, array($tag,$this->depth, $type));218 }219 } else if($tag == 'link') {220 array_push($this->current->links, $attrs);221 } else if($tag == 'category') {222 array_push($this->current->categories, $attrs);223 }214 if(in_array('src',array_keys($attrs))) { 215 $this->current->$tag = $attrs; 216 } else { 217 array_push($this->in_content, array($tag,$this->depth, $type)); 218 } 219 } else if($tag == 'link') { 220 array_push($this->current->links, $attrs); 221 } else if($tag == 'category') { 222 array_push($this->current->categories, $attrs); 223 } 224 224 225 $this->ns_decls = array();226 }225 $this->ns_decls = array(); 226 } 227 227 228 function end_element($parser, $name) {228 function end_element($parser, $name) { 229 229 230 $tag = array_pop(split(":", $name));230 $tag = array_pop(split(":", $name)); 231 231 232 $ccount = count($this->in_content);232 $ccount = count($this->in_content); 233 233 234 # if we are *in* content, then let's proceed to serialize it235 if(!empty($this->in_content)) {236 # if we are ending the original content element237 # then let's finalize the content238 if($this->in_content[0][0] == $tag &&239 $this->in_content[0][1] == $this->depth) {240 $origtype = $this->in_content[0][2];241 array_shift($this->in_content);242 $newcontent = array();243 foreach($this->in_content as $c) {244 if(count($c) == 3) {245 array_push($newcontent, $c[2]);246 } else {247 if($this->is_xhtml || $this->is_text) {248 array_push($newcontent, $this->xml_escape($c));249 } else {250 array_push($newcontent, $c);251 }252 }253 }254 if(in_array($tag, $this->ATOM_CONTENT_ELEMENTS)) {255 $this->current->$tag = array($origtype, join('',$newcontent));256 } else {257 $this->current->$tag = join('',$newcontent);258 }259 $this->in_content = array();260 } else if($this->in_content[$ccount-1][0] == $tag &&261 $this->in_content[$ccount-1][1] == $this->depth) {262 $this->in_content[$ccount-1][2] = substr($this->in_content[$ccount-1][2],0,-1) . "/>";263 } else {264 # else, just finalize the current element's content265 $endtag = $this->ns_to_prefix($name);266 array_push($this->in_content, array($tag, $this->depth, "</$endtag[1]>"));267 }268 }234 # if we are *in* content, then let's proceed to serialize it 235 if(!empty($this->in_content)) { 236 # if we are ending the original content element 237 # then let's finalize the content 238 if($this->in_content[0][0] == $tag && 239 $this->in_content[0][1] == $this->depth) { 240 $origtype = $this->in_content[0][2]; 241 array_shift($this->in_content); 242 $newcontent = array(); 243 foreach($this->in_content as $c) { 244 if(count($c) == 3) { 245 array_push($newcontent, $c[2]); 246 } else { 247 if($this->is_xhtml || $this->is_text) { 248 array_push($newcontent, $this->xml_escape($c)); 249 } else { 250 array_push($newcontent, $c); 251 } 252 } 253 } 254 if(in_array($tag, $this->ATOM_CONTENT_ELEMENTS)) { 255 $this->current->$tag = array($origtype, join('',$newcontent)); 256 } else { 257 $this->current->$tag = join('',$newcontent); 258 } 259 $this->in_content = array(); 260 } else if($this->in_content[$ccount-1][0] == $tag && 261 $this->in_content[$ccount-1][1] == $this->depth) { 262 $this->in_content[$ccount-1][2] = substr($this->in_content[$ccount-1][2],0,-1) . "/>"; 263 } else { 264 # else, just finalize the current element's content 265 $endtag = $this->ns_to_prefix($name); 266 array_push($this->in_content, array($tag, $this->depth, "</$endtag[1]>")); 267 } 268 } 269 269 270 array_shift($this->ns_contexts);270 array_shift($this->ns_contexts); 271 271 272 $this->depth--;272 $this->depth--; 273 273 274 if($name == ($this->NS . ':entry')) {275 array_push($this->feed->entries, $this->current);276 $this->current = null;277 }274 if($name == ($this->NS . ':entry')) { 275 array_push($this->feed->entries, $this->current); 276 $this->current = null; 277 } 278 278 279 $this->_p("end_element('$name')");280 }279 $this->_p("end_element('$name')"); 280 } 281 281 282 function start_ns($parser, $prefix, $uri) {283 $this->_p("starting: " . $prefix . ":" . $uri);284 array_push($this->ns_decls, array($prefix,$uri));285 }282 function start_ns($parser, $prefix, $uri) { 283 $this->_p("starting: " . $prefix . ":" . $uri); 284 array_push($this->ns_decls, array($prefix,$uri)); 285 } 286 286 287 function end_ns($parser, $prefix) {288 $this->_p("ending: #" . $prefix . "#");289 }287 function end_ns($parser, $prefix) { 288 $this->_p("ending: #" . $prefix . "#"); 289 } 290 290 291 function cdata($parser, $data) {292 $this->_p("data: #" . str_replace(array("\n"), array("\\n"), trim($data)) . "#");293 if(!empty($this->in_content)) {294 array_push($this->in_content, $data);295 }296 }291 function cdata($parser, $data) { 292 $this->_p("data: #" . str_replace(array("\n"), array("\\n"), trim($data)) . "#"); 293 if(!empty($this->in_content)) { 294 array_push($this->in_content, $data); 295 } 296 } 297 297 298 function _default($parser, $data) {299 # when does this gets called?300 }298 function _default($parser, $data) { 299 # when does this gets called? 300 } 301 301 302 302 303 function ns_to_prefix($qname, $attr=false) {304 # split 'http://www.w3.org/1999/xhtml:div' into ('http','//www.w3.org/1999/xhtml','div')305 $components = split(":", $qname);303 function ns_to_prefix($qname, $attr=false) { 304 # split 'http://www.w3.org/1999/xhtml:div' into ('http','//www.w3.org/1999/xhtml','div') 305 $components = split(":", $qname); 306 306 307 # grab the last one (e.g 'div')308 $name = array_pop($components);307 # grab the last one (e.g 'div') 308 $name = array_pop($components); 309 309 310 if(!empty($components)) {311 # re-join back the namespace component312 $ns = join(":",$components);313 foreach($this->ns_contexts as $context) {314 foreach($context as $mapping) {315 if($mapping[1] == $ns && strlen($mapping[0]) > 0) {316 return array($mapping, "$mapping[0]:$name");317 }318 }319 }320 }310 if(!empty($components)) { 311 # re-join back the namespace component 312 $ns = join(":",$components); 313 foreach($this->ns_contexts as $context) { 314 foreach($context as $mapping) { 315 if($mapping[1] == $ns && strlen($mapping[0]) > 0) { 316 return array($mapping, "$mapping[0]:$name"); 317 } 318 } 319 } 320 } 321 321 322 if($attr) {323 return array(null, $name);324 } else {325 foreach($this->ns_contexts as $context) {326 foreach($context as $mapping) {327 if(strlen($mapping[0]) == 0) {328 return array($mapping, $name);329 }330 }331 }332 }333 }322 if($attr) { 323 return array(null, $name); 324 } else { 325 foreach($this->ns_contexts as $context) { 326 foreach($context as $mapping) { 327 if(strlen($mapping[0]) == 0) { 328 return array($mapping, $name); 329 } 330 } 331 } 332 } 333 } 334 334 335 function is_declared_content_ns($new_mapping) {336 foreach($this->content_ns_contexts as $context) {337 foreach($context as $mapping) {338 if($new_mapping == $mapping) {339 return true;340 }341 }342 }343 return false;344 }335 function is_declared_content_ns($new_mapping) { 336 foreach($this->content_ns_contexts as $context) { 337 foreach($context as $mapping) { 338 if($new_mapping == $mapping) { 339 return true; 340 } 341 } 342 } 343 return false; 344 } 345 345 346 function xml_escape($string)347 {348 return str_replace(array('&','"',"'",'<','>'),349 array('&','"',''','<','>'),350 $string );351 }346 function xml_escape($string) 347 { 348 return str_replace(array('&','"',"'",'<','>'), 349 array('&','"',''','<','>'), 350 $string ); 351 } 352 352 } 353 353 354 354 ?> -
class-IXR.php
20 20 * @since 1.5 21 21 */ 22 22 class IXR_Value { 23 var $data;24 var $type;23 var $data; 24 var $type; 25 25 26 function IXR_Value ($data, $type = false) {27 $this->data = $data;28 if (!$type) {29 $type = $this->calculateType();30 }31 $this->type = $type;32 if ($type == 'struct') {33 /* Turn all the values in the array in to new IXR_Value objects */34 foreach ($this->data as $key => $value) {35 $this->data[$key] = new IXR_Value($value);36 }37 }38 if ($type == 'array') {39 for ($i = 0, $j = count($this->data); $i < $j; $i++) {40 $this->data[$i] = new IXR_Value($this->data[$i]);41 }42 }43 }26 function IXR_Value ($data, $type = false) { 27 $this->data = $data; 28 if (!$type) { 29 $type = $this->calculateType(); 30 } 31 $this->type = $type; 32 if ($type == 'struct') { 33 /* Turn all the values in the array in to new IXR_Value objects */ 34 foreach ($this->data as $key => $value) { 35 $this->data[$key] = new IXR_Value($value); 36 } 37 } 38 if ($type == 'array') { 39 for ($i = 0, $j = count($this->data); $i < $j; $i++) { 40 $this->data[$i] = new IXR_Value($this->data[$i]); 41 } 42 } 43 } 44 44 45 function calculateType() {46 if ($this->data === true || $this->data === false) {47 return 'boolean';48 }49 if (is_integer($this->data)) {50 return 'int';51 }52 if (is_double($this->data)) {53 return 'double';54 }55 // Deal with IXR object types base64 and date56 if (is_object($this->data) && is_a($this->data, 'IXR_Date')) {57 return 'date';58 }59 if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) {60 return 'base64';61 }62 // If it is a normal PHP object convert it in to a struct63 if (is_object($this->data)) {45 function calculateType() { 46 if ($this->data === true || $this->data === false) { 47 return 'boolean'; 48 } 49 if (is_integer($this->data)) { 50 return 'int'; 51 } 52 if (is_double($this->data)) { 53 return 'double'; 54 } 55 // Deal with IXR object types base64 and date 56 if (is_object($this->data) && is_a($this->data, 'IXR_Date')) { 57 return 'date'; 58 } 59 if (is_object($this->data) && is_a($this->data, 'IXR_Base64')) { 60 return 'base64'; 61 } 62 // If it is a normal PHP object convert it in to a struct 63 if (is_object($this->data)) { 64 64 65 $this->data = get_object_vars($this->data);66 return 'struct';67 }68 if (!is_array($this->data)) {69 return 'string';70 }71 /* We have an array - is it an array or a struct ? */72 if ($this->isStruct($this->data)) {73 return 'struct';74 } else {75 return 'array';76 }77 }65 $this->data = get_object_vars($this->data); 66 return 'struct'; 67 } 68 if (!is_array($this->data)) { 69 return 'string'; 70 } 71 /* We have an array - is it an array or a struct ? */ 72 if ($this->isStruct($this->data)) { 73 return 'struct'; 74 } else { 75 return 'array'; 76 } 77 } 78 78 79 function getXml() {80 /* Return XML for this value */81 switch ($this->type) {82 case 'boolean':83 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>';84 break;85 case 'int':86 return '<int>'.$this->data.'</int>';87 break;88 case 'double':89 return '<double>'.$this->data.'</double>';90 break;91 case 'string':92 return '<string>'.htmlspecialchars($this->data).'</string>';93 break;94 case 'array':95 $return = '<array><data>'."\n";96 foreach ($this->data as $item) {97 $return .= ' <value>'.$item->getXml()."</value>\n";98 }99 $return .= '</data></array>';100 return $return;101 break;102 case 'struct':103 $return = '<struct>'."\n";104 foreach ($this->data as $name => $value) {79 function getXml() { 80 /* Return XML for this value */ 81 switch ($this->type) { 82 case 'boolean': 83 return '<boolean>'.(($this->data) ? '1' : '0').'</boolean>'; 84 break; 85 case 'int': 86 return '<int>'.$this->data.'</int>'; 87 break; 88 case 'double': 89 return '<double>'.$this->data.'</double>'; 90 break; 91 case 'string': 92 return '<string>'.htmlspecialchars($this->data).'</string>'; 93 break; 94 case 'array': 95 $return = '<array><data>'."\n"; 96 foreach ($this->data as $item) { 97 $return .= ' <value>'.$item->getXml()."</value>\n"; 98 } 99 $return .= '</data></array>'; 100 return $return; 101 break; 102 case 'struct': 103 $return = '<struct>'."\n"; 104 foreach ($this->data as $name => $value) { 105 105 $name = htmlspecialchars($name); 106 $return .= " <member><name>$name</name><value>";107 $return .= $value->getXml()."</value></member>\n";108 }109 $return .= '</struct>';110 return $return;111 break;112 case 'date':113 case 'base64':114 return $this->data->getXml();115 break;116 }117 return false;118 }106 $return .= " <member><name>$name</name><value>"; 107 $return .= $value->getXml()."</value></member>\n"; 108 } 109 $return .= '</struct>'; 110 return $return; 111 break; 112 case 'date': 113 case 'base64': 114 return $this->data->getXml(); 115 break; 116 } 117 return false; 118 } 119 119 120 function isStruct($array) {121 /* Nasty function to check if an array is a struct or not */122 $expected = 0;123 foreach ($array as $key => $value) {124 if ((string)$key != (string)$expected) {125 return true;126 }127 $expected++;128 }129 return false;130 }120 function isStruct($array) { 121 /* Nasty function to check if an array is a struct or not */ 122 $expected = 0; 123 foreach ($array as $key => $value) { 124 if ((string)$key != (string)$expected) { 125 return true; 126 } 127 $expected++; 128 } 129 return false; 130 } 131 131 } 132 132 133 133 /** … … 137 137 * @since 1.5 138 138 */ 139 139 class IXR_Message { 140 var $message;141 var $messageType; // methodCall / methodResponse / fault142 var $faultCode;143 var $faultString;144 var $methodName;145 var $params;146 // Current variable stacks147 var $_arraystructs = array(); // The stack used to keep track of the current array/struct148 var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array149 var $_currentStructName = array(); // A stack as well150 var $_param;151 var $_value;152 var $_currentTag;153 var $_currentTagContents;154 // The XML parser155 var $_parser;156 function IXR_Message ($message) {157 $this->message = $message;158 }159 function parse() {160 // first remove the XML declaration161 $this->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $this->message);162 if (trim($this->message) == '') {163 return false;164 }165 $this->_parser = xml_parser_create();166 // Set XML parser to take the case of tags in to account167 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false);168 // Set XML parser callback functions169 xml_set_object($this->_parser, $this);170 xml_set_element_handler($this->_parser, 'tag_open', 'tag_close');171 xml_set_character_data_handler($this->_parser, 'cdata');172 if (!xml_parse($this->_parser, $this->message)) {173 /* die(sprintf('XML error: %s at line %d',174 xml_error_string(xml_get_error_code($this->_parser)),175 xml_get_current_line_number($this->_parser))); */176 return false;177 }178 xml_parser_free($this->_parser);179 // Grab the error messages, if any180 if ($this->messageType == 'fault') {181 $this->faultCode = $this->params[0]['faultCode'];182 $this->faultString = $this->params[0]['faultString'];183 }184 return true;185 }186 function tag_open($parser, $tag, $attr) {187 $this->_currentTagContents = '';188 $this->currentTag = $tag;189 switch($tag) {190 case 'methodCall':191 case 'methodResponse':192 case 'fault':193 $this->messageType = $tag;194 break;195 /* Deal with stacks of arrays and structs */196 case 'data':// data is to all intents and puposes more interesting than array197 $this->_arraystructstypes[] = 'array';198 $this->_arraystructs[] = array();199 break;200 case 'struct':201 $this->_arraystructstypes[] = 'struct';202 $this->_arraystructs[] = array();203 break;204 }205 }206 function cdata($parser, $cdata) {207 $this->_currentTagContents .= $cdata;208 }209 function tag_close($parser, $tag) {210 $valueFlag = false;211 switch($tag) {212 case 'int':213 case 'i4':214 $value = (int) trim($this->_currentTagContents);215 $valueFlag = true;216 break;217 case 'double':218 $value = (double) trim($this->_currentTagContents);219 $valueFlag = true;220 break;221 case 'string':222 $value = $this->_currentTagContents;223 $valueFlag = true;224 break;225 case 'dateTime.iso8601':226 $value = new IXR_Date(trim($this->_currentTagContents));227 // $value = $iso->getTimestamp();228 $valueFlag = true;229 break;230 case 'value':231 // "If no type is indicated, the type is string."232 if (trim($this->_currentTagContents) != '') {233 $value = (string)$this->_currentTagContents;234 $valueFlag = true;235 }236 break;237 case 'boolean':238 $value = (boolean) trim($this->_currentTagContents);239 $valueFlag = true;240 break;241 case 'base64':242 $value = base64_decode( trim( $this->_currentTagContents ) );243 $valueFlag = true;244 break;245 /* Deal with stacks of arrays and structs */246 case 'data':247 case 'struct':248 $value = array_pop($this->_arraystructs);249 array_pop($this->_arraystructstypes);250 $valueFlag = true;251 break;252 case 'member':253 array_pop($this->_currentStructName);254 break;255 case 'name':256 $this->_currentStructName[] = trim($this->_currentTagContents);257 break;258 case 'methodName':259 $this->methodName = trim($this->_currentTagContents);260 break;261 }262 if ($valueFlag) {263 if (count($this->_arraystructs) > 0) {264 // Add value to struct or array265 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') {266 // Add to struct267 $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value;268 } else {269 // Add to array270 $this->_arraystructs[count($this->_arraystructs)-1][] = $value;271 }272 } else {273 // Just add as a paramater274 $this->params[] = $value;275 }276 }277 $this->_currentTagContents = '';278 }140 var $message; 141 var $messageType; // methodCall / methodResponse / fault 142 var $faultCode; 143 var $faultString; 144 var $methodName; 145 var $params; 146 // Current variable stacks 147 var $_arraystructs = array(); // The stack used to keep track of the current array/struct 148 var $_arraystructstypes = array(); // Stack keeping track of if things are structs or array 149 var $_currentStructName = array(); // A stack as well 150 var $_param; 151 var $_value; 152 var $_currentTag; 153 var $_currentTagContents; 154 // The XML parser 155 var $_parser; 156 function IXR_Message ($message) { 157 $this->message = $message; 158 } 159 function parse() { 160 // first remove the XML declaration 161 $this->message = preg_replace('/<\?xml(.*)?\?'.'>/', '', $this->message); 162 if (trim($this->message) == '') { 163 return false; 164 } 165 $this->_parser = xml_parser_create(); 166 // Set XML parser to take the case of tags in to account 167 xml_parser_set_option($this->_parser, XML_OPTION_CASE_FOLDING, false); 168 // Set XML parser callback functions 169 xml_set_object($this->_parser, $this); 170 xml_set_element_handler($this->_parser, 'tag_open', 'tag_close'); 171 xml_set_character_data_handler($this->_parser, 'cdata'); 172 if (!xml_parse($this->_parser, $this->message)) { 173 /* die(sprintf('XML error: %s at line %d', 174 xml_error_string(xml_get_error_code($this->_parser)), 175 xml_get_current_line_number($this->_parser))); */ 176 return false; 177 } 178 xml_parser_free($this->_parser); 179 // Grab the error messages, if any 180 if ($this->messageType == 'fault') { 181 $this->faultCode = $this->params[0]['faultCode']; 182 $this->faultString = $this->params[0]['faultString']; 183 } 184 return true; 185 } 186 function tag_open($parser, $tag, $attr) { 187 $this->_currentTagContents = ''; 188 $this->currentTag = $tag; 189 switch($tag) { 190 case 'methodCall': 191 case 'methodResponse': 192 case 'fault': 193 $this->messageType = $tag; 194 break; 195 /* Deal with stacks of arrays and structs */ 196 case 'data': // data is to all intents and puposes more interesting than array 197 $this->_arraystructstypes[] = 'array'; 198 $this->_arraystructs[] = array(); 199 break; 200 case 'struct': 201 $this->_arraystructstypes[] = 'struct'; 202 $this->_arraystructs[] = array(); 203 break; 204 } 205 } 206 function cdata($parser, $cdata) { 207 $this->_currentTagContents .= $cdata; 208 } 209 function tag_close($parser, $tag) { 210 $valueFlag = false; 211 switch($tag) { 212 case 'int': 213 case 'i4': 214 $value = (int) trim($this->_currentTagContents); 215 $valueFlag = true; 216 break; 217 case 'double': 218 $value = (double) trim($this->_currentTagContents); 219 $valueFlag = true; 220 break; 221 case 'string': 222 $value = $this->_currentTagContents; 223 $valueFlag = true; 224 break; 225 case 'dateTime.iso8601': 226 $value = new IXR_Date(trim($this->_currentTagContents)); 227 // $value = $iso->getTimestamp(); 228 $valueFlag = true; 229 break; 230 case 'value': 231 // "If no type is indicated, the type is string." 232 if (trim($this->_currentTagContents) != '') { 233 $value = (string)$this->_currentTagContents; 234 $valueFlag = true; 235 } 236 break; 237 case 'boolean': 238 $value = (boolean) trim($this->_currentTagContents); 239 $valueFlag = true; 240 break; 241 case 'base64': 242 $value = base64_decode( trim( $this->_currentTagContents ) ); 243 $valueFlag = true; 244 break; 245 /* Deal with stacks of arrays and structs */ 246 case 'data': 247 case 'struct': 248 $value = array_pop($this->_arraystructs); 249 array_pop($this->_arraystructstypes); 250 $valueFlag = true; 251 break; 252 case 'member': 253 array_pop($this->_currentStructName); 254 break; 255 case 'name': 256 $this->_currentStructName[] = trim($this->_currentTagContents); 257 break; 258 case 'methodName': 259 $this->methodName = trim($this->_currentTagContents); 260 break; 261 } 262 if ($valueFlag) { 263 if (count($this->_arraystructs) > 0) { 264 // Add value to struct or array 265 if ($this->_arraystructstypes[count($this->_arraystructstypes)-1] == 'struct') { 266 // Add to struct 267 $this->_arraystructs[count($this->_arraystructs)-1][$this->_currentStructName[count($this->_currentStructName)-1]] = $value; 268 } else { 269 // Add to array 270 $this->_arraystructs[count($this->_arraystructs)-1][] = $value; 271 } 272 } else { 273 // Just add as a paramater 274 $this->params[] = $value; 275 } 276 } 277 $this->_currentTagContents = ''; 278 } 279 279 } 280 280 281 281 /** … … 285 285 * @since 1.5 286 286 */ 287 287 class IXR_Server { 288 var $data;289 var $callbacks = array();290 var $message;291 var $capabilities;292 function IXR_Server($callbacks = false, $data = false) {293 $this->setCapabilities();294 if ($callbacks) {295 $this->callbacks = $callbacks;296 }297 $this->setCallbacks();298 $this->serve($data);299 }300 function serve($data = false) {301 if (!$data) {302 global $HTTP_RAW_POST_DATA;303 if (!$HTTP_RAW_POST_DATA) {304 die('XML-RPC server accepts POST requests only.');305 }306 $data = $HTTP_RAW_POST_DATA;307 }308 $this->message = new IXR_Message($data);309 if (!$this->message->parse()) {310 $this->error(-32700, 'parse error. not well formed');311 }312 if ($this->message->messageType != 'methodCall') {313 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall');314 }315 $result = $this->call($this->message->methodName, $this->message->params);316 // Is the result an error?317 if (is_a($result, 'IXR_Error')) {318 $this->error($result);319 }320 // Encode the result321 $r = new IXR_Value($result);322 $resultxml = $r->getXml();323 // Create the XML324 $xml = <<<EOD288 var $data; 289 var $callbacks = array(); 290 var $message; 291 var $capabilities; 292 function IXR_Server($callbacks = false, $data = false) { 293 $this->setCapabilities(); 294 if ($callbacks) { 295 $this->callbacks = $callbacks; 296 } 297 $this->setCallbacks(); 298 $this->serve($data); 299 } 300 function serve($data = false) { 301 if (!$data) { 302 global $HTTP_RAW_POST_DATA; 303 if (!$HTTP_RAW_POST_DATA) { 304 die('XML-RPC server accepts POST requests only.'); 305 } 306 $data = $HTTP_RAW_POST_DATA; 307 } 308 $this->message = new IXR_Message($data); 309 if (!$this->message->parse()) { 310 $this->error(-32700, 'parse error. not well formed'); 311 } 312 if ($this->message->messageType != 'methodCall') { 313 $this->error(-32600, 'server error. invalid xml-rpc. not conforming to spec. Request must be a methodCall'); 314 } 315 $result = $this->call($this->message->methodName, $this->message->params); 316 // Is the result an error? 317 if (is_a($result, 'IXR_Error')) { 318 $this->error($result); 319 } 320 // Encode the result 321 $r = new IXR_Value($result); 322 $resultxml = $r->getXml(); 323 // Create the XML 324 $xml = <<<EOD 325 325 <methodResponse> 326 326 <params> 327 <param>328 <value>329 $resultxml330 </value>331 </param>327 <param> 328 <value> 329 $resultxml 330 </value> 331 </param> 332 332 </params> 333 333 </methodResponse> 334 334 335 335 EOD; 336 // Send it337 $this->output($xml);338 }339 function call($methodname, $args) {340 if (!$this->hasMethod($methodname)) {341 return new IXR_Error(-32601, 'server error. requested method '.342 $methodname.' does not exist.');343 }344 $method = $this->callbacks[$methodname];345 // Perform the callback and send the response346 if (count($args) == 1) {347 // If only one paramater just send that instead of the whole array348 $args = $args[0];349 }350 // Are we dealing with a function or a method?351 if (substr($method, 0, 5) == 'this:') {352 // It's a class method - check it exists353 $method = substr($method, 5);354 if (!method_exists($this, $method)) {355 return new IXR_Error(-32601, 'server error. requested class method "'.356 $method.'" does not exist.');357 }358 // Call the method359 $result = $this->$method($args);360 } else {361 // It's a function - does it exist?362 if (is_array($method)) {363 if (!method_exists($method[0], $method[1])) {364 return new IXR_Error(-32601, 'server error. requested object method "'.365 $method[1].'" does not exist.');366 }367 } else if (!function_exists($method)) {368 return new IXR_Error(-32601, 'server error. requested function "'.369 $method.'" does not exist.');370 }371 // Call the function372 $result = call_user_func($method, $args);373 }374 return $result;375 }336 // Send it 337 $this->output($xml); 338 } 339 function call($methodname, $args) { 340 if (!$this->hasMethod($methodname)) { 341 return new IXR_Error(-32601, 'server error. requested method '. 342 $methodname.' does not exist.'); 343 } 344 $method = $this->callbacks[$methodname]; 345 // Perform the callback and send the response 346 if (count($args) == 1) { 347 // If only one paramater just send that instead of the whole array 348 $args = $args[0]; 349 } 350 // Are we dealing with a function or a method? 351 if (substr($method, 0, 5) == 'this:') { 352 // It's a class method - check it exists 353 $method = substr($method, 5); 354 if (!method_exists($this, $method)) { 355 return new IXR_Error(-32601, 'server error. requested class method "'. 356 $method.'" does not exist.'); 357 } 358 // Call the method 359 $result = $this->$method($args); 360 } else { 361 // It's a function - does it exist? 362 if (is_array($method)) { 363 if (!method_exists($method[0], $method[1])) { 364 return new IXR_Error(-32601, 'server error. requested object method "'. 365 $method[1].'" does not exist.'); 366 } 367 } else if (!function_exists($method)) { 368 return new IXR_Error(-32601, 'server error. requested function "'. 369 $method.'" does not exist.'); 370 } 371 // Call the function 372 $result = call_user_func($method, $args); 373 } 374 return $result; 375 } 376 376 377 function error($error, $message = false) {378 // Accepts either an error object or an error code and message379 if ($message && !is_object($error)) {380 $error = new IXR_Error($error, $message);381 }382 $this->output($error->getXml());383 }384 function output($xml) {385 $xml = '<?xml version="1.0"?>'."\n".$xml;386 $length = strlen($xml);387 header('Connection: close');388 header('Content-Length: '.$length);389 header('Content-Type: text/xml');390 header('Date: '.date('r'));391 echo $xml;392 exit;393 }394 function hasMethod($method) {395 return in_array($method, array_keys($this->callbacks));396 }397 function setCapabilities() {398 // Initialises capabilities array399 $this->capabilities = array(400 'xmlrpc' => array(401 'specUrl' => 'http://www.xmlrpc.com/spec',402 'specVersion' => 1403 ),404 'faults_interop' => array(405 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php',406 'specVersion' => 20010516407 ),408 'system.multicall' => array(409 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208',410 'specVersion' => 1411 ),412 );413 }414 function getCapabilities($args) {415 return $this->capabilities;416 }417 function setCallbacks() {418 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities';419 $this->callbacks['system.listMethods'] = 'this:listMethods';420 $this->callbacks['system.multicall'] = 'this:multiCall';421 }422 function listMethods($args) {423 // Returns a list of methods - uses array_reverse to ensure user defined424 // methods are listed before server defined methods425 return array_reverse(array_keys($this->callbacks));426 }427 function multiCall($methodcalls) {428 // See http://www.xmlrpc.com/discuss/msgReader$1208429 $return = array();430 foreach ($methodcalls as $call) {431 $method = $call['methodName'];432 $params = $call['params'];433 if ($method == 'system.multicall') {434 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden');435 } else {436 $result = $this->call($method, $params);437 }438 if (is_a($result, 'IXR_Error')) {439 $return[] = array(440 'faultCode' => $result->code,441 'faultString' => $result->message442 );443 } else {444 $return[] = array($result);445 }446 }447 return $return;448 }377 function error($error, $message = false) { 378 // Accepts either an error object or an error code and message 379 if ($message && !is_object($error)) { 380 $error = new IXR_Error($error, $message); 381 } 382 $this->output($error->getXml()); 383 } 384 function output($xml) { 385 $xml = '<?xml version="1.0"?>'."\n".$xml; 386 $length = strlen($xml); 387 header('Connection: close'); 388 header('Content-Length: '.$length); 389 header('Content-Type: text/xml'); 390 header('Date: '.date('r')); 391 echo $xml; 392 exit; 393 } 394 function hasMethod($method) { 395 return in_array($method, array_keys($this->callbacks)); 396 } 397 function setCapabilities() { 398 // Initialises capabilities array 399 $this->capabilities = array( 400 'xmlrpc' => array( 401 'specUrl' => 'http://www.xmlrpc.com/spec', 402 'specVersion' => 1 403 ), 404 'faults_interop' => array( 405 'specUrl' => 'http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php', 406 'specVersion' => 20010516 407 ), 408 'system.multicall' => array( 409 'specUrl' => 'http://www.xmlrpc.com/discuss/msgReader$1208', 410 'specVersion' => 1 411 ), 412 ); 413 } 414 function getCapabilities($args) { 415 return $this->capabilities; 416 } 417 function setCallbacks() { 418 $this->callbacks['system.getCapabilities'] = 'this:getCapabilities'; 419 $this->callbacks['system.listMethods'] = 'this:listMethods'; 420 $this->callbacks['system.multicall'] = 'this:multiCall'; 421 } 422 function listMethods($args) { 423 // Returns a list of methods - uses array_reverse to ensure user defined 424 // methods are listed before server defined methods 425 return array_reverse(array_keys($this->callbacks)); 426 } 427 function multiCall($methodcalls) { 428 // See http://www.xmlrpc.com/discuss/msgReader$1208 429 $return = array(); 430 foreach ($methodcalls as $call) { 431 $method = $call['methodName']; 432 $params = $call['params']; 433 if ($method == 'system.multicall') { 434 $result = new IXR_Error(-32600, 'Recursive calls to system.multicall are forbidden'); 435 } else { 436 $result = $this->call($method, $params); 437 } 438 if (is_a($result, 'IXR_Error')) { 439 $return[] = array( 440 'faultCode' => $result->code, 441 'faultString' => $result->message 442 ); 443 } else { 444 $return[] = array($result); 445 } 446 } 447 return $return; 448 } 449 449 } 450 450 451 451 /** … … 455 455 * @since 1.5 456 456 */ 457 457 class IXR_Request { 458 var $method;459 var $args;460 var $xml;461 function IXR_Request($method, $args) {462 $this->method = $method;463 $this->args = $args;464 $this->xml = <<<EOD458 var $method; 459 var $args; 460 var $xml; 461 function IXR_Request($method, $args) { 462 $this->method = $method; 463 $this->args = $args; 464 $this->xml = <<<EOD 465 465 <?xml version="1.0"?> 466 466 <methodCall> 467 467 <methodName>{$this->method}</methodName> 468 468 <params> 469 469 470 470 EOD; 471 foreach ($this->args as $arg) {472 $this->xml .= '<param><value>';473 $v = new IXR_Value($arg);474 $this->xml .= $v->getXml();475 $this->xml .= "</value></param>\n";476 }477 $this->xml .= '</params></methodCall>';478 }479 function getLength() {480 return strlen($this->xml);481 }482 function getXml() {483 return $this->xml;484 }471 foreach ($this->args as $arg) { 472 $this->xml .= '<param><value>'; 473 $v = new IXR_Value($arg); 474 $this->xml .= $v->getXml(); 475 $this->xml .= "</value></param>\n"; 476 } 477 $this->xml .= '</params></methodCall>'; 478 } 479 function getLength() { 480 return strlen($this->xml); 481 } 482 function getXml() { 483 return $this->xml; 484 } 485 485 } 486 486 487 487 /** … … 491 491 * @since 1.5 492 492 */ 493 493 class IXR_Client { 494 var $server;495 var $port;496 var $path;497 var $useragent;498 var $response;499 var $message = false;500 var $debug = false;501 var $timeout;502 // Storage place for an error message503 var $error = false;504 function IXR_Client($server, $path = false, $port = 80, $timeout = false) {505 if (!$path) {506 // Assume we have been given a URL instead507 $bits = parse_url($server);508 $this->server = $bits['host'];509 $this->port = isset($bits['port']) ? $bits['port'] : 80;510 $this->path = isset($bits['path']) ? $bits['path'] : '/';511 // Make absolutely sure we have a path512 if (!$this->path) {513 $this->path = '/';514 }515 } else {516 $this->server = $server;517 $this->path = $path;518 $this->port = $port;519 }520 $this->useragent = 'The Incutio XML-RPC PHP Library';521 $this->timeout = $timeout;522 }523 function query() {524 $args = func_get_args();525 $method = array_shift($args);526 $request = new IXR_Request($method, $args);527 $length = $request->getLength();528 $xml = $request->getXml();529 $r = "\r\n";530 $request = "POST {$this->path} HTTP/1.0$r";531 $request .= "Host: {$this->server}$r";532 $request .= "Content-Type: text/xml$r";533 $request .= "User-Agent: {$this->useragent}$r";534 $request .= "Content-length: {$length}$r$r";535 $request .= $xml;536 // Now send the request537 if ($this->debug) {538 echo '<pre>'.htmlspecialchars($request)."\n</pre>\n\n";539 }540 if ($this->timeout) {541 $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout);542 } else {543 $fp = @fsockopen($this->server, $this->port, $errno, $errstr);544 }545 if (!$fp) {546 $this->error = new IXR_Error(-32300, "transport error - could not open socket: $errno $errstr");547 return false;548 }549 fputs($fp, $request);550 $contents = '';551 $gotFirstLine = false;552 $gettingHeaders = true;553 while (!feof($fp)) {554 $line = fgets($fp, 4096);555 if (!$gotFirstLine) {556 // Check line for '200'557 if (strstr($line, '200') === false) {558 $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200');559 return false;560 }561 $gotFirstLine = true;562 }563 if (trim($line) == '') {564 $gettingHeaders = false;565 }566 if (!$gettingHeaders) {567 $contents .= trim($line);568 }569 }570 if ($this->debug) {571 echo '<pre>'.htmlspecialchars($contents)."\n</pre>\n\n";572 }573 // Now parse what we've got back574 $this->message = new IXR_Message($contents);575 if (!$this->message->parse()) {576 // XML error577 $this->error = new IXR_Error(-32700, 'parse error. not well formed');578 return false;579 }580 // Is the message a fault?581 if ($this->message->messageType == 'fault') {582 $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString);583 return false;584 }585 // Message must be OK586 return true;587 }588 function getResponse() {589 // methodResponses can only have one param - return that590 return $this->message->params[0];591 }592 function isError() {593 return (is_object($this->error));594 }595 function getErrorCode() {596 return $this->error->code;597 }598 function getErrorMessage() {599 return $this->error->message;600 }494 var $server; 495 var $port; 496 var $path; 497 var $useragent; 498 var $response; 499 var $message = false; 500 var $debug = false; 501 var $timeout; 502 // Storage place for an error message 503 var $error = false; 504 function IXR_Client($server, $path = false, $port = 80, $timeout = false) { 505 if (!$path) { 506 // Assume we have been given a URL instead 507 $bits = parse_url($server); 508 $this->server = $bits['host']; 509 $this->port = isset($bits['port']) ? $bits['port'] : 80; 510 $this->path = isset($bits['path']) ? $bits['path'] : '/'; 511 // Make absolutely sure we have a path 512 if (!$this->path) { 513 $this->path = '/'; 514 } 515 } else { 516 $this->server = $server; 517 $this->path = $path; 518 $this->port = $port; 519 } 520 $this->useragent = 'The Incutio XML-RPC PHP Library'; 521 $this->timeout = $timeout; 522 } 523 function query() { 524 $args = func_get_args(); 525 $method = array_shift($args); 526 $request = new IXR_Request($method, $args); 527 $length = $request->getLength(); 528 $xml = $request->getXml(); 529 $r = "\r\n"; 530 $request = "POST {$this->path} HTTP/1.0$r"; 531 $request .= "Host: {$this->server}$r"; 532 $request .= "Content-Type: text/xml$r"; 533 $request .= "User-Agent: {$this->useragent}$r"; 534 $request .= "Content-length: {$length}$r$r"; 535 $request .= $xml; 536 // Now send the request 537 if ($this->debug) { 538 echo '<pre>'.htmlspecialchars($request)."\n</pre>\n\n"; 539 } 540 if ($this->timeout) { 541 $fp = @fsockopen($this->server, $this->port, $errno, $errstr, $this->timeout); 542 } else { 543 $fp = @fsockopen($this->server, $this->port, $errno, $errstr); 544 } 545 if (!$fp) { 546 $this->error = new IXR_Error(-32300, "transport error - could not open socket: $errno $errstr"); 547 return false; 548 } 549 fputs($fp, $request); 550 $contents = ''; 551 $gotFirstLine = false; 552 $gettingHeaders = true; 553 while (!feof($fp)) { 554 $line = fgets($fp, 4096); 555 if (!$gotFirstLine) { 556 // Check line for '200' 557 if (strstr($line, '200') === false) { 558 $this->error = new IXR_Error(-32300, 'transport error - HTTP status code was not 200'); 559 return false; 560 } 561 $gotFirstLine = true; 562 } 563 if (trim($line) == '') { 564 $gettingHeaders = false; 565 } 566 if (!$gettingHeaders) { 567 $contents .= trim($line); 568 } 569 } 570 if ($this->debug) { 571 echo '<pre>'.htmlspecialchars($contents)."\n</pre>\n\n"; 572 } 573 // Now parse what we've got back 574 $this->message = new IXR_Message($contents); 575 if (!$this->message->parse()) { 576 // XML error 577 $this->error = new IXR_Error(-32700, 'parse error. not well formed'); 578 return false; 579 } 580 // Is the message a fault? 581 if ($this->message->messageType == 'fault') { 582 $this->error = new IXR_Error($this->message->faultCode, $this->message->faultString); 583 return false; 584 } 585 // Message must be OK 586 return true; 587 } 588 function getResponse() { 589 // methodResponses can only have one param - return that 590 return $this->message->params[0]; 591 } 592 function isError() { 593 return (is_object($this->error)); 594 } 595 function getErrorCode() { 596 return $this->error->code; 597 } 598 function getErrorMessage() { 599 return $this->error->message; 600 } 601 601 } 602 602 603 603 /** … … 607 607 * @since 1.5 608 608 */ 609 609 class IXR_Error { 610 var $code;611 var $message;612 function IXR_Error($code, $message) {613 $this->code = $code;614 // WP adds htmlspecialchars(). See #5666615 $this->message = htmlspecialchars($message);616 }617 function getXml() {618 $xml = <<<EOD610 var $code; 611 var $message; 612 function IXR_Error($code, $message) { 613 $this->code = $code; 614 // WP adds htmlspecialchars(). See #5666 615 $this->message = htmlspecialchars($message); 616 } 617 function getXml() { 618 $xml = <<<EOD 619 619 <methodResponse> 620 620 <fault> 621 <value>622 <struct>623 <member>624 <name>faultCode</name>625 <value><int>{$this->code}</int></value>626 </member>627 <member>628 <name>faultString</name>629 <value><string>{$this->message}</string></value>630 </member>631 </struct>632 </value>621 <value> 622 <struct> 623 <member> 624 <name>faultCode</name> 625 <value><int>{$this->code}</int></value> 626 </member> 627 <member> 628 <name>faultString</name> 629 <value><string>{$this->message}</string></value> 630 </member> 631 </struct> 632 </value> 633 633 </fault> 634 634 </methodResponse> 635 635 636 636 EOD; 637 return $xml;638 }637 return $xml; 638 } 639 639 } 640 640 641 641 /** … … 645 645 * @since 1.5 646 646 */ 647 647 class IXR_Date { 648 var $year;649 var $month;650 var $day;651 var $hour;652 var $minute;653 var $second;654 var $timezone;655 function IXR_Date($time) {656 // $time can be a PHP timestamp or an ISO one657 if (is_numeric($time)) {658 $this->parseTimestamp($time);659 } else {660 $this->parseIso($time);661 }662 }663 function parseTimestamp($timestamp) {664 $this->year = date('Y', $timestamp);665 $this->month = date('m', $timestamp);666 $this->day = date('d', $timestamp);667 $this->hour = date('H', $timestamp);668 $this->minute = date('i', $timestamp);669 $this->second = date('s', $timestamp);670 // WP adds timezone. See #2036671 $this->timezone = '';672 }673 function parseIso($iso) {674 $this->year = substr($iso, 0, 4);675 $this->month = substr($iso, 4, 2);676 $this->day = substr($iso, 6, 2);677 $this->hour = substr($iso, 9, 2);678 $this->minute = substr($iso, 12, 2);679 $this->second = substr($iso, 15, 2);680 // WP adds timezone. See #2036681 $this->timezone = substr($iso, 17);682 }683 function getIso() {684 // WP adds timezone. See #2036685 return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone;686 }687 function getXml() {688 return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>';689 }690 function getTimestamp() {691 return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year);692 }648 var $year; 649 var $month; 650 var $day; 651 var $hour; 652 var $minute; 653 var $second; 654 var $timezone; 655 function IXR_Date($time) { 656 // $time can be a PHP timestamp or an ISO one 657 if (is_numeric($time)) { 658 $this->parseTimestamp($time); 659 } else { 660 $this->parseIso($time); 661 } 662 } 663 function parseTimestamp($timestamp) { 664 $this->year = date('Y', $timestamp); 665 $this->month = date('m', $timestamp); 666 $this->day = date('d', $timestamp); 667 $this->hour = date('H', $timestamp); 668 $this->minute = date('i', $timestamp); 669 $this->second = date('s', $timestamp); 670 // WP adds timezone. See #2036 671 $this->timezone = ''; 672 } 673 function parseIso($iso) { 674 $this->year = substr($iso, 0, 4); 675 $this->month = substr($iso, 4, 2); 676 $this->day = substr($iso, 6, 2); 677 $this->hour = substr($iso, 9, 2); 678 $this->minute = substr($iso, 12, 2); 679 $this->second = substr($iso, 15, 2); 680 // WP adds timezone. See #2036 681 $this->timezone = substr($iso, 17); 682 } 683 function getIso() { 684 // WP adds timezone. See #2036 685 return $this->year.$this->month.$this->day.'T'.$this->hour.':'.$this->minute.':'.$this->second.$this->timezone; 686 } 687 function getXml() { 688 return '<dateTime.iso8601>'.$this->getIso().'</dateTime.iso8601>'; 689 } 690 function getTimestamp() { 691 return mktime($this->hour, $this->minute, $this->second, $this->month, $this->day, $this->year); 692 } 693 693 } 694 694 695 695 /** … … 699 699 * @since 1.5 700 700 */ 701 701 class IXR_Base64 { 702 var $data;703 function IXR_Base64($data) {704 $this->data = $data;705 }706 function getXml() {707 return '<base64>'.base64_encode($this->data).'</base64>';708 }702 var $data; 703 function IXR_Base64($data) { 704 $this->data = $data; 705 } 706 function getXml() { 707 return '<base64>'.base64_encode($this->data).'</base64>'; 708 } 709 709 } 710 710 711 711 /** … … 715 715 * @since 1.5 716 716 */ 717 717 class IXR_IntrospectionServer extends IXR_Server { 718 var $signatures;719 var $help;720 function IXR_IntrospectionServer() {721 $this->setCallbacks();722 $this->setCapabilities();723 $this->capabilities['introspection'] = array(724 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html',725 'specVersion' => 1726 );727 $this->addCallback(728 'system.methodSignature',729 'this:methodSignature',730 array('array', 'string'),731 'Returns an array describing the return type and required parameters of a method'732 );733 $this->addCallback(734 'system.getCapabilities',735 'this:getCapabilities',736 array('struct'),737 'Returns a struct describing the XML-RPC specifications supported by this server'738 );739 $this->addCallback(740 'system.listMethods',741 'this:listMethods',742 array('array'),743 'Returns an array of available methods on this server'744 );745 $this->addCallback(746 'system.methodHelp',747 'this:methodHelp',748 array('string', 'string'),749 'Returns a documentation string for the specified method'750 );751 }752 function addCallback($method, $callback, $args, $help) {753 $this->callbacks[$method] = $callback;754 $this->signatures[$method] = $args;755 $this->help[$method] = $help;756 }757 function call($methodname, $args) {758 // Make sure it's in an array759 if ($args && !is_array($args)) {760 $args = array($args);761 }762 // Over-rides default call method, adds signature check763 if (!$this->hasMethod($methodname)) {764 return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.');765 }766 $method = $this->callbacks[$methodname];767 $signature = $this->signatures[$methodname];768 $returnType = array_shift($signature);769 // Check the number of arguments770 if (count($args) != count($signature)) {771 return new IXR_Error(-32602, 'server error. wrong number of method parameters');772 }773 // Check the argument types774 $ok = true;775 $argsbackup = $args;776 for ($i = 0, $j = count($args); $i < $j; $i++) {777 $arg = array_shift($args);778 $type = array_shift($signature);779 switch ($type) {780 case 'int':781 case 'i4':782 if (is_array($arg) || !is_int($arg)) {783 $ok = false;784 }785 break;786 case 'base64':787 case 'string':788 if (!is_string($arg)) {789 $ok = false;790 }791 break;792 case 'boolean':793 if ($arg !== false && $arg !== true) {794 $ok = false;795 }796 break;797 case 'float':798 case 'double':799 if (!is_float($arg)) {800 $ok = false;801 }802 break;803 case 'date':804 case 'dateTime.iso8601':805 if (!is_a($arg, 'IXR_Date')) {806 $ok = false;807 }808 break;809 }810 if (!$ok) {811 return new IXR_Error(-32602, 'server error. invalid method parameters');812 }813 }814 // It passed the test - run the "real" method call815 return parent::call($methodname, $argsbackup);816 }817 function methodSignature($method) {818 if (!$this->hasMethod($method)) {819 return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.');820 }821 // We should be returning an array of types822 $types = $this->signatures[$method];823 $return = array();824 foreach ($types as $type) {825 switch ($type) {826 case 'string':827 $return[] = 'string';828 break;829 case 'int':830 case 'i4':831 $return[] = 42;832 break;833 case 'double':834 $return[] = 3.1415;835 break;836 case 'dateTime.iso8601':837 $return[] = new IXR_Date(time());838 break;839 case 'boolean':840 $return[] = true;841 break;842 case 'base64':843 $return[] = new IXR_Base64('base64');844 break;845 case 'array':846 $return[] = array('array');847 break;848 case 'struct':849 $return[] = array('struct' => 'struct');850 break;851 }852 }853 return $return;854 }855 function methodHelp($method) {856 return $this->help[$method];857 }718 var $signatures; 719 var $help; 720 function IXR_IntrospectionServer() { 721 $this->setCallbacks(); 722 $this->setCapabilities(); 723 $this->capabilities['introspection'] = array( 724 'specUrl' => 'http://xmlrpc.usefulinc.com/doc/reserved.html', 725 'specVersion' => 1 726 ); 727 $this->addCallback( 728 'system.methodSignature', 729 'this:methodSignature', 730 array('array', 'string'), 731 'Returns an array describing the return type and required parameters of a method' 732 ); 733 $this->addCallback( 734 'system.getCapabilities', 735 'this:getCapabilities', 736 array('struct'), 737 'Returns a struct describing the XML-RPC specifications supported by this server' 738 ); 739 $this->addCallback( 740 'system.listMethods', 741 'this:listMethods', 742 array('array'), 743 'Returns an array of available methods on this server' 744 ); 745 $this->addCallback( 746 'system.methodHelp', 747 'this:methodHelp', 748 array('string', 'string'), 749 'Returns a documentation string for the specified method' 750 ); 751 } 752 function addCallback($method, $callback, $args, $help) { 753 $this->callbacks[$method] = $callback; 754 $this->signatures[$method] = $args; 755 $this->help[$method] = $help; 756 } 757 function call($methodname, $args) { 758 // Make sure it's in an array 759 if ($args && !is_array($args)) { 760 $args = array($args); 761 } 762 // Over-rides default call method, adds signature check 763 if (!$this->hasMethod($methodname)) { 764 return new IXR_Error(-32601, 'server error. requested method "'.$this->message->methodName.'" not specified.'); 765 } 766 $method = $this->callbacks[$methodname]; 767 $signature = $this->signatures[$methodname]; 768 $returnType = array_shift($signature); 769 // Check the number of arguments 770 if (count($args) != count($signature)) { 771 return new IXR_Error(-32602, 'server error. wrong number of method parameters'); 772 } 773 // Check the argument types 774 $ok = true; 775 $argsbackup = $args; 776 for ($i = 0, $j = count($args); $i < $j; $i++) { 777 $arg = array_shift($args); 778 $type = array_shift($signature); 779 switch ($type) { 780 case 'int': 781 case 'i4': 782 if (is_array($arg) || !is_int($arg)) { 783 $ok = false; 784 } 785 break; 786 case 'base64': 787 case 'string': 788 if (!is_string($arg)) { 789 $ok = false; 790 } 791 break; 792 case 'boolean': 793 if ($arg !== false && $arg !== true) { 794 $ok = false; 795 } 796 break; 797 case 'float': 798 case 'double': 799 if (!is_float($arg)) { 800 $ok = false; 801 } 802 break; 803 case 'date': 804 case 'dateTime.iso8601': 805 if (!is_a($arg, 'IXR_Date')) { 806 $ok = false; 807 } 808 break; 809 } 810 if (!$ok) { 811 return new IXR_Error(-32602, 'server error. invalid method parameters'); 812 } 813 } 814 // It passed the test - run the "real" method call 815 return parent::call($methodname, $argsbackup); 816 } 817 function methodSignature($method) { 818 if (!$this->hasMethod($method)) { 819 return new IXR_Error(-32601, 'server error. requested method "'.$method.'" not specified.'); 820 } 821 // We should be returning an array of types 822 $types = $this->signatures[$method]; 823 $return = array(); 824 foreach ($types as $type) { 825 switch ($type) { 826 case 'string': 827 $return[] = 'string'; 828 break; 829 case 'int': 830 case 'i4': 831 $return[] = 42; 832 break; 833 case 'double': 834 $return[] = 3.1415; 835 break; 836 case 'dateTime.iso8601': 837 $return[] = new IXR_Date(time()); 838 break; 839 case 'boolean': 840 $return[] = true; 841 break; 842 case 'base64': 843 $return[] = new IXR_Base64('base64'); 844 break; 845 case 'array': 846 $return[] = array('array'); 847 break; 848 case 'struct': 849 $return[] = array('struct' => 'struct'); 850 break; 851 } 852 } 853 return $return; 854 } 855 function methodHelp($method) { 856 return $this->help[$method]; 857 } 858 858 } 859 859 860 860 /** … … 864 864 * @since 1.5 865 865 */ 866 866 class IXR_ClientMulticall extends IXR_Client { 867 var $calls = array();868 function IXR_ClientMulticall($server, $path = false, $port = 80) {869 parent::IXR_Client($server, $path, $port);870 $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)';871 }872 function addCall() {873 $args = func_get_args();874 $methodName = array_shift($args);875 $struct = array(876 'methodName' => $methodName,877 'params' => $args878 );879 $this->calls[] = $struct;880 }881 function query() {882 // Prepare multicall, then call the parent::query() method883 return parent::query('system.multicall', $this->calls);884 }867 var $calls = array(); 868 function IXR_ClientMulticall($server, $path = false, $port = 80) { 869 parent::IXR_Client($server, $path, $port); 870 $this->useragent = 'The Incutio XML-RPC PHP Library (multicall client)'; 871 } 872 function addCall() { 873 $args = func_get_args(); 874 $methodName = array_shift($args); 875 $struct = array( 876 'methodName' => $methodName, 877 'params' => $args 878 ); 879 $this->calls[] = $struct; 880 } 881 function query() { 882 // Prepare multicall, then call the parent::query() method 883 return parent::query('system.multicall', $this->calls); 884 } 885 885 } 886 886 887 887 ?> -
class-phpass.php
65 65 $output = ''; 66 66 for ($i = 0; $i < $count; $i += 16) { 67 67 $this->random_state = 68 md5(microtime() . $this->random_state);68 md5(microtime() . $this->random_state); 69 69 $output .= 70 pack('H*', md5($this->random_state));70 pack('H*', md5($this->random_state)); 71 71 } 72 72 $output = substr($output, 0, $count); 73 73 } … … 217 217 if (CRYPT_BLOWFISH == 1 && !$this->portable_hashes) { 218 218 $random = $this->get_random_bytes(16); 219 219 $hash = 220 crypt($password, $this->gensalt_blowfish($random));220 crypt($password, $this->gensalt_blowfish($random)); 221 221 if (strlen($hash) == 60) 222 222 return $hash; 223 223 } … … 226 226 if (strlen($random) < 3) 227 227 $random = $this->get_random_bytes(3); 228 228 $hash = 229 crypt($password, $this->gensalt_extended($random));229 crypt($password, $this->gensalt_extended($random)); 230 230 if (strlen($hash) == 20) 231 231 return $hash; 232 232 } … … 234 234 if (strlen($random) < 6) 235 235 $random = $this->get_random_bytes(6); 236 236 $hash = 237 $this->crypt_private($password,238 $this->gensalt_private($random));237 $this->crypt_private($password, 238 $this->gensalt_private($random)); 239 239 if (strlen($hash) == 34) 240 240 return $hash; 241 241 -
class-snoopy.php
213 213 if(!$this->curl_path) 214 214 return false; 215 215 if(function_exists("is_executable")) 216 if (!is_executable($this->curl_path))217 return false;216 if (!is_executable($this->curl_path)) 217 return false; 218 218 $this->host = $URI_PARTS["host"]; 219 219 if(!empty($URI_PARTS["port"])) 220 220 $this->port = $URI_PARTS["port"]; … … 372 372 if(!$this->curl_path) 373 373 return false; 374 374 if(function_exists("is_executable")) 375 if (!is_executable($this->curl_path))376 return false;375 if (!is_executable($this->curl_path)) 376 return false; 377 377 $this->host = $URI_PARTS["host"]; 378 378 if(!empty($URI_PARTS["port"])) 379 379 $this->port = $URI_PARTS["port"]; … … 893 893 894 894 if(preg_match("|^HTTP/|",$currentHeader)) 895 895 { 896 if(preg_match("|^HTTP/[^\s]*\s(.*?)\s|",$currentHeader, $status))896 if(preg_match("|^HTTP/[^\s]*\s(.*?)\s|",$currentHeader, $status)) 897 897 { 898 898 $this->status= $status[1]; 899 }899 } 900 900 $this->response_code = $currentHeader; 901 901 } 902 902 … … 905 905 906 906 $results = ''; 907 907 do { 908 $_data = fread($fp, $this->maxlength);909 if (strlen($_data) == 0) {910 break;911 }912 $results .= $_data;908 $_data = fread($fp, $this->maxlength); 909 if (strlen($_data) == 0) { 910 break; 911 } 912 $results .= $_data; 913 913 } while(true); 914 914 915 915 if ($this->read_timeout > 0 && $this->_check_timeout($fp)) -
streams.php
6 6 * @subpackage PHP-gettext 7 7 * 8 8 * @internal 9 Copyright (c) 2003, 2005 Danilo Segan <danilo@kvota.net>.9 Copyright (c) 2003, 2005 Danilo Segan <danilo@kvota.net>. 10 10 11 This file is part of PHP-gettext.11 This file is part of PHP-gettext. 12 12 13 PHP-gettext is free software; you can redistribute it and/or modify14 it under the terms of the GNU General Public License as published by15 the Free Software Foundation; either version 2 of the License, or16 (at your option) any later version.13 PHP-gettext is free software; you can redistribute it and/or modify 14 it under the terms of the GNU General Public License as published by 15 the Free Software Foundation; either version 2 of the License, or 16 (at your option) any later version. 17 17 18 PHP-gettext is distributed in the hope that it will be useful,19 but WITHOUT ANY WARRANTY; without even the implied warranty of20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.See the21 GNU General Public License for more details.18 PHP-gettext is distributed in the hope that it will be useful, 19 but WITHOUT ANY WARRANTY; without even the implied warranty of 20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 21 GNU General Public License for more details. 22 22 23 You should have received a copy of the GNU General Public License24 along with PHP-gettext; if not, write to the Free Software25 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307USA23 You should have received a copy of the GNU General Public License 24 along with PHP-gettext; if not, write to the Free Software 25 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 26 26 27 27 */ 28 28 … … 30 30 // Simple class to wrap file streams, string streams, etc. 31 31 // seek is essential, and it should be byte stream 32 32 class StreamReader { 33 // should return a string [FIXME: perhaps return array of bytes?]34 function read($bytes) {35 return false;36 }33 // should return a string [FIXME: perhaps return array of bytes?] 34 function read($bytes) { 35 return false; 36 } 37 37 38 // should return new position39 function seekto($position) {40 return false;41 }38 // should return new position 39 function seekto($position) { 40 return false; 41 } 42 42 43 // returns current position44 function currentpos() {45 return false;46 }43 // returns current position 44 function currentpos() { 45 return false; 46 } 47 47 48 // returns length of entire stream (limit for seekto()s)49 function length() {50 return false;51 }48 // returns length of entire stream (limit for seekto()s) 49 function length() { 50 return false; 51 } 52 52 } 53 53 54 54 class StringReader { 55 var $_pos;56 var $_str;55 var $_pos; 56 var $_str; 57 57 58 function StringReader($str='') {59 $this->_str = $str;60 $this->_pos = 0;61 // If string functions are overloaded, we need to use the mb versions62 $this->is_overloaded = ((ini_get("mbstring.func_overload") & 2) != 0) && function_exists('mb_substr');63 }58 function StringReader($str='') { 59 $this->_str = $str; 60 $this->_pos = 0; 61 // If string functions are overloaded, we need to use the mb versions 62 $this->is_overloaded = ((ini_get("mbstring.func_overload") & 2) != 0) && function_exists('mb_substr'); 63 } 64 64 65 function _substr($string, $start, $length) {65 function _substr($string, $start, $length) { 66 66 if ($this->is_overloaded) { 67 67 return mb_substr($string,$start,$length,'ascii'); 68 68 } else { 69 69 return substr($string,$start,$length); 70 70 } 71 }71 } 72 72 73 function _strlen($string) {73 function _strlen($string) { 74 74 if ($this->is_overloaded) { 75 75 return mb_strlen($string,'ascii'); 76 76 } else { 77 77 return strlen($string); 78 78 } 79 }79 } 80 80 81 function read($bytes) {82 $data = $this->_substr($this->_str, $this->_pos, $bytes);83 $this->_pos += $bytes;84 if ($this->_strlen($this->_str)<$this->_pos)85 $this->_pos = $this->_strlen($this->_str);81 function read($bytes) { 82 $data = $this->_substr($this->_str, $this->_pos, $bytes); 83 $this->_pos += $bytes; 84 if ($this->_strlen($this->_str)<$this->_pos) 85 $this->_pos = $this->_strlen($this->_str); 86 86 87 return $data;88 }87 return $data; 88 } 89 89 90 function seekto($pos) {91 $this->_pos = $pos;92 if ($this->_strlen($this->_str)<$this->_pos)93 $this->_pos = $this->_strlen($this->_str);94 return $this->_pos;95 }90 function seekto($pos) { 91 $this->_pos = $pos; 92 if ($this->_strlen($this->_str)<$this->_pos) 93 $this->_pos = $this->_strlen($this->_str); 94 return $this->_pos; 95 } 96 96 97 function currentpos() {98 return $this->_pos;99 }97 function currentpos() { 98 return $this->_pos; 99 } 100 100 101 function length() {102 return $this->_strlen($this->_str);103 }101 function length() { 102 return $this->_strlen($this->_str); 103 } 104 104 } 105 105 106 106 107 107 class FileReader { 108 var $_pos;109 var $_fd;110 var $_length;108 var $_pos; 109 var $_fd; 110 var $_length; 111 111 112 function FileReader($filename) {113 if (file_exists($filename)) {112 function FileReader($filename) { 113 if (file_exists($filename)) { 114 114 115 $this->_length=filesize($filename);116 $this->_pos = 0;117 $this->_fd = fopen($filename,'rb');118 if (!$this->_fd) {115 $this->_length=filesize($filename); 116 $this->_pos = 0; 117 $this->_fd = fopen($filename,'rb'); 118 if (!$this->_fd) { 119 119 $this->error = 3; // Cannot read file, probably permissions 120 120 return false; 121 }122 } else {123 $this->error = 2; // File doesn't exist124 return false;125 }126 }121 } 122 } else { 123 $this->error = 2; // File doesn't exist 124 return false; 125 } 126 } 127 127 128 function read($bytes) {129 if ($bytes) {130 fseek($this->_fd, $this->_pos);128 function read($bytes) { 129 if ($bytes) { 130 fseek($this->_fd, $this->_pos); 131 131 132 // PHP 5.1.1 does not read more than 8192 bytes in one fread()133 // the discussions at PHP Bugs suggest it's the intended behaviour134 while ($bytes > 0) {135 $chunk= fread($this->_fd, $bytes);136 $data.= $chunk;137 $bytes -= strlen($chunk);138 }139 $this->_pos = ftell($this->_fd);132 // PHP 5.1.1 does not read more than 8192 bytes in one fread() 133 // the discussions at PHP Bugs suggest it's the intended behaviour 134 while ($bytes > 0) { 135 $chunk = fread($this->_fd, $bytes); 136 $data .= $chunk; 137 $bytes -= strlen($chunk); 138 } 139 $this->_pos = ftell($this->_fd); 140 140 141 return $data;142 } else return '';143 }141 return $data; 142 } else return ''; 143 } 144 144 145 function seekto($pos) {146 fseek($this->_fd, $pos);147 $this->_pos = ftell($this->_fd);148 return $this->_pos;149 }145 function seekto($pos) { 146 fseek($this->_fd, $pos); 147 $this->_pos = ftell($this->_fd); 148 return $this->_pos; 149 } 150 150 151 function currentpos() {152 return $this->_pos;153 }151 function currentpos() { 152 return $this->_pos; 153 } 154 154 155 function length() {156 return $this->_length;157 }155 function length() { 156 return $this->_length; 157 } 158 158 159 function close() {160 fclose($this->_fd);161 }159 function close() { 160 fclose($this->_fd); 161 } 162 162 163 163 } 164 164 165 165 // Preloads entire file in memory first, then creates a StringReader 166 166 // over it (it assumes knowledge of StringReader internals) 167 167 class CachedFileReader extends StringReader { 168 function CachedFileReader($filename) {169 parent::StringReader();168 function CachedFileReader($filename) { 169 parent::StringReader(); 170 170 171 if (file_exists($filename)) {171 if (file_exists($filename)) { 172 172 173 $length=filesize($filename);174 $fd = fopen($filename,'rb');173 $length=filesize($filename); 174 $fd = fopen($filename,'rb'); 175 175 176 if (!$fd) {177 $this->error = 3; // Cannot read file, probably permissions178 return false;179 }180 $this->_str = fread($fd, $length);181 fclose($fd);176 if (!$fd) { 177 $this->error = 3; // Cannot read file, probably permissions 178 return false; 179 } 180 $this->_str = fread($fd, $length); 181 fclose($fd); 182 182 183 } else {184 $this->error = 2; // File doesn't exist185 return false;186 }187 }183 } else { 184 $this->error = 2; // File doesn't exist 185 return false; 186 } 187 } 188 188 } 189 189 190 190 -
Text/Diff.php
19 19 */ 20 20 class Text_Diff { 21 21 22 /**23 * Array of changes.24 *25 * @var array26 */27 var $_edits;22 /** 23 * Array of changes. 24 * 25 * @var array 26 */ 27 var $_edits; 28 28 29 /**30 * Computes diffs between sequences of strings.31 *32 * @param string $engineName of the diffing engine to use. 'auto'33 *will automatically select the best.34 * @param array $paramsParameters to pass to the diffing engine.35 *Normally an array of two arrays, each36 *containing the lines from a file.37 */38 function Text_Diff($engine, $params)39 {40 // Backward compatibility workaround.41 if (!is_string($engine)) {42 $params = array($engine, $params);43 $engine = 'auto';44 }29 /** 30 * Computes diffs between sequences of strings. 31 * 32 * @param string $engine Name of the diffing engine to use. 'auto' 33 * will automatically select the best. 34 * @param array $params Parameters to pass to the diffing engine. 35 * Normally an array of two arrays, each 36 * containing the lines from a file. 37 */ 38 function Text_Diff($engine, $params) 39 { 40 // Backward compatibility workaround. 41 if (!is_string($engine)) { 42 $params = array($engine, $params); 43 $engine = 'auto'; 44 } 45 45 46 if ($engine == 'auto') {47 $engine = extension_loaded('xdiff') ? 'xdiff' : 'native';48 } else {49 $engine = basename($engine);50 }46 if ($engine == 'auto') { 47 $engine = extension_loaded('xdiff') ? 'xdiff' : 'native'; 48 } else { 49 $engine = basename($engine); 50 } 51 51 52 // WP #739153 require_once dirname(__FILE__).'/Diff/Engine/' . $engine . '.php';54 $class = 'Text_Diff_Engine_' . $engine;55 $diff_engine = new $class();52 // WP #7391 53 require_once dirname(__FILE__).'/Diff/Engine/' . $engine . '.php'; 54 $class = 'Text_Diff_Engine_' . $engine; 55 $diff_engine = new $class(); 56 56 57 $this->_edits = call_user_func_array(array($diff_engine, 'diff'), $params);58 }57 $this->_edits = call_user_func_array(array($diff_engine, 'diff'), $params); 58 } 59 59 60 /**61 * Returns the array of differences.62 */63 function getDiff()64 {65 return $this->_edits;66 }60 /** 61 * Returns the array of differences. 62 */ 63 function getDiff() 64 { 65 return $this->_edits; 66 } 67 67 68 /**69 * Computes a reversed diff.70 *71 * Example:72 * <code>73 * $diff = new Text_Diff($lines1, $lines2);74 * $rev = $diff->reverse();75 * </code>76 *77 * @return Text_Diff A Diff object representing the inverse of the78 *original diff. Note that we purposely don't return a79 *reference here, since this essentially is a clone()80 *method.81 */82 function reverse()83 {84 if (version_compare(zend_version(), '2', '>')) {85 $rev = clone($this);86 } else {87 $rev = $this;88 }89 $rev->_edits = array();90 foreach ($this->_edits as $edit) {91 $rev->_edits[] = $edit->reverse();92 }93 return $rev;94 }68 /** 69 * Computes a reversed diff. 70 * 71 * Example: 72 * <code> 73 * $diff = new Text_Diff($lines1, $lines2); 74 * $rev = $diff->reverse(); 75 * </code> 76 * 77 * @return Text_Diff A Diff object representing the inverse of the 78 * original diff. Note that we purposely don't return a 79 * reference here, since this essentially is a clone() 80 * method. 81 */ 82 function reverse() 83 { 84 if (version_compare(zend_version(), '2', '>')) { 85 $rev = clone($this); 86 } else { 87 $rev = $this; 88 } 89 $rev->_edits = array(); 90 foreach ($this->_edits as $edit) { 91 $rev->_edits[] = $edit->reverse(); 92 } 93 return $rev; 94 } 95 95 96 /**97 * Checks for an empty diff.98 *99 * @return boolean True if two sequences were identical.100 */101 function isEmpty()102 {103 foreach ($this->_edits as $edit) {104 if (!is_a($edit, 'Text_Diff_Op_copy')) {105 return false;106 }107 }108 return true;109 }96 /** 97 * Checks for an empty diff. 98 * 99 * @return boolean True if two sequences were identical. 100 */ 101 function isEmpty() 102 { 103 foreach ($this->_edits as $edit) { 104 if (!is_a($edit, 'Text_Diff_Op_copy')) { 105 return false; 106 } 107 } 108 return true; 109 } 110 110 111 /**112 * Computes the length of the Longest Common Subsequence (LCS).113 *114 * This is mostly for diagnostic purposes.115 *116 * @return integer The length of the LCS.117 */118 function lcs()119 {120 $lcs = 0;121 foreach ($this->_edits as $edit) {122 if (is_a($edit, 'Text_Diff_Op_copy')) {123 $lcs += count($edit->orig);124 }125 }126 return $lcs;127 }111 /** 112 * Computes the length of the Longest Common Subsequence (LCS). 113 * 114 * This is mostly for diagnostic purposes. 115 * 116 * @return integer The length of the LCS. 117 */ 118 function lcs() 119 { 120 $lcs = 0; 121 foreach ($this->_edits as $edit) { 122 if (is_a($edit, 'Text_Diff_Op_copy')) { 123 $lcs += count($edit->orig); 124 } 125 } 126 return $lcs; 127 } 128 128 129 /**130 * Gets the original set of lines.131 *132 * This reconstructs the $from_lines parameter passed to the constructor.133 *134 * @return array The original sequence of strings.135 */136 function getOriginal()137 {138 $lines = array();139 foreach ($this->_edits as $edit) {140 if ($edit->orig) {141 array_splice($lines, count($lines), 0, $edit->orig);142 }143 }144 return $lines;145 }129 /** 130 * Gets the original set of lines. 131 * 132 * This reconstructs the $from_lines parameter passed to the constructor. 133 * 134 * @return array The original sequence of strings. 135 */ 136 function getOriginal() 137 { 138 $lines = array(); 139 foreach ($this->_edits as $edit) { 140 if ($edit->orig) { 141 array_splice($lines, count($lines), 0, $edit->orig); 142 } 143 } 144 return $lines; 145 } 146 146 147 /**148 * Gets the final set of lines.149 *150 * This reconstructs the $to_lines parameter passed to the constructor.151 *152 * @return array The sequence of strings.153 */154 function getFinal()155 {156 $lines = array();157 foreach ($this->_edits as $edit) {158 if ($edit->final) {159 array_splice($lines, count($lines), 0, $edit->final);160 }161 }162 return $lines;163 }147 /** 148 * Gets the final set of lines. 149 * 150 * This reconstructs the $to_lines parameter passed to the constructor. 151 * 152 * @return array The sequence of strings. 153 */ 154 function getFinal() 155 { 156 $lines = array(); 157 foreach ($this->_edits as $edit) { 158 if ($edit->final) { 159 array_splice($lines, count($lines), 0, $edit->final); 160 } 161 } 162 return $lines; 163 } 164 164 165 /**166 * Removes trailing newlines from a line of text. This is meant to be used167 * with array_walk().168 *169 * @param string $line The line to trim.170 * @param integer $key The index of the line in the array. Not used.171 */172 function trimNewlines(&$line, $key)173 {174 $line = str_replace(array("\n", "\r"), '', $line);175 }165 /** 166 * Removes trailing newlines from a line of text. This is meant to be used 167 * with array_walk(). 168 * 169 * @param string $line The line to trim. 170 * @param integer $key The index of the line in the array. Not used. 171 */ 172 function trimNewlines(&$line, $key) 173 { 174 $line = str_replace(array("\n", "\r"), '', $line); 175 } 176 176 177 /**178 * Determines the location of the system temporary directory.179 *180 * @static181 *182 * @access protected183 *184 * @return string A directory name which can be used for temp files.185 *Returns false if one could not be found.186 */187 function _getTempDir()188 {189 $tmp_locations = array('/tmp', '/var/tmp', 'c:\WUTemp', 'c:\temp',190 'c:\windows\temp', 'c:\winnt\temp');177 /** 178 * Determines the location of the system temporary directory. 179 * 180 * @static 181 * 182 * @access protected 183 * 184 * @return string A directory name which can be used for temp files. 185 * Returns false if one could not be found. 186 */ 187 function _getTempDir() 188 { 189 $tmp_locations = array('/tmp', '/var/tmp', 'c:\WUTemp', 'c:\temp', 190 'c:\windows\temp', 'c:\winnt\temp'); 191 191 192 /* Try PHP's upload_tmp_dir directive. */193 $tmp = ini_get('upload_tmp_dir');192 /* Try PHP's upload_tmp_dir directive. */ 193 $tmp = ini_get('upload_tmp_dir'); 194 194 195 /* Otherwise, try to determine the TMPDIR environment variable. */196 if (!strlen($tmp)) {197 $tmp = getenv('TMPDIR');198 }195 /* Otherwise, try to determine the TMPDIR environment variable. */ 196 if (!strlen($tmp)) { 197 $tmp = getenv('TMPDIR'); 198 } 199 199 200 /* If we still cannot determine a value, then cycle through a list of201 * preset possibilities. */202 while (!strlen($tmp) && count($tmp_locations)) {203 $tmp_check = array_shift($tmp_locations);204 if (@is_dir($tmp_check)) {205 $tmp = $tmp_check;206 }207 }200 /* If we still cannot determine a value, then cycle through a list of 201 * preset possibilities. */ 202 while (!strlen($tmp) && count($tmp_locations)) { 203 $tmp_check = array_shift($tmp_locations); 204 if (@is_dir($tmp_check)) { 205 $tmp = $tmp_check; 206 } 207 } 208 208 209 /* If it is still empty, we have failed, so return false; otherwise210 * return the directory determined. */211 return strlen($tmp) ? $tmp : false;212 }209 /* If it is still empty, we have failed, so return false; otherwise 210 * return the directory determined. */ 211 return strlen($tmp) ? $tmp : false; 212 } 213 213 214 /**215 * Checks a diff for validity.216 *217 * This is here only for debugging purposes.218 */219 function _check($from_lines, $to_lines)220 {221 if (serialize($from_lines) != serialize($this->getOriginal())) {222 trigger_error("Reconstructed original doesn't match", E_USER_ERROR);223 }224 if (serialize($to_lines) != serialize($this->getFinal())) {225 trigger_error("Reconstructed final doesn't match", E_USER_ERROR);226 }214 /** 215 * Checks a diff for validity. 216 * 217 * This is here only for debugging purposes. 218 */ 219 function _check($from_lines, $to_lines) 220 { 221 if (serialize($from_lines) != serialize($this->getOriginal())) { 222 trigger_error("Reconstructed original doesn't match", E_USER_ERROR); 223 } 224 if (serialize($to_lines) != serialize($this->getFinal())) { 225 trigger_error("Reconstructed final doesn't match", E_USER_ERROR); 226 } 227 227 228 $rev = $this->reverse();229 if (serialize($to_lines) != serialize($rev->getOriginal())) {230 trigger_error("Reversed original doesn't match", E_USER_ERROR);231 }232 if (serialize($from_lines) != serialize($rev->getFinal())) {233 trigger_error("Reversed final doesn't match", E_USER_ERROR);234 }228 $rev = $this->reverse(); 229 if (serialize($to_lines) != serialize($rev->getOriginal())) { 230 trigger_error("Reversed original doesn't match", E_USER_ERROR); 231 } 232 if (serialize($from_lines) != serialize($rev->getFinal())) { 233 trigger_error("Reversed final doesn't match", E_USER_ERROR); 234 } 235 235 236 $prevtype = null;237 foreach ($this->_edits as $edit) {238 if ($prevtype == get_class($edit)) {239 trigger_error("Edit sequence is non-optimal", E_USER_ERROR);240 }241 $prevtype = get_class($edit);242 }236 $prevtype = null; 237 foreach ($this->_edits as $edit) { 238 if ($prevtype == get_class($edit)) { 239 trigger_error("Edit sequence is non-optimal", E_USER_ERROR); 240 } 241 $prevtype = get_class($edit); 242 } 243 243 244 return true;245 }244 return true; 245 } 246 246 247 247 } 248 248 … … 252 252 */ 253 253 class Text_MappedDiff extends Text_Diff { 254 254 255 /**256 * Computes a diff between sequences of strings.257 *258 * This can be used to compute things like case-insensitve diffs, or diffs259 * which ignore changes in white-space.260 *261 * @param array $from_linesAn array of strings.262 * @param array $to_linesAn array of strings.263 * @param array $mapped_from_lines This array should have the same size264 *number of elements as $from_lines. The265 *elements in $mapped_from_lines and266 *$mapped_to_lines are what is actually267 *compared when computing the diff.268 * @param array $mapped_to_linesThis array should have the same number269 *of elements as $to_lines.270 */271 function Text_MappedDiff($from_lines, $to_lines,272 $mapped_from_lines, $mapped_to_lines)273 {274 assert(count($from_lines) == count($mapped_from_lines));275 assert(count($to_lines) == count($mapped_to_lines));255 /** 256 * Computes a diff between sequences of strings. 257 * 258 * This can be used to compute things like case-insensitve diffs, or diffs 259 * which ignore changes in white-space. 260 * 261 * @param array $from_lines An array of strings. 262 * @param array $to_lines An array of strings. 263 * @param array $mapped_from_lines This array should have the same size 264 * number of elements as $from_lines. The 265 * elements in $mapped_from_lines and 266 * $mapped_to_lines are what is actually 267 * compared when computing the diff. 268 * @param array $mapped_to_lines This array should have the same number 269 * of elements as $to_lines. 270 */ 271 function Text_MappedDiff($from_lines, $to_lines, 272 $mapped_from_lines, $mapped_to_lines) 273 { 274 assert(count($from_lines) == count($mapped_from_lines)); 275 assert(count($to_lines) == count($mapped_to_lines)); 276 276 277 parent::Text_Diff($mapped_from_lines, $mapped_to_lines);277 parent::Text_Diff($mapped_from_lines, $mapped_to_lines); 278 278 279 $xi = $yi = 0;280 for ($i = 0; $i < count($this->_edits); $i++) {281 $orig = &$this->_edits[$i]->orig;282 if (is_array($orig)) {283 $orig = array_slice($from_lines, $xi, count($orig));284 $xi += count($orig);285 }279 $xi = $yi = 0; 280 for ($i = 0; $i < count($this->_edits); $i++) { 281 $orig = &$this->_edits[$i]->orig; 282 if (is_array($orig)) { 283 $orig = array_slice($from_lines, $xi, count($orig)); 284 $xi += count($orig); 285 } 286 286 287 $final = &$this->_edits[$i]->final;288 if (is_array($final)) {289 $final = array_slice($to_lines, $yi, count($final));290 $yi += count($final);291 }292 }293 }287 $final = &$this->_edits[$i]->final; 288 if (is_array($final)) { 289 $final = array_slice($to_lines, $yi, count($final)); 290 $yi += count($final); 291 } 292 } 293 } 294 294 295 295 } 296 296 … … 302 302 */ 303 303 class Text_Diff_Op { 304 304 305 var $orig;306 var $final;305 var $orig; 306 var $final; 307 307 308 function &reverse()309 {310 trigger_error('Abstract method', E_USER_ERROR);311 }308 function &reverse() 309 { 310 trigger_error('Abstract method', E_USER_ERROR); 311 } 312 312 313 function norig()314 {315 return $this->orig ? count($this->orig) : 0;316 }313 function norig() 314 { 315 return $this->orig ? count($this->orig) : 0; 316 } 317 317 318 function nfinal()319 {320 return $this->final ? count($this->final) : 0;321 }318 function nfinal() 319 { 320 return $this->final ? count($this->final) : 0; 321 } 322 322 323 323 } 324 324 … … 330 330 */ 331 331 class Text_Diff_Op_copy extends Text_Diff_Op { 332 332 333 function Text_Diff_Op_copy($orig, $final = false)334 {335 if (!is_array($final)) {336 $final = $orig;337 }338 $this->orig = $orig;339 $this->final = $final;340 }333 function Text_Diff_Op_copy($orig, $final = false) 334 { 335 if (!is_array($final)) { 336 $final = $orig; 337 } 338 $this->orig = $orig; 339 $this->final = $final; 340 } 341 341 342 function &reverse()343 {344 $reverse = &new Text_Diff_Op_copy($this->final, $this->orig);345 return $reverse;346 }342 function &reverse() 343 { 344 $reverse = &new Text_Diff_Op_copy($this->final, $this->orig); 345 return $reverse; 346 } 347 347 348 348 } 349 349 … … 355 355 */ 356 356 class Text_Diff_Op_delete extends Text_Diff_Op { 357 357 358 function Text_Diff_Op_delete($lines)359 {360 $this->orig = $lines;361 $this->final = false;362 }358 function Text_Diff_Op_delete($lines) 359 { 360 $this->orig = $lines; 361 $this->final = false; 362 } 363 363 364 function &reverse()365 {366 $reverse = &new Text_Diff_Op_add($this->orig);367 return $reverse;368 }364 function &reverse() 365 { 366 $reverse = &new Text_Diff_Op_add($this->orig); 367 return $reverse; 368 } 369 369 370 370 } 371 371 … … 377 377 */ 378 378 class Text_Diff_Op_add extends Text_Diff_Op { 379 379 380 function Text_Diff_Op_add($lines)381 {382 $this->final = $lines;383 $this->orig = false;384 }380 function Text_Diff_Op_add($lines) 381 { 382 $this->final = $lines; 383 $this->orig = false; 384 } 385 385 386 function &reverse()387 {388 $reverse = &new Text_Diff_Op_delete($this->final);389 return $reverse;390 }386 function &reverse() 387 { 388 $reverse = &new Text_Diff_Op_delete($this->final); 389 return $reverse; 390 } 391 391 392 392 } 393 393 … … 399 399 */ 400 400 class Text_Diff_Op_change extends Text_Diff_Op { 401 401 402 function Text_Diff_Op_change($orig, $final)403 {404 $this->orig = $orig;405 $this->final = $final;406 }402 function Text_Diff_Op_change($orig, $final) 403 { 404 $this->orig = $orig; 405 $this->final = $final; 406 } 407 407 408 function &reverse()409 {410 $reverse = &new Text_Diff_Op_change($this->final, $this->orig);411 return $reverse;412 }408 function &reverse() 409 { 410 $reverse = &new Text_Diff_Op_change($this->final, $this->orig); 411 return $reverse; 412 } 413 413 414 414 } -
Text/Diff/Engine/native.php
29 29 */ 30 30 class Text_Diff_Engine_native { 31 31 32 function diff($from_lines, $to_lines)33 {34 array_walk($from_lines, array('Text_Diff', 'trimNewlines'));35 array_walk($to_lines, array('Text_Diff', 'trimNewlines'));32 function diff($from_lines, $to_lines) 33 { 34 array_walk($from_lines, array('Text_Diff', 'trimNewlines')); 35 array_walk($to_lines, array('Text_Diff', 'trimNewlines')); 36 36 37 $n_from = count($from_lines);38 $n_to = count($to_lines);37 $n_from = count($from_lines); 38 $n_to = count($to_lines); 39 39 40 $this->xchanged = $this->ychanged = array();41 $this->xv = $this->yv = array();42 $this->xind = $this->yind = array();43 unset($this->seq);44 unset($this->in_seq);45 unset($this->lcs);40 $this->xchanged = $this->ychanged = array(); 41 $this->xv = $this->yv = array(); 42 $this->xind = $this->yind = array(); 43 unset($this->seq); 44 unset($this->in_seq); 45 unset($this->lcs); 46 46 47 // Skip leading common lines.48 for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) {49 if ($from_lines[$skip] !== $to_lines[$skip]) {50 break;51 }52 $this->xchanged[$skip] = $this->ychanged[$skip] = false;53 }47 // Skip leading common lines. 48 for ($skip = 0; $skip < $n_from && $skip < $n_to; $skip++) { 49 if ($from_lines[$skip] !== $to_lines[$skip]) { 50 break; 51 } 52 $this->xchanged[$skip] = $this->ychanged[$skip] = false; 53 } 54 54 55 // Skip trailing common lines.56 $xi = $n_from; $yi = $n_to;57 for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) {58 if ($from_lines[$xi] !== $to_lines[$yi]) {59 break;60 }61 $this->xchanged[$xi] = $this->ychanged[$yi] = false;62 }55 // Skip trailing common lines. 56 $xi = $n_from; $yi = $n_to; 57 for ($endskip = 0; --$xi > $skip && --$yi > $skip; $endskip++) { 58 if ($from_lines[$xi] !== $to_lines[$yi]) { 59 break; 60 } 61 $this->xchanged[$xi] = $this->ychanged[$yi] = false; 62 } 63 63 64 // Ignore lines which do not exist in both files.65 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {66 $xhash[$from_lines[$xi]] = 1;67 }68 for ($yi = $skip; $yi < $n_to - $endskip; $yi++) {69 $line = $to_lines[$yi];70 if (($this->ychanged[$yi] = empty($xhash[$line]))) {71 continue;72 }73 $yhash[$line] = 1;74 $this->yv[] = $line;75 $this->yind[] = $yi;76 }77 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) {78 $line = $from_lines[$xi];79 if (($this->xchanged[$xi] = empty($yhash[$line]))) {80 continue;81 }82 $this->xv[] = $line;83 $this->xind[] = $xi;84 }64 // Ignore lines which do not exist in both files. 65 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { 66 $xhash[$from_lines[$xi]] = 1; 67 } 68 for ($yi = $skip; $yi < $n_to - $endskip; $yi++) { 69 $line = $to_lines[$yi]; 70 if (($this->ychanged[$yi] = empty($xhash[$line]))) { 71 continue; 72 } 73 $yhash[$line] = 1; 74 $this->yv[] = $line; 75 $this->yind[] = $yi; 76 } 77 for ($xi = $skip; $xi < $n_from - $endskip; $xi++) { 78 $line = $from_lines[$xi]; 79 if (($this->xchanged[$xi] = empty($yhash[$line]))) { 80 continue; 81 } 82 $this->xv[] = $line; 83 $this->xind[] = $xi; 84 } 85 85 86 // Find the LCS.87 $this->_compareseq(0, count($this->xv), 0, count($this->yv));86 // Find the LCS. 87 $this->_compareseq(0, count($this->xv), 0, count($this->yv)); 88 88 89 // Merge edits when possible.90 $this->_shiftBoundaries($from_lines, $this->xchanged, $this->ychanged);91 $this->_shiftBoundaries($to_lines, $this->ychanged, $this->xchanged);89 // Merge edits when possible. 90 $this->_shiftBoundaries($from_lines, $this->xchanged, $this->ychanged); 91 $this->_shiftBoundaries($to_lines, $this->ychanged, $this->xchanged); 92 92 93 // Compute the edit operations.94 $edits = array();95 $xi = $yi = 0;96 while ($xi < $n_from || $yi < $n_to) {97 assert($yi < $n_to || $this->xchanged[$xi]);98 assert($xi < $n_from || $this->ychanged[$yi]);93 // Compute the edit operations. 94 $edits = array(); 95 $xi = $yi = 0; 96 while ($xi < $n_from || $yi < $n_to) { 97 assert($yi < $n_to || $this->xchanged[$xi]); 98 assert($xi < $n_from || $this->ychanged[$yi]); 99 99 100 // Skip matching "snake".101 $copy = array();102 while ($xi < $n_from && $yi < $n_to103 && !$this->xchanged[$xi] && !$this->ychanged[$yi]) {104 $copy[] = $from_lines[$xi++];105 ++$yi;106 }107 if ($copy) {108 $edits[] = &new Text_Diff_Op_copy($copy);109 }100 // Skip matching "snake". 101 $copy = array(); 102 while ($xi < $n_from && $yi < $n_to 103 && !$this->xchanged[$xi] && !$this->ychanged[$yi]) { 104 $copy[] = $from_lines[$xi++]; 105 ++$yi; 106 } 107 if ($copy) { 108 $edits[] = &new Text_Diff_Op_copy($copy); 109 } 110 110 111 // Find deletes & adds.112 $delete = array();113 while ($xi < $n_from && $this->xchanged[$xi]) {114 $delete[] = $from_lines[$xi++];115 }111 // Find deletes & adds. 112 $delete = array(); 113 while ($xi < $n_from && $this->xchanged[$xi]) { 114 $delete[] = $from_lines[$xi++]; 115 } 116 116 117 $add = array();118 while ($yi < $n_to && $this->ychanged[$yi]) {119 $add[] = $to_lines[$yi++];120 }117 $add = array(); 118 while ($yi < $n_to && $this->ychanged[$yi]) { 119 $add[] = $to_lines[$yi++]; 120 } 121 121 122 if ($delete && $add) {123 $edits[] = &new Text_Diff_Op_change($delete, $add);124 } elseif ($delete) {125 $edits[] = &new Text_Diff_Op_delete($delete);126 } elseif ($add) {127 $edits[] = &new Text_Diff_Op_add($add);128 }129 }122 if ($delete && $add) { 123 $edits[] = &new Text_Diff_Op_change($delete, $add); 124 } elseif ($delete) { 125 $edits[] = &new Text_Diff_Op_delete($delete); 126 } elseif ($add) { 127 $edits[] = &new Text_Diff_Op_add($add); 128 } 129 } 130 130 131 return $edits;132 }131 return $edits; 132 } 133 133 134 /**135 * Divides the Largest Common Subsequence (LCS) of the sequences (XOFF,136 * XLIM) and (YOFF, YLIM) into NCHUNKS approximately equally sized137 * segments.138 *139 * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an array of140 * NCHUNKS+1 (X, Y) indexes giving the diving points between sub141 * sequences. The first sub-sequence is contained in (X0, X1), (Y0, Y1),142 * the second in (X1, X2), (Y1, Y2) and so on. Note that (X0, Y0) ==143 * (XOFF, YOFF) and (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM).144 *145 * This function assumes that the first lines of the specified portions of146 * the two files do not match, and likewise that the last lines do not147 * match. The caller must trim matching lines from the beginning and end148 * of the portions it is going to specify.149 */150 function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks)151 {152 $flip = false;134 /** 135 * Divides the Largest Common Subsequence (LCS) of the sequences (XOFF, 136 * XLIM) and (YOFF, YLIM) into NCHUNKS approximately equally sized 137 * segments. 138 * 139 * Returns (LCS, PTS). LCS is the length of the LCS. PTS is an array of 140 * NCHUNKS+1 (X, Y) indexes giving the diving points between sub 141 * sequences. The first sub-sequence is contained in (X0, X1), (Y0, Y1), 142 * the second in (X1, X2), (Y1, Y2) and so on. Note that (X0, Y0) == 143 * (XOFF, YOFF) and (X[NCHUNKS], Y[NCHUNKS]) == (XLIM, YLIM). 144 * 145 * This function assumes that the first lines of the specified portions of 146 * the two files do not match, and likewise that the last lines do not 147 * match. The caller must trim matching lines from the beginning and end 148 * of the portions it is going to specify. 149 */ 150 function _diag ($xoff, $xlim, $yoff, $ylim, $nchunks) 151 { 152 $flip = false; 153 153 154 if ($xlim - $xoff > $ylim - $yoff) {155 /* Things seems faster (I'm not sure I understand why) when the156 * shortest sequence is in X. */157 $flip = true;158 list ($xoff, $xlim, $yoff, $ylim)159 = array($yoff, $ylim, $xoff, $xlim);160 }154 if ($xlim - $xoff > $ylim - $yoff) { 155 /* Things seems faster (I'm not sure I understand why) when the 156 * shortest sequence is in X. */ 157 $flip = true; 158 list ($xoff, $xlim, $yoff, $ylim) 159 = array($yoff, $ylim, $xoff, $xlim); 160 } 161 161 162 if ($flip) {163 for ($i = $ylim - 1; $i >= $yoff; $i--) {164 $ymatches[$this->xv[$i]][] = $i;165 }166 } else {167 for ($i = $ylim - 1; $i >= $yoff; $i--) {168 $ymatches[$this->yv[$i]][] = $i;169 }170 }162 if ($flip) { 163 for ($i = $ylim - 1; $i >= $yoff; $i--) { 164 $ymatches[$this->xv[$i]][] = $i; 165 } 166 } else { 167 for ($i = $ylim - 1; $i >= $yoff; $i--) { 168 $ymatches[$this->yv[$i]][] = $i; 169 } 170 } 171 171 172 $this->lcs = 0;173 $this->seq[0]= $yoff - 1;174 $this->in_seq = array();175 $ymids[0] = array();172 $this->lcs = 0; 173 $this->seq[0]= $yoff - 1; 174 $this->in_seq = array(); 175 $ymids[0] = array(); 176 176 177 $numer = $xlim - $xoff + $nchunks - 1;178 $x = $xoff;179 for ($chunk = 0; $chunk < $nchunks; $chunk++) {180 if ($chunk > 0) {181 for ($i = 0; $i <= $this->lcs; $i++) {182 $ymids[$i][$chunk - 1] = $this->seq[$i];183 }184 }177 $numer = $xlim - $xoff + $nchunks - 1; 178 $x = $xoff; 179 for ($chunk = 0; $chunk < $nchunks; $chunk++) { 180 if ($chunk > 0) { 181 for ($i = 0; $i <= $this->lcs; $i++) { 182 $ymids[$i][$chunk - 1] = $this->seq[$i]; 183 } 184 } 185 185 186 $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $chunk) / $nchunks);187 for (; $x < $x1; $x++) {188 $line = $flip ? $this->yv[$x] : $this->xv[$x];189 if (empty($ymatches[$line])) {190 continue;191 }192 $matches = $ymatches[$line];193 reset($matches);194 while (list(, $y) = each($matches)) {195 if (empty($this->in_seq[$y])) {196 $k = $this->_lcsPos($y);197 assert($k > 0);198 $ymids[$k] = $ymids[$k - 1];199 break;200 }201 }202 while (list(, $y) = each($matches)) {203 if ($y > $this->seq[$k - 1]) {204 assert($y <= $this->seq[$k]);205 /* Optimization: this is a common case: next match is206 * just replacing previous match. */207 $this->in_seq[$this->seq[$k]] = false;208 $this->seq[$k] = $y;209 $this->in_seq[$y] = 1;210 } elseif (empty($this->in_seq[$y])) {211 $k = $this->_lcsPos($y);212 assert($k > 0);213 $ymids[$k] = $ymids[$k - 1];214 }215 }216 }217 }186 $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $chunk) / $nchunks); 187 for (; $x < $x1; $x++) { 188 $line = $flip ? $this->yv[$x] : $this->xv[$x]; 189 if (empty($ymatches[$line])) { 190 continue; 191 } 192 $matches = $ymatches[$line]; 193 reset($matches); 194 while (list(, $y) = each($matches)) { 195 if (empty($this->in_seq[$y])) { 196 $k = $this->_lcsPos($y); 197 assert($k > 0); 198 $ymids[$k] = $ymids[$k - 1]; 199 break; 200 } 201 } 202 while (list(, $y) = each($matches)) { 203 if ($y > $this->seq[$k - 1]) { 204 assert($y <= $this->seq[$k]); 205 /* Optimization: this is a common case: next match is 206 * just replacing previous match. */ 207 $this->in_seq[$this->seq[$k]] = false; 208 $this->seq[$k] = $y; 209 $this->in_seq[$y] = 1; 210 } elseif (empty($this->in_seq[$y])) { 211 $k = $this->_lcsPos($y); 212 assert($k > 0); 213 $ymids[$k] = $ymids[$k - 1]; 214 } 215 } 216 } 217 } 218 218 219 $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff);220 $ymid = $ymids[$this->lcs];221 for ($n = 0; $n < $nchunks - 1; $n++) {222 $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks);223 $y1 = $ymid[$n] + 1;224 $seps[] = $flip ? array($y1, $x1) : array($x1, $y1);225 }226 $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim);219 $seps[] = $flip ? array($yoff, $xoff) : array($xoff, $yoff); 220 $ymid = $ymids[$this->lcs]; 221 for ($n = 0; $n < $nchunks - 1; $n++) { 222 $x1 = $xoff + (int)(($numer + ($xlim - $xoff) * $n) / $nchunks); 223 $y1 = $ymid[$n] + 1; 224 $seps[] = $flip ? array($y1, $x1) : array($x1, $y1); 225 } 226 $seps[] = $flip ? array($ylim, $xlim) : array($xlim, $ylim); 227 227 228 return array($this->lcs, $seps);229 }228 return array($this->lcs, $seps); 229 } 230 230 231 function _lcsPos($ypos)232 {233 $end = $this->lcs;234 if ($end == 0 || $ypos > $this->seq[$end]) {235 $this->seq[++$this->lcs] = $ypos;236 $this->in_seq[$ypos] = 1;237 return $this->lcs;238 }231 function _lcsPos($ypos) 232 { 233 $end = $this->lcs; 234 if ($end == 0 || $ypos > $this->seq[$end]) { 235 $this->seq[++$this->lcs] = $ypos; 236 $this->in_seq[$ypos] = 1; 237 return $this->lcs; 238 } 239 239 240 $beg = 1;241 while ($beg < $end) {242 $mid = (int)(($beg + $end) / 2);243 if ($ypos > $this->seq[$mid]) {244 $beg = $mid + 1;245 } else {246 $end = $mid;247 }248 }240 $beg = 1; 241 while ($beg < $end) { 242 $mid = (int)(($beg + $end) / 2); 243 if ($ypos > $this->seq[$mid]) { 244 $beg = $mid + 1; 245 } else { 246 $end = $mid; 247 } 248 } 249 249 250 assert($ypos != $this->seq[$end]);250 assert($ypos != $this->seq[$end]); 251 251 252 $this->in_seq[$this->seq[$end]] = false;253 $this->seq[$end] = $ypos;254 $this->in_seq[$ypos] = 1;255 return $end;256 }252 $this->in_seq[$this->seq[$end]] = false; 253 $this->seq[$end] = $ypos; 254 $this->in_seq[$ypos] = 1; 255 return $end; 256 } 257 257 258 /**259 * Finds LCS of two sequences.260 *261 * The results are recorded in the vectors $this->{x,y}changed[], by262 * storing a 1 in the element for each line that is an insertion or263 * deletion (ie. is not in the LCS).264 *265 * The subsequence of file 0 is (XOFF, XLIM) and likewise for file 1.266 *267 * Note that XLIM, YLIM are exclusive bounds. All line numbers are268 * origin-0 and discarded lines are not counted.269 */270 function _compareseq ($xoff, $xlim, $yoff, $ylim)271 {272 /* Slide down the bottom initial diagonal. */273 while ($xoff < $xlim && $yoff < $ylim274 && $this->xv[$xoff] == $this->yv[$yoff]) {275 ++$xoff;276 ++$yoff;277 }258 /** 259 * Finds LCS of two sequences. 260 * 261 * The results are recorded in the vectors $this->{x,y}changed[], by 262 * storing a 1 in the element for each line that is an insertion or 263 * deletion (ie. is not in the LCS). 264 * 265 * The subsequence of file 0 is (XOFF, XLIM) and likewise for file 1. 266 * 267 * Note that XLIM, YLIM are exclusive bounds. All line numbers are 268 * origin-0 and discarded lines are not counted. 269 */ 270 function _compareseq ($xoff, $xlim, $yoff, $ylim) 271 { 272 /* Slide down the bottom initial diagonal. */ 273 while ($xoff < $xlim && $yoff < $ylim 274 && $this->xv[$xoff] == $this->yv[$yoff]) { 275 ++$xoff; 276 ++$yoff; 277 } 278 278 279 /* Slide up the top initial diagonal. */280 while ($xlim > $xoff && $ylim > $yoff281 && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) {282 --$xlim;283 --$ylim;284 }279 /* Slide up the top initial diagonal. */ 280 while ($xlim > $xoff && $ylim > $yoff 281 && $this->xv[$xlim - 1] == $this->yv[$ylim - 1]) { 282 --$xlim; 283 --$ylim; 284 } 285 285 286 if ($xoff == $xlim || $yoff == $ylim) {287 $lcs = 0;288 } else {289 /* This is ad hoc but seems to work well. $nchunks =290 * sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); $nchunks =291 * max(2,min(8,(int)$nchunks)); */292 $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1;293 list($lcs, $seps)294 = $this->_diag($xoff, $xlim, $yoff, $ylim, $nchunks);295 }286 if ($xoff == $xlim || $yoff == $ylim) { 287 $lcs = 0; 288 } else { 289 /* This is ad hoc but seems to work well. $nchunks = 290 * sqrt(min($xlim - $xoff, $ylim - $yoff) / 2.5); $nchunks = 291 * max(2,min(8,(int)$nchunks)); */ 292 $nchunks = min(7, $xlim - $xoff, $ylim - $yoff) + 1; 293 list($lcs, $seps) 294 = $this->_diag($xoff, $xlim, $yoff, $ylim, $nchunks); 295 } 296 296 297 if ($lcs == 0) {298 /* X and Y sequences have no common subsequence: mark all299 * changed. */300 while ($yoff < $ylim) {301 $this->ychanged[$this->yind[$yoff++]] = 1;302 }303 while ($xoff < $xlim) {304 $this->xchanged[$this->xind[$xoff++]] = 1;305 }306 } else {307 /* Use the partitions to split this problem into subproblems. */308 reset($seps);309 $pt1 = $seps[0];310 while ($pt2 = next($seps)) {311 $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]);312 $pt1 = $pt2;313 }314 }315 }297 if ($lcs == 0) { 298 /* X and Y sequences have no common subsequence: mark all 299 * changed. */ 300 while ($yoff < $ylim) { 301 $this->ychanged[$this->yind[$yoff++]] = 1; 302 } 303 while ($xoff < $xlim) { 304 $this->xchanged[$this->xind[$xoff++]] = 1; 305 } 306 } else { 307 /* Use the partitions to split this problem into subproblems. */ 308 reset($seps); 309 $pt1 = $seps[0]; 310 while ($pt2 = next($seps)) { 311 $this->_compareseq ($pt1[0], $pt2[0], $pt1[1], $pt2[1]); 312 $pt1 = $pt2; 313 } 314 } 315 } 316 316 317 /**318 * Adjusts inserts/deletes of identical lines to join changes as much as319 * possible.320 *321 * We do something when a run of changed lines include a line at one end322 * and has an excluded, identical line at the other. We are free to323 * choose which identical line is included. `compareseq' usually chooses324 * the one at the beginning, but usually it is cleaner to consider the325 * following identical line to be the "change".326 *327 * This is extracted verbatim from analyze.c (GNU diffutils-2.7).328 */329 function _shiftBoundaries($lines, &$changed, $other_changed)330 {331 $i = 0;332 $j = 0;317 /** 318 * Adjusts inserts/deletes of identical lines to join changes as much as 319 * possible. 320 * 321 * We do something when a run of changed lines include a line at one end 322 * and has an excluded, identical line at the other. We are free to 323 * choose which identical line is included. `compareseq' usually chooses 324 * the one at the beginning, but usually it is cleaner to consider the 325 * following identical line to be the "change". 326 * 327 * This is extracted verbatim from analyze.c (GNU diffutils-2.7). 328 */ 329 function _shiftBoundaries($lines, &$changed, $other_changed) 330 { 331 $i = 0; 332 $j = 0; 333 333 334 assert('count($lines) == count($changed)');335 $len = count($lines);336 $other_len = count($other_changed);334 assert('count($lines) == count($changed)'); 335 $len = count($lines); 336 $other_len = count($other_changed); 337 337 338 while (1) {339 /* Scan forward to find the beginning of another run of340 * changes. Also keep track of the corresponding point in the341 * other file.342 *343 * Throughout this code, $i and $j are adjusted together so that344 * the first $i elements of $changed and the first $j elements of345 * $other_changed both contain the same number of zeros (unchanged346 * lines).347 *348 * Furthermore, $j is always kept so that $j == $other_len or349 * $other_changed[$j] == false. */350 while ($j < $other_len && $other_changed[$j]) {351 $j++;352 }338 while (1) { 339 /* Scan forward to find the beginning of another run of 340 * changes. Also keep track of the corresponding point in the 341 * other file. 342 * 343 * Throughout this code, $i and $j are adjusted together so that 344 * the first $i elements of $changed and the first $j elements of 345 * $other_changed both contain the same number of zeros (unchanged 346 * lines). 347 * 348 * Furthermore, $j is always kept so that $j == $other_len or 349 * $other_changed[$j] == false. */ 350 while ($j < $other_len && $other_changed[$j]) { 351 $j++; 352 } 353 353 354 while ($i < $len && ! $changed[$i]) {355 assert('$j < $other_len && ! $other_changed[$j]');356 $i++; $j++;357 while ($j < $other_len && $other_changed[$j]) {358 $j++;359 }360 }354 while ($i < $len && ! $changed[$i]) { 355 assert('$j < $other_len && ! $other_changed[$j]'); 356 $i++; $j++; 357 while ($j < $other_len && $other_changed[$j]) { 358 $j++; 359 } 360 } 361 361 362 if ($i == $len) {363 break;364 }362 if ($i == $len) { 363 break; 364 } 365 365 366 $start = $i;366 $start = $i; 367 367 368 /* Find the end of this run of changes. */369 while (++$i < $len && $changed[$i]) {370 continue;371 }368 /* Find the end of this run of changes. */ 369 while (++$i < $len && $changed[$i]) { 370 continue; 371 } 372 372 373 do {374 /* Record the length of this run of changes, so that we can375 * later determine whether the run has grown. */376 $runlength = $i - $start;373 do { 374 /* Record the length of this run of changes, so that we can 375 * later determine whether the run has grown. */ 376 $runlength = $i - $start; 377 377 378 /* Move the changed region back, so long as the previous379 * unchanged line matches the last changed one. This merges380 * with previous changed regions. */381 while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) {382 $changed[--$start] = 1;383 $changed[--$i] = false;384 while ($start > 0 && $changed[$start - 1]) {385 $start--;386 }387 assert('$j > 0');388 while ($other_changed[--$j]) {389 continue;390 }391 assert('$j >= 0 && !$other_changed[$j]');392 }378 /* Move the changed region back, so long as the previous 379 * unchanged line matches the last changed one. This merges 380 * with previous changed regions. */ 381 while ($start > 0 && $lines[$start - 1] == $lines[$i - 1]) { 382 $changed[--$start] = 1; 383 $changed[--$i] = false; 384 while ($start > 0 && $changed[$start - 1]) { 385 $start--; 386 } 387 assert('$j > 0'); 388 while ($other_changed[--$j]) { 389 continue; 390 } 391 assert('$j >= 0 && !$other_changed[$j]'); 392 } 393 393 394 /* Set CORRESPONDING to the end of the changed run, at the395 * last point where it corresponds to a changed run in the396 * other file. CORRESPONDING == LEN means no such point has397 * been found. */398 $corresponding = $j < $other_len ? $i : $len;394 /* Set CORRESPONDING to the end of the changed run, at the 395 * last point where it corresponds to a changed run in the 396 * other file. CORRESPONDING == LEN means no such point has 397 * been found. */ 398 $corresponding = $j < $other_len ? $i : $len; 399 399 400 /* Move the changed region forward, so long as the first401 * changed line matches the following unchanged one. This402 * merges with following changed regions. Do this second, so403 * that if there are no merges, the changed region is moved404 * forward as far as possible. */405 while ($i < $len && $lines[$start] == $lines[$i]) {406 $changed[$start++] = false;407 $changed[$i++] = 1;408 while ($i < $len && $changed[$i]) {409 $i++;410 }400 /* Move the changed region forward, so long as the first 401 * changed line matches the following unchanged one. This 402 * merges with following changed regions. Do this second, so 403 * that if there are no merges, the changed region is moved 404 * forward as far as possible. */ 405 while ($i < $len && $lines[$start] == $lines[$i]) { 406 $changed[$start++] = false; 407 $changed[$i++] = 1; 408 while ($i < $len && $changed[$i]) { 409 $i++; 410 } 411 411 412 assert('$j < $other_len && ! $other_changed[$j]');413 $j++;414 if ($j < $other_len && $other_changed[$j]) {415 $corresponding = $i;416 while ($j < $other_len && $other_changed[$j]) {417 $j++;418 }419 }420 }421 } while ($runlength != $i - $start);412 assert('$j < $other_len && ! $other_changed[$j]'); 413 $j++; 414 if ($j < $other_len && $other_changed[$j]) { 415 $corresponding = $i; 416 while ($j < $other_len && $other_changed[$j]) { 417 $j++; 418 } 419 } 420 } 421 } while ($runlength != $i - $start); 422 422 423 /* If possible, move the fully-merged run of changes back to a424 * corresponding run in the other file. */425 while ($corresponding < $i) {426 $changed[--$start] = 1;427 $changed[--$i] = 0;428 assert('$j > 0');429 while ($other_changed[--$j]) {430 continue;431 }432 assert('$j >= 0 && !$other_changed[$j]');433 }434 }435 }423 /* If possible, move the fully-merged run of changes back to a 424 * corresponding run in the other file. */ 425 while ($corresponding < $i) { 426 $changed[--$start] = 1; 427 $changed[--$i] = 0; 428 assert('$j > 0'); 429 while ($other_changed[--$j]) { 430 continue; 431 } 432 assert('$j >= 0 && !$other_changed[$j]'); 433 } 434 } 435 } 436 436 437 437 } -
Text/Diff/Engine/shell.php
18 18 */ 19 19 class Text_Diff_Engine_shell { 20 20 21 /**22 * Path to the diff executable23 *24 * @var string25 */26 var $_diffCommand = 'diff';21 /** 22 * Path to the diff executable 23 * 24 * @var string 25 */ 26 var $_diffCommand = 'diff'; 27 27 28 /**29 * Returns the array of differences.30 *31 * @param array $from_lines lines of text from old file32 * @param array $to_lines lines of text from new file33 *34 * @return array all changes made (array with Text_Diff_Op_* objects)35 */36 function diff($from_lines, $to_lines)37 {38 array_walk($from_lines, array('Text_Diff', 'trimNewlines'));39 array_walk($to_lines, array('Text_Diff', 'trimNewlines'));28 /** 29 * Returns the array of differences. 30 * 31 * @param array $from_lines lines of text from old file 32 * @param array $to_lines lines of text from new file 33 * 34 * @return array all changes made (array with Text_Diff_Op_* objects) 35 */ 36 function diff($from_lines, $to_lines) 37 { 38 array_walk($from_lines, array('Text_Diff', 'trimNewlines')); 39 array_walk($to_lines, array('Text_Diff', 'trimNewlines')); 40 40 41 $temp_dir = Text_Diff::_getTempDir();41 $temp_dir = Text_Diff::_getTempDir(); 42 42 43 // Execute gnu diff or similar to get a standard diff file.44 $from_file = tempnam($temp_dir, 'Text_Diff');45 $to_file = tempnam($temp_dir, 'Text_Diff');46 $fp = fopen($from_file, 'w');47 fwrite($fp, implode("\n", $from_lines));48 fclose($fp);49 $fp = fopen($to_file, 'w');50 fwrite($fp, implode("\n", $to_lines));51 fclose($fp);52 $diff = shell_exec($this->_diffCommand . ' ' . $from_file . ' ' . $to_file);53 unlink($from_file);54 unlink($to_file);43 // Execute gnu diff or similar to get a standard diff file. 44 $from_file = tempnam($temp_dir, 'Text_Diff'); 45 $to_file = tempnam($temp_dir, 'Text_Diff'); 46 $fp = fopen($from_file, 'w'); 47 fwrite($fp, implode("\n", $from_lines)); 48 fclose($fp); 49 $fp = fopen($to_file, 'w'); 50 fwrite($fp, implode("\n", $to_lines)); 51 fclose($fp); 52 $diff = shell_exec($this->_diffCommand . ' ' . $from_file . ' ' . $to_file); 53 unlink($from_file); 54 unlink($to_file); 55 55 56 if (is_null($diff)) {57 // No changes were made58 return array(new Text_Diff_Op_copy($from_lines));59 }56 if (is_null($diff)) { 57 // No changes were made 58 return array(new Text_Diff_Op_copy($from_lines)); 59 } 60 60 61 $from_line_no = 1;62 $to_line_no = 1;63 $edits = array();61 $from_line_no = 1; 62 $to_line_no = 1; 63 $edits = array(); 64 64 65 // Get changed lines by parsing something like:66 // 0a1,267 // 1,2c4,668 // 1,5d669 preg_match_all('#^(\d+)(?:,(\d+))?([adc])(\d+)(?:,(\d+))?$#m', $diff,70 $matches, PREG_SET_ORDER);65 // Get changed lines by parsing something like: 66 // 0a1,2 67 // 1,2c4,6 68 // 1,5d6 69 preg_match_all('#^(\d+)(?:,(\d+))?([adc])(\d+)(?:,(\d+))?$#m', $diff, 70 $matches, PREG_SET_ORDER); 71 71 72 foreach ($matches as $match) {73 if (!isset($match[5])) {74 // This paren is not set every time (see regex).75 $match[5] = false;76 }72 foreach ($matches as $match) { 73 if (!isset($match[5])) { 74 // This paren is not set every time (see regex). 75 $match[5] = false; 76 } 77 77 78 if ($match[3] == 'a') {79 $from_line_no--;80 }78 if ($match[3] == 'a') { 79 $from_line_no--; 80 } 81 81 82 if ($match[3] == 'd') {83 $to_line_no--;84 }82 if ($match[3] == 'd') { 83 $to_line_no--; 84 } 85 85 86 if ($from_line_no < $match[1] || $to_line_no < $match[4]) {87 // copied lines88 assert('$match[1] - $from_line_no == $match[4] - $to_line_no');89 array_push($edits,90 new Text_Diff_Op_copy(91 $this->_getLines($from_lines, $from_line_no, $match[1] - 1),92 $this->_getLines($to_lines, $to_line_no, $match[4] - 1)));93 }86 if ($from_line_no < $match[1] || $to_line_no < $match[4]) { 87 // copied lines 88 assert('$match[1] - $from_line_no == $match[4] - $to_line_no'); 89 array_push($edits, 90 new Text_Diff_Op_copy( 91 $this->_getLines($from_lines, $from_line_no, $match[1] - 1), 92 $this->_getLines($to_lines, $to_line_no, $match[4] - 1))); 93 } 94 94 95 switch ($match[3]) {96 case 'd':97 // deleted lines98 array_push($edits,99 new Text_Diff_Op_delete(100 $this->_getLines($from_lines, $from_line_no, $match[2])));101 $to_line_no++;102 break;95 switch ($match[3]) { 96 case 'd': 97 // deleted lines 98 array_push($edits, 99 new Text_Diff_Op_delete( 100 $this->_getLines($from_lines, $from_line_no, $match[2]))); 101 $to_line_no++; 102 break; 103 103 104 case 'c':105 // changed lines106 array_push($edits,107 new Text_Diff_Op_change(108 $this->_getLines($from_lines, $from_line_no, $match[2]),109 $this->_getLines($to_lines, $to_line_no, $match[5])));110 break;104 case 'c': 105 // changed lines 106 array_push($edits, 107 new Text_Diff_Op_change( 108 $this->_getLines($from_lines, $from_line_no, $match[2]), 109 $this->_getLines($to_lines, $to_line_no, $match[5]))); 110 break; 111 111 112 case 'a':113 // added lines114 array_push($edits,115 new Text_Diff_Op_add(116 $this->_getLines($to_lines, $to_line_no, $match[5])));117 $from_line_no++;118 break;119 }120 }112 case 'a': 113 // added lines 114 array_push($edits, 115 new Text_Diff_Op_add( 116 $this->_getLines($to_lines, $to_line_no, $match[5]))); 117 $from_line_no++; 118 break; 119 } 120 } 121 121 122 if (!empty($from_lines)) {123 // Some lines might still be pending. Add them as copied124 array_push($edits,125 new Text_Diff_Op_copy(126 $this->_getLines($from_lines, $from_line_no,127 $from_line_no + count($from_lines) - 1),128 $this->_getLines($to_lines, $to_line_no,129 $to_line_no + count($to_lines) - 1)));130 }122 if (!empty($from_lines)) { 123 // Some lines might still be pending. Add them as copied 124 array_push($edits, 125 new Text_Diff_Op_copy( 126 $this->_getLines($from_lines, $from_line_no, 127 $from_line_no + count($from_lines) - 1), 128 $this->_getLines($to_lines, $to_line_no, 129 $to_line_no + count($to_lines) - 1))); 130 } 131 131 132 return $edits;133 }132 return $edits; 133 } 134 134 135 /**136 * Get lines from either the old or new text137 *138 * @access private139 *140 * @param array &$text_lines Either $from_lines or $to_lines141 * @param int &$line_noCurrent line number142 * @param int $endOptional end line, when we want to chop more143 *than one line.144 *145 * @return array The chopped lines146 */147 function _getLines(&$text_lines, &$line_no, $end = false)148 {149 if (!empty($end)) {150 $lines = array();151 // We can shift even more152 while ($line_no <= $end) {153 array_push($lines, array_shift($text_lines));154 $line_no++;155 }156 } else {157 $lines = array(array_shift($text_lines));158 $line_no++;159 }135 /** 136 * Get lines from either the old or new text 137 * 138 * @access private 139 * 140 * @param array &$text_lines Either $from_lines or $to_lines 141 * @param int &$line_no Current line number 142 * @param int $end Optional end line, when we want to chop more 143 * than one line. 144 * 145 * @return array The chopped lines 146 */ 147 function _getLines(&$text_lines, &$line_no, $end = false) 148 { 149 if (!empty($end)) { 150 $lines = array(); 151 // We can shift even more 152 while ($line_no <= $end) { 153 array_push($lines, array_shift($text_lines)); 154 $line_no++; 155 } 156 } else { 157 $lines = array(array_shift($text_lines)); 158 $line_no++; 159 } 160 160 161 return $lines;162 }161 return $lines; 162 } 163 163 164 164 } -
Text/Diff/Engine/string.php
24 24 */ 25 25 class Text_Diff_Engine_string { 26 26 27 /**28 * Parses a unified or context diff.29 *30 * First param contains the whole diff and the second can be used to force31 * a specific diff type. If the second parameter is 'autodetect', the32 * diff will be examined to find out which type of diff this is.33 *34 * @param string $diff The diff content.35 * @param string $mode The diff mode of the content in $diff. One of36 *'context', 'unified', or 'autodetect'.37 *38 * @return array List of all diff operations.39 */40 function diff($diff, $mode = 'autodetect')41 {42 if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') {43 return PEAR::raiseError('Type of diff is unsupported');44 }27 /** 28 * Parses a unified or context diff. 29 * 30 * First param contains the whole diff and the second can be used to force 31 * a specific diff type. If the second parameter is 'autodetect', the 32 * diff will be examined to find out which type of diff this is. 33 * 34 * @param string $diff The diff content. 35 * @param string $mode The diff mode of the content in $diff. One of 36 * 'context', 'unified', or 'autodetect'. 37 * 38 * @return array List of all diff operations. 39 */ 40 function diff($diff, $mode = 'autodetect') 41 { 42 if ($mode != 'autodetect' && $mode != 'context' && $mode != 'unified') { 43 return PEAR::raiseError('Type of diff is unsupported'); 44 } 45 45 46 if ($mode == 'autodetect') {47 $context = strpos($diff, '***');48 $unified = strpos($diff, '---');49 if ($context === $unified) {50 return PEAR::raiseError('Type of diff could not be detected');51 } elseif ($context === false || $context === false) {52 $mode = $context !== false ? 'context' : 'unified';53 } else {54 $mode = $context < $unified ? 'context' : 'unified';55 }56 }46 if ($mode == 'autodetect') { 47 $context = strpos($diff, '***'); 48 $unified = strpos($diff, '---'); 49 if ($context === $unified) { 50 return PEAR::raiseError('Type of diff could not be detected'); 51 } elseif ($context === false || $context === false) { 52 $mode = $context !== false ? 'context' : 'unified'; 53 } else { 54 $mode = $context < $unified ? 'context' : 'unified'; 55 } 56 } 57 57 58 // split by new line and remove the diff header59 $diff = explode("\n", $diff);60 array_shift($diff);61 array_shift($diff);58 // split by new line and remove the diff header 59 $diff = explode("\n", $diff); 60 array_shift($diff); 61 array_shift($diff); 62 62 63 if ($mode == 'context') {64 return $this->parseContextDiff($diff);65 } else {66 return $this->parseUnifiedDiff($diff);67 }68 }63 if ($mode == 'context') { 64 return $this->parseContextDiff($diff); 65 } else { 66 return $this->parseUnifiedDiff($diff); 67 } 68 } 69 69 70 /**71 * Parses an array containing the unified diff.72 *73 * @param array $diff Array of lines.74 *75 * @return array List of all diff operations.76 */77 function parseUnifiedDiff($diff)78 {79 $edits = array();80 $end = count($diff) - 1;81 for ($i = 0; $i < $end;) {82 $diff1 = array();83 switch (substr($diff[$i], 0, 1)) {84 case ' ':85 do {86 $diff1[] = substr($diff[$i], 1);87 } while (++$i < $end && substr($diff[$i], 0, 1) == ' ');88 $edits[] = &new Text_Diff_Op_copy($diff1);89 break;70 /** 71 * Parses an array containing the unified diff. 72 * 73 * @param array $diff Array of lines. 74 * 75 * @return array List of all diff operations. 76 */ 77 function parseUnifiedDiff($diff) 78 { 79 $edits = array(); 80 $end = count($diff) - 1; 81 for ($i = 0; $i < $end;) { 82 $diff1 = array(); 83 switch (substr($diff[$i], 0, 1)) { 84 case ' ': 85 do { 86 $diff1[] = substr($diff[$i], 1); 87 } while (++$i < $end && substr($diff[$i], 0, 1) == ' '); 88 $edits[] = &new Text_Diff_Op_copy($diff1); 89 break; 90 90 91 case '+':92 // get all new lines93 do {94 $diff1[] = substr($diff[$i], 1);95 } while (++$i < $end && substr($diff[$i], 0, 1) == '+');96 $edits[] = &new Text_Diff_Op_add($diff1);97 break;91 case '+': 92 // get all new lines 93 do { 94 $diff1[] = substr($diff[$i], 1); 95 } while (++$i < $end && substr($diff[$i], 0, 1) == '+'); 96 $edits[] = &new Text_Diff_Op_add($diff1); 97 break; 98 98 99 case '-':100 // get changed or removed lines101 $diff2 = array();102 do {103 $diff1[] = substr($diff[$i], 1);104 } while (++$i < $end && substr($diff[$i], 0, 1) == '-');99 case '-': 100 // get changed or removed lines 101 $diff2 = array(); 102 do { 103 $diff1[] = substr($diff[$i], 1); 104 } while (++$i < $end && substr($diff[$i], 0, 1) == '-'); 105 105 106 while ($i < $end && substr($diff[$i], 0, 1) == '+') {107 $diff2[] = substr($diff[$i++], 1);108 }109 if (count($diff2) == 0) {110 $edits[] = &new Text_Diff_Op_delete($diff1);111 } else {112 $edits[] = &new Text_Diff_Op_change($diff1, $diff2);113 }114 break;106 while ($i < $end && substr($diff[$i], 0, 1) == '+') { 107 $diff2[] = substr($diff[$i++], 1); 108 } 109 if (count($diff2) == 0) { 110 $edits[] = &new Text_Diff_Op_delete($diff1); 111 } else { 112 $edits[] = &new Text_Diff_Op_change($diff1, $diff2); 113 } 114 break; 115 115 116 default:117 $i++;118 break;119 }120 }116 default: 117 $i++; 118 break; 119 } 120 } 121 121 122 return $edits;123 }122 return $edits; 123 } 124 124 125 /**126 * Parses an array containing the context diff.127 *128 * @param array $diff Array of lines.129 *130 * @return array List of all diff operations.131 */132 function parseContextDiff(&$diff)133 {134 $edits = array();135 $i = $max_i = $j = $max_j = 0;136 $end = count($diff) - 1;137 while ($i < $end && $j < $end) {138 while ($i >= $max_i && $j >= $max_j) {139 // Find the boundaries of the diff output of the two files140 for ($i = $j;141 $i < $end && substr($diff[$i], 0, 3) == '***';142 $i++);143 for ($max_i = $i;144 $max_i < $end && substr($diff[$max_i], 0, 3) != '---';145 $max_i++);146 for ($j = $max_i;147 $j < $end && substr($diff[$j], 0, 3) == '---';148 $j++);149 for ($max_j = $j;150 $max_j < $end && substr($diff[$max_j], 0, 3) != '***';151 $max_j++);152 }125 /** 126 * Parses an array containing the context diff. 127 * 128 * @param array $diff Array of lines. 129 * 130 * @return array List of all diff operations. 131 */ 132 function parseContextDiff(&$diff) 133 { 134 $edits = array(); 135 $i = $max_i = $j = $max_j = 0; 136 $end = count($diff) - 1; 137 while ($i < $end && $j < $end) { 138 while ($i >= $max_i && $j >= $max_j) { 139 // Find the boundaries of the diff output of the two files 140 for ($i = $j; 141 $i < $end && substr($diff[$i], 0, 3) == '***'; 142 $i++); 143 for ($max_i = $i; 144 $max_i < $end && substr($diff[$max_i], 0, 3) != '---'; 145 $max_i++); 146 for ($j = $max_i; 147 $j < $end && substr($diff[$j], 0, 3) == '---'; 148 $j++); 149 for ($max_j = $j; 150 $max_j < $end && substr($diff[$max_j], 0, 3) != '***'; 151 $max_j++); 152 } 153 153 154 // find what hasn't been changed155 $array = array();156 while ($i < $max_i &&157 $j < $max_j &&158 strcmp($diff[$i], $diff[$j]) == 0) {159 $array[] = substr($diff[$i], 2);160 $i++;161 $j++;162 }154 // find what hasn't been changed 155 $array = array(); 156 while ($i < $max_i && 157 $j < $max_j && 158 strcmp($diff[$i], $diff[$j]) == 0) { 159 $array[] = substr($diff[$i], 2); 160 $i++; 161 $j++; 162 } 163 163 164 while ($i < $max_i && ($max_j-$j) <= 1) {165 if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') {166 break;167 }168 $array[] = substr($diff[$i++], 2);169 }164 while ($i < $max_i && ($max_j-$j) <= 1) { 165 if ($diff[$i] != '' && substr($diff[$i], 0, 1) != ' ') { 166 break; 167 } 168 $array[] = substr($diff[$i++], 2); 169 } 170 170 171 while ($j < $max_j && ($max_i-$i) <= 1) {172 if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') {173 break;174 }175 $array[] = substr($diff[$j++], 2);176 }177 if (count($array) > 0) {178 $edits[] = &new Text_Diff_Op_copy($array);179 }171 while ($j < $max_j && ($max_i-$i) <= 1) { 172 if ($diff[$j] != '' && substr($diff[$j], 0, 1) != ' ') { 173 break; 174 } 175 $array[] = substr($diff[$j++], 2); 176 } 177 if (count($array) > 0) { 178 $edits[] = &new Text_Diff_Op_copy($array); 179 } 180 180 181 if ($i < $max_i) {182 $diff1 = array();183 switch (substr($diff[$i], 0, 1)) {184 case '!':185 $diff2 = array();186 do {187 $diff1[] = substr($diff[$i], 2);188 if ($j < $max_j && substr($diff[$j], 0, 1) == '!') {189 $diff2[] = substr($diff[$j++], 2);190 }191 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '!');192 $edits[] = &new Text_Diff_Op_change($diff1, $diff2);193 break;181 if ($i < $max_i) { 182 $diff1 = array(); 183 switch (substr($diff[$i], 0, 1)) { 184 case '!': 185 $diff2 = array(); 186 do { 187 $diff1[] = substr($diff[$i], 2); 188 if ($j < $max_j && substr($diff[$j], 0, 1) == '!') { 189 $diff2[] = substr($diff[$j++], 2); 190 } 191 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '!'); 192 $edits[] = &new Text_Diff_Op_change($diff1, $diff2); 193 break; 194 194 195 case '+':196 do {197 $diff1[] = substr($diff[$i], 2);198 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '+');199 $edits[] = &new Text_Diff_Op_add($diff1);200 break;195 case '+': 196 do { 197 $diff1[] = substr($diff[$i], 2); 198 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '+'); 199 $edits[] = &new Text_Diff_Op_add($diff1); 200 break; 201 201 202 case '-':203 do {204 $diff1[] = substr($diff[$i], 2);205 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '-');206 $edits[] = &new Text_Diff_Op_delete($diff1);207 break;208 }209 }202 case '-': 203 do { 204 $diff1[] = substr($diff[$i], 2); 205 } while (++$i < $max_i && substr($diff[$i], 0, 1) == '-'); 206 $edits[] = &new Text_Diff_Op_delete($diff1); 207 break; 208 } 209 } 210 210 211 if ($j < $max_j) {212 $diff2 = array();213 switch (substr($diff[$j], 0, 1)) {214 case '+':215 do {216 $diff2[] = substr($diff[$j++], 2);217 } while ($j < $max_j && substr($diff[$j], 0, 1) == '+');218 $edits[] = &new Text_Diff_Op_add($diff2);219 break;211 if ($j < $max_j) { 212 $diff2 = array(); 213 switch (substr($diff[$j], 0, 1)) { 214 case '+': 215 do { 216 $diff2[] = substr($diff[$j++], 2); 217 } while ($j < $max_j && substr($diff[$j], 0, 1) == '+'); 218 $edits[] = &new Text_Diff_Op_add($diff2); 219 break; 220 220 221 case '-':222 do {223 $diff2[] = substr($diff[$j++], 2);224 } while ($j < $max_j && substr($diff[$j], 0, 1) == '-');225 $edits[] = &new Text_Diff_Op_delete($diff2);226 break;227 }228 }229 }221 case '-': 222 do { 223 $diff2[] = substr($diff[$j++], 2); 224 } while ($j < $max_j && substr($diff[$j], 0, 1) == '-'); 225 $edits[] = &new Text_Diff_Op_delete($diff2); 226 break; 227 } 228 } 229 } 230 230 231 return $edits;232 }231 return $edits; 232 } 233 233 234 234 } -
Text/Diff/Engine/xdiff.php
17 17 */ 18 18 class Text_Diff_Engine_xdiff { 19 19 20 /**21 */22 function diff($from_lines, $to_lines)23 {24 array_walk($from_lines, array('Text_Diff', 'trimNewlines'));25 array_walk($to_lines, array('Text_Diff', 'trimNewlines'));20 /** 21 */ 22 function diff($from_lines, $to_lines) 23 { 24 array_walk($from_lines, array('Text_Diff', 'trimNewlines')); 25 array_walk($to_lines, array('Text_Diff', 'trimNewlines')); 26 26 27 /* Convert the two input arrays into strings for xdiff processing. */28 $from_string = implode("\n", $from_lines);29 $to_string = implode("\n", $to_lines);27 /* Convert the two input arrays into strings for xdiff processing. */ 28 $from_string = implode("\n", $from_lines); 29 $to_string = implode("\n", $to_lines); 30 30 31 /* Diff the two strings and convert the result to an array. */32 $diff = xdiff_string_diff($from_string, $to_string, count($to_lines));33 $diff = explode("\n", $diff);31 /* Diff the two strings and convert the result to an array. */ 32 $diff = xdiff_string_diff($from_string, $to_string, count($to_lines)); 33 $diff = explode("\n", $diff); 34 34 35 /* Walk through the diff one line at a time. We build the $edits36 * array of diff operations by reading the first character of the37 * xdiff output (which is in the "unified diff" format).38 *39 * Note that we don't have enough information to detect "changed"40 * lines using this approach, so we can't add Text_Diff_Op_changed41 * instances to the $edits array. The result is still perfectly42 * valid, albeit a little less descriptive and efficient. */43 $edits = array();44 foreach ($diff as $line) {45 switch ($line[0]) {46 case ' ':47 $edits[] = &new Text_Diff_Op_copy(array(substr($line, 1)));48 break;35 /* Walk through the diff one line at a time. We build the $edits 36 * array of diff operations by reading the first character of the 37 * xdiff output (which is in the "unified diff" format). 38 * 39 * Note that we don't have enough information to detect "changed" 40 * lines using this approach, so we can't add Text_Diff_Op_changed 41 * instances to the $edits array. The result is still perfectly 42 * valid, albeit a little less descriptive and efficient. */ 43 $edits = array(); 44 foreach ($diff as $line) { 45 switch ($line[0]) { 46 case ' ': 47 $edits[] = &new Text_Diff_Op_copy(array(substr($line, 1))); 48 break; 49 49 50 case '+':51 $edits[] = &new Text_Diff_Op_add(array(substr($line, 1)));52 break;50 case '+': 51 $edits[] = &new Text_Diff_Op_add(array(substr($line, 1))); 52 break; 53 53 54 case '-':55 $edits[] = &new Text_Diff_Op_delete(array(substr($line, 1)));56 break;57 }58 }54 case '-': 55 $edits[] = &new Text_Diff_Op_delete(array(substr($line, 1))); 56 break; 57 } 58 } 59 59 60 return $edits;61 }60 return $edits; 61 } 62 62 63 63 } -
Text/Diff/Renderer.php
16 16 */ 17 17 class Text_Diff_Renderer { 18 18 19 /**20 * Number of leading context "lines" to preserve.21 *22 * This should be left at zero for this class, but subclasses may want to23 * set this to other values.24 */25 var $_leading_context_lines = 0;19 /** 20 * Number of leading context "lines" to preserve. 21 * 22 * This should be left at zero for this class, but subclasses may want to 23 * set this to other values. 24 */ 25 var $_leading_context_lines = 0; 26 26 27 /**28 * Number of trailing context "lines" to preserve.29 *30 * This should be left at zero for this class, but subclasses may want to31 * set this to other values.32 */33 var $_trailing_context_lines = 0;27 /** 28 * Number of trailing context "lines" to preserve. 29 * 30 * This should be left at zero for this class, but subclasses may want to 31 * set this to other values. 32 */ 33 var $_trailing_context_lines = 0; 34 34 35 /**36 * Constructor.37 */38 function Text_Diff_Renderer($params = array())39 {40 foreach ($params as $param => $value) {41 $v = '_' . $param;42 if (isset($this->$v)) {43 $this->$v = $value;44 }45 }46 }35 /** 36 * Constructor. 37 */ 38 function Text_Diff_Renderer($params = array()) 39 { 40 foreach ($params as $param => $value) { 41 $v = '_' . $param; 42 if (isset($this->$v)) { 43 $this->$v = $value; 44 } 45 } 46 } 47 47 48 /**49 * Get any renderer parameters.50 *51 * @return array All parameters of this renderer object.52 */53 function getParams()54 {55 $params = array();56 foreach (get_object_vars($this) as $k => $v) {57 if ($k[0] == '_') {58 $params[substr($k, 1)] = $v;59 }60 }48 /** 49 * Get any renderer parameters. 50 * 51 * @return array All parameters of this renderer object. 52 */ 53 function getParams() 54 { 55 $params = array(); 56 foreach (get_object_vars($this) as $k => $v) { 57 if ($k[0] == '_') { 58 $params[substr($k, 1)] = $v; 59 } 60 } 61 61 62 return $params;63 }62 return $params; 63 } 64 64 65 /**66 * Renders a diff.67 *68 * @param Text_Diff $diff A Text_Diff object.69 *70 * @return string The formatted output.71 */72 function render($diff)73 {74 $xi = $yi = 1;75 $block = false;76 $context = array();65 /** 66 * Renders a diff. 67 * 68 * @param Text_Diff $diff A Text_Diff object. 69 * 70 * @return string The formatted output. 71 */ 72 function render($diff) 73 { 74 $xi = $yi = 1; 75 $block = false; 76 $context = array(); 77 77 78 $nlead = $this->_leading_context_lines;79 $ntrail = $this->_trailing_context_lines;78 $nlead = $this->_leading_context_lines; 79 $ntrail = $this->_trailing_context_lines; 80 80 81 $output = $this->_startDiff();81 $output = $this->_startDiff(); 82 82 83 $diffs = $diff->getDiff();84 foreach ($diffs as $i => $edit) {85 /* If these are unchanged (copied) lines, and we want to keep86 * leading or trailing context lines, extract them from the copy87 * block. */88 if (is_a($edit, 'Text_Diff_Op_copy')) {89 /* Do we have any diff blocks yet? */90 if (is_array($block)) {91 /* How many lines to keep as context from the copy92 * block. */93 $keep = $i == count($diffs) - 1 ? $ntrail : $nlead + $ntrail;94 if (count($edit->orig) <= $keep) {95 /* We have less lines in the block than we want for96 * context => keep the whole block. */97 $block[] = $edit;98 } else {99 if ($ntrail) {100 /* Create a new block with as many lines as we need101 * for the trailing context. */102 $context = array_slice($edit->orig, 0, $ntrail);103 $block[] = &new Text_Diff_Op_copy($context);104 }105 /* @todo */106 $output .= $this->_block($x0, $ntrail + $xi - $x0,107 $y0, $ntrail + $yi - $y0,108 $block);109 $block = false;110 }111 }112 /* Keep the copy block as the context for the next block. */113 $context = $edit->orig;114 } else {115 /* Don't we have any diff blocks yet? */116 if (!is_array($block)) {117 /* Extract context lines from the preceding copy block. */118 $context = array_slice($context, count($context) - $nlead);119 $x0 = $xi - count($context);120 $y0 = $yi - count($context);121 $block = array();122 if ($context) {123 $block[] = &new Text_Diff_Op_copy($context);124 }125 }126 $block[] = $edit;127 }83 $diffs = $diff->getDiff(); 84 foreach ($diffs as $i => $edit) { 85 /* If these are unchanged (copied) lines, and we want to keep 86 * leading or trailing context lines, extract them from the copy 87 * block. */ 88 if (is_a($edit, 'Text_Diff_Op_copy')) { 89 /* Do we have any diff blocks yet? */ 90 if (is_array($block)) { 91 /* How many lines to keep as context from the copy 92 * block. */ 93 $keep = $i == count($diffs) - 1 ? $ntrail : $nlead + $ntrail; 94 if (count($edit->orig) <= $keep) { 95 /* We have less lines in the block than we want for 96 * context => keep the whole block. */ 97 $block[] = $edit; 98 } else { 99 if ($ntrail) { 100 /* Create a new block with as many lines as we need 101 * for the trailing context. */ 102 $context = array_slice($edit->orig, 0, $ntrail); 103 $block[] = &new Text_Diff_Op_copy($context); 104 } 105 /* @todo */ 106 $output .= $this->_block($x0, $ntrail + $xi - $x0, 107 $y0, $ntrail + $yi - $y0, 108 $block); 109 $block = false; 110
