1<?php
2/**
3 * A class to render Diffs in different formats.
4 *
5 * This class renders the diff in classic diff format. It is intended that
6 * this class be customized via inheritance, to obtain fancier outputs.
7 *
8 * Copyright 2004-2017 Horde LLC (http://www.horde.org/)
9 *
10 * See the enclosed file COPYING for license information (LGPL). If you did
11 * not receive this file, see http://www.horde.org/licenses/lgpl21.
12 *
13 * @package Text_Diff
14 */
15
16// Disallow direct access to this file for security reasons
17if(!defined("IN_MYBB"))
18{
19	die("Direct initialization of this file is not allowed.<br /><br />Please make sure IN_MYBB is defined.");
20}
21
22class Horde_Text_Diff_Renderer
23{
24    /**
25     * Number of leading context "lines" to preserve.
26     *
27     * This should be left at zero for this class, but subclasses may want to
28     * set this to other values.
29     */
30    protected $_leading_context_lines = 0;
31
32    /**
33     * Number of trailing context "lines" to preserve.
34     *
35     * This should be left at zero for this class, but subclasses may want to
36     * set this to other values.
37     */
38    protected $_trailing_context_lines = 0;
39
40    /**
41     * Constructor.
42     */
43    public function __construct($params = array())
44    {
45        foreach ($params as $param => $value) {
46            $v = '_' . $param;
47            if (isset($this->$v)) {
48                $this->$v = $value;
49            }
50        }
51    }
52
53    /**
54     * Get any renderer parameters.
55     *
56     * @return array  All parameters of this renderer object.
57     */
58    public function getParams()
59    {
60        $params = array();
61        foreach (get_object_vars($this) as $k => $v) {
62            if ($k[0] == '_') {
63                $params[substr($k, 1)] = $v;
64            }
65        }
66
67        return $params;
68    }
69
70    /**
71     * Renders a diff.
72     *
73     * @param Horde_Text_Diff $diff  A Horde_Text_Diff object.
74     *
75     * @return string  The formatted output.
76     */
77    public function render($diff)
78    {
79        $xi = $yi = 1;
80        $block = false;
81        $context = array();
82
83        $nlead = $this->_leading_context_lines;
84        $ntrail = $this->_trailing_context_lines;
85
86        $output = $this->_startDiff();
87
88        $diffs = $diff->getDiff();
89        foreach ($diffs as $i => $edit) {
90            /* If these are unchanged (copied) lines, and we want to keep
91             * leading or trailing context lines, extract them from the copy
92             * block. */
93            if ($edit instanceof Horde_Text_Diff_Op_Copy) {
94                /* Do we have any diff blocks yet? */
95                if (is_array($block)) {
96                    /* How many lines to keep as context from the copy
97                     * block. */
98                    $keep = $i == count($diffs) - 1 ? $ntrail : $nlead + $ntrail;
99                    if (count($edit->orig) <= $keep) {
100                        /* We have less lines in the block than we want for
101                         * context => keep the whole block. */
102                        $block[] = $edit;
103                    } else {
104                        if ($ntrail) {
105                            /* Create a new block with as many lines as we need
106                             * for the trailing context. */
107                            $context = array_slice($edit->orig, 0, $ntrail);
108                            $block[] = new Horde_Text_Diff_Op_Copy($context);
109                        }
110                        /* @todo */
111                        $output .= $this->_block($x0, $ntrail + $xi - $x0,
112                                                 $y0, $ntrail + $yi - $y0,
113                                                 $block);
114                        $block = false;
115                    }
116                }
117                /* Keep the copy block as the context for the next block. */
118                $context = $edit->orig;
119            } else {
120                /* Don't we have any diff blocks yet? */
121                if (!is_array($block)) {
122                    /* Extract context lines from the preceding copy block. */
123                    $context = array_slice($context, count($context) - $nlead);
124                    $x0 = $xi - count($context);
125                    $y0 = $yi - count($context);
126                    $block = array();
127                    if ($context) {
128                        $block[] = new Horde_Text_Diff_Op_Copy($context);
129                    }
130                }
131                $block[] = $edit;
132            }
133
134            if ($edit->orig) {
135                $xi += count($edit->orig);
136            }
137            if ($edit->final) {
138                $yi += count($edit->final);
139            }
140        }
141
142        if (is_array($block)) {
143            $output .= $this->_block($x0, $xi - $x0,
144                                     $y0, $yi - $y0,
145                                     $block);
146        }
147
148        return $output . $this->_endDiff();
149    }
150
151    protected function _block($xbeg, $xlen, $ybeg, $ylen, &$edits)
152    {
153        $output = $this->_startBlock($this->_blockHeader($xbeg, $xlen, $ybeg, $ylen));
154
155        foreach ($edits as $edit) {
156            switch (get_class($edit)) {
157            case 'Horde_Text_Diff_Op_Copy':
158                $output .= $this->_context($edit->orig);
159                break;
160
161            case 'Horde_Text_Diff_Op_Add':
162                $output .= $this->_added($edit->final);
163                break;
164
165            case 'Horde_Text_Diff_Op_Delete':
166                $output .= $this->_deleted($edit->orig);
167                break;
168
169            case 'Horde_Text_Diff_Op_Change':
170                $output .= $this->_changed($edit->orig, $edit->final);
171                break;
172            }
173        }
174
175        return $output . $this->_endBlock();
176    }
177
178    protected function _startDiff()
179    {
180        return '';
181    }
182
183    protected function _endDiff()
184    {
185        return '';
186    }
187
188    protected function _blockHeader($xbeg, $xlen, $ybeg, $ylen)
189    {
190        if ($xlen > 1) {
191            $xbeg .= ',' . ($xbeg + $xlen - 1);
192        }
193        if ($ylen > 1) {
194            $ybeg .= ',' . ($ybeg + $ylen - 1);
195        }
196
197        // this matches the GNU Diff behaviour
198        if ($xlen && !$ylen) {
199            $ybeg--;
200        } elseif (!$xlen) {
201            $xbeg--;
202        }
203
204        return $xbeg . ($xlen ? ($ylen ? 'c' : 'd') : 'a') . $ybeg;
205    }
206
207    protected function _startBlock($header)
208    {
209        return $header . "\n";
210    }
211
212    protected function _endBlock()
213    {
214        return '';
215    }
216
217    protected function _lines($lines, $prefix = ' ')
218    {
219        return $prefix . implode("\n$prefix", $lines) . "\n";
220    }
221
222    protected function _context($lines)
223    {
224        return $this->_lines($lines, '  ');
225    }
226
227    protected function _added($lines)
228    {
229        return $this->_lines($lines, '> ');
230    }
231
232    protected function _deleted($lines)
233    {
234        return $this->_lines($lines, '< ');
235    }
236
237    protected function _changed($orig, $final)
238    {
239        return $this->_deleted($orig) . "---\n" . $this->_added($final);
240    }
241}
242