Page MenuHomePhorge

No OneTemporary

Size
17 KB
Referenced Files
None
Subscribers
None
diff --git a/program/lib/Roundcube/rcube_text2html.php b/program/lib/Roundcube/rcube_text2html.php
index 2ffe5301d..5c240d515 100644
--- a/program/lib/Roundcube/rcube_text2html.php
+++ b/program/lib/Roundcube/rcube_text2html.php
@@ -1,320 +1,316 @@
<?php
/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2008-2014, The Roundcube Dev Team |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Converts plain text to HTML |
+-----------------------------------------------------------------------+
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
/**
* Converts plain text to HTML
*
* @package Framework
* @subpackage Utils
*/
class rcube_text2html
{
/**
* Contains the HTML content after conversion.
*
* @var string $html
*/
protected $html;
/**
* Contains the plain text.
*
* @var string $text
*/
protected $text;
/**
* Configuration
*
* @var array $config
*/
protected $config = array(
// non-breaking space
'space' => "\xC2\xA0",
- // word-joiner (zero-width no-break space)
- // 'wordjoiner' => "\xEF\xBB\xBF", // U+2060
- // use deprecated U+FEFF character because of webkit issue with displaying U+2060 (#1490353)
- 'wordjoiner' => "\xEF\xBB\xBF", // U+FEFF
// enables format=flowed parser
'flowed' => false,
// enables wrapping for non-flowed text
'wrap' => true,
// line-break tag
'break' => "<br>\n",
// prefix and suffix (wrapper element)
'begin' => '<div class="pre">',
'end' => '</div>',
// enables links replacement
'links' => true,
// string replacer class
'replacer' => 'rcube_string_replacer',
+ // prefix and suffix of unwrappable line
+ 'nobr_start' => '<span style="white-space:nowrap">',
+ 'nobr_end' => '</span>',
);
/**
* Constructor.
*
* If the plain text source string (or file) is supplied, the class
* will instantiate with that source propagated, all that has
* to be done it to call get_html().
*
* @param string $source Plain text
* @param boolean $from_file Indicates $source is a file to pull content from
* @param array $config Class configuration
*/
function __construct($source = '', $from_file = false, $config = array())
{
if (!empty($source)) {
$this->set_text($source, $from_file);
}
if (!empty($config) && is_array($config)) {
$this->config = array_merge($this->config, $config);
}
}
/**
* Loads source text into memory, either from $source string or a file.
*
* @param string $source Plain text
* @param boolean $from_file Indicates $source is a file to pull content from
*/
function set_text($source, $from_file = false)
{
if ($from_file && file_exists($source)) {
$this->text = file_get_contents($source);
}
else {
$this->text = $source;
}
$this->_converted = false;
}
/**
* Returns the HTML content.
*
* @return string HTML content
*/
function get_html()
{
if (!$this->_converted) {
$this->_convert();
}
return $this->html;
}
/**
* Prints the HTML.
*/
function print_html()
{
print $this->get_html();
}
/**
* Workhorse function that does actual conversion (calls _converter() method).
*/
protected function _convert()
{
// Convert TXT to HTML
$this->html = $this->_converter($this->text);
$this->_converted = true;
}
/**
* Workhorse function that does actual conversion.
*
* @param string Plain text
*/
protected function _converter($text)
{
// make links and email-addresses clickable
$attribs = array('link_attribs' => array('rel' => 'noreferrer', 'target' => '_blank'));
$replacer = new $this->config['replacer']($attribs);
if ($this->config['flowed']) {
$flowed_char = 0x01;
$text = rcube_mime::unfold_flowed($text, chr($flowed_char));
}
// search for patterns like links and e-mail addresses and replace with tokens
if ($this->config['links']) {
$text = $replacer->replace($text);
}
// split body into single lines
$text = preg_split('/\r?\n/', $text);
$quote_level = 0;
$last = null;
// wrap quoted lines with <blockquote>
for ($n = 0, $cnt = count($text); $n < $cnt; $n++) {
$flowed = false;
if ($this->config['flowed'] && ord($text[$n][0]) == $flowed_char) {
$flowed = true;
$text[$n] = substr($text[$n], 1);
}
if ($text[$n][0] == '>' && preg_match('/^(>+ {0,1})+/', $text[$n], $regs)) {
$q = substr_count($regs[0], '>');
$text[$n] = substr($text[$n], strlen($regs[0]));
$text[$n] = $this->_convert_line($text[$n], $flowed || $this->config['wrap']);
$_length = strlen(str_replace(' ', '', $text[$n]));
if ($q > $quote_level) {
if ($last !== null) {
$text[$last] .= (!$length ? "\n" : '')
. $replacer->get_replacement($replacer->add(
str_repeat('<blockquote>', $q - $quote_level)))
. $text[$n];
unset($text[$n]);
}
else {
$text[$n] = $replacer->get_replacement($replacer->add(
str_repeat('<blockquote>', $q - $quote_level))) . $text[$n];
$last = $n;
}
}
else if ($q < $quote_level) {
$text[$last] .= (!$length ? "\n" : '')
. $replacer->get_replacement($replacer->add(
str_repeat('</blockquote>', $quote_level - $q)))
. $text[$n];
unset($text[$n]);
}
else {
$last = $n;
}
}
else {
$text[$n] = $this->_convert_line($text[$n], $flowed || $this->config['wrap']);
$q = 0;
$_length = strlen(str_replace(' ', '', $text[$n]));
if ($quote_level > 0) {
$text[$last] .= (!$length ? "\n" : '')
. $replacer->get_replacement($replacer->add(
str_repeat('</blockquote>', $quote_level)))
. $text[$n];
unset($text[$n]);
}
else {
$last = $n;
}
}
$quote_level = $q;
$length = $_length;
}
if ($quote_level > 0) {
$text[$last] .= $replacer->get_replacement($replacer->add(
str_repeat('</blockquote>', $quote_level)));
}
$text = join("\n", $text);
// colorize signature (up to <sig_max_lines> lines)
$len = strlen($text);
$sig_sep = "--" . $this->config['space'] . "\n";
$sig_max_lines = rcube::get_instance()->config->get('sig_max_lines', 15);
while (($sp = strrpos($text, $sig_sep, $sp ? -$len+$sp-1 : 0)) !== false) {
if ($sp == 0 || $text[$sp-1] == "\n") {
// do not touch blocks with more that X lines
if (substr_count($text, "\n", $sp) < $sig_max_lines) {
$text = substr($text, 0, max(0, $sp))
.'<span class="sig">'.substr($text, $sp).'</span>';
}
break;
}
}
// insert url/mailto links and citation tags
$text = $replacer->resolve($text);
// replace line breaks
$text = str_replace("\n", $this->config['break'], $text);
return $this->config['begin'] . $text . $this->config['end'];
}
/**
* Converts spaces in line of text
*/
protected function _convert_line($text, $is_flowed)
{
static $table;
if (empty($table)) {
$table = get_html_translation_table(HTML_SPECIALCHARS);
unset($table['?']);
}
// skip signature separator
if ($text == '-- ') {
return '--' . $this->config['space'];
}
// replace HTML special characters
$text = strtr($text, $table);
- $nbsp = $this->config['space'];
- $nobr = $this->config['wordjoiner'];
-
// replace some whitespace characters
$text = str_replace(array("\r", "\t"), array('', ' '), $text);
+ $nbsp = $this->config['space'];
+
// replace spaces with non-breaking spaces
if ($is_flowed) {
$pos = 0;
$diff = 0;
$len = strlen($nbsp);
$copy = $text;
while (($pos = strpos($text, ' ', $pos)) !== false) {
if ($pos == 0 || $text[$pos-1] == ' ') {
$copy = substr_replace($copy, $nbsp, $pos + $diff, 1);
$diff += $len - 1;
}
$pos++;
}
$text = $copy;
}
- // make the whole line non-breakable
- else {
- $repl = array(
- ' ' => $nbsp,
- '-' => $nobr . '-' . $nobr,
- '/' => $nobr . '/',
- );
-
- $text = str_replace(array_keys($repl), array_values($repl), $text);
+ // make the whole line non-breakable if needed
+ else if ($text !== '' && preg_match('/[^a-zA-Z0-9_]/', $text)) {
+ // use non-breakable spaces to correctly display
+ // trailing/leading spaces and multi-space inside
+ $text = str_replace(' ', $nbsp, $text);
+ // wrap in nobr element, so it's not wrapped on e.g. - or /
+ $text = $this->config['nobr_start'] . $text . $this->config['nobr_end'];
}
return $text;
}
}
diff --git a/tests/Framework/Text2Html.php b/tests/Framework/Text2Html.php
index 771e14703..a08f9c036 100644
--- a/tests/Framework/Text2Html.php
+++ b/tests/Framework/Text2Html.php
@@ -1,94 +1,98 @@
<?php
/**
* Test class to test rcube_text2html class
*
* @package Tests
*/
class Framework_Text2Html extends PHPUnit_Framework_TestCase
{
/**
* Data for test_text2html()
*/
function data_text2html()
{
$options = array(
'begin' => '',
'end' => '',
'break' => '<br>',
'links' => false,
'flowed' => false,
'wrap' => false,
'space' => '_', // replace UTF-8 non-breaking space for simpler testing
+ 'nobr_start' => '>',
+ 'nobr_end' => '<',
);
- $data[] = array(" aaaa", "_aaaa", $options);
- $data[] = array("aaaa aaaa", "aaaa_aaaa", $options);
- $data[] = array("aaaa aaaa", "aaaa__aaaa", $options);
- $data[] = array("aaaa aaaa", "aaaa___aaaa", $options);
- $data[] = array("aaaa\taaaa", "aaaa____aaaa", $options);
+ $data[] = array(" aaaa", ">_aaaa<", $options);
+ $data[] = array("aa>aa", ">aa&gt;aa<", $options);
+ $data[] = array("aaaa aaaa", ">aaaa_aaaa<", $options);
+ $data[] = array("aaaa aaaa", ">aaaa__aaaa<", $options);
+ $data[] = array("aaaa aaaa", ">aaaa___aaaa<", $options);
+ $data[] = array("aaaa\taaaa", ">aaaa____aaaa<", $options);
$data[] = array("aaaa\naaaa", "aaaa<br>aaaa", $options);
- $data[] = array("aaaa\n aaaa", "aaaa<br>_aaaa", $options);
- $data[] = array("aaaa\n aaaa", "aaaa<br>__aaaa", $options);
- $data[] = array("aaaa\n aaaa", "aaaa<br>___aaaa", $options);
- $data[] = array("\taaaa", "____aaaa", $options);
+ $data[] = array("aaaa\n aaaa", "aaaa<br>>_aaaa<", $options);
+ $data[] = array("aaaa\n aaaa", "aaaa<br>>__aaaa<", $options);
+ $data[] = array("aaaa\n aaaa", "aaaa<br>>___aaaa<", $options);
+ $data[] = array("\n", "<br>", $options);
+ $data[] = array("\taaaa", ">____aaaa<", $options);
$data[] = array("\naaaa", "<br>aaaa", $options);
- $data[] = array("\n aaaa", "<br>_aaaa", $options);
- $data[] = array("\n aaaa", "<br>__aaaa", $options);
- $data[] = array("\n aaaa", "<br>___aaaa", $options);
+ $data[] = array("\n aaaa", "<br>>_aaaa<", $options);
+ $data[] = array("\n aaaa", "<br>>__aaaa<", $options);
+ $data[] = array("\n aaaa", "<br>>___aaaa<", $options);
$data[] = array("aaaa\n\nbbbb", "aaaa<br><br>bbbb", $options);
- $data[] = array(">aaaa \n>aaaa", "<blockquote>aaaa_<br>aaaa</blockquote>", $options);
+ $data[] = array(">aaaa \n>aaaa", "<blockquote>>aaaa_<<br>aaaa</blockquote>", $options);
$data[] = array(">aaaa\n>aaaa", "<blockquote>aaaa<br>aaaa</blockquote>", $options);
- $data[] = array(">aaaa \n>bbbb\ncccc dddd", "<blockquote>aaaa_<br>bbbb</blockquote>cccc_dddd", $options);
- $data[] = array("aaaa-bbbb/cccc", "aaaa\xEF\xBB\xBF-\xEF\xBB\xBFbbbb\xEF\xBB\xBF/cccc", $options);
+ $data[] = array(">aaaa \n>bbbb\ncccc dddd", "<blockquote>>aaaa_<<br>bbbb</blockquote>>cccc_dddd<", $options);
+ $data[] = array("aaaa-bbbb/cccc", ">aaaa-bbbb/cccc<", $options);
$options['flowed'] = true;
$data[] = array(" aaaa", "aaaa", $options);
- $data[] = array("aaaa aaaa", "aaaa_aaaa", $options);
- $data[] = array("aaaa aaaa", "aaaa__aaaa", $options);
- $data[] = array("aaaa aaaa", "aaaa___aaaa", $options);
- $data[] = array("aaaa\taaaa", "aaaa____aaaa", $options);
+ $data[] = array("aaaa aaaa", ">aaaa_aaaa<", $options);
+ $data[] = array("aaaa aaaa", ">aaaa__aaaa<", $options);
+ $data[] = array("aaaa aaaa", ">aaaa___aaaa<", $options);
+ $data[] = array("aaaa\taaaa", ">aaaa____aaaa<", $options);
$data[] = array("aaaa\naaaa", "aaaa<br>aaaa", $options);
$data[] = array("aaaa\n aaaa", "aaaa<br>aaaa", $options);
- $data[] = array("aaaa\n aaaa", "aaaa<br>_aaaa", $options);
- $data[] = array("aaaa\n aaaa", "aaaa<br>__aaaa", $options);
- $data[] = array("\taaaa", "____aaaa", $options);
+ $data[] = array("aaaa\n aaaa", "aaaa<br>>_aaaa<", $options);
+ $data[] = array("aaaa\n aaaa", "aaaa<br>>__aaaa<", $options);
+ $data[] = array("\taaaa", ">____aaaa<", $options);
$data[] = array("\naaaa", "<br>aaaa", $options);
$data[] = array("\n aaaa", "<br>aaaa", $options);
- $data[] = array("\n aaaa", "<br>_aaaa", $options);
- $data[] = array("\n aaaa", "<br>__aaaa", $options);
+ $data[] = array("\n aaaa", "<br>>_aaaa<", $options);
+ $data[] = array("\n aaaa", "<br>>__aaaa<", $options);
$data[] = array("aaaa\n\nbbbb", "aaaa<br><br>bbbb", $options);
$data[] = array(">aaaa \n>aaaa", "<blockquote>aaaa aaaa</blockquote>", $options);
$data[] = array(">aaaa\n>aaaa", "<blockquote>aaaa<br>aaaa</blockquote>", $options);
- $data[] = array(">aaaa \n>bbbb\ncccc dddd", "<blockquote>aaaa bbbb</blockquote>cccc_dddd", $options);
- $data[] = array(chr(0x002).chr(0x003), chr(0x002).chr(0x003), $options);
+ $data[] = array(">aaaa \n>bbbb\ncccc dddd", "<blockquote>aaaa bbbb</blockquote>>cccc_dddd<", $options);
+ $data[] = array("\x02\x03", ">\x02\x03<", $options);
$options['flowed'] = false;
$options['wrap'] = true;
$data[] = array(">>aaaa bbbb\n>>\n>>>\n>cccc\n\ndddd eeee",
"<blockquote><blockquote>aaaa bbbb<br><br><blockquote><br></blockquote></blockquote>cccc</blockquote><br>dddd eeee", $options);
$data[] = array("\n>>aaaa\n\ndddd",
"<br><blockquote><blockquote>aaaa</blockquote></blockquote><br>dddd", $options);
$data[] = array("aaaa\n>bbbb\n>cccc\n\ndddd\n>>test",
"aaaa<blockquote>bbbb<br>cccc</blockquote><br>dddd<blockquote><blockquote>test</blockquote></blockquote>", $options);
return $data;
}
/**
* Test text to html conversion
*
* @dataProvider data_text2html
*/
function test_text2html($input, $output, $options)
{
$t2h = new rcube_text2html($input, false, $options);
$html = $t2h->get_html();
$this->assertEquals($output, $html);
}
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, Mar 19, 9:08 AM (23 h, 56 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
457955
Default Alt Text
(17 KB)

Event Timeline