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