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 | b6a96bdd6b29 |
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 } |
