Mercurial > hg > rc2
comparison program/lib/Roundcube/rcube_text2html.php @ 0:4681f974d28b
vanilla 1.3.3 distro, I hope
author | Charlie Root |
---|---|
date | Thu, 04 Jan 2018 15:52:31 -0500 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4681f974d28b |
---|---|
1 <?php | |
2 | |
3 /** | |
4 +-----------------------------------------------------------------------+ | |
5 | This file is part of the Roundcube Webmail client | | |
6 | Copyright (C) 2008-2014, The Roundcube Dev Team | | |
7 | | | |
8 | Licensed under the GNU General Public License version 3 or | | |
9 | any later version with exceptions for skins & plugins. | | |
10 | See the README file for a full license statement. | | |
11 | | | |
12 | PURPOSE: | | |
13 | Converts plain text to HTML | | |
14 +-----------------------------------------------------------------------+ | |
15 | Author: Aleksander Machniak <alec@alec.pl> | | |
16 +-----------------------------------------------------------------------+ | |
17 */ | |
18 | |
19 /** | |
20 * Converts plain text to HTML | |
21 * | |
22 * @package Framework | |
23 * @subpackage Utils | |
24 */ | |
25 class rcube_text2html | |
26 { | |
27 /** | |
28 * Contains the HTML content after conversion. | |
29 * | |
30 * @var string $html | |
31 */ | |
32 protected $html; | |
33 | |
34 /** | |
35 * Contains the plain text. | |
36 * | |
37 * @var string $text | |
38 */ | |
39 protected $text; | |
40 | |
41 /** | |
42 * Configuration | |
43 * | |
44 * @var array $config | |
45 */ | |
46 protected $config = array( | |
47 // non-breaking space | |
48 'space' => "\xC2\xA0", | |
49 // enables format=flowed parser | |
50 'flowed' => false, | |
51 // enables delsp=yes parser | |
52 'delsp' => false, | |
53 // enables wrapping for non-flowed text | |
54 'wrap' => true, | |
55 // line-break tag | |
56 'break' => "<br>\n", | |
57 // prefix and suffix (wrapper element) | |
58 'begin' => '<div class="pre">', | |
59 'end' => '</div>', | |
60 // enables links replacement | |
61 'links' => true, | |
62 // string replacer class | |
63 'replacer' => 'rcube_string_replacer', | |
64 // prefix and suffix of unwrappable line | |
65 'nobr_start' => '<span style="white-space:nowrap">', | |
66 'nobr_end' => '</span>', | |
67 ); | |
68 | |
69 | |
70 /** | |
71 * Constructor. | |
72 * | |
73 * If the plain text source string (or file) is supplied, the class | |
74 * will instantiate with that source propagated, all that has | |
75 * to be done it to call get_html(). | |
76 * | |
77 * @param string $source Plain text | |
78 * @param boolean $from_file Indicates $source is a file to pull content from | |
79 * @param array $config Class configuration | |
80 */ | |
81 function __construct($source = '', $from_file = false, $config = array()) | |
82 { | |
83 if (!empty($source)) { | |
84 $this->set_text($source, $from_file); | |
85 } | |
86 | |
87 if (!empty($config) && is_array($config)) { | |
88 $this->config = array_merge($this->config, $config); | |
89 } | |
90 } | |
91 | |
92 /** | |
93 * Loads source text into memory, either from $source string or a file. | |
94 * | |
95 * @param string $source Plain text | |
96 * @param boolean $from_file Indicates $source is a file to pull content from | |
97 */ | |
98 function set_text($source, $from_file = false) | |
99 { | |
100 if ($from_file && file_exists($source)) { | |
101 $this->text = file_get_contents($source); | |
102 } | |
103 else { | |
104 $this->text = $source; | |
105 } | |
106 | |
107 $this->_converted = false; | |
108 } | |
109 | |
110 /** | |
111 * Returns the HTML content. | |
112 * | |
113 * @return string HTML content | |
114 */ | |
115 function get_html() | |
116 { | |
117 if (!$this->_converted) { | |
118 $this->_convert(); | |
119 } | |
120 | |
121 return $this->html; | |
122 } | |
123 | |
124 /** | |
125 * Prints the HTML. | |
126 */ | |
127 function print_html() | |
128 { | |
129 print $this->get_html(); | |
130 } | |
131 | |
132 /** | |
133 * Workhorse function that does actual conversion (calls _converter() method). | |
134 */ | |
135 protected function _convert() | |
136 { | |
137 // Convert TXT to HTML | |
138 $this->html = $this->_converter($this->text); | |
139 $this->_converted = true; | |
140 } | |
141 | |
142 /** | |
143 * Workhorse function that does actual conversion. | |
144 * | |
145 * @param string Plain text | |
146 */ | |
147 protected function _converter($text) | |
148 { | |
149 // make links and email-addresses clickable | |
150 $attribs = array('link_attribs' => array('rel' => 'noreferrer', 'target' => '_blank')); | |
151 $replacer = new $this->config['replacer']($attribs); | |
152 | |
153 if ($this->config['flowed']) { | |
154 $flowed_char = 0x01; | |
155 $delsp = $this->config['delsp']; | |
156 $text = rcube_mime::unfold_flowed($text, chr($flowed_char), $delsp); | |
157 } | |
158 | |
159 // search for patterns like links and e-mail addresses and replace with tokens | |
160 if ($this->config['links']) { | |
161 $text = $replacer->replace($text); | |
162 } | |
163 | |
164 // split body into single lines | |
165 $text = preg_split('/\r?\n/', $text); | |
166 $quote_level = 0; | |
167 $last = null; | |
168 | |
169 // wrap quoted lines with <blockquote> | |
170 for ($n = 0, $cnt = count($text); $n < $cnt; $n++) { | |
171 $flowed = false; | |
172 if ($this->config['flowed'] && ord($text[$n][0]) == $flowed_char) { | |
173 $flowed = true; | |
174 $text[$n] = substr($text[$n], 1); | |
175 } | |
176 | |
177 if ($text[$n][0] == '>' && preg_match('/^(>+ {0,1})+/', $text[$n], $regs)) { | |
178 $q = substr_count($regs[0], '>'); | |
179 $text[$n] = substr($text[$n], strlen($regs[0])); | |
180 $text[$n] = $this->_convert_line($text[$n], $flowed || $this->config['wrap']); | |
181 $_length = strlen(str_replace(' ', '', $text[$n])); | |
182 | |
183 if ($q > $quote_level) { | |
184 if ($last !== null) { | |
185 $text[$last] .= (!$length ? "\n" : '') | |
186 . $replacer->get_replacement($replacer->add( | |
187 str_repeat('<blockquote>', $q - $quote_level))) | |
188 . $text[$n]; | |
189 | |
190 unset($text[$n]); | |
191 } | |
192 else { | |
193 $text[$n] = $replacer->get_replacement($replacer->add( | |
194 str_repeat('<blockquote>', $q - $quote_level))) . $text[$n]; | |
195 | |
196 $last = $n; | |
197 } | |
198 } | |
199 else if ($q < $quote_level) { | |
200 $text[$last] .= (!$length ? "\n" : '') | |
201 . $replacer->get_replacement($replacer->add( | |
202 str_repeat('</blockquote>', $quote_level - $q))) | |
203 . $text[$n]; | |
204 | |
205 unset($text[$n]); | |
206 } | |
207 else { | |
208 $last = $n; | |
209 } | |
210 } | |
211 else { | |
212 $text[$n] = $this->_convert_line($text[$n], $flowed || $this->config['wrap']); | |
213 $q = 0; | |
214 $_length = strlen(str_replace(' ', '', $text[$n])); | |
215 | |
216 if ($quote_level > 0) { | |
217 $text[$last] .= (!$length ? "\n" : '') | |
218 . $replacer->get_replacement($replacer->add( | |
219 str_repeat('</blockquote>', $quote_level))) | |
220 . $text[$n]; | |
221 | |
222 unset($text[$n]); | |
223 } | |
224 else { | |
225 $last = $n; | |
226 } | |
227 } | |
228 | |
229 $quote_level = $q; | |
230 $length = $_length; | |
231 } | |
232 | |
233 if ($quote_level > 0) { | |
234 $text[$last] .= $replacer->get_replacement($replacer->add( | |
235 str_repeat('</blockquote>', $quote_level))); | |
236 } | |
237 | |
238 $text = join("\n", $text); | |
239 | |
240 // colorize signature (up to <sig_max_lines> lines) | |
241 $len = strlen($text); | |
242 $sig_sep = "--" . $this->config['space'] . "\n"; | |
243 $sig_max_lines = rcube::get_instance()->config->get('sig_max_lines', 15); | |
244 | |
245 while (($sp = strrpos($text, $sig_sep, $sp ? -$len+$sp-1 : 0)) !== false) { | |
246 if ($sp == 0 || $text[$sp-1] == "\n") { | |
247 // do not touch blocks with more that X lines | |
248 if (substr_count($text, "\n", $sp) < $sig_max_lines) { | |
249 $text = substr($text, 0, max(0, $sp)) | |
250 .'<span class="sig">'.substr($text, $sp).'</span>'; | |
251 } | |
252 | |
253 break; | |
254 } | |
255 } | |
256 | |
257 // insert url/mailto links and citation tags | |
258 $text = $replacer->resolve($text); | |
259 | |
260 // replace line breaks | |
261 $text = str_replace("\n", $this->config['break'], $text); | |
262 | |
263 return $this->config['begin'] . $text . $this->config['end']; | |
264 } | |
265 | |
266 /** | |
267 * Converts spaces in line of text | |
268 */ | |
269 protected function _convert_line($text, $is_flowed) | |
270 { | |
271 static $table; | |
272 | |
273 if (empty($table)) { | |
274 $table = get_html_translation_table(HTML_SPECIALCHARS); | |
275 unset($table['?']); | |
276 | |
277 // replace some whitespace characters | |
278 $table["\r"] = ''; | |
279 $table["\t"] = ' '; | |
280 } | |
281 | |
282 // skip signature separator | |
283 if ($text == '-- ') { | |
284 return '--' . $this->config['space']; | |
285 } | |
286 | |
287 // replace HTML special and whitespace characters | |
288 $text = strtr($text, $table); | |
289 | |
290 $nbsp = $this->config['space']; | |
291 | |
292 // replace spaces with non-breaking spaces | |
293 if ($is_flowed) { | |
294 $pos = 0; | |
295 $diff = 0; | |
296 $len = strlen($nbsp); | |
297 $copy = $text; | |
298 | |
299 while (($pos = strpos($text, ' ', $pos)) !== false) { | |
300 if ($pos == 0 || $text[$pos-1] == ' ') { | |
301 $copy = substr_replace($copy, $nbsp, $pos + $diff, 1); | |
302 $diff += $len - 1; | |
303 } | |
304 $pos++; | |
305 } | |
306 | |
307 $text = $copy; | |
308 } | |
309 // make the whole line non-breakable if needed | |
310 else if ($text !== '' && preg_match('/[^a-zA-Z0-9_]/', $text)) { | |
311 // use non-breakable spaces to correctly display | |
312 // trailing/leading spaces and multi-space inside | |
313 $text = str_replace(' ', $nbsp, $text); | |
314 // wrap in nobr element, so it's not wrapped on e.g. - or / | |
315 $text = $this->config['nobr_start'] . $text . $this->config['nobr_end']; | |
316 } | |
317 | |
318 return $text; | |
319 } | |
320 } |