add text helpers
authorChuck Hagenbuch <chuck@horde.org>
Mon, 23 Feb 2009 02:42:48 +0000 (21:42 -0500)
committerChuck Hagenbuch <chuck@horde.org>
Mon, 23 Feb 2009 02:42:48 +0000 (21:42 -0500)
framework/View/lib/Horde/View/Helper/Text.php [new file with mode: 0644]
framework/View/lib/Horde/View/Helper/Text/Cycle.php [new file with mode: 0644]
framework/View/package.xml
framework/View/test/Horde/View/Helper/TextTest.php [new file with mode: 0644]

diff --git a/framework/View/lib/Horde/View/Helper/Text.php b/framework/View/lib/Horde/View/Helper/Text.php
new file mode 100644 (file)
index 0000000..435258d
--- /dev/null
@@ -0,0 +1,237 @@
+<?php
+/**
+ * Copyright 2007 Maintainable Software, LLC
+ * Copyright 2006-2009 The Horde Project (http://www.horde.org/)
+ *
+ * @author     Mike Naberezny <mike@maintainable.com>
+ * @author     Derek DeVries <derek@maintainable.com>
+ * @author     Chuck Hagenbuch <chuck@horde.org>
+ * @license    http://opensource.org/licenses/bsd-license.php
+ * @category   Horde
+ * @package    Horde_View
+ * @subpackage Helper
+ */
+
+/**
+ * View helpers for URLs
+ *
+ * @author     Mike Naberezny <mike@maintainable.com>
+ * @author     Derek DeVries <derek@maintainable.com>
+ * @author     Chuck Hagenbuch <chuck@horde.org>
+ * @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.
+     *
+     * <code>
+     *   <p><?= $this->h($this->templateVar) ?></p>
+     * </code>
+     *
+     * @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:
+     *
+     *   <? foreach($items as $item): ?>
+     *     <tr class="<?= $this->cycle("even", "odd") ?>">
+     *       <td>item</td>
+     *     </tr>
+     *   <% endforeach %>
+     *
+     * You can use named cycles to allow nesting in loops.  Passing an array as
+     * the last parameter with a <tt>name</tt> key will create a named cycle.
+     * You can manually reset a cycle by calling resetCycle() and passing the
+     * name of the cycle.
+     *
+     *   <? foreach($items as $item): ?>
+     *     <tr class="<?= $this->cycle("even", "odd", array('name' => "row_class")) ?>">
+     *       <td>
+     *         <? foreach ($item->values as $value) ?>
+     *           <span style="color:<?= $this->cycle("red", "green", "blue", array('name' => "colors")) ?>">
+     *             value
+     *           </span>
+     *         <% end %>
+     *         <? $this->resetCycle("colors") ?>
+     *       </td>
+     *    </tr>
+     *   <% 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
+     * <strong class="highlight">I'm highlighted</strong>. 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='<strong class="highlight">$1</strong>';
+        }
+        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.
+     *
+     * <code>
+     * $this->truncate("Once upon a time in a world far far away", 14);
+     * => Once upon a...
+     * </code>
+     *
+     * @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.
+     *
+     * <code>
+     *  <?php
+     *  ...
+     *  $str = "The quick brown fox jumps over the lazy dog tomorrow morning.";
+     *  $shortStr = truncateMiddle($str, 40);
+     *  // $shortStr = "The quick brown fox... tomorrow morning."
+     *  ...
+     *  ?>
+     * </code>
+     *
+     * @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('/<wbr>', '_<wbr>'),
+            $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 (file)
index 0000000..32ba56c
--- /dev/null
@@ -0,0 +1,83 @@
+<?php
+/**
+ * Copyright 2007 Maintainable Software, LLC
+ * Copyright 2006-2009 The Horde Project (http://www.horde.org/)
+ *
+ * @author     Mike Naberezny <mike@maintainable.com>
+ * @author     Derek DeVries <derek@maintainable.com>
+ * @author     Chuck Hagenbuch <chuck@horde.org>
+ * @license    http://opensource.org/licenses/bsd-license.php
+ * @category   Horde
+ * @package    Horde_View
+ * @subpackage Helper
+ */
+
+/**
+ * View helpers for URLs
+ *
+ * @author     Mike Naberezny <mike@maintainable.com>
+ * @author     Derek DeVries <derek@maintainable.com>
+ * @author     Chuck Hagenbuch <chuck@horde.org>
+ * @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;
+    }
+
+}
index 975e7f7..c1a45ff 100644 (file)
@@ -62,6 +62,10 @@ http://pear.php.net/dtd/package-2.0.xsd">
        <file name="FormTag.php" role="php" />
        <file name="Javascript.php" role="php" />
        <file name="Tag.php" role="php" />
+       <dir name="Text">
+        <file name="Cycle.php" role="php" />
+       </dir> <!-- /lib/Horde/View/Helper/Text -->
+       <file name="Text.php" role="php" />
        <file name="Url.php" role="php" />
       </dir> <!-- /lib/Horde/View/Helper -->
       <file name="Base.php" role="php" />
@@ -100,6 +104,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
    <install name="lib/Horde/View/Helper/FormTag.php" as="Horde/View/Helper/FormTag.php" />
    <install name="lib/Horde/View/Helper/Javascript.php" as="Horde/View/Helper/Javascript.php" />
    <install name="lib/Horde/View/Helper/Tag.php" as="Horde/View/Helper/Tag.php" />
+   <install name="lib/Horde/View/Helper/Text/Cycle.php" as="Horde/View/Helper/Text/Cycle.php" />
+   <install name="lib/Horde/View/Helper/Text.php" as="Horde/View/Helper/Text.php" />
    <install name="lib/Horde/View/Helper/Url.php" as="Horde/View/Helper/Url.php" />
    <install name="lib/Horde/View/Base.php" as="Horde/View/Base.php" />
    <install name="lib/Horde/View/Exception.php" as="Horde/View/Exception.php" />
diff --git a/framework/View/test/Horde/View/Helper/TextTest.php b/framework/View/test/Horde/View/Helper/TextTest.php
new file mode 100644 (file)
index 0000000..215fad1
--- /dev/null
@@ -0,0 +1,182 @@
+<?php
+/**
+ * Copyright 2007-2008 Maintainable Software, LLC
+ * Copyright 2008-2009 The Horde Project (http://www.horde.org/)
+ *
+ * @author     Mike Naberezny <mike@maintainable.com>
+ * @author     Derek DeVries <derek@maintainable.com>
+ * @author     Chuck Hagenbuch <chuck@horde.org>
+ * @license    http://opensource.org/licenses/bsd-license.php
+ * @category   Horde
+ * @package    Horde_View
+ * @subpackage UnitTests
+ */
+
+/**
+ * @group      view
+ * @author     Mike Naberezny <mike@maintainable.com>
+ * @author     Derek DeVries <derek@maintainable.com>
+ * @author     Chuck Hagenbuch <chuck@horde.org>
+ * @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 &#039;escaping html&#039; &quot;quotes&quot; and &amp; 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 <strong class="highlight">brown</strong> 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 <em>brown</em> fox jumps over the dog.';
+        $this->assertEquals($expected, $this->helper->highlight($str, 'brown', '<em>$1</em>'));
+    }
+
+    // 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'));
+    }
+
+}