summaryrefslogtreecommitdiff
path: root/lib/phptal/PHPTAL/RepeatController.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/phptal/PHPTAL/RepeatController.php')
-rw-r--r--lib/phptal/PHPTAL/RepeatController.php323
1 files changed, 323 insertions, 0 deletions
diff --git a/lib/phptal/PHPTAL/RepeatController.php b/lib/phptal/PHPTAL/RepeatController.php
new file mode 100644
index 0000000..5d33914
--- /dev/null
+++ b/lib/phptal/PHPTAL/RepeatController.php
@@ -0,0 +1,323 @@
+<?php
+/**
+ * PHPTAL templating engine
+ *
+ * PHP Version 5
+ *
+ * @category HTML
+ * @package PHPTAL
+ * @author Laurent Bedubourg <lbedubourg@motion-twin.com>
+ * @author Kornel Lesiński <kornel@aardvarkmedia.co.uk>
+ * @author Iván Montes <drslump@pollinimini.net>
+ * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License
+ * @version SVN: $Id$
+ * @link http://phptal.org/
+ */
+
+/**
+ * Stores tal:repeat information during template execution.
+ *
+ * An instance of this class is created and stored into PHPTAL context on each
+ * tal:repeat usage.
+ *
+ * repeat/item/index
+ * repeat/item/number
+ * ...
+ * are provided by this instance.
+ *
+ * 'repeat' is an stdClass instance created to handle RepeatControllers,
+ * 'item' is an instance of this class.
+ *
+ * @package PHPTAL
+ * @subpackage Php
+ * @author Laurent Bedubourg <lbedubourg@motion-twin.com>
+ */
+class PHPTAL_RepeatController implements Iterator
+{
+ public $key;
+ private $current;
+ private $valid;
+ private $validOnNext;
+
+ private $uses_groups = false;
+
+ protected $iterator;
+ public $index;
+ public $end;
+
+ /**
+ * computed lazily
+ */
+ private $length = null;
+
+ /**
+ * Construct a new RepeatController.
+ *
+ * @param $source array, string, iterator, iterable.
+ */
+ public function __construct($source)
+ {
+ if ( is_string($source) ) {
+ $this->iterator = new ArrayIterator( str_split($source) ); // FIXME: invalid for UTF-8 encoding, use preg_match_all('/./u') trick
+ } elseif ( is_array($source) ) {
+ $this->iterator = new ArrayIterator($source);
+ } elseif ($source instanceof IteratorAggregate) {
+ $this->iterator = $source->getIterator();
+ } elseif ($source instanceof DOMNodeList) {
+ $array = array();
+ foreach ($source as $k=>$v) {
+ $array[$k] = $v;
+ }
+ $this->iterator = new ArrayIterator($array);
+ } elseif ($source instanceof Iterator) {
+ $this->iterator = $source;
+ } elseif ($source instanceof Traversable) {
+ $this->iterator = new IteratorIterator($source);
+ } elseif ($source instanceof Closure) {
+ $this->iterator = new ArrayIterator( (array) $source() );
+ } elseif ($source instanceof stdClass) {
+ $this->iterator = new ArrayIterator( (array) $source );
+ } else {
+ $this->iterator = new ArrayIterator( array() );
+ }
+ }
+
+ /**
+ * Returns the current element value in the iteration
+ *
+ * @return Mixed The current element value
+ */
+ public function current()
+ {
+ return $this->current;
+ }
+
+ /**
+ * Returns the current element key in the iteration
+ *
+ * @return String/Int The current element key
+ */
+ public function key()
+ {
+ return $this->key;
+ }
+
+ /**
+ * Tells if the iteration is over
+ *
+ * @return bool True if the iteration is not finished yet
+ */
+ public function valid()
+ {
+ $valid = $this->valid || $this->validOnNext;
+ $this->validOnNext = $this->valid;
+
+ return $valid;
+ }
+
+ public function length()
+ {
+ if ($this->length === null) {
+ if ($this->iterator instanceof Countable) {
+ return $this->length = count($this->iterator);
+ } elseif ( is_object($this->iterator) ) {
+ // for backwards compatibility with existing PHPTAL templates
+ if ( method_exists($this->iterator, 'size') ) {
+ return $this->length = $this->iterator->size();
+ } elseif ( method_exists($this->iterator, 'length') ) {
+ return $this->length = $this->iterator->length();
+ }
+ }
+ $this->length = '_PHPTAL_LENGTH_UNKNOWN_';
+ }
+
+ if ($this->length === '_PHPTAL_LENGTH_UNKNOWN_') // return length if end is discovered
+ {
+ return $this->end ? $this->index + 1 : null;
+ }
+ return $this->length;
+ }
+
+ /**
+ * Restarts the iteration process going back to the first element
+ *
+ */
+ public function rewind()
+ {
+ $this->index = 0;
+ $this->length = null;
+ $this->end = false;
+
+ $this->iterator->rewind();
+
+ // Prefetch the next element
+ if ($this->iterator->valid()) {
+ $this->validOnNext = true;
+ $this->prefetch();
+ } else {
+ $this->validOnNext = false;
+ }
+
+ if ($this->uses_groups) {
+ // Notify the grouping helper of the change
+ $this->groups->reset();
+ }
+ }
+
+ /**
+ * Fetches the next element in the iteration and advances the pointer
+ *
+ */
+ public function next()
+ {
+ $this->index++;
+
+ // Prefetch the next element
+ if ($this->validOnNext) $this->prefetch();
+
+ if ($this->uses_groups) {
+ // Notify the grouping helper of the change
+ $this->groups->reset();
+ }
+ }
+
+ /**
+ * Ensures that $this->groups works.
+ *
+ * Groups are rarely-used feature, which is why they're lazily loaded.
+ */
+ private function initializeGroups()
+ {
+ if (!$this->uses_groups) {
+ $this->groups = new PHPTAL_RepeatControllerGroups();
+ $this->uses_groups = true;
+ }
+ }
+
+ /**
+ * Gets an object property
+ *
+ * @return $var Mixed The variable value
+ */
+ public function __get($var)
+ {
+ switch ($var) {
+ case 'number':
+ return $this->index + 1;
+ case 'start':
+ return $this->index === 0;
+ case 'even':
+ return ($this->index % 2) === 0;
+ case 'odd':
+ return ($this->index % 2) === 1;
+ case 'length':
+ return $this->length();
+ case 'letter':
+ return strtolower( $this->int2letter($this->index+1) );
+ case 'Letter':
+ return strtoupper( $this->int2letter($this->index+1) );
+ case 'roman':
+ return strtolower( $this->int2roman($this->index+1) );
+ case 'Roman':
+ return strtoupper( $this->int2roman($this->index+1) );
+
+ case 'groups':
+ $this->initializeGroups();
+ return $this->groups;
+
+ case 'first':
+ $this->initializeGroups();
+ // Compare the current one with the previous in the dictionary
+ $res = $this->groups->first($this->current);
+ return is_bool($res) ? $res : $this->groups;
+
+ case 'last':
+ $this->initializeGroups();
+ // Compare the next one with the dictionary
+ $res = $this->groups->last( $this->iterator->current() );
+ return is_bool($res) ? $res : $this->groups;
+
+ default:
+ throw new PHPTAL_VariableNotFoundException("Unable to find part '$var' in repeat variable");
+ }
+ }
+
+ /**
+ * Fetches the next element from the source data store and
+ * updates the end flag if needed.
+ *
+ * @access protected
+ */
+ protected function prefetch()
+ {
+ $this->valid = true;
+ $this->current = $this->iterator->current();
+ $this->key = $this->iterator->key();
+
+ $this->iterator->next();
+ if ( !$this->iterator->valid() ) {
+ $this->valid = false;
+ $this->end = true;
+ }
+ }
+
+ /**
+ * Converts an integer number (1 based) to a sequence of letters
+ *
+ * @param int $int The number to convert
+ *
+ * @return String The letters equivalent as a, b, c-z ... aa, ab, ac-zz ...
+ * @access protected
+ */
+ protected function int2letter($int)
+ {
+ $lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ $size = strlen($lookup);
+
+ $letters = '';
+ while ($int > 0) {
+ $int--;
+ $letters = $lookup[$int % $size] . $letters;
+ $int = floor($int / $size);
+ }
+ return $letters;
+ }
+
+ /**
+ * Converts an integer number (1 based) to a roman numeral
+ *
+ * @param int $int The number to convert
+ *
+ * @return String The roman numeral
+ * @access protected
+ */
+ protected function int2roman($int)
+ {
+ $lookup = array(
+ '1000' => 'M',
+ '900' => 'CM',
+ '500' => 'D',
+ '400' => 'CD',
+ '100' => 'C',
+ '90' => 'XC',
+ '50' => 'L',
+ '40' => 'XL',
+ '10' => 'X',
+ '9' => 'IX',
+ '5' => 'V',
+ '4' => 'IV',
+ '1' => 'I',
+ );
+
+ $roman = '';
+ foreach ($lookup as $max => $letters) {
+ while ($int >= $max) {
+ $roman .= $letters;
+ $int -= $max;
+ }
+ }
+
+ return $roman;
+ }
+}
+