X-Git-Url: https://jasonwoof.com/gitweb/?a=blobdiff_plain;f=template.php;h=9ccce8708e5eb991175eb22ce783d08ce408d148;hb=ba50d9a564ca3b8637212c634b5a61a38e6feb9f;hp=1187a5445bafac746d79156e4afa2c6aaa54f1c3;hpb=22d5fb7ab7d4ee86bd59e194387dca268bd577a1;p=wfpl.git
diff --git a/template.php b/template.php
index 1187a54..9ccce87 100644
--- a/template.php
+++ b/template.php
@@ -1,244 +1,239 @@
#
-# This file is part of wfpl.
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+
+# This is a simple template-handling system. You pass it a big data
+# structure with key/value pairs, and a template string to fill out.
#
-# wfpl 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.
-#
-# wfpl 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 wfpl; if not, write to the Free Software Foundation, Inc., 51
-# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
-
-# This file contains generally useful template handling code. It is wrapped in
-# an object so that if you want/need to you can make more than one instance of
-# it and they won't step on each other's toes. Also there are a set of global
-# functions at the bottom so you don't have to mess around with objects if you
-# don't want to. The documentation will be on the object methods, but just know
-# that each has a straight function wrapper at the bottom with 'tem_' prepended
-# to the name.
-
-# This is designed to be as simple as it can be for your project. The simple
-# way to use it is to set some key/value pairs with tem_set() then call
-# tem_output('filename.html') to output the page. A more complex example
-# including the use of sub-templates can be found in tem_test.php
-
-# FIXME: sub-sub templates need to be cleared when the sub template containing
-# them is run
+# Within a template, it recognizes tags delimited by tildes (~). When
+# the template is filled out, the tags will be replaced with the
+# corresponding data. Tags ending with '?' and '.' mark the start and
+# end of a sub-template (for optional or repeated text), and can be
+# wrapped in HTML comments (which will be removed along with the tags
+# when the template is filled out).
require_once('code/wfpl/encode.php');
-require_once('code/wfpl/misc.php'); # to get read_whole_file()
-
-class tem {
- var $keyval; # an array containing key/value pairs
- var $filename; # template filename (sometimes not set)
- var $template; # contents of template
- var $sub_templates; # tag-name/template-string pairs
- var $sub_subs; # key: sub-template name value: array of names of the sub-templates of this one
-
- # initialize variables
- function tem() {
- $this->keyval = array('' => '~'); # so that ~~ in the template creates a single ~
- $this->sub_templates = array();
- }
+require_once('code/wfpl/file.php');
+require_once('code/wfpl/misc.php');
- # set a key/value pair. if a ~tag~ in the template matches key it will be replaced by value
- function set($key, $value) {
- $this->keyval[$key] = $value;
- }
- # clear a value. Functionally equivalent to set($key, '') but cleaner and more efficient
- function clear($key) {
- unset($this->keyval[$key]);
- }
+# Public functions
+# ----------------
- # grab a value you stuck in earlier with set()
- function get($key) {
- return $this->keyval[$key];
- }
+function template($data, $template) {
+ return fill_template($data, parse_template($template));
+}
+
+function template_file($data, $filename) {
+ return fill_template($data, parse_template_file($filename));
+}
- # run the template engine on one of the sub-templates and append the result
- # to the keyval in the main array. See tem_test.php for an example of how
- # this can be used.
- function sub($sub_template_name) {
- $this->keyval[$sub_template_name] .= template_run($this->sub_templates[$sub_template_name], $this->keyval);
+function parse_template_file($filename) {
+ return parse_template(file_get_contents($filename));
+}
- # after running a sub-template, clear its sub-templates
- if(isset($this->sub_subs[$sub_template_name])) {
- foreach($this->sub_subs[$sub_template_name] as $sub_sub) {
- $this->clear($sub_sub);
+# First we take the template string and break it up into an array
+# of strings and sub-arrays. The first item in a sub-array is the name
+# of the value or sub-template.
+
+function parse_template($string) {
+ # Don't mess with the $stack/$tem assignments! Since
+ # PHP references point to the variable, not the data,
+ # it really does have to be written exactly like this.
+ $stack[] = array('name' => 'root', 'pieces' => array());
+ $tem = &last($stack);
+ # note: for some reason this captures ''.
+ $matches = preg_split("/()/", $string, -1, PREG_SPLIT_DELIM_CAPTURE);
+ foreach($matches as $match) {
+ if(substr($match,0,1) == '~') {
+ $args = explode(' ', substr($match,1,-1));
+
+ if(count($args) == 1 and $args[0] == '}') $name = '';
+ else $name = array_shift($args);
+
+ if(last($args) == '{') { # open block
+ array_pop($args);
+ $stack[] = array('name' => $name, 'pieces' => array(), 'args' => $args);
+ $tem['pieces'][] = &last($stack);
+ $tem = &last($stack);
+ } elseif(last($args) == '}') { # close block
+ array_pop($args);
+ $cur = $stack[count($stack)-1]['name'];
+ if($name && $name != $cur) {
+ die("Invalid template: tried to close '$name', but '$cur' is current.");
+ }
+ array_pop($stack); $tem = &last($stack);
+ } else { # value slot
+ $tem['pieces'][] = array('name' => $name, 'args' => $args);
}
+ } elseif($match and $match != '', substr($in, 0, 12)) == 0) {
- $in = substr($in, 12);
- $parent = array_pop($parents);
- return;
- }
+# Replace top-level values in $main with top-level templates from $tem.
+function merge_templates($main, $tem) {
+ $out = array('name' => $main['name'], 'pieces' => array());
- $matches = array();
- # this limits sub_template names to 50 chars
- if(ereg('^', substr($in, 0, 65), $matches)) {
- list($start_tag, $tag_name) = $matches;
+ $subs = top_sub_templates($tem);
- # keep track of the tree
- if(!isset($this->sub_subs[$parent])) {
- $this->sub_subs[$parent] = array();
- }
- array_push($this->sub_subs[$parent], $tag_name);
- array_push($parents, $parent);
- $parent = $tag_name;
-
- $out .= '~' . $tag_name . '~';
- $in = substr($in, strlen($start_tag));
- $this->sub_templates[$tag_name] = '';
- $this->_load($in, $this->sub_templates[$tag_name], $parents, $parent);
- } else {
- # it's not a start tag or end tag, so let's pass it through:
- $out .= substr($in, 0, 5);
- $in = substr($in, 5);
- }
- } #repeat
+ foreach($main['pieces'] as $piece) {
+ if(is_array($piece) and !$piece['pieces'] and $subs[$piece['name']]) {
+ $piece = $subs[$piece['name']];
+ }
+ $out['pieces'][] = $piece;
}
+ return $out;
+}
- # This is useful when you have sub-templates that you want to mess with
- # before the main template is run. But can also be used to simply specify
- # the filename ahead of time.
- function load($filename) {
- $this->filename = $filename;
- $tmp = read_whole_file($filename);
- $this->template = '';
- $parents = array('top_level_subs' => array());
- $parent = 'top_level_subs';
- $this->_load($tmp, $this->template, $parents, $parent);
- }
-
- # Run the template. Pass a filename, or a string, unless you've already
- # specified a template with load()
- function run($templ = false) {
- if($templ !== false) {
- if(strlen($templ) < 150 && file_exists($templ)) {
- $this->filename = $templ;
- unset($this->template);
- } else {
- $this->template = $templ;
- }
- }
- if(!$this->template) {
- if(!$this->filename) {
- print "sorry, no template to run\n";
- exit(1);
- }
- $this->template = read_whole_file($this->filename);
- }
-
- return template_run($this->template, $this->keyval);
- }
+# tem_auto functions
+# ------------------
+#
+# If a { tag has an argument, the corresponding tem_auto function is called.
+# This allows it to mangle the data to automate some common cases.
+
+# 'sep' (separator) sections will be shown for all but the last parent row.
+# Sample usage:
+#
+#
+# row content...
+#
+#
+#
+#
+function tem_auto_sep($piece, $context, $keychain) {
+ list($name, $index, $this_name) = array_slice($keychain, -3);
+ $array = _tem_get($name, $context);
+ if($index != count($array)-1) return true;
+}
- # same as run() except the output is print()ed
- function output($templ = false) {
- print($this->run($templ));
- }
+# 'once' sections will be shown once unless the corresponding data value
+# is false. We check only for false; 0 or '' will not work.
+
+function tem_auto_once($piece, $context, $keychain) {
+ $value = _tem_get(array_pop($keychain), $context);
+ if($value !== false) return true;
}
-# Below are functions so you can use the above class without allocating or
-# keeping track of it.
+# 'evenodd' sections are given an 'evenodd' attribute whose value
+# alternates between 'even' and 'odd'.
-# get a reference to the current template object
-function tem_init() {
- if(!$GLOBALS['wfpl_template']) {
- $GLOBALS['wfpl_template'] = new tem();
+function tem_auto_evenodd($piece, $context, $keychain) {
+ $rows = _tem_get(array_pop($keychain), $context);
+ $even = 0;
+ $text = array('even', 'odd');
+ foreach($rows as $key => $value) {
+ $rows[$key]['evenodd'] = $text[$even];
+ $even = 1 - $even;
}
-}
-
-function tem_set($key, $value) {
- tem_init();
- $GLOBALS['wfpl_template']->set($key, $value);
-}
-
-function tem_get($key) {
- tem_init();
- return $GLOBALS['wfpl_template']->get($key);
+ return $rows;
}
-function tem_run($templ = false) {
- tem_init();
- return $GLOBALS['wfpl_template']->run($templ);
-}
-function tem_sub($sub_template_name) {
- tem_init();
- $GLOBALS['wfpl_template']->sub($sub_template_name);
-}
-function tem_load($filename) {
- tem_init();
- $GLOBALS['wfpl_template']->load($filename);
+# Internal functions
+# ------------------
+#
+# Of course, nothing stops you from using these, but I don't know
+# why you would want to...
+
+
+# Convert value to array of hashes for use in sub-template expansion.
+# This adds flexibility to how you represent your data.
+function template_rows($value) {
+ if(is_array($value)) {
+ # numeric keys, is already array of arrays -- expand sub-template for each.
+ if(array_key_exists(0, $value)) return $value;
+ # key/value pairs -- expand sub-template once.
+ else return array($value);
+ } elseif($value) {
+ # value -- expand sub-template once using only parent values
+ return array(array());
+ } else {
+ # empty value -- don't expand sub-template
+ return array();
+ }
}
-function tem_output($filename = false) {
- tem_init();
- $GLOBALS['wfpl_template']->output($filename);
+function _tem_get($key, $context) {
+ while($context) {
+ $data = array_pop($context);
+ if(array_key_exists($key, $data)) return $data[$key];
+ }
}
+function tem_get($piece, $context, $keychain)
+{
+ if(count($piece['args'])) {
+ $func = "tem_auto_" . $piece['args'][0];
+ if(function_exists($func)) return $func($piece, $context, $keychain);
+ else die("ERROR: template auto function '$func' not found.
\n");
+ } else return _tem_get($piece['name'], $context);
+}
-
-# this is used in template_run() and should be of no other use
-function template_filler($matches) {
- list($tag, $enc) = explode('.', $matches[1], 2);
- $value = $GLOBALS['wfpl_template_keyval'][$tag];
- if($enc) {
- $encs = explode('.', $enc);
- foreach($encs as $enc) {
- $enc = "enc_$enc";
- if(function_exists($enc)) {
- $value = $enc($value);
- } else {
- print "ERROR: encoder function '$enc' not found.
\n";
- exit(1);
- }
- }
+# $tag is a hash with keys 'name' and 'args'.
+function tem_get_enc($tag, $context)
+{
+ $key = $tag['name'];
+ $value = _tem_get($key, $context);
+ foreach($tag['args'] as $encoding) {
+ $func = "enc_$encoding";
+ if(function_exists($func)) $value = $func($value, $key);
+ else die("ERROR: encoder function '$func' not found.
\n");
}
return $value;
}
-
-# pass a template string and an associative array of the key/values and it
-# returns the result.
-function template_run($template, &$keyval) {
- $GLOBALS['wfpl_template_keyval'] =& $keyval;
- return preg_replace_callback(array('||', '|~([^~]*)~|', '|([^<]*)|', '|([^<]*)
|'), 'template_filler', $template);
+function top_sub_templates($tem) {
+ $subs = array();
+ foreach($tem['pieces'] as $piece) {
+ if(is_array($piece) and $piece['pieces']) {
+ $subs[$piece['name']] = $piece;
+ }
+ }
+ return $subs;
}
-
?>