From 8f498a4bf5fead9dffd55cbe2c47eabf0f7c99cf Mon Sep 17 00:00:00 2001 From: Chuck Hagenbuch Date: Sun, 22 Feb 2009 21:42:48 -0500 Subject: [PATCH] add text helpers --- framework/View/lib/Horde/View/Helper/Text.php | 237 +++++++++++++++++++++ .../View/lib/Horde/View/Helper/Text/Cycle.php | 83 ++++++++ framework/View/package.xml | 6 + framework/View/test/Horde/View/Helper/TextTest.php | 182 ++++++++++++++++ 4 files changed, 508 insertions(+) create mode 100644 framework/View/lib/Horde/View/Helper/Text.php create mode 100644 framework/View/lib/Horde/View/Helper/Text/Cycle.php create mode 100644 framework/View/test/Horde/View/Helper/TextTest.php diff --git a/framework/View/lib/Horde/View/Helper/Text.php b/framework/View/lib/Horde/View/Helper/Text.php new file mode 100644 index 000000000..435258dfb --- /dev/null +++ b/framework/View/lib/Horde/View/Helper/Text.php @@ -0,0 +1,237 @@ + + * @author Derek DeVries + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php + * @category Horde + * @package Horde_View + * @subpackage Helper + */ + +/** + * View helpers for URLs + * + * @author Mike Naberezny + * @author Derek DeVries + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php + * @category Horde + * @package Horde_View + * @subpackage Helper + */ +class Horde_View_Helper_Text extends Horde_View_Helper_Base +{ + /** + * @var array + */ + protected $_cycles = array(); + + /** + * @var Horde_Support_Inflector + */ + protected $_inflector; + + /** + * Escapes a value for output in a view template. + * + * + *

h($this->templateVar) ?>

+ *
+ * + * @param mixed $var The output to escape. + * @return mixed The escaped value. + */ + public function h($var) + { + return htmlentities($var, ENT_QUOTES, 'utf-8'); + } + + /** + * Pluralize the $singular word unless $count is one. If $plural + * form is not supplied, inflector will be used. + * + * @param integer $count Count determines singular or plural + * @param string $singular Singular form + * @param string|null $plural Plural form (optional) + */ + public function pluralize($count, $singular, $plural = null) + { + if ($count == '1') { + $word = $singular; + } else if ($plural) { + $word = $plural; + } else { + if (!$this->_inflector) { $this->_inflector = new Horde_Support_Inflector(); } + $word = $this->_inflector->pluralize($singular); + } + + return "$count $word"; + } + + /** + * Creates a Cycle object whose __toString() method cycles through elements of an + * array every time it is called. This can be used for example, to alternate + * classes for table rows: + * + * + * "> + * item + * + * <% endforeach %> + * + * You can use named cycles to allow nesting in loops. Passing an array as + * the last parameter with a name key will create a named cycle. + * You can manually reset a cycle by calling resetCycle() and passing the + * name of the cycle. + * + * + * "row_class")) ?>"> + * + * values as $value) ?> + * "colors")) ?>"> + * value + * + * <% end %> + * resetCycle("colors") ?> + * + * + * <% endforeach %> + * + */ + public function cycle($firstValue) + { + $values = func_get_args(); + + $last = end($values); + if (is_array($last)) { + $options = array_pop($values); + $name = isset($options['name']) ? $options['name'] : 'default'; + } else { + $name = 'default'; + } + + if (empty($this->_cycles[$name]) || $this->_cycles[$name]->getValues() != $values) { + $this->_cycles[$name] = new Horde_View_Helper_Text_Cycle($values); + } + return $this->_cycles[$name]; + } + + /** + * Resets a cycle so that it starts from the first element the next time + * it is called. Pass in $name to reset a named cycle. + * + * @param string $name Name of cycle to reset (defaults to "default") + * @return void + */ + public function resetCycle($name = 'default') + { + if (isset($this->_cycles[$name])) { + $this->_cycles[$name]->reset(); + } + } + + /** + * Highlights the phrase where it is found in the text by surrounding it like + * I'm highlighted. The Highlighter can + * be customized by passing highlighter as a single-quoted string with $1 + * where the prhase is supposed to be inserted. + * + * @param string $text + * @param string $phrase + * @param string $highlighter + */ + public function highlight($text, $phrase, $highlighter=null) + { + if (empty($highlighter)) { + $highlighter='$1'; + } + if (empty($phrase) || empty($text)) { + return $text; + } + return preg_replace("/($phrase)/", $highlighter, $text); + } + + /** + * If $text is longer than $length, $text will be truncated to the + * length of $length and the last three characters will be replaced + * with the $truncateString. + * + * + * $this->truncate("Once upon a time in a world far far away", 14); + * => Once upon a... + * + * + * @param string $text + * @param integer $length + * @param string $truncateString + * @return string + */ + public function truncate($text, $length=30, $truncateString = '...') + { + if (empty($text)) { return $text; } + $l = $length - strlen($truncateString); + return strlen($text) > $length ? substr($text, 0, $l).$truncateString : $text; + } + + /** + * Limit a string to a given maximum length in a smarter way than just using + * substr. Namely, cut from the MIDDLE instead of from the end so that if + * we're doing this on (for instance) a bunch of binder names that start off + * with the same verbose description, and then are different only at the + * very end, they'll still be different from one another after truncating. + * + * + * + * + * + * @todo This is not a Rails helper... + * @param string $str + * @param int $maxLength + * @param string $joiner + * @return string + */ + public function truncateMiddle($str, $maxLength=80, $joiner='...') + { + if (strlen($str) <= $maxLength) { + return $str; + } + $maxLength = $maxLength - strlen($joiner); + if ($maxLength <= 0) { + return $str; + } + $startPieceLength = (int) ceil($maxLength / 2); + $endPieceLength = (int) floor($maxLength / 2); + $trimmedString = substr($str, 0, $startPieceLength) . $joiner; + if ($endPieceLength > 0) { + $trimmedString .= substr($str, (-1 * $endPieceLength)); + } + return $trimmedString; + } + + /** + * Allow linebreaks in a string after slashes or underscores + * + * @todo This is not a Rails helper... + * @param string $str + * @return string + */ + public function makeBreakable($str) + { + return str_replace( + array('/', '_'), + array('/', '_'), + $str + ); + } + +} diff --git a/framework/View/lib/Horde/View/Helper/Text/Cycle.php b/framework/View/lib/Horde/View/Helper/Text/Cycle.php new file mode 100644 index 000000000..32ba56c9c --- /dev/null +++ b/framework/View/lib/Horde/View/Helper/Text/Cycle.php @@ -0,0 +1,83 @@ + + * @author Derek DeVries + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php + * @category Horde + * @package Horde_View + * @subpackage Helper + */ + +/** + * View helpers for URLs + * + * @author Mike Naberezny + * @author Derek DeVries + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php + * @category Horde + * @package Horde_View + * @subpackage Helper + */ +class Horde_View_Helper_Text_Cycle +{ + /** + * Array of values to cycle through + * @var array + */ + private $_values; + + /** + * Construct a new cycler + * + * @param array $values Values to cycle through + */ + public function __construct($values) + { + if (func_num_args() != 1 || !is_array($values)) { + throw new InvalidArgumentException(); + } + + if (count($values) < 2) { + throw new InvalidArgumentException('must have at least two values'); + } + + $this->_values = $values; + $this->reset(); + } + + /** + * Returns the current element in the cycle + * and advance the cycle + * + * @return mixed Current element + */ + public function __toString() + { + $value = next($this->_values); + return strval(($value !== false) ? $value : reset($this->_values)); + } + + /** + * Reset the cycle + */ + public function reset() + { + end($this->_values); + } + + /** + * Returns the values of this cycler. + * + * @return array + */ + public function getValues() + { + return $this->_values; + } + +} diff --git a/framework/View/package.xml b/framework/View/package.xml index 975e7f711..c1a45ff02 100644 --- a/framework/View/package.xml +++ b/framework/View/package.xml @@ -62,6 +62,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + + @@ -100,6 +104,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + diff --git a/framework/View/test/Horde/View/Helper/TextTest.php b/framework/View/test/Horde/View/Helper/TextTest.php new file mode 100644 index 000000000..215fad13c --- /dev/null +++ b/framework/View/test/Horde/View/Helper/TextTest.php @@ -0,0 +1,182 @@ + + * @author Derek DeVries + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php + * @category Horde + * @package Horde_View + * @subpackage UnitTests + */ + +/** + * @group view + * @author Mike Naberezny + * @author Derek DeVries + * @author Chuck Hagenbuch + * @license http://opensource.org/licenses/bsd-license.php + * @category Horde + * @package Horde_View + * @subpackage UnitTests + */ +class Horde_View_Helper_TextTest extends Horde_Test_Case +{ + public function setUp() + { + $this->helper = new Horde_View_Helper_Text(new Horde_View()); + } + + // test escaping data + public function testEscape() + { + $text = "Test 'escaping html' \"quotes\" and & amps"; + $expected = "Test 'escaping html' "quotes" and & amps"; + $this->assertEquals($expected, $this->helper->h($text)); + } + + // test truncate + public function testTruncate() + { + $str = 'The quick brown fox jumps over the lazy dog tomorrow morning.'; + $expected = 'The quick brown fox jumps over the la...'; + $this->assertEquals($expected, $this->helper->truncate($str, 40)); + } + + // test truncate + public function testTruncateMiddle() + { + $str = 'The quick brown fox jumps over the lazy dog tomorrow morning.'; + $expected = 'The quick brown fox... tomorrow morning.'; + $this->assertEquals($expected, $this->helper->truncateMiddle($str, 40)); + } + + // text too short to trucate + public function testTruncateMiddleTooShort() + { + $str = 'The quick brown fox jumps over the dog.'; + $expected = 'The quick brown fox jumps over the dog.'; + $this->assertEquals($expected, $this->helper->truncateMiddle($str, 40)); + } + + + // test highlighter + public function testHighlightDefault() + { + $str = 'The quick brown fox jumps over the dog.'; + $expected = 'The quick brown fox jumps over the dog.'; + $this->assertEquals($expected, $this->helper->highlight($str, 'brown')); + } + + // test failure to highlight + public function testHighlightCustom() + { + $str = 'The quick brown fox jumps over the dog.'; + $expected = 'The quick brown fox jumps over the dog.'; + $this->assertEquals($expected, $this->helper->highlight($str, 'brown', '$1')); + } + + // test failure to highlight + public function testHighlightNoMatch() + { + $str = 'The quick brown fox jumps over the dog.'; + $this->assertEquals($str, $this->helper->highlight($str, 'black')); + } + + public function testCycleClass() + { + $value = new Horde_View_Helper_Text_Cycle(array('one', 2, '3')); + + $this->assertEquals('one', (string)$value); + $this->assertEquals('2', (string)$value); + $this->assertEquals('3', (string)$value); + $this->assertEquals('one', (string)$value); + $value->reset(); + $this->assertEquals('one', (string)$value); + $this->assertEquals('2', (string)$value); + $this->assertEquals('3', (string)$value); + } + + public function testCycleClassWithInvalidArguments() + { + try { + $value = new Horde_View_Helper_Text_Cycle('bad'); + $this->fail(); + } catch (InvalidArgumentException $e) {} + + try { + $value = new Horde_View_Helper_Text_Cycle(array('foo')); + $this->fail(); + } catch (InvalidArgumentException $e) {} + + try { + $value = new Horde_View_Helper_Text_Cycle(array('foo', 'bar'), 'bad-arg'); + $this->fail(); + } catch (InvalidArgumentException $e) {} + } + + public function testCycleResetsWithNewValues() + { + $this->assertEquals('even', (string)$this->helper->cycle('even', 'odd')); + $this->assertEquals('odd', (string)$this->helper->cycle('even', 'odd')); + $this->assertEquals('even', (string)$this->helper->cycle('even', 'odd')); + $this->assertEquals('1', (string)$this->helper->cycle(1, 2, 3)); + $this->assertEquals('2', (string)$this->helper->cycle(1, 2, 3)); + $this->assertEquals('3', (string)$this->helper->cycle(1, 2, 3)); + } + + public function testNamedCycles() + { + $this->assertEquals('1', (string)$this->helper->cycle(1, 2, 3, array('name' => 'numbers'))); + $this->assertEquals('red', (string)$this->helper->cycle('red', 'blue', array('name' => 'colors'))); + $this->assertEquals('2', (string)$this->helper->cycle(1, 2, 3, array('name' => 'numbers'))); + $this->assertEquals('blue', (string)$this->helper->cycle('red', 'blue', array('name' => 'colors'))); + $this->assertEquals('3', (string)$this->helper->cycle(1, 2, 3, array('name' => 'numbers'))); + $this->assertEquals('red', (string)$this->helper->cycle('red', 'blue', array('name' => 'colors'))); + } + + public function testDefaultNamedCycle() + { + $this->assertEquals('1', (string)$this->helper->cycle(1, 2, 3)); + $this->assertEquals('2', (string)$this->helper->cycle(1, 2, 3, array('name' => 'default'))); + $this->assertEquals('3', (string)$this->helper->cycle(1, 2, 3)); + } + + public function testResetCycle() + { + $this->assertEquals('1', (string)$this->helper->cycle(1, 2, 3)); + $this->assertEquals('2', (string)$this->helper->cycle(1, 2, 3)); + $this->helper->resetCycle(); + $this->assertEquals('1', (string)$this->helper->cycle(1, 2, 3)); + } + + public function testResetUnknownCycle() + { + $this->helper->resetCycle('colors'); + } + + public function testResetNamedCycle() + { + $this->assertEquals('1', (string)$this->helper->cycle(1, 2, 3, array('name' => 'numbers'))); + $this->assertEquals('red', (string)$this->helper->cycle('red', 'blue', array('name' => 'colors'))); + $this->helper->resetCycle('numbers'); + $this->assertEquals('1', (string)$this->helper->cycle(1, 2, 3, array('name' => 'numbers'))); + $this->assertEquals('blue', (string)$this->helper->cycle('red', 'blue', array('name' => 'colors'))); + $this->assertEquals('2', (string)$this->helper->cycle(1, 2, 3, array('name' => 'numbers'))); + $this->assertEquals('red', (string)$this->helper->cycle('red', 'blue', array('name' => 'colors'))); + } + + public function testPluralization() + { + $this->assertEquals('1 count', $this->helper->pluralize(1, 'count')); + $this->assertEquals('2 counts', $this->helper->pluralize(2, 'count')); + $this->assertEquals('1 count', $this->helper->pluralize('1', 'count')); + $this->assertEquals('2 counts', $this->helper->pluralize('2', 'count')); + $this->assertEquals('1,066 counts', $this->helper->pluralize('1,066', 'count')); + $this->assertEquals('1.25 counts', $this->helper->pluralize('1.25', 'count')); + $this->assertEquals('2 counters', $this->helper->pluralize('2', 'count', 'counters')); + } + +} -- 2.11.0