From e1e4fade0aefad93d23014f9ac622d11d7de0c81 Mon Sep 17 00:00:00 2001 From: Michael M Slusarz Date: Wed, 10 Jun 2009 12:52:05 -0600 Subject: [PATCH] Import Horde_Template from CVS HEAD --- framework/Template/lib/Horde/Template.php | 557 +++++++++++++++++++++ framework/Template/package.xml | 121 +++++ .../Template/test/Horde/Template/array_assoc.html | 1 + .../Template/test/Horde/Template/array_assoc.phpt | 17 + .../Template/test/Horde/Template/array_if.html | 8 + .../Template/test/Horde/Template/array_if.phpt | 22 + .../Template/test/Horde/Template/array_nested.html | 10 + .../Template/test/Horde/Template/array_nested.phpt | 44 ++ .../Template/test/Horde/Template/array_simple.html | 6 + .../Template/test/Horde/Template/array_simple.phpt | 24 + .../Template/test/Horde/Template/divider.phpt | 13 + framework/Template/test/Horde/Template/if.html | 4 + framework/Template/test/Horde/Template/if.phpt | 22 + .../Template/test/Horde/Template/iterator.phpt | 27 + framework/Template/test/Horde/Template/scalar.html | 2 + framework/Template/test/Horde/Template/scalar.phpt | 19 + 16 files changed, 897 insertions(+) create mode 100644 framework/Template/lib/Horde/Template.php create mode 100644 framework/Template/package.xml create mode 100644 framework/Template/test/Horde/Template/array_assoc.html create mode 100644 framework/Template/test/Horde/Template/array_assoc.phpt create mode 100644 framework/Template/test/Horde/Template/array_if.html create mode 100644 framework/Template/test/Horde/Template/array_if.phpt create mode 100644 framework/Template/test/Horde/Template/array_nested.html create mode 100644 framework/Template/test/Horde/Template/array_nested.phpt create mode 100644 framework/Template/test/Horde/Template/array_simple.html create mode 100644 framework/Template/test/Horde/Template/array_simple.phpt create mode 100644 framework/Template/test/Horde/Template/divider.phpt create mode 100644 framework/Template/test/Horde/Template/if.html create mode 100644 framework/Template/test/Horde/Template/if.phpt create mode 100644 framework/Template/test/Horde/Template/iterator.phpt create mode 100644 framework/Template/test/Horde/Template/scalar.html create mode 100644 framework/Template/test/Horde/Template/scalar.phpt diff --git a/framework/Template/lib/Horde/Template.php b/framework/Template/lib/Horde/Template.php new file mode 100644 index 000000000..20f491c93 --- /dev/null +++ b/framework/Template/lib/Horde/Template.php @@ -0,0 +1,557 @@ +. + * + * Horde_Template provides a basic template engine with tags, loops, + * and if conditions. However, it is also a simple interface with + * several essential functions: set(), fetch(), and + * parse(). Subclasses or decorators can implement (or delegate) these + * three methods, plus the options api, and easily implement other + * template engines (PHP code, XSLT, etc.) without requiring usage + * changes. + * + * Copyright 2002-2009 The Horde Project (http://www.horde.org/) + * + * See the enclosed file COPYING for license information (LGPL). If you + * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. + * + * @author Chuck Hagenbuch + * @author Michael Slusarz + * @package Horde_Template + */ +class Horde_Template +{ + /** + * Option values. + * + * @var array + */ + protected $_options = array(); + + /** + * Directory that templates should be read from. + * + * @var string + */ + protected $_basepath = ''; + + /** + * Tag (scalar) values. + * + * @var array + */ + protected $_scalars = array(); + + /** + * Loop tag values. + * + * @var array + */ + protected $_arrays = array(); + + /** + * Cloop tag values. + * + * @var array + */ + protected $_carrays = array(); + + /** + * If tag values. + * + * @var array + */ + protected $_ifs = array(); + + /** + * Name of cached template file. + * + * @var string + */ + protected $_templateFile = null; + + /** + * Cached source of template file. + * + * @var string + */ + protected $_template = null; + + /** + * Constructor. Can set the template base path and whether or not + * to drop template variables after a parsing a template. + * + * @param string $basepath The directory where templates are read from. + */ + public function __construct($basepath = null) + { + if (!is_null($basepath)) { + $this->_basepath = $basepath; + } + } + + /** + * Sets an option. + * + * @param string $option The option name. + * @param mixed $val The option's value. + */ + public function setOption($option, $val) + { + $this->_options[$option] = $val; + } + + /** + * Set the template contents to a string. + * + * @param string $template The template text. + */ + public function setTemplate($template) + { + $this->_template = $template; + $this->_templateFile = 'string'; + } + + /** + * Returns an option's value. + * + * @param string $option The option name. + * + * @return mixed The option's value. + */ + public function getOption($option) + { + return isset($this->_options[$option]) + ? $this->_options[$option] + : null; + } + + /** + * Sets a tag, loop, or if variable. + * + * @param string|array $tag Either the tag name or a hash with tag names + * as keys and tag values as values. + * @param mixed $var The value to replace the tag with. + * @param boolean $isIf Is this for an tag? (Default: no). + */ + public function set($tag, $var, $isIf = false) + { + if (is_array($tag)) { + foreach ($tag as $tTag => $tVar) { + $this->set($tTag, $tVar, $isIf); + } + } elseif (is_array($var) || is_object($var)) { + $this->_arrays[$tag] = $var; + if ($isIf) { + // Just store the same variable that we stored in + // $this->_arrays - if we don't modify it, PHP's + // reference counting ensures we're not using any + // additional memory here. + $this->_ifs[$tag] = $var; + } + } else { + $this->_scalars[$tag] = $var; + if ($isIf) { + // Just store the same variable that we stored in + // $this->_scalars - if we don't modify it, PHP's + // reference counting ensures we're not using any + // additional memory here. + $this->_ifs[$tag] = $var; + } + } + } + + /** + * Sets values for a cloop. + * + * @param string $tag The name of the cloop. + * @param array $array The values for the cloop. + * @param array $cases The cases (test values) for the cloops. + */ + public function setCloop($tag, $array, $cases) + { + $this->_carrays[$tag] = array( + 'array' => $array, + 'cases' => $cases, + ); + } + + /** + * Returns the value of a tag or loop. + * + * @param string $tag The tag name. + * + * @return mixed The tag value or null if the tag hasn't been set yet. + */ + public function get($tag) + { + if (isset($this->_arrays[$tag])) { + return $this->_arrays[$tag]; + } elseif (isset($this->_scalars[$tag])) { + return $this->_scalars[$tag]; + } + return null; + } + + /** + * Fetches a template from the specified file and return the parsed + * contents. + * + * @param string $filename The file to fetch the template from. + * + * @return string The parsed template. + * @throws Horde_Exception + */ + public function fetch($filename) + { + $contents = $this->_getTemplate($filename); + + // Parse and return the contents. + return $this->parse($contents); + } + + /** + * Parses all variables/tags in the template. + * + * @param string $contents The unparsed template. + * + * @return string The parsed template. + */ + public function parse($contents = null) + { + if (is_null($contents)) { + $contents = $this->_template; + } + + // Process ifs. + if (!empty($this->_ifs)) { + foreach (array_keys($this->_ifs) as $tag) { + $contents = $this->_parseIf($tag, $contents); + } + } + + // Process tags. + $replace = $search = array(); + reset($this->_scalars); + while (list($key, $value) = each($this->_scalars)) { + $search[] = $this->_getTag($key); + $replace[] = $value; + } + if (count($search)) { + $contents = str_replace($search, $replace, $contents); + } + + // Process cloops. + reset($this->_carrays); + while (list($key, $array) = each($this->_carrays)) { + $contents = $this->_parseCloop($key, $array, $contents); + } + + // Parse gettext tags, if the option is enabled. + if ($this->getOption('gettext')) { + $contents = $this->_parseGettext($contents); + } + + // Process loops and arrays. + reset($this->_arrays); + while (list($key, $array) = each($this->_arrays)) { + $contents = $this->_parseLoop($key, $array, $contents); + } + + // Return parsed template. + return $contents; + } + + /** + * Returns full start and end tags for a named tag. + * + * @param string $tag The name of the tag. + * @param string $directive The kind of tag [tag, if, loop, cloop]. + * + * @return array 'b' => Start tag, 'e' => End tag. + */ + protected function _getTags($tag, $directive) + { + return array( + 'b' => '<' . $directive . ':' . $tag . '>', + 'e' => '' + ); + } + + /** + * Formats a scalar tag (default format is ). + * + * @param string $tag The name of the tag. + * + * @return string The full tag with the current start/end delimiters. + */ + protected function _getTag($tag) + { + return ''; + } + + /** + * Extracts a portion of a template. + * + * @param array $t The tag to extract. Hash format is: + * $t['b'] - The start tag + * $t['e'] - The end tag + * @param string &$contents The template to extract from. + */ + protected function _getStatement($t, &$contents) + { + // Locate the statement. + $pos = strpos($contents, $t['b']); + if ($pos === false) { + return false; + } + + $tag_length = strlen($t['b']); + $fpos = $pos + $tag_length; + $lpos = strpos($contents, $t['e']); + $length = $lpos - $fpos; + + // Extract & return the statement. + return substr($contents, $fpos, $length); + } + + /** + * Parses gettext tags. + * + * @param string $contents The unparsed content of the file. + * + * @return string The parsed contents of the gettext blocks. + */ + protected function _parseGettext($contents) + { + // Get the tags & loop. + $t = array( + 'b' => '', + 'e' => '' + ); + + while ($text = $this->_getStatement($t, $contents)) { + $contents = str_replace($t['b'] . $text . $t['e'], _($text), $contents); + } + + return $contents; + } + + /** + * Parses a given if statement. + * + * @param string $tag The name of the if block to parse. + * @param string $contents The unparsed contents of the if block. + * + * @return string The parsed contents of the if block. + */ + protected function _parseIf($tag, $contents, $key = null) + { + // Get the tags & if statement. + $t = $this->_getTags($tag, 'if'); + $et = $this->_getTags($tag, 'else'); + + // explode the tag, so we have the correct keys for the array + if (isset($key)) { + list($tg, $k) = explode('.', $tag); + } + while (($if = $this->_getStatement($t, $contents)) !== false) { + // Check for else statement. + if ($else = $this->_getStatement($et, $if)) { + // Process the if statement. + $replace = ((isset($key) && $this->_ifs[$tg][$key][$k]) || (isset($this->_ifs[$tag]) && $this->_ifs[$tag])) + ? str_replace($et['b'] . $else . $et['e'], '', $if) + : $else; + } else { + // Process the if statement. + $replace = isset($key) + ? ($this->_ifs[$tg][$key][$k] ? $if : null) + : ($this->_ifs[$tag] ? $if : null); + } + + // Parse the template. + $contents = str_replace($t['b'] . $if . $t['e'], $replace, $contents); + } + + // Return parsed template. + return $contents; + } + + /** + * Parses the given array for any loops or other uses of the array. + * + * @param string $tag The name of the loop to parse. + * @param array $array The values for the loop. + * @param string $contents The unparsed contents of the loop. + * + * @return string The parsed contents of the loop. + */ + protected function _parseLoop($tag, $array, $contents) + { + // Get the tags & loop. + $t = $this->_getTags($tag, 'loop'); + $loop = $this->_getStatement($t, $contents); + + // See if we have a divider. + $l = $this->_getTags($tag, 'divider'); + $divider = $this->_getStatement($l, $loop); + $contents = str_replace($l['b'] . $divider . $l['e'], '', $contents); + + // Process the array. + do { + $parsed = ''; + $first = true; + reset($array); + while (list($key, $value) = each($array)) { + if (is_array($value) || is_object($value)) { + $i = $loop; + reset($value); + while (list($key2, $value2) = each($value)) { + if (!is_array($value2) && !is_object($value2)) { + // Replace associative array tags. + $aa_tag = $tag . '.' . $key2; + $i = str_replace($this->_getTag($aa_tag), $value2, $i); + $pos = strpos($tag, '.'); + if (($pos !== false) && + !empty($this->_ifs[substr($tag, 0, $pos)])) { + $this->_ifs[$aa_tag] = $value2; + $i = $this->_parseIf($aa_tag, $i); + unset($this->_ifs[$aa_tag]); + } + } else { + // Check to see if it's a nested loop. + $i = $this->_parseLoop($tag . '.' . $key2, $value2, $i); + } + } + $i = str_replace($this->_getTag($tag), $key, $i); + } elseif (is_string($key) && !is_array($value) && !is_object($value)) { + $contents = str_replace($this->_getTag($tag . '.' . $key), $value, $contents); + } elseif (!is_array($value) && !is_object($value)) { + $i = str_replace($this->_getTag($tag . ''), $value, $loop); + } else { + $i = null; + } + + // Parse conditions in the array. + if (!empty($this->_ifs[$tag][$key]) && + is_array($this->_ifs[$tag][$key]) && + $this->_ifs[$tag][$key]) { + reset($this->_ifs[$tag][$key]); + foreach (array_keys($this->_ifs[$tag][$key]) as $cTag) { + $i = $this->_parseIf($tag . '.' . $cTag, $i, $key); + } + } + + // Add the parsed iteration. + if (isset($i)) { + // If it's not the first time through, prefix the + // loop divider, if there is one. + if (!$first) { + $i = $divider . $i; + } + $parsed .= rtrim($i); + } + + // No longer the first time through. + $first = false; + } + + // Replace the parsed pieces of the template. + $contents = str_replace($t['b'] . $loop . $t['e'], $parsed, $contents); + } while ($loop = $this->_getStatement($t, $contents)); + + return $contents; + } + + /** + * Parses the given case loop (cloop). + * + * @param string $tag The name of the cloop to parse. + * @param array $array The values for the cloop. + * @param string $contents The unparsed contents of the cloop. + * + * @return string The parsed contents of the cloop. + */ + protected function _parseCloop($tag, $array, $contents) + { + // Get the tags & cloop. + $t = $this->_getTags($tag, 'cloop'); + + while ($loop = $this->_getStatement($t, $contents)) { + // Set up the cases. + $array['cases'][] = 'default'; + $case_content = array(); + + // Get the case strings. + foreach ($array['cases'] as $case) { + $ctags[$case] = $this->_getTags($case, 'case'); + $case_content[$case] = $this->_getStatement($ctags[$case], $loop); + } + + // Process the cloop. + $parsed = ''; + reset($array['array']); + while (list($key, $value) = each($array['array'])) { + if (is_numeric($key) && + (is_array($value) || is_object($value))) { + // Set up the cases. + $current_case = isset($value['case']) + ? $value['case'] + : 'default'; + unset($value['case']); + $i = $case_content[$current_case]; + + // Loop through each value. + reset($value); + while (list($key2, $value2) = each($value)) { + $i = (is_array($value2) || is_object($value2)) + ? $this->_parseLoop($tag . '.' . $key2, $value2, $i) + : str_replace($this->_getTag($tag . '.' . $key2), $value2, $i); + } + } + + // Add the parsed iteration. + $parsed .= rtrim($i); + } + + // Parse the cloop. + $contents = str_replace($t['b'] . $loop . $t['e'], $parsed, $contents); + } + + return $contents; + } + + /** + * Fetch the contents of a template into $this->_template; cache + * the filename in $this->_templateFile. + * + * @param string $filename Location of template file on disk. + * + * @return string The loaded template content. + * @throws Horde_Exception + */ + protected function _getTemplate($filename = null) + { + if (!is_null($filename) && ($filename != $this->_templateFile)) { + $this->_template = null; + } + + if (!is_null($this->_template)) { + return $this->_template; + } + + // Get the contents of the file. + $file = $this->_basepath . $filename; + $contents = file_get_contents($file); + if ($contents === false) { + throw new Horde_Exception(sprintf(_("Template \"%s\" not found."), $file)); + } + + $this->_template = $contents; + $this->_templateFile = $filename; + + return $this->_template; + } + +} diff --git a/framework/Template/package.xml b/framework/Template/package.xml new file mode 100644 index 000000000..34ca70e19 --- /dev/null +++ b/framework/Template/package.xml @@ -0,0 +1,121 @@ + + + Template + pear.horde.org + Horde Template System + Horde Template system. Adapted from bTemplate, by Brian Lozier <brian@massassi.net>. + + + Chuck Hagenbuch + chuck + chuck@horde.org + yes + + + Michael Slusarz + slusarz + slusarz@horde.org + yes + + 2009-06-10 + + 0.1.0 + 0.1.0 + + + beta + beta + + LGPL + * Initial Horde 4 package. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 5.2.0 + + + 1.5.4 + + + Horde + pear.horde.org + + + + + gettext + + + + + + + + + + + 2006-05-08 + + + 0.0.2 + 0.0.2 + + + alpha + alpha + + LGPL + + - Converted to package.xml 2.0 for pear.horde.org + - Remove numeric array key constraint (Request #4413). + + + + + 0.0.1 + 0.0.1 + + + alpha + alpha + + 2003-07-05 + LGPL + Initial release as a PEAR package + + + + diff --git a/framework/Template/test/Horde/Template/array_assoc.html b/framework/Template/test/Horde/Template/array_assoc.html new file mode 100644 index 000000000..f3e92af66 --- /dev/null +++ b/framework/Template/test/Horde/Template/array_assoc.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/framework/Template/test/Horde/Template/array_assoc.phpt b/framework/Template/test/Horde/Template/array_assoc.phpt new file mode 100644 index 000000000..38b39afab --- /dev/null +++ b/framework/Template/test/Horde/Template/array_assoc.phpt @@ -0,0 +1,17 @@ +--TEST-- +Associative Array Test +--FILE-- +set('foo', array('one' => 'one', 'two' => 2)); +echo $template->fetch('/array_assoc.html'); + +?> +--EXPECT-- +one 2 diff --git a/framework/Template/test/Horde/Template/array_if.html b/framework/Template/test/Horde/Template/array_if.html new file mode 100644 index 000000000..b54f0044b --- /dev/null +++ b/framework/Template/test/Horde/Template/array_if.html @@ -0,0 +1,8 @@ + + + + + + +else + diff --git a/framework/Template/test/Horde/Template/array_if.phpt b/framework/Template/test/Horde/Template/array_if.phpt new file mode 100644 index 000000000..c05d4753d --- /dev/null +++ b/framework/Template/test/Horde/Template/array_if.phpt @@ -0,0 +1,22 @@ +--TEST-- +If Array Test +--FILE-- +set('foo', array('one', 'two', 'three'), true); +$template->set('bar', array(), true); +echo $template->fetch('/array_if.html'); + +?> +--EXPECT-- +one +two +three + +else diff --git a/framework/Template/test/Horde/Template/array_nested.html b/framework/Template/test/Horde/Template/array_nested.html new file mode 100644 index 000000000..7401e4847 --- /dev/null +++ b/framework/Template/test/Horde/Template/array_nested.html @@ -0,0 +1,10 @@ + +> + /> +> + + + + + + diff --git a/framework/Template/test/Horde/Template/array_nested.phpt b/framework/Template/test/Horde/Template/array_nested.phpt new file mode 100644 index 000000000..f3939969f --- /dev/null +++ b/framework/Template/test/Horde/Template/array_nested.phpt @@ -0,0 +1,44 @@ +--TEST-- +Nested Array Test +--FILE-- + array('apple', 'pear'), + 'veggie' => array('tomato', 'potato', 'carrot', 'onion'), + 'thing' => array('spoon', 'paperbag', 'tool')); +$template->set('categories', $categories); +foreach ($categories as $c) { + $template->set('subcat_' . $c, $subcats[$c]); +} +$template->set('keyed', array('widgets' => array( + 'key1' => 'zipit', + 'key2' => 'twisty', + 'key3' => 'doowhopper' +))); +echo $template->fetch('/array_nested.html'); + +?> +--EXPECT-- +fruit + apple + pear +veggie + tomato + potato + carrot + onion +thing + spoon + paperbag + tool +widgets + zipit + twisty + doowhopper diff --git a/framework/Template/test/Horde/Template/array_simple.html b/framework/Template/test/Horde/Template/array_simple.html new file mode 100644 index 000000000..c3c3e63ef --- /dev/null +++ b/framework/Template/test/Horde/Template/array_simple.html @@ -0,0 +1,6 @@ + + + + + + diff --git a/framework/Template/test/Horde/Template/array_simple.phpt b/framework/Template/test/Horde/Template/array_simple.phpt new file mode 100644 index 000000000..e691bc7af --- /dev/null +++ b/framework/Template/test/Horde/Template/array_simple.phpt @@ -0,0 +1,24 @@ +--TEST-- +Simple Array Test +--FILE-- +set('string', array('one', 'two', 'three')); +$template->set('int', array(1, 2, 3)); +echo $template->fetch('/array_simple.html'); + +?> +--EXPECT-- +one +two +three + +1 +2 +3 diff --git a/framework/Template/test/Horde/Template/divider.phpt b/framework/Template/test/Horde/Template/divider.phpt new file mode 100644 index 000000000..a01c0c4d4 --- /dev/null +++ b/framework/Template/test/Horde/Template/divider.phpt @@ -0,0 +1,13 @@ +--TEST-- +Divider Test +--FILE-- +set('a', array('a', 'b', 'c', 'd')); +echo $template->parse(","); + +?> +--EXPECT-- +a,b,c,d diff --git a/framework/Template/test/Horde/Template/if.html b/framework/Template/test/Horde/Template/if.html new file mode 100644 index 000000000..853d01f3d --- /dev/null +++ b/framework/Template/test/Horde/Template/if.html @@ -0,0 +1,4 @@ +foo +bar +truefalsevoid + diff --git a/framework/Template/test/Horde/Template/if.phpt b/framework/Template/test/Horde/Template/if.phpt new file mode 100644 index 000000000..20d7ec6e8 --- /dev/null +++ b/framework/Template/test/Horde/Template/if.phpt @@ -0,0 +1,22 @@ +--TEST-- +If/Else Test +--FILE-- +set('foo', true, true); +$template->set('bar', false, true); +$template->set('baz', 'baz', true); +echo $template->fetch('/if.html'); + +?> +--EXPECT-- +foo + +false +baz diff --git a/framework/Template/test/Horde/Template/iterator.phpt b/framework/Template/test/Horde/Template/iterator.phpt new file mode 100644 index 000000000..c79b0dd38 --- /dev/null +++ b/framework/Template/test/Horde/Template/iterator.phpt @@ -0,0 +1,27 @@ +--TEST-- +Iterator Test +--SKIPIF-- + +--FILE-- + 'one', 'two' => 2)); + +require dirname(__FILE__) . '/../../../lib/Horde/Template.php'; +$template = new Horde_Template(dirname(__FILE__)); +$template->set('s', $s); +$template->set('i', $i); +$template->set('a', $a); +echo $template->parse(",\n,\n,,"); + +?> +--EXPECT-- +one,two,three, +1,2,3, +one,2, diff --git a/framework/Template/test/Horde/Template/scalar.html b/framework/Template/test/Horde/Template/scalar.html new file mode 100644 index 000000000..439cfcb79 --- /dev/null +++ b/framework/Template/test/Horde/Template/scalar.html @@ -0,0 +1,2 @@ + + diff --git a/framework/Template/test/Horde/Template/scalar.phpt b/framework/Template/test/Horde/Template/scalar.phpt new file mode 100644 index 000000000..ac5450440 --- /dev/null +++ b/framework/Template/test/Horde/Template/scalar.phpt @@ -0,0 +1,19 @@ +--TEST-- +Scalar Test +--FILE-- +set('one', 'one'); +$template->set('two', 2); +echo $template->fetch('/scalar.html'); + +?> +--EXPECT-- +one +2 -- 2.11.0