/* * BSLTemplate.java * * Brazil project web application toolkit, * export version: 2.3 * Copyright (c) 1999-2009 Sun Microsystems, Inc. * * Sun Public License Notice * * The contents of this file are subject to the Sun Public License Version * 1.0 (the "License"). You may not use this file except in compliance with * the License. A copy of the License is included as the file "license.terms", * and also available at http://www.sun.com/ * * The Original Code is from: * Brazil project web application toolkit release 2.3. * The Initial Developer of the Original Code is: cstevens. * Portions created by cstevens are Copyright (C) Sun Microsystems, Inc. * All Rights Reserved. * * Contributor(s): cstevens, drach, guym, suhler. * * Version: 2.21 * Created by cstevens on 99/10/21 * Last modified by suhler on 09/06/30 09:34:58 * * Version Histories: * * 2.21 09/06/30-09:34:58 (suhler) * add "index=nnn" to to provide the iteration count of each item, * starting at nnn. * * 2.20 09/06/03-10:55:58 (suhler) * added "track" option to track some variables on the console * * 2.19 09/01/20-16:13:41 (suhler) * use static inner class * * 2.18 08/08/29-10:04:05 (suhler) * doc fixes * * 2.17 08/07/24-16:35:12 (suhler) * preserve accumulation state * * 2.16 06/11/13-10:36:59 (suhler) * added "namespace" qualifier to foreach "glob" and "match" * * 2.15 06/07/26-15:38:48 (suhler) * add "map" attributes for when all=false * . * * 2.14 05/06/17-15:46:52 (suhler) * added as an if option * * 2.13 05/06/16-08:04:57 (suhler) * move getNamespaceProperties into the rewriteContext * * 2.12 05/05/11-11:55:33 (suhler) * added namespace=xxx to * * 2.11 05/05/11-11:33:05 (suhler) * add "namespace=xxx" option to to allow extraction results * to go the the specified namespace * * 2.10 04/12/30-12:38:04 (suhler) * javadoc fixes * * 2.9 04/12/15-12:30:13 (suhler) * When using * then the matched delimiter and all of its sub-matches are made * available in the scope of the foreach. * * 2.8 04/10/26-11:28:48 (suhler) * doc fixes * * 2.7 04/05/24-15:13:13 (suhler) * add "map" attribute to * * 2.6 04/04/28-13:54:54 (suhler) * Mark the propertiesList that holds the foreach iteration values * "transient", so any done inside of a loop aren't lost * when the loop exits * * 2.5 03/10/06-09:19:36 (suhler) * Fix NPE when result doesn't match on "all" case * * 2.4 03/07/10-09:24:04 (suhler) * Use common "isTrue/isFalse" code in utin/format. * * 2.3 03/07/09-12:28:19 (suhler) * bug fix for tagPrefix handling * * 2.2 03/07/07-14:04:45 (suhler) * modified to use closingTag() conveniencer macros, so tagPrefixes work * properly * * 2.1 02/10/01-16:36:45 (suhler) * version change * * 1.51 02/07/24-10:47:18 (suhler) * doc updates * * 1.50 02/05/31-11:21:22 (suhler) * change references from PropsTemplate to SetTemplate * * 1.49 02/05/13-11:49:38 (suhler) * add numeric sorting to glob and match * * 1.48 02/05/13-10:07:38 (suhler) * now uses the propertyName(glob) method of propertyLists * * 1.47 01/11/21-11:43:02 (suhler) * doc fixes * * 1.46 01/08/29-08:57:14 (suhler) * use the Glob enumerator in PropertiesList. The * should be similarly redone * * 1.45 01/08/28-20:54:53 (suhler) * added * * 1.44 01/08/10-16:49:07 (guym) * * 1.43 01/08/10-14:56:24 (guym) * Per code review, made Abort, Break, Continue flags an enum, plus fixed a couple of bugs * * 1.42 01/08/03-20:27:29 (guym) * Finished documentation for , , - also made code thread-safe. * * 1.41 01/08/03-15:27:35 (drach) * Add PropertiesList * * 1.40 01/08/03-11:06:55 (guym) * * 1.39 01/08/01-15:00:40 (guym) * Added , , and tags to BSL - right now, only the tag has passed all regressions tests however. * * 1.38 01/07/26-16:41:43 (guym) * Merged changes between child workspace "/home/guym/ws/brazil/naws" and * parent workspace "/export/ws/brazil/naws". * * 1.33.1.1 01/07/26-16:30:01 (guym) * Added , and functionality * * 1.37 01/07/17-18:40:17 (suhler) * doc fixes * * 1.36 01/07/16-16:46:17 (suhler) * use new Template convenience methods * * 1.35 01/07/11-16:27:04 (suhler) * added option. * * 1.34 01/06/25-10:44:15 (suhler) * Removed "while". No-one is using it, and we can always put it back * later * Unified the treatment of "Boolean" attributes * Added a nocase option to regexp matching * * 1.33 01/06/04-11:06:01 (suhler) * allow an alternative to the empty string for missing properties * * 1.32 01/05/24-10:43:46 (suhler) * added regular expression delimiters * * 1.31 01/05/22-16:58:13 (suhler) * added "all" attribute to to permit extraction from all matches * * 1.30 01/05/11-14:50:08 (suhler) * use template.debug() * * 1.29 01/05/11-10:42:15 (suhler) * "compareToIgnoreCase" doesn't exist in jdk1.1 * * 1.28 01/05/08-15:23:19 (cstevens) * Case-insensitive sorting. * * 1.26.1.1 01/04/04-11:49:36 (suhler) * * 1.27 01/04/02-12:05:55 (cstevens) * BSLTemplate.java: When glob or regexp pattern had more than 20 subexpressions, * a limit was being exceeded that should have been detected, but instead was * causing an ArrayOutOfBoundsException due to an off-by-one programming error. * BSLTemplate.java: Add server property "${prefix}.limit" to the tag to * prevent excessive recursion. It's a server property not a request property so * accidental or malicious HTML cannot change the limit. * BSLTemplate.java: The tag now creates temporary variables in a new * Properties object rather than in the main request.properties. Seems to be a * cleaner implementation. It took longer to (A) save any old values, set the * temporary values, and then restore the old values than to (B) push a new * Properties object that lives for the body of the . * * 1.26 01/03/23-13:23:27 (cstevens) * tag and documentation * * 1.25 01/02/13-13:40:48 (cstevens) * Merged changes between child workspace "/home/cstevens/ws/brazil/naws" and * parent workspace "/export/ws/brazil/naws". * * 1.23.1.1 01/02/12-17:11:35 (cstevens) * Record whether tag worked * * 1.24 01/01/14-14:51:42 (suhler) * doc fixes. Change prefix -> prepend * * 1.23 01/01/11-18:47:20 (cstevens) * tag for BSL * * 1.22 00/12/11-13:30:30 (suhler) * add class=props for automatic property extraction * * 1.21 00/10/26-15:59:52 (suhler) * implement serializable * . * * 1.20 00/10/05-15:51:50 (cstevens) * PropsTemplate.subst() and PropsTemplate.getProperty() moved to the Format * class. * * 1.19 00/07/06-15:49:21 (suhler) * doc update * * 1.18 00/07/06-11:35:34 (cstevens) * broke "sort=" in BSL. * * 1.17 00/07/05-14:11:49 (cstevens) * PropsTemplate and BSLTemplate allow '[' and ']' to specify a variable * substitution wherever a constant was used before. For example, as in * '' to compare the value of property "stock" * not to the constant "[favorite]", but to the value of the property "favorite". * In order to include a literal "[" in an option, use "\[" -- a backslash * escapes the following character, whatever it is. * * 1.16 00/06/29-10:46:41 (suhler) * added the '[]' capability to glob and match values of foreach * * 1.15 00/05/31-13:50:26 (suhler) * docs * * 1.14 00/05/24-11:26:46 (suhler) * add docs * * 1.13 00/05/10-16:31:20 (suhler) * Added the "delim=" option to and * to specifiy the token delimiter in property lists. * * 1.12 00/05/10-13:37:01 (suhler) * added docs * * 1.11 00/04/27-16:04:39 (cstevens) * Merged changes between child workspace "/home/cstevens/ws/brazil/naws" and * parent workspace "/export/ws/brazil/naws". * * 1.9.1.1 00/04/27-16:02:24 (cstevens) * Sorting in tag. * * 1.10 00/04/26-16:08:27 (suhler) * added documentation * * 1.9 00/04/17-14:25:58 (cstevens) * PropsTemplate.java: * 1. exposes public method to parse and dereference composed property * names (using the '[' and ']' characters). Method is used by BSLTemplate. * * 1.8 00/04/12-15:55:48 (cstevens) * debugging comments * * 1.7 00/03/29-16:16:10 (cstevens) * PropsTemplate exposes public method to parse and dereference composed property * names (using the '[' and ']' characters). Method is used by BSLTemplate. * * 1.6 00/02/07-11:36:49 (cstevens) * Merged changes between child workspace "/home/cstevens/ws/brazil/naws" and * parent workspace "/export/ws/brazil/naws". * * 1.4.1.1 00/02/07-11:32:35 (cstevens) * If was missing, BSL template got a null-pointer exception. * * 1.5 00/02/02-09:35:35 (suhler) * claim to be serializable * * 1.4 99/11/17-10:20:03 (suhler) * fixed debug flag * * 1.3 99/10/26-20:51:07 (cstevens) * docs * * 1.2 99/10/26-17:12:53 (cstevens) * Changed TclServerTemplate and BSLTemplate to use the config parameter "debug" * to decide whether to emit comments into the resultant HTML file. * * 1.2 99/10/21-18:22:20 (Codemgr) * SunPro Code Manager data about conflicts, renames, etc... * Name history : 1 0 handlers/templates/BSLTemplate.java * * 1.1 99/10/21-18:22:19 (cstevens) * date and time created 99/10/21 18:22:19 by cstevens * */ package sunlabs.brazil.template; import sunlabs.brazil.properties.PropertiesList; import sunlabs.brazil.server.Request; import sunlabs.brazil.util.Glob; import sunlabs.brazil.util.Sort; import sunlabs.brazil.util.Format; import sunlabs.brazil.util.Calculator; import sunlabs.brazil.util.regexp.Regexp; import sunlabs.brazil.util.regexp.Regsub; import sunlabs.brazil.session.SessionManager; import java.util.Enumeration; import java.util.Properties; import java.util.StringTokenizer; import java.util.Vector; import java.io.Serializable; /** * The BSLTemplate takes an HTML document with embedded "BSL" * markup tags in it and evaluates those special tags to produce a * standard HTML document. *

* BSL stands for Brazil Scripting Language. BSL can be used to substitute * data from the request properties into the resultant document. However, * rather than simple property substitution as is provided by the * SetTemplate, this class provides the ability to iterate * over and choose amongst the values substituted with a set of simple * flow-control constructs. *

* BSL uses the following special tags as its language constructs:

    *
  • <if> *
  • <foreach> *
  • <abort> *
  • <break> *
  • <continue> *
  • <extract> *
*

* This template recursively evalutes the bodies/clauses of the BSL commands, * meaning that they may contain nested BSL and/or other tags defined by * other templates. *

* The following configuration parameter is used to initialize this * template. *

*
debug *
If this configuration parameter is present, this template replaces * the BSL tags with comments, so the user can keep track of where * the dynamically generated content is coming from by examining the * comments in the resultant HTML document. By default, the BSL tags * are completely eliminated from the HTML document rather than changed * into comments. *
*
*

<if> TAG

* The <if> tag evaluates one of its clauses dependant * upon the value of the provided conditions. The other clauses are not * evaluated and do not appear in the resultant HTML document. * The general format of the <if> tag is as follows: *
 * <if [not] condition>
 *     clause
 * <elseif [not] condition>
 *     clause
 * <else>
 *     clause
 * </if> 
* * The <elseif> and <else> tags are * optional, and multiple <elseif> tags may be present. * <elseif> may also be spelled <elif> * or <else if>. The optional parameter not * reverses the sense of the specified condition. *

* * Following are the formats of the condition:

*

*

<if name=var> *
Test if the value of property var is set and is not "", * "false", "no", "off", or the number 0. *

*

<if name=var value=string> *
Test if the value of property var is equal to the * given string. *

*

<if name=pattern any> *
Test if any property exists that matches matches the given * {@link sunlabs.brazil.util.Glob glob} pattern. * Note: This may be expensive if there are large numbers of * properties. *

*

<if name=var glob=pattern> *
Test if the value of property var matches the given * {@link sunlabs.brazil.util.Glob glob} pattern. *

*

<if name=var match=pattern> *
Test if the value of property var matches the given * {@link sunlabs.brazil.util.regexp.Regexp regular expression} * pattern. *

* if the attribute nocase is present, then a case * insensitive match is performed. *

<if expr=numeric expression> *
The numeric expression is evaluated. If the result is "1", then * the condition is satisfied. This uses {@link sunlabs.brazil.util.Calculator} to evaluate the expression. Any vaiable that is defined, but not "0"m "off" or "no" is considered * to have a value of "1" for the purposes of the expression evaluation. This allows (as an example) the expression "x && y && ! z" to evaluate to "true" only if the variables "x" and "y", but not "z" are defined *(and not "0", "no" or "false". *
* Normally, the variable is look-up in the Properties Stack, starting * with the Request Properties. The attribute "namespace" may be used * to look-up the variable in the specified namespace. *
*

<foreach> TAG

* The <foreach> tag repeatedly evaluates its body a * selected number of times. Each time the body is evaluated, the provided * named property is set to the next word in the provided list of words. * The body is terminated by the </foreach> tag. * This tag is especially useful for dynamically producing lists and tables. *
    *
  •  * <foreach name=var list="value1 value2 ..." [delim="chars index=i"]>
     *     <get var>
     *     body
     * </foreach>
    * * Iterate over the set of values "value1 value2 ...". The named * property var is assigned each value in turn. *

    * If the optional parameter delim specifies, the * delimiter for splitting the list into elements. *

      *
    • If delim is * a single character, then that character is used as the delimiter. *
    • If delim is not * specified or is the empty string "", the delimiter is whitespace. *
    • Otherwise, the delimeter is taken to be a regular expression. * If the regular expression is invalid, the entire list is taken as * a single element. *
    • If the delimiter is a regular expression, and no sorting * is requested (I was lazy), in addition to the property * var, the properties * var.delime, var.delim.1 ... * are made available that represent the value of the previous * delimiter and all of its sub-matches (if any). *
    • * If the optional parameter index is supplied, then at each * iteration, the value "name.count" counts the items, starting with the * supplied index value (or 1). *
    . * *

  •  * <foreach name=var property=property [delim="chars" index="i"]>
     *     <get var>
     *     body
     * </foreach> 
    * * Iterate over the values in the other property. The value * of the other property is broken into elements and each element is * assigned to the named property var in turn. This form is * equivalent to <foreach name=var list=${property}>. *

    * If the optional parameter delim is specified, the characters * are delimiters for splitting the list into elements using the * StringTokenizer rules. If delim is not * specified or is the empty string "", the delimiter is whitespace. * *

  •  * <foreach name=var glob=pattern [index=i]>
     *     <get var.name>
     *     <get var.value>
     *
     *     <get var.name.1>
     *     <get var.name.2>
     *     body
     * </foreach> 
    * * Iterate over all the properties whose name matches the * {@link sunlabs.brazil.util.Glob glob} pattern. In turn, the * following properties are set: *
    * var.name is the name of the property. *
    * var.value is the value of the property. *
    * var.name.1, var.name.2, ... * are the substrings matching the wildcard characters in the pattern, if any. * *

  •  * <foreach name=var match=pattern [index=i]>
     *     <get var.name>
     *     <get var.value>
     *
     *     <get var.name.0>
     *     <get var.name.1>
     *     <get var.name.2>
     *     body
     * </foreach> 
    * * Iterate over all the properties whose name matches the * {@link sunlabs.brazil.util.regexp.Regexp regular expression} * pattern. In turn, the following properties are set: *
    * var.name is the name of the property. *
    * var.value is the value of the property. *
    * var.name.0 is the substring that matched the whole * pattern. *
    * var.name.1, var.name.2, ... * are the substrings matching the parenthesized subexpressions, if any. *
    * If the optional parameter index is supplied, then * var.count * counts the items, starting with the supplied index value (or 1). *

    * NOTE: In the current implementation, when there are large numbers of * property values, using glob is (potentially) much more * efficient than match for locating names. *

* All the temporary properties that this tag creates are visible only * within the body for the duration of the * <foreach>. *

* if the attribute nocase is present, then a case * insensitive match is performed. *

* When either glob or match is specified, then * the "namespace" attribute may be used to restrict the name lookups to start * with that namespace. However, the values aren't restricted to being in the * specified namespace. *

BUG: * You must only specify a "namespace" if it has already been imported. *

Sorting using foreach

* The <foreach> tag contains a feature to * change the order of iteration. This facility is intended for * common sorting operations. For general purpose manipulation of * the iteration order, the order should be defined either in another * handler, or by using the * {@link sunlabs.brazil.tcl.TclServerTemplate <server>} * directive. *

* The four additional parameters used to control sorting are: *

*
reverse *
The list of items is iterated in the reverse order. *

*

sort[=key] *
The items to be iterated over are sorted. If no key is * supplied, the items are sorted by the property name. If a * key is supplied, its value is used as the sort key for * the iteration. For this to be meaningful, the key should contain * variable substitutions (e.g. ${...}, see * {@link sunlabs.brazil.util.Format#getProperty getProperty}). A * sample use of the sort key would be: *
 * <foreach name=id property=employee.ids sort="${employee.${id}.last}, ${employee.${id}.first}">
* This option can be tricky to use correctly. The following example * will not sort employees by last name: *
 * 1. <foreach name=id property=employee.ids sort="employee.${id}.last">
* Why? Because another level of ${...} needs to be inserted in the * sort key: *
 * 2. <foreach name=id property=employee.ids sort="${employee.${id}.last}">
* Example (1) will just sort the literal strings "employee.1234.last", * "employee.5678.last", etc. while example (2) will do the correct * thing and sort the values "Stevens" and "Johnson". Remember that BSL * sorts based on exactly what you pass it and does not know that the * provided string should be treated as another variable itself. *

*

numeric *
When used in conjunction with the sort parameter, it * causes the items to be interpreted as numbers (or zero if the item * doesn't look like a number). *

*

nocase *
When used in conjunction with the sort parameter, it * causes the items to be sorted in a case-insensitive fashion. *

* Note that when * used with "glob=..." or "match=...", it * does NOT cause the glob or regular expression to be case * insensitive. It causes the results of the glob or regexp to be * sorted in a case-insensitive fashion. There is currently no way * to specify a case-insensitive glob or regular expression. *

*
*

<abort> TAG

* The <abort> tag terminates processing of the current * HTML page at the point it is evaluated. All HTML on the page after the * <abort> tag is discarded, and the HTML processed up to * that point is returned. This tag can be placed anywhere on a page, * including within a <foreach> or <if> * construct. *

* The following is an example usage of this tag: *

*

 * <foreach name=x list="0 1 2 3">
 *   <if name=x value=3>
 *     <abort>
 *   </if>
 *   <get name=x>
 * </foreach> 
 * Testing
 * 
* This example produces the output: *

*

 * 0 1 2
 * 
*
*

<break> TAG

* The <break> tag terminates processing within a * <foreach> construct. The processing of HTML * continues immediately after the </foreach>. This * tag can only be used inside of a <foreach> tag. *

* The following is an example usage of this tag: *

*

 * <foreach name=x list="0 1 2 3">
 *   <if name=x value=3>
 *     <break>
 *   </if>
 *   <get name=x>
 * </foreach> 
 * Testing
 * 
* This example produces the output: *

*

 * 0 1 2 Testing
 * 
*
*

<continue> TAG

* The <continue> tag continues processing at the top * of a <foreach> construct. This skips any HTML * after the <continue> tag and before the * </foreach>. This tag can only be used inside of a * <foreach> tag. *

* The following is an example usage of this tag: *

*

 * <foreach name=x list="0 1 2 3">
 *   <if name=x value=2>
 *     <continue>
 *   </if>
 *   <get name=x>
 * </foreach> 
 * Testing
 * 
* This example produces the output: *

*

 * 0 1 3 Testing
 * 
*
*

<extract> TAG

* The <extract> tag permits portions of a property's * value to be extracted into additional properties, based on either glob * or regular expression patterns. The extract tag takes the following * tag parameters: *
*
name=var *
The name of the property whose value will be split up and * extracted. *

*

prepend=base *
Optional parameter that specifies the base string to * prepend to the extracted properties. The default value for * base is the specified property name var. *

*

glob=pattern *
The {@link sunlabs.brazil.util.Glob glob} pattern to * match against. In turn, the following properties are set: *
* base.1, base.2, ... * are the substrings that matched the wildcard characters in * the pattern. *

*

match=pattern *
The {@link sunlabs.brazil.util.regexp.Regexp regular expression} * pattern to match against. In turn, the following * properties are set: *
* base.0 is the substring that matched the * whole pattern. *
* base.1, base.2, ... * are the substrings that matched the parenthesized subexpressions * in pattern, if any. *

* If the attribute all is present, then all matches and * submatches are extracted into properties. The properties * base.0, base.1, * etc., are set to the 1st matched expression, the 2nd matched * expression, etc. The properties * base.0.0, base.0.1... * are set to the sub-matches of the first full match, and so forth. * If any matches are found the following additional * properties are set: *

*
basematches *
The number of times the regular expression was matched. *
basesubmatches. *
The number of sub-expressions for this regular expression. *
basematchelist. *
The list of matches (e.g. "1 2 3 ..."). *
*
replace=substitution *
If specified (with match), then no portions of the value are * extracted. Instead, * a regular expression substitution is performed (see * {@link sunlabs.brazil.util.regexp.Regexp#sub substitution}). * The resultant substituted value is placed in the property: * prefix.replace. *
map *
A white space separated list of names that will be used to * name sub-matches, instead of .1, .2, ... etc. See * {@link #tag_extract} for more detail. *
* One of glob or match must be specified. *

* In addition, the property base.matches is * set to a value indicating the number of matches and submatches stored. * This property can be examined and compared with 0 to * determine if the <extract> tag matched at all. If * there was no match, the numbered properties base.N * are not set or changed from their previous value. *


* Anytime an argument is specified to one of the BSL tags, variable * substitution as described in {@link sunlabs.brazil.util.Format#getProperty * getProperty} may be used. *

* Any time a boolean parameter (XXX) is allowed * (nocase, not, numeric, or reverse) it is considered false if * it takes any of the forms: XXX=0, XXX=no, XXX=false XXX="". If * it takes the forms XXX or XXX="anything else", the value is true. *

* see a sample HTML page that contains some BSL * markup. * @see SetTemplate */ public class BSLTemplate extends Template implements Serializable { String tagPrefix = ""; // prefix all our tags with this public boolean init(RewriteContext hr) { return super.init(hr); } /* Public states that we can put our RewriteContext into */ public final static int ABORT = 0x1; public final static int BREAK = 0x2; public final static int CONTINUE = 0x4; /** * Handles the "abort" tag. */ public void tag_abort(RewriteContext hr) { hr.killToken(); /* Tell our RewriteContext to abort */ hr.abort(); } /** * Handles the "break" tag. */ public void tag_break(RewriteContext hr) { hr.killToken(); /* Perform our break operation */ if (!hr.setRewriteState(BREAK)) { debug(hr,"No enclosing for this "); } } /** * Handles the "continue" tag. */ public void tag_continue(RewriteContext hr) { hr.killToken(); /* Perform our continue operation */ if (!hr.setRewriteState(CONTINUE)) { debug(hr,"No enclosing for this "); } } /** * Handles the "foreach" tag. */ public void tag_foreach(RewriteContext hr) { String name = hr.get("name"); if (name == null) { return; } hr.incrNestingLevel(); hr.killToken(); debug(hr); String arg; boolean done = false; if ((arg = hr.get("list")) != null) { done = foreach_list(hr, name, arg, hr.get("delim")); } else if ((arg = hr.get("glob")) != null) { done = foreach_match(hr, name, arg, true); } else if ((arg = hr.get("match")) != null) { done = foreach_match(hr, name, arg, false); } else if ((arg = hr.get("property")) != null) { arg = hr.request.props.getProperty(arg, ""); done = foreach_list(hr, name, arg, hr.get("delim")); } if (done == false) { /* * This doesn't mean "error", but that the body of the * was not processed, so we are not sitting at the * and we need to skip to it. * For instance, . */ boolean was = hr.accumulate(false); skipTo(hr, "foreach"); hr.accumulate(was); } debug(hr); hr.decrNestingLevel(); } private static class SortThing { String index; String s; double d; public SortThing(String index, String key) { this.index = index; this.s = key; } public SortThing(String index, double d) { this.index = index; this.d = d; } } private static class Compare implements Sort.Compare { boolean numeric; boolean reverse; boolean nocase; Compare(boolean numeric, boolean reverse, boolean nocase) { this.numeric = numeric; this.reverse = reverse; this.nocase = nocase; } public int compare(Object array, int index1, int index2) { SortThing[] sa = (SortThing[]) array; SortThing s1 = sa[index1]; SortThing s2 = sa[index2]; int result = 0; if (numeric) { if (s1.d > s2.d) { result = 1; } else if (s1.d == s2.d) { result = 0; } else { result = -1; } } else if (nocase) { result = s1.s.toLowerCase().compareTo(s2.s.toLowerCase()); } else { result = s1.s.compareTo(s2.s); } if (reverse) { result = -result; } return result; } } /* This method checks to see if a or tag has been processed */ /* It returns a boolean value which is used in foreach_list() and */ /* foreach_match() to determine whether processing of the incoming HTML */ /* should continue. */ private boolean isBreakAbort(RewriteContext hr) { if (!hr.checkRewriteState(ABORT) && !hr.checkRewriteState(BREAK)) { /* We haven't hit an or tag - continue */ /* processing */ return false; } else if (hr.checkRewriteState(BREAK)) { /* We've encountered a tag - set the flag back to */ /* false so we can pop "up" one nesting level and continue */ /* processing */ hr.unsetRewriteState(BREAK); return true; } else if (hr.getNestingLevel() == 1) { /* We've encountered an tag within the outermost */ /* - set the flag to false. */ hr.unsetRewriteState(ABORT); return true; } else { /* We've encountered an tag within a nested */ /* - leave the flag set to true and pop */ /* "up" one nesting level to continue processing the */ /* abort "event" */ return true; } } /* This method processes each element of a list within a tag */ /* e.g. - */ private boolean foreach_list(RewriteContext hr, String name, String list, String delim) { BSLProps local = new BSLProps(hr.request); int counter = Format.stringToInt(hr.get("index"), 1); boolean shouldCount = hr.get("index")!=null; boolean isRe = false; StringTokenizer st; if (delim == null || (delim.length() == 0)) { st = new StringTokenizer(list); } else if (delim.length()==1) { st = new StringTokenizer(list, delim); } else { try { st = new ReStringTokenizer(list, delim); isRe = true; } catch (IllegalArgumentException e) { debug(hr, "Bad re: " + delim); st = new StringTokenizer(list, ""); } } String rest = hr.lex.rest(); boolean any = false; String sort = hr.get("sort", false); // don't expand ${..} yet boolean reverse = hr.isTrue("reverse"); if ((sort == null) && (reverse == false)) { while (st.hasMoreTokens()) { if (isRe) { // previous token Regsub rs = ((ReStringTokenizer)st).getRs(); String matched = rs.matched(); if (matched != null) { local.push(name + ".delim", matched); int subs = rs.getRegexp().subspecs(); for(int i=1; i or event */ if (isBreakAbort(hr)) { break; } } } else if ((sort == null) && (reverse == true)) { Vector v = new Vector(); while (st.hasMoreTokens()) { v.addElement(st.nextToken()); } for (int i = v.size(); --i >= 0; ) { local.push(name, v.elementAt(i)); if (shouldCount) local.push(name + ".count", "" + (counter++)); any = true; processTo(hr, rest, "foreach"); /* Check for a or event */ if (isBreakAbort(hr)) { break; } } } else { boolean numeric = hr.isTrue("numeric"); boolean nocase = hr.isTrue("nocase"); if (sort.length() == 0) { sort = null; } Vector v = new Vector(); while (st.hasMoreTokens()) { String value = st.nextToken(); String key = value; if (sort != null) { local.push(name, value); key = Format.subst(local, sort); } if (numeric) { v.addElement(new SortThing(value, getDouble(key))); } else { v.addElement(new SortThing(value, key)); } } SortThing[] array = new SortThing[v.size()]; v.copyInto(array); Sort.qsort(array, new Compare(numeric, reverse, nocase)); for (int i = 0; i < array.length; i++) { local.push(name, array[i].index); if (shouldCount) local.push(name + ".count", "" + (counter++)); any = true; processTo(hr, rest, "foreach"); /* Check for a or event */ if (isBreakAbort(hr)) { break; } } } local.restore(); return any; } static private double getDouble(String s) { double d = 0; try { d = Double.valueOf(s).doubleValue(); } catch (Exception e) {} return d; } private static class Bag { BSLProps local; Regexp r; int subspecs; String pat; String nameProp; String valueProp; String[] sub; String[] subName; } private final static int MAX_MATCHES = 20; private boolean foreach_match(RewriteContext hr, String name, String pat, boolean glob) { Enumeration names; int counter = Format.stringToInt(hr.get("index"), 1); boolean shouldCount = hr.get("index")!=null; Properties props = hr.getNamespaceProperties(); if (glob) { if (props instanceof PropertiesList) { names = ((PropertiesList) props).propertyNames(pat); } else { names = new PropertiesList(props).propertyNames(pat); } } else { names = props.propertyNames(); } BSLProps local = new BSLProps(hr.request); Bag bag = new Bag(); bag.local = local; bag.subspecs = MAX_MATCHES; bag.pat = pat; bag.nameProp = name + ".name"; bag.valueProp = name + ".value"; if (glob) { bag.subspecs = MAX_MATCHES; bag.sub = new String[bag.subspecs + 1]; } else { try { bag.r = new Regexp(pat); } catch (IllegalArgumentException e) { return false; } bag.subspecs = bag.r.subspecs(); bag.sub = new String[bag.subspecs]; } bag.subName = new String[bag.sub.length]; for (int i = 0; i < bag.subName.length; i++) { bag.subName[i] = bag.nameProp + "." + i; } String rest = hr.lex.rest(); boolean any = false; String sort = hr.get("sort", false); // don't expand ${..} yet boolean numeric = hr.isTrue("numeric"); boolean reverse = hr.isTrue("reverse"); if (sort == null) { /* * Elements are being retrieved in random (hashtable) order. * Don't bother returning randomly-ordered elements in reverse * order. */ while (names.hasMoreElements()) { String value = (String) names.nextElement(); if (matches(bag, true, value)) { any = true; if (shouldCount) local.push(name + ".count", "" + (counter++)); processTo(hr, rest, "foreach"); /* Check for a or event */ if (isBreakAbort(hr)) { break; } } } } else { boolean setProps = (sort.length() > 0); Vector v = new Vector(); while (names.hasMoreElements()) { String value = (String) names.nextElement(); if (matches(bag, setProps, value)) { String key = value; if (setProps) { key = Format.subst(local, sort); } if (numeric) { v.addElement(new SortThing(value, getDouble(key))); } else { v.addElement(new SortThing(value, key)); } } } SortThing[] array = new SortThing[v.size()]; v.copyInto(array); boolean nocase = hr.isTrue("nocase"); Sort.qsort(array, new Compare(numeric, reverse, nocase)); for (int i = 0; i < array.length; i++) { String value = array[i].index; matches(bag, true, value); any = true; if (shouldCount) local.push(name + ".count", "" + (counter++)); processTo(hr, rest, "foreach"); /* Check for a or event */ if (isBreakAbort(hr)) { break; } } } local.restore(); return any; } private boolean matches(Bag bag, boolean setProps, String name) { boolean match; if (bag.r == null) { match = Glob.match(bag.pat, name, bag.sub); if (match && setProps) { bag.sub[MAX_MATCHES] = null; for (int i = 0; bag.sub[i] != null; i++) { bag.local.push(bag.subName[i + 1], bag.sub[i]); } } } else { match = bag.r.match(name, bag.sub); if (match && setProps) { for (int i = 0; i < bag.subspecs; i++) { if (bag.sub[i] == null) { bag.sub[i] = ""; } bag.local.push(bag.subName[i], bag.sub[i]); } } } if (match && setProps) { bag.local.push(bag.nameProp, name); bag.local.push(bag.valueProp, bag.local.getUncovered(name)); } return match; } /** * Handles the "if" tag. */ public void tag_if(RewriteContext hr) { hr.killToken(); boolean was = hr.accumulate(true); hr.accumulate(was); boolean test = true; while (test) { debug(hr); if (isTrue(hr)) { processClause(hr); break; } else { test = false; hr.accumulate(false); while (hr.nextTag()) { if (hr.isClosingFor("if", false)) { skipTo(hr, "if"); } else if (hr.isClosingFor("if")) { break; } else if (hr.isClosingFor("else", false)) { hr.accumulate(was); if (hr.get("if", false) != null) { test = true; break; } debug(hr); processClause(hr); break; } else if (hr.isClosingFor("elseif", false) || hr.isClosingFor("elif", false)) { hr.accumulate(was); test = true; break; } } } } debug(hr); hr.accumulate(was); } /* This method evaluates the arguments of an and returns */ /* a boolean indicating whether the arguments are true or false */ private boolean isTrue(RewriteContext hr) { boolean not = hr.isTrue("not"); String name = hr.get("name"); String expr = hr.get("expr"); // use the calculator boolean result = false; // new expr="expression" options if (name==null && expr != null) { Calculator calc = new Calculator(hr.request.props); calc.stringsValid(true); try { result = Format.isTrue(calc.getValue(expr)); } catch (ArithmeticException e) { debug(hr, "Invalid expression: " + expr); } return not ^ result; } if (name == null) { name = hr.getArgs(); } Properties props = hr.getNamespaceProperties(); String value = Format.getProperty(props, name, ""); String arg; if ((arg = hr.get("value")) != null) { result = arg.equals(value); } else if ((arg = hr.get("match")) != null) { boolean ignoreCase = hr.isTrue("nocase"); try { result = (new Regexp(arg, ignoreCase).match(value) != null); } catch (IllegalArgumentException e) {} } else if ((arg = hr.get("glob")) != null) { result = Glob.match(arg, value); } else if (hr.isTrue("any")) { result=hr.request.props.propertyNames(name).hasMoreElements(); } else { /* * Unknown or no argument specified, treat value as boolean: if * named property exists and is not "", "false", "no", "off", or * 0, then return true. */ if ((value.length() == 0) || Format.isFalse(value)) { result = false; } else { try { result = Integer.decode(value).intValue() != 0; } catch (Exception e) { result = true; } } } return not ^ result; } /* This method processes the body of an tag - it is */ /* called when the isTrue() method returns true */ private void processClause(RewriteContext hr) { while (hr.nextTag()) { if (hr.isClosingFor("if")) { break; } else if (hr.isClosingFor("else", false) || hr.isClosingFor("elseif", false) || hr.isClosingFor("elif", false)) { hr.accumulate(false); skipTo(hr, "if"); break; } else { hr.process(); } } } /* This is a convenience method which grabs all tokens from the */ /* current one to the end token that matches the one passed if */ private static void skipTo(RewriteContext hr, String match) { while (hr.nextTag()) { if (hr.isClosingFor(match, false)) { skipTo(hr, match); } else if (hr.isClosingFor(match)) { break; } } } /* This method processes the body of a tag - it is */ /* called once for each iteration of the */ private void processTo(RewriteContext hr, String source, String tag) { hr.lex.replace(source); while (hr.nextTag()) { if (hr.isClosingFor(tag)) { /* If we have hit the closing tag (usually a /foreach) */ /* take the token out of the output stream and return */ hr.killToken(); break; } /* See if any templates have an interest in this token */ hr.process(); /* Check to see if we've encountered any , */ /* or tags as a result of the template processing. */ /* If we encounter an , we just return. If we get */ /* a or , we 'eat' the remaining tokens */ /* until the endTag, then return. */ if (hr.checkRewriteState(ABORT)) { return; } else if (hr.checkRewriteState(BREAK) || hr.checkRewriteState(CONTINUE)) { boolean was = hr.accumulate(false); skipTo(hr,tag); hr.accumulate(was); break; } } /* Set our CONTINUE flag back to false */ hr.unsetRewriteState(CONTINUE); } /** * Handle the [experimental] "extract" tag. * This permits parts of a property's value to be extracted into * additional properties, based on either glob or regular expression * patterns. *
* <extract name= prepend= glob= match=> *

*
name
The name of the property to extract *
prepend
The base name for all extracted properties * (defaults to "name"). If it doesn't end with a ".", * one is added. *
glob
The glob pattern to use for extraction. The text * matching each wildcard in the pattern is extracted. *
match
The regular expression pattern to use for extraction. * The text matching each sub-expression is extracted. If * "glob" is specified, then "match" is ignored. *
null
the value to return if there was no match * [or sub-match] (defaults to ""). *
map
a white space separated list of names that will be used to * name sub-matches, instead of .1, .2, ... etc. If there are * more sub expressions than names, then the indeces are used * after the names run out. The example: *
     *		<set name=entry value="joe:211A:x3321">
     *		<extract name=entry glob=*:*:* map="name room phone">
     *		
* Will return the values: *
     *          entry.name=joe
     *          entry.room=211A
     *          entry.phone=x3321
     *          
* In "glob" extraction, each wildcard in the glob pattern is assigned * the next token in "map". in Regular expression extractions, when * "all" is specified, the map names are used to name the sub-expressions. * Without "all" the names are assigned like "glob", only the first name * gets the entire match (e.g. you need one more name for "match" than * for "glob". * namespace
* Normally, results are extracted into the current request * namespace. If namespace is specified, then the results * are placed into the named namespace. The names "server" * and "local" are special (see SetTemplate). *

NOTE: The namespace will be accessable by any other * templates associated with the same TemplateRunner, using * the default sessionTable (see SetTemplate). *

*/ public void tag_extract(RewriteContext hr) { String name = hr.get("name"); String prefix = hr.get("prepend"); String glob = hr.get("glob"); String match = hr.get("match"); String nullStr = hr.get("null"); String map = hr.get("map"); String mapTable[] = {}; boolean shouldTrack = Format.isTrue(hr.get("track", null)); if (nullStr == null) { // what to return for no match nullStr = ""; } if (name == null) { return; } if (map != null) { StringTokenizer st = new StringTokenizer(map); mapTable = new String[st.countTokens()]; int i=0; while (st.hasMoreTokens()) { mapTable[i++] = st.nextToken(); } } String value = Format.getProperty(hr.request.props, name, ""); hr.killToken(); debug(hr); if (prefix == null) { prefix = name; } if (!prefix.endsWith(".") && !prefix.equals("")) { prefix += "."; } // pick the proper namespace for extraction Properties props = hr.getNamespaceProperties(); int matches = 0; if (glob != null) { String[] sub = new String[MAX_MATCHES + 1]; if (Glob.match(glob, value, sub)) { sub[MAX_MATCHES] = null; for (int i = 0; sub[i] != null; i++) { String s = mapTable.length > i ? mapTable[i] : "" + (i+1); props.put(prefix + s, sub[i]); if (shouldTrack) { System.err.println(": " + prefix + s + "=" + sub[i]); } matches++; } } } else if (match != null) { Regexp r; try { r = new Regexp(match, hr.isTrue("nocase")); String replace = hr.get("replace"); boolean all = hr.isTrue("all"); if (replace != null) { String result; if (all) { result = r.subAll(value, replace); } else { result = r.sub(value, replace); } props.put(prefix + "replace", result==null ? nullStr :result); } else { int subMatches = Math.min(MAX_MATCHES, r.subspecs()); String[] sub = new String[subMatches]; if (all) { // all matches Regsub rs = new Regsub(r, value); StringBuffer counter = new StringBuffer(); while(rs.nextMatch()) { matches++; props.put(prefix + matches, rs.matched()); for (int i = 1; i < subMatches; i++) { String s = mapTable.length >= i ? mapTable[i-1] : "" + i; String n = prefix + matches + "." + s; String v = rs.submatch(i)==null ? nullStr : rs.submatch(i); props.put(n,v); if (shouldTrack) { System.err.println(": " + n + "=" + v); } } counter.append(matches + " "); } if (matches>0) { props.put(prefix + "submatches", "" + subMatches); props.put(prefix + "matchlist", counter.toString()); } } else if (r.match(value, sub)) { // first match for (int i = 0; i < sub.length; i++) { if (sub[i] == null) { sub[i] = ""; } String s = mapTable.length > i ? mapTable[i] : "" + i; props.put(prefix + s, sub[i]); if (shouldTrack) { System.err.println(": " + prefix + s + "=" + sub[i]); } matches++; } } } } catch (IllegalArgumentException e) {} } props.put(prefix + "matches", Integer.toString(matches)); } /** * Properties object used by foreach for its local variables. */ static class BSLProps extends PropertiesList { Request r; PropertiesList defaults; public BSLProps(Request r) { this.r = r; defaults = r.props; addBefore(defaults); r.props = this; } public Object put(Object key, Object value) { return defaults.put(key, value); } public Object remove(Object key) { return defaults.remove(key); } /** * Mark this properties as transient, 'cause it will be * removed after the foreach ends. */ public boolean isTransient() { return true; } public void push(Object key, Object value) { super.put(key, value); } public String getUncovered(String key) { return defaults.getProperty(key); } public void restore() { remove(); r.props = defaults; } } /** * StringTokenizer that uses regular expression patters as delimiters. * This version implements only the * the subset of StringTokenizer used by this file. * It's not general purpose. If the * usage above changes, then this will probably break. */ static class ReStringTokenizer extends StringTokenizer { Regsub rs; // the regexp to split on boolean more; // have more tokens boolean done=false; public ReStringTokenizer(String str, String re) throws IllegalArgumentException { super(str, re); rs = new Regsub((new Regexp(re)), str); } /** * StringTokenizer method */ public boolean hasMoreTokens() { if (done) { return false; } else { more = rs.nextMatch(); // return (more || rs.rest().length() > 0); return true; } } /** * StringTokenizer method */ public String nextToken() { if (more) { return rs.skipped(); } else { done=true; return rs.rest(); } } /** * Allow access to the underlying regsub object */ public Regsub getRs() { return rs; } } }