* @author Kornel Lesiński * @author Iván Montes * @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 */ 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; } }