sunlabs.brazil.template.BSLTemplate Maven / Gradle / Ivy
Show all versions of sunlabs.brazil Show documentation
/*
* BSLTemplate.java
*
* Brazil project web application toolkit,
* export version: 2.3
* Copyright (c) 1999-2006 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.16
* Created by cstevens on 99/10/21
* Last modified by suhler on 06/11/13 10:36:59
*
* Version Histories:
*
* 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 Reqest 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"]>
* <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).
*
.
*
*
* <foreach name=var property=property [delim="chars"]>
* <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>
* <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>
* <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.
*
* 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 specifies, 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. [Not clear if this is useful for anything]
*
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, .
*/
hr.accumulate(false);
skipTo(hr, "foreach");
hr.accumulate(true);
}
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);
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));
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);
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;
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;
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;
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 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(true);
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(true);
test = true;
break;
}
}
}
}
debug(hr);
hr.accumulate(true);
}
/* 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)) {
hr.accumulate(false);
skipTo(hr,tag);
hr.accumulate(true);
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[] = {};
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(".") == false) {
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]);
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;
props.put(prefix + matches + "." + s,
(rs.submatch(i)==null ? nullStr : rs.submatch(i)));
}
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]);
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.
*/
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;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy