includes/diffengine/Renderer/xhtml.php
changeset 1227 bdac73ed481e
parent 1 fe660c52c48f
equal deleted inserted replaced
1226:de56132c008d 1227:bdac73ed481e
    11  * @author  Dan Fuhry
    11  * @author  Dan Fuhry
    12  * @package Text_Diff
    12  * @package Text_Diff
    13  */
    13  */
    14 class Text_Diff_Renderer_xhtml extends Text_Diff_Renderer {
    14 class Text_Diff_Renderer_xhtml extends Text_Diff_Renderer {
    15 
    15 
    16     /**
    16 		/**
    17      * Number of leading context "lines" to preserve.
    17  		* Number of leading context "lines" to preserve.
    18      */
    18  		*/
    19     var $_leading_context_lines = 5;
    19 		var $_leading_context_lines = 5;
    20 
    20 
    21     /**
    21 		/**
    22      * Number of trailing context "lines" to preserve.
    22  		* Number of trailing context "lines" to preserve.
    23      */
    23  		*/
    24     var $_trailing_context_lines = 3;
    24 		var $_trailing_context_lines = 3;
    25 
    25 
    26     /**
    26 		/**
    27      * Prefix for inserted text.
    27  		* Prefix for inserted text.
    28      */
    28  		*/
    29     var $_ins_prefix = "<!-- Start added text -->\n<tr><td style='width: 0px;'>+</td><td class=\"diff-added\" style='width: 100%;'>";
    29 		var $_ins_prefix = "<!-- Start added text -->\n<tr><td style='width: 0px;'>+</td><td class=\"diff-added\" style='width: 100%;'>";
    30 
    30 
    31     /**
    31 		/**
    32      * Suffix for inserted text.
    32  		* Suffix for inserted text.
    33      */
    33  		*/
    34     var $_ins_suffix = "</td></tr>\n<!-- End added text -->\n\n";
    34 		var $_ins_suffix = "</td></tr>\n<!-- End added text -->\n\n";
    35 
    35 
    36     /**
    36 		/**
    37      * Prefix for deleted text.
    37  		* Prefix for deleted text.
    38      */
    38  		*/
    39     var $_del_prefix = "<!-- Start deleted text -->\n<tr><td style='width: 0px;'>-</td><td class=\"diff-deleted\" style='width: 100%;'>";
    39 		var $_del_prefix = "<!-- Start deleted text -->\n<tr><td style='width: 0px;'>-</td><td class=\"diff-deleted\" style='width: 100%;'>";
    40 
    40 
    41     /**
    41 		/**
    42      * Suffix for deleted text.
    42  		* Suffix for deleted text.
    43      */
    43  		*/
    44     var $_del_suffix = "</td></tr>\n<!-- End deleted text -->\n\n";
    44 		var $_del_suffix = "</td></tr>\n<!-- End deleted text -->\n\n";
    45 
    45 
    46     /**
    46 		/**
    47      * Header for each change block.
    47  		* Header for each change block.
    48      */
    48  		*/
    49     var $_block_header = '';
    49 		var $_block_header = '';
    50 
    50 
    51     /**
    51 		/**
    52      * What are we currently splitting on? Used to recurse to show word-level
    52  		* What are we currently splitting on? Used to recurse to show word-level
    53      * changes.
    53  		* changes.
    54      */
    54  		*/
    55     var $_split_level = 'lines';
    55 		var $_split_level = 'lines';
    56     
    56 		
    57     function _blockHeader($xbeg, $xlen, $ybeg, $ylen)
    57 		function _blockHeader($xbeg, $xlen, $ybeg, $ylen)
    58     {
    58 		{
    59       return "<!-- Start block -->\n<tr><td colspan='2' class='diff-block'>Line $xbeg: {$this->_block_header}</td></tr>";
    59 			return "<!-- Start block -->\n<tr><td colspan='2' class='diff-block'>Line $xbeg: {$this->_block_header}</td></tr>";
    60     }
    60 		}
    61 
    61 
    62     function _startBlock($header)
    62 		function _startBlock($header)
    63     {
    63 		{
    64         return $header;
    64 				return $header;
    65     }
    65 		}
    66 
    66 
    67     function _lines($lines, $prefix = ' ', $encode = true)
    67 		function _lines($lines, $prefix = ' ', $encode = true)
    68     {
    68 		{
    69         if ($encode) {
    69 				if ($encode) {
    70             array_walk($lines, array(&$this, '_encode'));
    70 						array_walk($lines, array(&$this, '_encode'));
    71         }
    71 				}
    72 
    72 
    73         if ($this->_split_level == 'words') {
    73 				if ($this->_split_level == 'words') {
    74             return implode('', $lines);
    74 						return implode('', $lines);
    75         } else {
    75 				} else {
    76             return implode("<br />", $lines) . "\n";
    76 						return implode("<br />", $lines) . "\n";
    77         }
    77 				}
    78     }
    78 		}
    79 
    79 
    80     function _added($lines)
    80 		function _added($lines)
    81     {
    81 		{
    82         array_walk($lines, array(&$this, '_encode'));
    82 				array_walk($lines, array(&$this, '_encode'));
    83         $lines[0] = $this->_ins_prefix . $lines[0];
    83 				$lines[0] = $this->_ins_prefix . $lines[0];
    84         $lines[count($lines) - 1] .= $this->_ins_suffix;
    84 				$lines[count($lines) - 1] .= $this->_ins_suffix;
    85         return $this->_lines($lines, ' ', false);
    85 				return $this->_lines($lines, ' ', false);
    86     }
    86 		}
    87 
    87 
    88     function _deleted($lines, $words = false)
    88 		function _deleted($lines, $words = false)
    89     {
    89 		{
    90         array_walk($lines, array(&$this, '_encode'));
    90 				array_walk($lines, array(&$this, '_encode'));
    91         $lines[0] = $this->_del_prefix . $lines[0];
    91 				$lines[0] = $this->_del_prefix . $lines[0];
    92         $lines[count($lines) - 1] .= $this->_del_suffix;
    92 				$lines[count($lines) - 1] .= $this->_del_suffix;
    93         return $this->_lines($lines, ' ', false);
    93 				return $this->_lines($lines, ' ', false);
    94     }
    94 		}
    95     
    95 		
    96     function _context($lines)
    96 		function _context($lines)
    97     {
    97 		{
    98         return "<!-- Start context -->\n<tr><td></td><td class=\"diff-context\">".$this->_lines($lines).'</td></tr>'."\n<!-- End context -->\n\n";
    98 				return "<!-- Start context -->\n<tr><td></td><td class=\"diff-context\">".$this->_lines($lines).'</td></tr>'."\n<!-- End context -->\n\n";
    99     }
    99 		}
   100 
   100 
   101     function _changed($orig, $final)
   101 		function _changed($orig, $final)
   102     {
   102 		{
   103         /* If we've already split on words, don't try to do so again - just display. */ 
   103 				/* If we've already split on words, don't try to do so again - just display. */ 
   104         if ($this->_split_level == 'words') {
   104 				if ($this->_split_level == 'words') {
   105             $prefix = '';
   105 						$prefix = '';
   106             while ($orig[0] !== false && $final[0] !== false &&
   106 						while ($orig[0] !== false && $final[0] !== false &&
   107                    substr($orig[0], 0, 1) == ' ' &&
   107  									substr($orig[0], 0, 1) == ' ' &&
   108                    substr($final[0], 0, 1) == ' ') {
   108  									substr($final[0], 0, 1) == ' ') {
   109                 $prefix .= substr($orig[0], 0, 1);
   109 								$prefix .= substr($orig[0], 0, 1);
   110                 $orig[0] = substr($orig[0], 1);
   110 								$orig[0] = substr($orig[0], 1);
   111                 $final[0] = substr($final[0], 1);
   111 								$final[0] = substr($final[0], 1);
   112             }
   112 						}
   113             $ret = $prefix . $this->_deleted($orig) . $this->_added($final) . "\n";
   113 						$ret = $prefix . $this->_deleted($orig) . $this->_added($final) . "\n";
   114             //echo 'DEBUG:<pre>'.htmlspecialchars($ret).'</pre>';
   114 						//echo 'DEBUG:<pre>'.htmlspecialchars($ret).'</pre>';
   115             return $ret;
   115 						return $ret;
   116         }
   116 				}
   117 
   117 
   118         $text1 = implode("\n", $orig);
   118 				$text1 = implode("\n", $orig);
   119         $text2 = implode("\n", $final);
   119 				$text2 = implode("\n", $final);
   120 
   120 
   121         /* Non-printing newline marker. */
   121 				/* Non-printing newline marker. */
   122         $nl = "\0";
   122 				$nl = "\0";
   123 
   123 
   124         /* We want to split on word boundaries, but we need to
   124 				/* We want to split on word boundaries, but we need to
   125          * preserve whitespace as well. Therefore we split on words,
   125  				* preserve whitespace as well. Therefore we split on words,
   126          * but include all blocks of whitespace in the wordlist. */
   126  				* but include all blocks of whitespace in the wordlist. */
   127         $diff = &new Text_Diff($this->_splitOnWords($text1, $nl),
   127 				$diff = &new Text_Diff($this->_splitOnWords($text1, $nl),
   128                                $this->_splitOnWords($text2, $nl));
   128  															$this->_splitOnWords($text2, $nl));
   129 
   129 
   130         /* Get the diff in inline format. */
   130 				/* Get the diff in inline format. */
   131         $renderer = &new Text_Diff_Renderer_inline(array_merge($this->getParams(),
   131 				$renderer = &new Text_Diff_Renderer_inline(array_merge($this->getParams(),
   132                                                                array('split_level' => 'words')));
   132  																															array('split_level' => 'words')));
   133 
   133 
   134         /* Run the diff and get the output. */
   134 				/* Run the diff and get the output. */
   135         $ret = str_replace($nl, "<br />", $renderer->render($diff));
   135 				$ret = str_replace($nl, "<br />", $renderer->render($diff));
   136         //echo 'DEBUG:<pre>'.htmlspecialchars($ret).'</pre>';
   136 				//echo 'DEBUG:<pre>'.htmlspecialchars($ret).'</pre>';
   137         return $ret . "\n";
   137 				return $ret . "\n";
   138     }
   138 		}
   139 
   139 
   140     function _splitOnWords($string, $newlineEscape = "<br />")
   140 		function _splitOnWords($string, $newlineEscape = "<br />")
   141     {
   141 		{
   142         $words = array();
   142 				$words = array();
   143         $length = strlen($string);
   143 				$length = strlen($string);
   144         $pos = 0;
   144 				$pos = 0;
   145 
   145 
   146         while ($pos < $length) {
   146 				while ($pos < $length) {
   147             // Eat a word with any preceding whitespace.
   147 						// Eat a word with any preceding whitespace.
   148             $spaces = strspn(substr($string, $pos), " \n");
   148 						$spaces = strspn(substr($string, $pos), " \n");
   149             $nextpos = strcspn(substr($string, $pos + $spaces), " \n");
   149 						$nextpos = strcspn(substr($string, $pos + $spaces), " \n");
   150             $words[] = str_replace("\n", $newlineEscape, substr($string, $pos, $spaces + $nextpos));
   150 						$words[] = str_replace("\n", $newlineEscape, substr($string, $pos, $spaces + $nextpos));
   151             $pos += $spaces + $nextpos;
   151 						$pos += $spaces + $nextpos;
   152         }
   152 				}
   153 
   153 
   154         return $words;
   154 				return $words;
   155     }
   155 		}
   156 
   156 
   157     function _encode(&$string)
   157 		function _encode(&$string)
   158     {
   158 		{
   159         $string = htmlspecialchars($string);
   159 				$string = htmlspecialchars($string);
   160     }
   160 		}
   161     
   161 		
   162     /**
   162 		/**
   163      * Renders a diff.
   163  		* Renders a diff.
   164      *
   164  		*
   165      * @param Text_Diff $diff  A Text_Diff object.
   165  		* @param Text_Diff $diff  A Text_Diff object.
   166      *
   166  		*
   167      * @return string  The formatted output.
   167  		* @return string  The formatted output.
   168      */
   168  		*/
   169     
   169 		
   170     function render($diff)
   170 		function render($diff)
   171     {
   171 		{
   172         $xi = $yi = 1;
   172 				$xi = $yi = 1;
   173         $block = false;
   173 				$block = false;
   174         $context = array();
   174 				$context = array();
   175 
   175 
   176         $nlead = $this->_leading_context_lines;
   176 				$nlead = $this->_leading_context_lines;
   177         $ntrail = $this->_trailing_context_lines;
   177 				$ntrail = $this->_trailing_context_lines;
   178 
   178 
   179         $output = $this->_startDiff();
   179 				$output = $this->_startDiff();
   180 
   180 
   181         $diffs = $diff->getDiff();
   181 				$diffs = $diff->getDiff();
   182         foreach ($diffs as $i => $edit) {
   182 				foreach ($diffs as $i => $edit) {
   183             if (is_a($edit, 'Text_Diff_Op_copy')) {
   183 						if (is_a($edit, 'Text_Diff_Op_copy')) {
   184                 if (is_array($block)) {
   184 								if (is_array($block)) {
   185                     $keep = $i == count($diffs) - 1 ? $ntrail : $nlead + $ntrail;
   185 										$keep = $i == count($diffs) - 1 ? $ntrail : $nlead + $ntrail;
   186                     if (count($edit->orig) <= $keep) {
   186 										if (count($edit->orig) <= $keep) {
   187                         $block[] = $edit;
   187 												$block[] = $edit;
   188                     } else {
   188 										} else {
   189                         if ($ntrail) {
   189 												if ($ntrail) {
   190                             $context = array_slice($edit->orig, 0, $ntrail);
   190 														$context = array_slice($edit->orig, 0, $ntrail);
   191                             $block[] = &new Text_Diff_Op_copy($context);
   191 														$block[] = &new Text_Diff_Op_copy($context);
   192                         }
   192 												}
   193                         $bk = $this->_block($x0, $ntrail + $xi - $x0,
   193 												$bk = $this->_block($x0, $ntrail + $xi - $x0,
   194                                             $y0, $ntrail + $yi - $y0,
   194 																						$y0, $ntrail + $yi - $y0,
   195                                             $block);
   195 																						$block);
   196                         $output .= $bk;
   196 												$output .= $bk;
   197                         $block = false;
   197 												$block = false;
   198                     }
   198 										}
   199                 }
   199 								}
   200                 $context = $edit->orig;
   200 								$context = $edit->orig;
   201             } else {
   201 						} else {
   202                 if (!is_array($block)) {
   202 								if (!is_array($block)) {
   203                     $context = array_slice($context, count($context) - $nlead);
   203 										$context = array_slice($context, count($context) - $nlead);
   204                     $x0 = $xi - count($context);
   204 										$x0 = $xi - count($context);
   205                     $y0 = $yi - count($context);
   205 										$y0 = $yi - count($context);
   206                     $block = array();
   206 										$block = array();
   207                     if ($context) {
   207 										if ($context) {
   208                         $block[] = &new Text_Diff_Op_copy($context);
   208 												$block[] = &new Text_Diff_Op_copy($context);
   209                     }
   209 										}
   210                 }
   210 								}
   211                 $block[] = $edit;
   211 								$block[] = $edit;
   212             }
   212 						}
   213 
   213 
   214             if ($edit->orig) {
   214 						if ($edit->orig) {
   215                 $xi += count($edit->orig);
   215 								$xi += count($edit->orig);
   216             }
   216 						}
   217             if ($edit->final) {
   217 						if ($edit->final) {
   218                 $yi += count($edit->final);
   218 								$yi += count($edit->final);
   219             }
   219 						}
   220         }
   220 				}
   221 
   221 
   222         if (is_array($block)) {
   222 				if (is_array($block)) {
   223             $bk = $this->_block($x0, $xi - $x0,
   223 						$bk = $this->_block($x0, $xi - $x0,
   224                                 $y0, $yi - $y0,
   224 																$y0, $yi - $y0,
   225                                 $block);
   225 																$block);
   226             $output .= $bk;
   226 						$output .= $bk;
   227         }
   227 				}
   228 
   228 
   229         $final = $output . $this->_endDiff();
   229 				$final = $output . $this->_endDiff();
   230         if ($final == '') $final = '<tr><td class="diff-block">No differences.</td></tr>';
   230 				if ($final == '') $final = '<tr><td class="diff-block">No differences.</td></tr>';
   231         //$final = preg_replace('#('.preg_quote($this->_ins_suffix).'|'.preg_quote($this->_del_suffix).')(.+?)('.preg_quote($this->_ins_prefix).'|'.preg_quote($this->_ins_suffix).')#', '\\1<tr><td></td><td class="diff-context>\\2</td></tr>\\3', $final);
   231 				//$final = preg_replace('#('.preg_quote($this->_ins_suffix).'|'.preg_quote($this->_del_suffix).')(.+?)('.preg_quote($this->_ins_prefix).'|'.preg_quote($this->_ins_suffix).')#', '\\1<tr><td></td><td class="diff-context>\\2</td></tr>\\3', $final);
   232         return '<table class="diff">'.$final.'</table>'."\n\n";
   232 				return '<table class="diff">'.$final.'</table>'."\n\n";
   233     }
   233 		}
   234     
   234 		
   235     function _block($xbeg, $xlen, $ybeg, $ylen, &$edits)
   235 		function _block($xbeg, $xlen, $ybeg, $ylen, &$edits)
   236     {
   236 		{
   237         $output = $this->_startBlock($this->_blockHeader($xbeg, $xlen, $ybeg, $ylen));
   237 				$output = $this->_startBlock($this->_blockHeader($xbeg, $xlen, $ybeg, $ylen));
   238 
   238 
   239         foreach ($edits as $edit) {
   239 				foreach ($edits as $edit) {
   240             switch (strtolower(get_class($edit))) {
   240 						switch (strtolower(get_class($edit))) {
   241             case 'text_diff_op_copy':
   241 						case 'text_diff_op_copy':
   242                 $output .= $this->_context($edit->orig);
   242 								$output .= $this->_context($edit->orig);
   243                 break;
   243 								break;
   244 
   244 
   245             case 'text_diff_op_add':
   245 						case 'text_diff_op_add':
   246                 $output .= $this->_added($edit->final);
   246 								$output .= $this->_added($edit->final);
   247                 break;
   247 								break;
   248 
   248 
   249             case 'text_diff_op_delete':
   249 						case 'text_diff_op_delete':
   250                 $output .= $this->_deleted($edit->orig);
   250 								$output .= $this->_deleted($edit->orig);
   251                 break;
   251 								break;
   252 
   252 
   253             case 'text_diff_op_change':
   253 						case 'text_diff_op_change':
   254                 $output .= $this->_changed($edit->orig, $edit->final);
   254 								$output .= $this->_changed($edit->orig, $edit->final);
   255                 break;
   255 								break;
   256             }
   256 						}
   257         }
   257 				}
   258 
   258 
   259         return $output . $this->_endBlock();
   259 				return $output . $this->_endBlock();
   260     }
   260 		}
   261 
   261 
   262 }
   262 }