phplibrary/PHPTAL/RepeatController.php
2016-07-14 16:29:26 +03:00

477 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/* vim: set expandtab tabstop=4 shiftwidth=4: */
//
// Copyright (c) 2004-2005 Laurent Bedubourg
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
// Authors: Laurent Bedubourg <lbedubourg@motion-twin.com>
//
/**
* 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
* @author Laurent Bedubourg <lbedubourg@motion-twin.com>
*/
class PHPTAL_RepeatController implements Iterator
{
private $key;
private $current;
private $valid;
private $validOnNext;
protected $iterator;
protected $index;
protected $end;
protected $length;
/**
* 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
} else if ( is_array($source) ) {
$this->iterator = new ArrayIterator($source);
} else if ( $source instanceof IteratorAggregate ) {
$this->iterator = $source->getIterator();
} else if ( $source instanceof Iterator ) {
$this->iterator = $source;
} else if ( $source instanceof SimpleXMLElement) { // has non-unique keys!
$array = array();
foreach ( $source as $v ) {
$array[] = $v;
}
$this->iterator = new ArrayIterator($array);
} else if ( $source instanceof Traversable || $source instanceof DOMNodeList ) {
// PDO Statements for example implement the engine internal Traversable
// interface. To make it fully iterable we traverse the set to populate
// an array which will be actually used for iteration.
$array = array();
foreach ( $source as $k=>$v ) {
$array[$k] = $v;
}
$this->iterator = new ArrayIterator($array);
} else {
$this->iterator = new ArrayIterator( array() );
}
// Try to find the set length
$this->length = 0;
if ( $this->iterator instanceof Countable ) {
$this->length = count($this->iterator);
} else if ( is_object($this->iterator) ) {
// This should be removed since there is already the Countable interface in PHP5
if ( method_exists( $this->iterator, 'size' ) ) {
$this->length = $this->iterator->size();
} else if ( method_exists( $this->iterator, 'length' ) ) {
$this->length = $this->iterator->length();
}
}
$this->groups = new PHPTAL_RepeatController_Groups();
$this->rewind();
}
/**
* 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;
}
/**
* Restarts the iteration process going back to the first element
*
*/
public function rewind()
{
$this->index = 0;
$this->end = false;
$this->iterator->rewind();
// Prefetch the next element
if ( $this->iterator->valid() ) {
$this->validOnNext = true;
$this->prefetch();
} else {
$this->validOnNext = false;
}
// 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
$this->prefetch();
// Notify the grouping helper of the change
$this->groups->reset();
}
/**
* Gets an object property
*
* @return $var Mixed The variable value
*/
public function __get( $var )
{
switch ( $var ) {
case 'index':
case 'end':
case 'length':
return $this->$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 'key':
return $this->key();
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 'first':
// 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':
// 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_Exception( "Unable to find part '$var' in repeater controller" );
}
}
/**
* 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->key = $this->iterator->key();
$this->current = $this->iterator->current();
$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;
}
}
/**
* Keeps track of variable contents when using grouping in a path (first/ and last/)
*
* @package phptal
* @author Ivбn Montes <drslump@pollinimini.net>
*/
class PHPTAL_RepeatController_Groups {
protected $dict = array();
protected $cache = array();
protected $data = null;
protected $vars = array();
protected $branch;
public function __construct()
{
$this->dict = array();
$this->reset();
}
/**
* Resets the result caches. Use it to signal an iteration in the loop
*
*/
public function reset()
{
$this->cache = array();
}
/**
* Checks if the data passed is the first one in a group
*
* @param $data Mixed The data to evaluate
* @return Mixed True if the first item in the group, false if not and
* this same object if the path is not finished
*/
public function first( $data )
{
if ( !is_array($data) && !is_object($data) && !is_null($data) ) {
if ( !isset($this->cache['F']) ) {
$hash = md5($data);
if ( !isset($this->dict['F']) || $this->dict['F'] !== $hash ) {
$this->dict['F'] = $hash;
$res = true;
} else {
$res = false;
}
$this->cache['F'] = $res;
}
return $this->cache['F'];
}
$this->data = $data;
$this->branch = 'F';
$this->vars = array();
return $this;
}
/**
* Checks if the data passed is the last one in a group
*
* @param $data Mixed The data to evaluate
* @return Mixed True if the last item in the group, false if not and
* this same object if the path is not finished
*/
public function last( $data )
{
if ( !is_array($data) && !is_object($data) && !is_null($data) ) {
if ( !isset($this->cache['L']) ) {
$hash = md5($data);
if (empty($this->dict['L'])) {
$this->dict['L'] = $hash;
$res = false;
} else if ( $this->dict['L'] !== $hash ) {
$this->dict['L'] = $hash;
$res = true;
} else {
$res = false;
}
$this->cache['L'] = $res;
}
return $this->cache['L'];
}
$this->data = $data;
$this->branch = 'L';
$this->vars = array();
return $this;
}
/**
* Handles variable accesses for the tal path resolver
*
* @param $var String The variable name to check
* @return Mixed An object/array if the path is not over or a boolean
*
* @todo replace the phptal_path() with custom code
*/
public function __get( $var )
{
// When the iterator item is empty we just let the tal
// expression consume by continuously returning this
// same object which should evaluate to true for 'last'
if ( is_null($this->data) ) {
return $this;
}
// Find the requested variable
$value = @phptal_path( $this->data, $var, true );
// Check if it's an object or an array
if ( is_array($value) || is_object($value) ) {
// Move the context to the requested variable and return
$this->data = $value;
$this->addVarName( $var );
return $this;
}
// get a hash of the variable contents
$hash = md5( $value );
// compute a path for the variable to use as dictionary key
$path = $this->branch . $this->getVarPath() . $var;
// If we don't know about this var store in the dictionary
if ( !isset($this->cache[$path]) ) {
if ( !isset($this->dict[$path]) ) {
$this->dict[$path] = $hash;
$res = $this->branch === 'F';
} else {
// Check if the value has changed
if ( $this->dict[$path] !== $hash ) {
$this->dict[$path] = $hash;
$res = true;
} else {
$res = false;
}
}
$this->cache[$path] = $res;
}
return $this->cache[$path];
}
/**
* Adds a variable name to the current path of variables
*
* @param $varname String The variable name to store as a path part
* @access protected
*/
protected function addVarName( $varname )
{
$this->vars[] = $varname;
}
/**
* Returns the current variable path separated by a slash
*
* @return String The current variable path
* @access protected
*/
protected function getVarPath()
{
return implode('/', $this->vars) . '/';
}
}