1<?php
2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
3//
4// All Rights Reserved. See copyright.txt for details and a complete list of authors.
5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
6// $Id$
7
8/**
9 * HTML diff renderer.
10 * This class renders the diff of an HTML page with best effort.
11 */
12
13include_once("Renderer.php");
14
15class Text_Diff_Renderer_htmldiff extends Tiki_Text_Diff_Renderer
16{
17	public function __construct($context_lines = 0, $words = 0)
18	{
19		$this->_leading_context_lines = $context_lines;
20		$this->_trailing_context_lines = $context_lines;
21		$this->_words = $words;
22	}
23
24	protected function _startDiff()
25	{
26		ob_start();
27		$this->original = [];
28		$this->final = [];
29		$this->n = 0;
30		$this->rspan = false;
31		$this->lspan = false;
32		//$this->tracked_tags = array ("table","ul","div");
33		$this->tracked_tags = ["table", "ul"];
34	}
35
36	protected function _endDiff()
37	{
38		for ($i = 0; $i <= $this->n; $i++) {
39			if ($this->original[$i] != "" and $this->final[$i] != "") {
40				echo "<tr><td width='50%' colspan='2' style='vertical-align:top'>" . $this->original[$i] . "</td><td width='50%' colspan='2' style='vertical-align:top'>" . $this->final[$i] . "</td></tr>\n";
41			}
42		}
43		//echo '</table>';
44		$val = ob_get_contents();
45		ob_end_clean();
46		return $val;
47	}
48
49	protected function _blockHeader($xbeg, $xlen, $ybeg, $ylen)
50	{
51		return "$xbeg,$xlen,$ybeg,$ylen";
52	}
53
54	protected function _startBlock($header)
55	{
56	}
57
58	protected function _endBlock()
59	{
60	}
61
62	protected function _insert_tag($line, $tag, &$span)
63	{
64		$string = "";
65		if ($line != '') {
66			if (strstr($line, "<") === false) {
67				if ($span === false) {
68					$string .= "<span class='$tag'>";
69					$span = true;
70				}
71				$string .= $line;
72			} else {
73				if ($span === true) {
74					$string .= "</span class='fin'>";
75					$span = false;
76				}
77				if (strstr($line, "class=") === false) {
78					$string .= preg_replace("#<([^/> ]+)(.*[^/]?)?>#", "<$1 class='$tag' $2>", $line);
79					$string = preg_replace("#<br class='(.*)'\s*/>#", "<span class='$1'>&crarr;</span><br class='$1' />", $string);
80				} else {
81					$string .= preg_replace("#<([^/> ]+)(.*)class=[\"']?([^\"']+)[\"']?(.*[^/]?)?>#", "<$1$2 class='$3 $tag' $4>", $line);
82				}
83			}
84		}
85		return $string;
86	}
87
88	protected function _count_tags($line, $version)
89	{
90
91		preg_match("#<(/?)([^ >]+)#", $line, $out);
92		if (count($out) > 1 && in_array($out[2], $this->tracked_tags)) {
93			if (isset($this->tags[$version][$out[2]])) {
94				if ($out[1] == '/') {
95					$this->tags[$version][$out[2]]--;
96				} else {
97					$this->tags[$version][$out[2]]++;
98				}
99			}
100		}
101	}
102
103	protected function _can_break($line)
104	{
105
106		if (preg_match("#<(p|h\d|br)#", $line) == 0) {
107			return false;
108		}
109
110		if (isset($this->tags)) {
111			foreach ($this->tags as $v) {
112				foreach ($v as $tag) {
113					if ($tag != 0) {
114						return false;
115					}
116				}
117			}
118		}
119		return true;
120	}
121
122	protected function _lines($lines, $prefix = '', $suffix = '', $type = '')
123	{
124		static $context = 0;
125
126		switch ($type) {
127			case 'context':
128				foreach ($lines as $line) {
129					if ($context == 0 and $this->_can_break($line)) {
130						$context = 1;
131						$this->n++;
132					}
133
134					$this->_count_tags($line, 'original');
135					$this->_count_tags($line, 'final');
136					if ($this->lspan === true) {
137						$this->original[$this->n] .= "</span>";
138						$this->lspan = false;
139					}
140					if ($this->rspan === true) {
141						$this->final[$this->n] .= "</span>";
142						$this->rspan = false;
143					}
144					if (! isset($this->original[$this->n])) {
145						$this->original[$this->n] = '';
146					}
147					$this->original[$this->n] .= "$line";
148					if (! isset($this->final[$this->n])) {
149						$this->final[$this->n] = '';
150					}
151					$this->final[$this->n] .= "$line";
152				}
153				break;
154			case 'change-added':
155			case 'added':
156				foreach ($lines as $line) {
157					if ($line != '') {
158						$this->_count_tags($line, 'final');
159						$this->final[$this->n] .= $this->_insert_tag($line, 'diffadded', $this->rspan);
160						$context = 0;
161					}
162				}
163				break;
164			case 'deleted':
165			case 'change-deleted':
166				foreach ($lines as $line) {
167					if ($line != '') {
168						$this->_count_tags($line, 'original');
169						$this->original[$this->n] .= $this->_insert_tag($line, 'diffdeleted', $this->lspan);
170						$context = 0;
171					}
172				}
173				break;
174		}
175	}
176
177	protected function _context($lines)
178	{
179		$this->_lines($lines, '', '', 'context');
180	}
181
182	protected function _added($lines, $changemode = false)
183	{
184		if ($changemode) {
185			$this->_lines($lines, '+', '', 'change-added');
186		} else {
187			$this->_lines($lines, '+', '', 'added');
188		}
189	}
190
191	protected function _deleted($lines, $changemode = false)
192	{
193		if ($changemode) {
194			$this->_lines($lines, '-', '', 'change-deleted');
195		} else {
196			$this->_lines($lines, '-', '', 'deleted');
197		}
198	}
199
200	protected function _changed($orig, $final)
201	{
202		$this->_deleted($orig, true);
203		$this->_added($final, true);
204	}
205}
206