You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@cocoon.apache.org by ha...@apache.org on 2003/10/01 16:50:17 UTC

cvs commit: cocoon-2.1/src/java/org/apache/cocoon/transformation SimpleFormTransformer.java

haul        2003/10/01 07:50:17

  Modified:    .        status.xml
               src/webapp/samples/simpleform/two descriptor.xml form.xml
                        success.xml
               src/webapp/samples/simpleform/three descriptor.xml form.xml
                        success.xml
               src/webapp/samples/simpleform/four descriptor.xml form.xml
                        success.xml
               src/java/org/apache/cocoon/transformation
                        SimpleFormTransformer.java
  Log:
     <action dev="CH" type="add">
       SimpleFormTransformer: multiple values not repeatedly inserted anymore,
       value is consumed instead. Add repeater tag. Make transformer inherit from
       AbstractSAXTransformer. Update examples to show basic repeat tag usage.
     </action>
  
  Revision  Changes    Path
  1.160     +6 -1      cocoon-2.1/status.xml
  
  Index: status.xml
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/status.xml,v
  retrieving revision 1.159
  retrieving revision 1.160
  diff -u -r1.159 -r1.160
  --- status.xml	30 Sep 2003 14:13:32 -0000	1.159
  +++ status.xml	1 Oct 2003 14:50:15 -0000	1.160
  @@ -191,6 +191,11 @@
     <changes>
   
    <release version="@version@" date="@date@">
  +   <action dev="CH" type="add">
  +     SimpleFormTransformer: multiple values not repeatedly inserted anymore,
  +     value is consumed instead. Add repeater tag. Make transformer inherit from
  +     AbstractSAXTransformer. Update examples to show basic repeat tag usage.
  +   </action>
      <action dev="VG" type="fix" fixes-bug="23516">
        Fix default namespace handling in Xscript variables.
      </action>
  
  
  
  1.2       +2 -0      cocoon-2.1/src/webapp/samples/simpleform/two/descriptor.xml
  
  Index: descriptor.xml
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/webapp/samples/simpleform/two/descriptor.xml,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- descriptor.xml	15 Aug 2003 15:57:32 -0000	1.1
  +++ descriptor.xml	1 Oct 2003 14:50:15 -0000	1.2
  @@ -6,6 +6,7 @@
      <parameter name="email" type="string" max-len="50" matches-regex="^[\d\w][\d\w\-_\.]*@([\d\w\-_]+\.)+\w\w\w?$"/>
      <parameter name="address" type="string" min-len="10" max-len="200" nullable="no"/>
      <parameter name="type" type="string" nullable="no" one-of="|cabrio|sedan|station|racing|"/>
  +   <parameter name="driver" type="string" nullable="yes" max-len="20"/>
   
      <constraint-set name="car-reservation">
          <validate name="persons"/>
  @@ -13,6 +14,7 @@
          <validate name="email"/>
          <validate name="address"/>
          <validate name="type"/>
  +	   <validate name="driver"/>
       </constraint-set>
   
   </root>
  
  
  
  1.2       +14 -1     cocoon-2.1/src/webapp/samples/simpleform/two/form.xml
  
  Index: form.xml
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/webapp/samples/simpleform/two/form.xml,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- form.xml	15 Aug 2003 15:57:32 -0000	1.1
  +++ form.xml	1 Oct 2003 14:50:15 -0000	1.2
  @@ -11,6 +11,7 @@
   }
   </style>
   </head>
  +  <title>Formvalidation and -prefilling</title>
   	<body>
   		<h1>Reserve a car with Cocoon Cars!</h1>
   
  @@ -69,6 +70,16 @@
   						</td>
   						<td><error name="address" when-ge="error">*</error></td>
   					</tr>
  +                    <!-- the following repeat tag inserts as many rows as -->
  +                    <!-- drivers were submitted on the request. -->
  +					<repeat on="driver">
  +						<tr>
  +							<td>
  +								Allowed driver	<input name="driver" type="text" size="20" />
  +							</td>
  +							<td><error name="driver" when-ge="error">*</error></td>
  +						</tr>
  +					</repeat>
   					<tr>
   						<td colspan="2">
   							<error name="*" when-ge="error">An error occurred. Please check your input and the messages below for more information.</error>
  @@ -82,7 +93,9 @@
   							<error name="address" when="too-small">Your address seems to be incorrect. Please fill in a correct billing address.</error>
   							<error name="type" when="is-null">Please select the type of car you would like to drive.</error>
   							<error name="type" when="no-match">Please select one of the existing type of car.</error>
  -						</td>
  +
  +							<error name="driver" when="error">Please enter at least one valid driver's name.</error>
  +      					</td>
   					</tr>
   					<tr>
   						<td></td>
  
  
  
  1.2       +9 -1      cocoon-2.1/src/webapp/samples/simpleform/two/success.xml
  
  Index: success.xml
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/webapp/samples/simpleform/two/success.xml,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- success.xml	15 Aug 2003 15:57:32 -0000	1.1
  +++ success.xml	1 Oct 2003 14:50:15 -0000	1.2
  @@ -1,6 +1,7 @@
   <?xml version="1.0" encoding="UTF-8"?>
   <page>
     <title>Formvalidation and -prefilling</title>
  +  <h1>Receipt</h1>
     <content>
   
       <para>
  @@ -14,8 +15,15 @@
   		 <input type="text" name="persons" readonly="true"/> has been reserved for you.
            Please leave the deposit of EUR <input type="text" name="deposit" readonly="true"/>
   		 at the reception. The invoice will be send to <textarea name="address"/> and a
  -		 confirmation email will be send to <input type="text" readonly="true" name="email"/>. Thank you.
  +		 confirmation email will be send to <input type="text" readonly="true" name="email"/>.
          </para>
  +	   <para>Allowed drivers are</para>
  +	   <ol>
  +	   	<repeat on="driver">
  +			<li><input type="text" name="driver" readonly="true"/></li>
  +		</repeat>
  +	   </ol>
  +	   <para>Thank you for choosing Cocoon.</para>
       </form>
   
     </content>
  
  
  
  1.2       +2 -0      cocoon-2.1/src/webapp/samples/simpleform/three/descriptor.xml
  
  Index: descriptor.xml
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/webapp/samples/simpleform/three/descriptor.xml,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- descriptor.xml	15 Aug 2003 15:57:32 -0000	1.1
  +++ descriptor.xml	1 Oct 2003 14:50:16 -0000	1.2
  @@ -6,6 +6,7 @@
      <parameter name="email" type="string" max-len="50" matches-regex="^[\d\w][\d\w\-_\.]*@([\d\w\-_]+\.)+\w\w\w?$"/>
      <parameter name="address" type="string" min-len="10" max-len="200" nullable="no"/>
      <parameter name="type" type="string" nullable="no" one-of="|cabrio|sedan|station|racing|"/>
  +   <parameter name="driver" type="string" nullable="yes" max-len="20"/>
   
      <constraint-set name="car-reservation">
          <validate name="persons"/>
  @@ -13,6 +14,7 @@
          <validate name="email"/>
          <validate name="address"/>
          <validate name="type"/>
  +	   <validate name="driver"/>
       </constraint-set>
   
   </root>
  
  
  
  1.2       +15 -1     cocoon-2.1/src/webapp/samples/simpleform/three/form.xml
  
  Index: form.xml
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/webapp/samples/simpleform/three/form.xml,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- form.xml	15 Aug 2003 15:57:32 -0000	1.1
  +++ form.xml	1 Oct 2003 14:50:16 -0000	1.2
  @@ -11,6 +11,7 @@
   }
   </style>
   </head>
  +  <title>Formvalidation and -prefilling</title>
   	<body>
   		<h1>Reserve a car with Cocoon Cars!</h1>
   
  @@ -33,6 +34,9 @@
   address.
                   </address>
                   <type>sedan</type>
  +				<driver>Peter</driver>
  +				<driver>Mary</driver>
  +				<driver/>
               </form-instance>
   			<table>
   				<tbody>
  @@ -83,6 +87,14 @@
   						</td>
   						<td><error name="address" when-ge="error">*</error></td>
   					</tr>
  +					<repeat on="driver">
  +						<tr>
  +							<td>
  +								Allowed driver	<input name="driver" type="text" size="20" />
  +							</td>
  +							<td><error name="driver" when-ge="error">*</error></td>
  +						</tr>
  +					</repeat>
   					<tr>
   						<td colspan="2">
   							<error name="*" when-ge="error">An error occurred. Please check your input and the messages below for more information.</error>
  @@ -96,7 +108,9 @@
   							<error name="address" when="too-small">Your address seems to be incorrect. Please fill in a correct billing address.</error>
   							<error name="type" when="is-null">Please select the type of car you would like to drive.</error>
   							<error name="type" when="no-match">Please select one of the existing type of car.</error>
  -						</td>
  +
  +							<error name="driver" when="error">Please enter at least one valid driver's name.</error>
  +      					</td>
   					</tr>
   					<tr>
   						<td></td>
  
  
  
  1.2       +9 -1      cocoon-2.1/src/webapp/samples/simpleform/three/success.xml
  
  Index: success.xml
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/webapp/samples/simpleform/three/success.xml,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- success.xml	15 Aug 2003 15:57:32 -0000	1.1
  +++ success.xml	1 Oct 2003 14:50:16 -0000	1.2
  @@ -1,6 +1,7 @@
   <?xml version="1.0" encoding="UTF-8"?>
   <page>
     <title>Formvalidation and -prefilling</title>
  +  <h1>Receipt</h1>
     <content>
   
       <para>
  @@ -14,8 +15,15 @@
   		 <input type="text" name="persons" readonly="true"/> has been reserved for you.
            Please leave the deposit of EUR <input type="text" name="deposit" readonly="true"/>
   		 at the reception. The invoice will be send to <textarea name="address"/> and a
  -		 confirmation email will be send to <input type="text" readonly="true" name="email"/>. Thank you.
  +		 confirmation email will be send to <input type="text" readonly="true" name="email"/>.
          </para>
  +	   <para>Allowed drivers are</para>
  +	   <ol>
  +	   	<repeat on="driver">
  +			<li><input type="text" name="driver" readonly="true"/></li>
  +		</repeat>
  +	   </ol>
  +	   <para>Thank you for choosing Cocoon.</para>
       </form>
   
     </content>
  
  
  
  1.2       +2 -0      cocoon-2.1/src/webapp/samples/simpleform/four/descriptor.xml
  
  Index: descriptor.xml
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/webapp/samples/simpleform/four/descriptor.xml,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- descriptor.xml	15 Aug 2003 15:57:33 -0000	1.1
  +++ descriptor.xml	1 Oct 2003 14:50:17 -0000	1.2
  @@ -6,6 +6,7 @@
      <parameter name="email" type="string" max-len="50" matches-regex="^[\d\w][\d\w\-_\.]*@([\d\w\-_]+\.)+\w\w\w?$"/>
      <parameter name="address" type="string" min-len="10" max-len="200" nullable="no"/>
      <parameter name="type" type="string" nullable="no" one-of="|cabrio|sedan|station|racing|"/>
  +   <parameter name="driver" type="string" nullable="yes" max-len="20"/>
   
      <constraint-set name="car-reservation">
          <validate name="persons"/>
  @@ -13,6 +14,7 @@
          <validate name="email"/>
          <validate name="address"/>
          <validate name="type"/>
  +	   <validate name="driver"/>
       </constraint-set>
   
   </root>
  
  
  
  1.2       +18 -4     cocoon-2.1/src/webapp/samples/simpleform/four/form.xml
  
  Index: form.xml
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/webapp/samples/simpleform/four/form.xml,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- form.xml	15 Aug 2003 15:57:33 -0000	1.1
  +++ form.xml	1 Oct 2003 14:50:17 -0000	1.2
  @@ -11,6 +11,7 @@
   }
   </style>
   </head>
  +  <title>Formvalidation and -prefilling</title>
   	<body>
   		<h1>Reserve a car with Cocoon Cars!</h1>
   
  @@ -19,7 +20,7 @@
              form using a combination of the SimpleFormInstanceTransformer and
              the SimpleFormTransformer. In addition, user input is validated
              with the FormValidatorAction and the submitted data is filled
  -           in with another SimpleFormTransformer which also selects which 
  +           in with another SimpleFormTransformer which also selects which
              error tags shall occurr in the resulting page.</p>
   
   		<h2>Car Reservation</h2>
  @@ -31,6 +32,9 @@
                   <persons>4</persons>
                   <deposit>50</deposit>
                   <email>your email</email>
  +				<driver/>
  +				<driver/>
  +				<driver/>
               </form-instance>
   			<table>
   				<tbody>
  @@ -38,7 +42,7 @@
   						<td>
               	           The car should seat
   						   <select size="1" name="persons">
  -							<option value="" disabled="true">Please select</option>
  +							<option value="" selected="true" disabled="true">Please select</option>
   						   	<option value="2">2</option>
   						   	<option value="4">4</option>
   						   	<option value="5">5</option>
  @@ -55,7 +59,7 @@
   						<td>
   							The car should by a
   							<select size="1" name="type">
  -							<option value="" disabled="true" selected="true">Please select</option>
  +							<option value="" selected="true" disabled="true">Please select</option>
   							<option value="sedan">Sedan Car</option>
   							<option value="station">Station Wagon</option>
   							<option value="cabrio">Cabrio</option>
  @@ -81,6 +85,14 @@
   						</td>
   						<td><error name="address" when-ge="error">*</error></td>
   					</tr>
  +					<repeat on="driver">
  +						<tr>
  +							<td>
  +								Allowed driver	<input name="driver" type="text" size="20" />
  +							</td>
  +							<td><error name="driver" when-ge="error">*</error></td>
  +						</tr>
  +					</repeat>
   					<tr>
   						<td colspan="2">
   							<error name="*" when-ge="error">An error occurred. Please check your input and the messages below for more information.</error>
  @@ -94,7 +106,9 @@
   							<error name="address" when="too-small">Your address seems to be incorrect. Please fill in a correct billing address.</error>
   							<error name="type" when="is-null">Please select the type of car you would like to drive.</error>
   							<error name="type" when="no-match">Please select one of the existing type of car.</error>
  -						</td>
  +
  +							<error name="driver" when="error">Please enter at least one valid driver's name.</error>
  +      					</td>
   					</tr>
   					<tr>
   						<td></td>
  
  
  
  1.2       +9 -1      cocoon-2.1/src/webapp/samples/simpleform/four/success.xml
  
  Index: success.xml
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/webapp/samples/simpleform/four/success.xml,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- success.xml	15 Aug 2003 15:57:33 -0000	1.1
  +++ success.xml	1 Oct 2003 14:50:17 -0000	1.2
  @@ -1,6 +1,7 @@
   <?xml version="1.0" encoding="UTF-8"?>
   <page>
     <title>Formvalidation and -prefilling</title>
  +  <h1>Receipt</h1>
     <content>
   
       <para>
  @@ -14,8 +15,15 @@
   		 <input type="text" name="persons" readonly="true"/> has been reserved for you.
            Please leave the deposit of EUR <input type="text" name="deposit" readonly="true"/>
   		 at the reception. The invoice will be send to <textarea name="address"/> and a
  -		 confirmation email will be send to <input type="text" readonly="true" name="email"/>. Thank you.
  +		 confirmation email will be send to <input type="text" readonly="true" name="email"/>.
          </para>
  +	   <para>Allowed drivers are</para>
  +	   <ol>
  +	   	<repeat on="driver">
  +			<li><input type="text" name="driver" readonly="true"/></li>
  +		</repeat>
  +	   </ol>
  +	   <para>Thank you for choosing Cocoon.</para>
       </form>
   
     </content>
  
  
  
  1.5       +455 -217  cocoon-2.1/src/java/org/apache/cocoon/transformation/SimpleFormTransformer.java
  
  Index: SimpleFormTransformer.java
  ===================================================================
  RCS file: /home/cvs/cocoon-2.1/src/java/org/apache/cocoon/transformation/SimpleFormTransformer.java,v
  retrieving revision 1.4
  retrieving revision 1.5
  diff -u -r1.4 -r1.5
  --- SimpleFormTransformer.java	24 Sep 2003 21:41:12 -0000	1.4
  +++ SimpleFormTransformer.java	1 Oct 2003 14:50:17 -0000	1.5
  @@ -50,33 +50,35 @@
   */
   package org.apache.cocoon.transformation;
   
  -import org.apache.avalon.excalibur.pool.Recyclable;
  -import org.apache.avalon.framework.component.ComponentManager;
   import org.apache.avalon.framework.component.ComponentSelector;
  -import org.apache.avalon.framework.component.Composable;
  -import org.apache.avalon.framework.configuration.Configurable;
   import org.apache.avalon.framework.configuration.Configuration;
   import org.apache.avalon.framework.configuration.ConfigurationException;
   import org.apache.avalon.framework.parameters.Parameters;
   import org.apache.avalon.framework.thread.ThreadSafe;
  +
  +import org.apache.avalon.excalibur.pool.Recyclable;
  +
   import org.apache.cocoon.ProcessingException;
   import org.apache.cocoon.acting.ValidatorActionResult;
   import org.apache.cocoon.components.language.markup.xsp.XSPFormValidatorHelper;
   import org.apache.cocoon.components.modules.input.InputModule;
  -import org.apache.cocoon.environment.ObjectModelHelper;
  -import org.apache.cocoon.environment.Request;
   import org.apache.cocoon.environment.SourceResolver;
   import org.apache.cocoon.util.HashMap;
  +import org.apache.cocoon.xml.dom.DOMStreamer;
  +
  +import org.w3c.dom.DocumentFragment;
   import org.xml.sax.Attributes;
   import org.xml.sax.SAXException;
   import org.xml.sax.helpers.AttributesImpl;
   
   import java.io.IOException;
  +import java.util.Iterator;
  +import java.util.LinkedList;
  +import java.util.List;
   import java.util.Map;
  -import java.util.Stack;
   
   /** 
  - * Eliminates the need for XSP to use FormValidatorAction. 
  + * Eliminates the need for XSP to use FormValidatorAction or HTML forms. 
    * Caveat: Select options need a value attribute to work correctly.
    *
    * <p>This transformer fills all HTML 4 form elements with values from
  @@ -104,6 +106,21 @@
    *
    * <p><em>Names of error elements are never augmented by prefix, suffix or
    * form name.</em></p>
  + * 
  + * <p>Page parts with multiple occurrences depending on the number of
  + * actual parameters can be enclosed in &lt;repeat on="expr" using="var"/&gt;
  + * elements. <em>expr</em> is used to determine the number of occurrences
  + * and <em>var</em> will be expanded with the ordinary number. Repeat elements
  + * can be nested.</p>
  + * 
  + * <p>Example:</p>
  + * <pre>
  + *  <repeat on="mult" using="i"><input type="text" name="mult[${i}]"/></repeat>
  + * </pre>
  + * <p>Will include as many input elements as mult parameters are present. Adding
  + * the repeater variable to the elements name is necessary only with structured
  + * parameters or when they should be numbered. See also the <em>strip-number</em>
  + * configuration parameter.</p>
    *
    * <p>To use this transformer, add the following to your
    * transformation pipeline: <pre>
  @@ -133,6 +150,10 @@
    *           separator will be added between rest of the name and suffix. ("")</td></tr>
    *   <tr><td>ignore-validation</td><td>(boolean) If set to true, all error
    *           tags are copied as is regardless of the validation results.("false")</td></tr>
  + *   <tr><td>decoration</td><td>(int) Length of decorations around repeat variable. Example:
  + *           when using JXPath based module, decoration would be "[" and "]", hence 1. (1)</td></tr>
  + *   <tr><td>strip-number</td><td>(boolean) If set to false, element names of repeated
  + *           elements will contain the expanded repeater variable. ("true")</td></tr>
    * </table>
    * </p>
    *
  @@ -142,6 +163,8 @@
    *   <tr><td>prefix</td><td>(String) Added to the input element's name</td></tr>
    *   <tr><td>suffix</td><td>(String) Added to the input element's name</td></tr>
    *   <tr><td>input</td><td>(String) InputModule name</td></tr>
  + *   <tr><td>decoration</td><td>(int) Length of decorations around repeat variable.</td></tr>
  + *   <tr><td>strip-number</td><td>(boolean) Expanded repeater variable.</td></tr>
    * </table>
    * </p>
    *
  @@ -153,10 +176,11 @@
    * @author <a href="mailto:haul@apache.org">Christian Haul</a>
    * @version CVS $Id$
    */
  -public class SimpleFormTransformer
  -    extends AbstractTransformer
  -    implements Composable, Configurable, Recyclable {
  +public class SimpleFormTransformer extends AbstractSAXTransformer implements Recyclable {
   
  +    /** strip numbers from repeated element name attributes */
  +    private boolean stripNumber = true;
  +    ;
       /** Symbolic names for elements */
       /** unknown element */
       private static final int ELEMENT_DEFAULT = 0;
  @@ -172,6 +196,8 @@
       private static final int ELEMENT_ERROR = 5;
       /** form element */
       private static final int ELEMENT_FORM = 6;
  +    /** repeat element */
  +    private static final int ELEMENT_REPEAT = 7;
       /** default element as Integer (needed as default in org.apache.cocoon.util.HashMap.get()) */
       private static final Integer defaultElement = new Integer(ELEMENT_DEFAULT);
   
  @@ -205,6 +231,7 @@
           names.put("textarea", new Integer(ELEMENT_TXTAREA));
           names.put("error", new Integer(ELEMENT_ERROR));
           names.put("form", new Integer(ELEMENT_FORM));
  +        names.put("repeat", new Integer(ELEMENT_REPEAT));
           elementNames = names;
           names = null;
   
  @@ -236,29 +263,12 @@
           names = null;
       }
   
  -    /** nesting level of ignored elements */
  -    protected int ignoreCount;
  -    /** ignored element needs closing tag */
  -    protected boolean ignoreThis = false;
  -
  -    /** stack of ignored element names */
  -    protected Stack stack = new Stack();
  -
       /** current element's request parameter values */
       protected Object[] values = null;
   
       /** current request's validation results (all validated elements) */
       protected Map validationResults = null;
   
  -    /** The current Request object */
  -    protected Request request;
  -    /** The current objectModel of the environment */
  -    protected Map objectModel;
  -    /** The parameters specified in the sitemap */
  -    protected Parameters parameters;
  -    /** The Avalon ComponentManager for getting Components */
  -    protected ComponentManager manager;
  -
       /** Should we skip inserting values? */
       private boolean fixed = false;
       /** Is the complete document protected? */
  @@ -274,6 +284,7 @@
       private boolean useFormName = false;
       private boolean useFormNameTwice = false;
       private boolean ignoreValidation = false;
  +    private int decorationSize = 1;
   
       private String defaultInput = "request-param";
       private Configuration defaultInputConf = null;
  @@ -282,24 +293,76 @@
       private ComponentSelector inputSelector = null;
       private String inputName = null;
   
  -    /** Empty attributes (for performance). This can be used
  -     *  do create own attributes, but make sure to clean them
  -     *  afterwords.
  +    /** Skip element's content only. Otherwise skip also surrounding element. */
  +    protected boolean skipChildrenOnly = false;
  +
  +    /** Count nested repeat elements. */
  +    protected int recordingCount = 0;
  +
  +    /** List of {@link RepeaterStatus} elements keeping track of nested repeat blocks. */
  +    protected List repeater = null;
  +
  +    /** Map of {@link ValueList} to track multiple parameters. */
  +    protected Map formValues = null;
  +
  +    /**
  +     * Keep track of repeater status. 
  +     */
  +    protected class RepeaterStatus {
  +        public String var = null;
  +        public String expr = null;
  +        public int count = 0;
  +
  +        public RepeaterStatus(String var, int count, String expr) {
  +            this.var = var;
  +            this.count = count;
  +            this.expr = expr;
  +        }
  +
  +        public String toString() {
  +            return "[" + this.var + "," + this.expr + "," + this.count + "]";
  +        }
  +    }
  +
  +    /**
  +     * Keep track of multiple values. 
        */
  -    protected AttributesImpl emptyAttributes = new AttributesImpl();
  +    protected class ValueList {
  +        private int current = -1;
  +        private Object[] values = null;
  +
  +        public ValueList(Object[] values) {
  +            this.values = values;
  +            this.current = (values != null && values.length > 0 ? 0 : -1);
  +        }
  +
  +        public Object getNext() {
  +            Object result = null;
  +            if (this.values != null) {
  +                if (this.current < this.values.length) {
  +                    result = this.values[this.current++];
  +                }
  +            }
  +            return result;
  +        }
  +    }
  +
  +    public SimpleFormTransformer() {
  +        this.defaultNamespaceURI = "";
  +        this.namespaceURI = "";
  +    }
   
       /** set per instance variables to defaults */
       private void reset() {
  -        this.objectModel = null;
  -        this.request = null;
  -        this.parameters = null;
  -        this.stack.clear();
  -        this.ignoreCount = 0;
  +        this.skipChildrenOnly = false;
           this.values = null;
           this.validationResults = null;
           this.documentFixed = false;
           this.fixed = false;
           this.formName = null;
  +        this.recordingCount = 0;
  +        this.repeater = new LinkedList();
  +        this.formValues = new HashMap();
   
           if (this.inputSelector != null) {
               if (this.input != null)
  @@ -329,6 +392,22 @@
           }
           this.ignoreValidation =
               config.getChild("ignore-validation").getValueAsBoolean(this.ignoreValidation);
  +        this.decorationSize = config.getChild("decoration").getValueAsInteger(this.decorationSize);
  +        this.stripNumber = config.getChild("strip-number").getValueAsBoolean(this.stripNumber);
  +    }
  +
  +    /**
  +     * Read sitemap parameters and set properties accordingly.
  +     */
  +    private void evaluateParameters() {
  +        this.documentFixed = this.parameters.getParameterAsBoolean("fixed", false);
  +        this.fixed = this.documentFixed;
  +        this.prefix = this.parameters.getParameter("prefix", this.defaultPrefix);
  +        this.suffix = this.parameters.getParameter("suffix", this.defaultSuffix);
  +        this.inputName = this.parameters.getParameter("input", null);
  +        this.decorationSize =
  +            this.parameters.getParameterAsInteger("decoration", this.decorationSize);
  +        this.stripNumber = this.parameters.getParameterAsBoolean("strip-number", this.stripNumber);
       }
   
       /**
  @@ -343,24 +422,27 @@
           throws ProcessingException, SAXException, IOException {
   
           this.reset();
  -        this.objectModel = objectModel;
   
  -        this.request = ObjectModelHelper.getRequest(objectModel);
  +        super.setup(resolver, objectModel, src, par);
  +
           if (request == null) {
               getLogger().debug("no request object");
               throw new ProcessingException("no request object");
           }
  -        this.parameters = par;
  -        this.documentFixed = par.getParameterAsBoolean("fixed", false);
  -        this.fixed = this.documentFixed;
  -        this.prefix = par.getParameter("prefix", this.defaultPrefix);
  -        this.suffix = par.getParameter("suffix", this.defaultSuffix);
  -        this.inputName = par.getParameter("input", null);
  +        this.evaluateParameters();
  +        this.setupInputModule();
  +
  +    }
  +
  +    /**
  +     * Setup and obtain reference to the input module.
  +     */
  +    private void setupInputModule() {
           this.inputConf = null;
           if (this.ignoreValidation) {
               this.validationResults = null;
           } else {
  -            this.validationResults = XSPFormValidatorHelper.getResults(objectModel);
  +            this.validationResults = XSPFormValidatorHelper.getResults(this.objectModel);
           }
   
           if (this.inputName == null) {
  @@ -401,7 +483,6 @@
                   getLogger().warn(
                       "A problem occurred setting up '" + this.inputName + "': " + e.getMessage());
           }
  -
       }
   
       /**
  @@ -412,14 +493,6 @@
           this.reset();
       }
   
  -    /**
  -     * Avalon Composable Interface
  -     * @param manager The Avalon Component Manager
  -     */
  -    public void compose(ComponentManager manager) {
  -        this.manager = manager;
  -    }
  -
       /** 
        * Generate string representation of attributes. For debug only.
        */
  @@ -440,6 +513,7 @@
        * i.e. checkbox and radio.
        */
       protected void startCheckableElement(
  +        String aName,
           String uri,
           String name,
           String raw,
  @@ -447,6 +521,7 @@
           throws SAXException {
   
           // @fixed and this.fixed already considered in startInputElement
  +        this.values = this.getValues(aName);
           String checked = attributes.getValue("checked");
           String value = attributes.getValue("value");
           boolean found = false;
  @@ -473,7 +548,7 @@
                   attributes.removeAttribute(attributes.getIndex("checked"));
               }
           }
  -        super.startElement(uri, name, raw, attributes);
  +        this.relayStartElement(uri, name, raw, attributes);
       }
   
       /**
  @@ -481,6 +556,7 @@
        * attributes, e.g. text, password, button.
        */
       protected void startNonCheckableElement(
  +        String aName,
           String uri,
           String name,
           String raw,
  @@ -488,6 +564,7 @@
           throws SAXException {
   
           // @fixed and this.fixed already considered in startInputElement
  +        Object fValue = this.getNextValue(aName);
           String value = attributes.getValue("value");
           if (getLogger().isDebugEnabled())
               getLogger().debug(
  @@ -495,21 +572,16 @@
                       + name
                       + " attributes "
                       + this.printAttributes(attributes));
  -        if (this.values != null) {
  +        if (fValue != null) {
               if (getLogger().isDebugEnabled())
                   getLogger().debug("replacing");
               if (value != null) {
  -                attributes.setValue(attributes.getIndex("value"), String.valueOf(this.values[0]));
  +                attributes.setValue(attributes.getIndex("value"), String.valueOf(fValue));
               } else {
  -                attributes.addAttribute(
  -                    "",
  -                    "value",
  -                    "value",
  -                    "CDATA",
  -                    String.valueOf(this.values[0]));
  +                attributes.addAttribute("", "value", "value", "CDATA", String.valueOf(fValue));
               }
           }
  -        super.startElement(uri, name, raw, attributes);
  +        this.relayStartElement(uri, name, raw, attributes);
       }
   
       /**
  @@ -527,13 +599,13 @@
               getLogger().debug(
                   "startInputElement " + name + " attributes " + this.printAttributes(attr));
           if (aName == null || this.fixed || (fixed != null && parseBoolean(fixed))) {
  -            super.startElement(uri, name, raw, attr);
  +            this.relayStartElement(uri, name, raw, attr);
   
           } else {
               if (getLogger().isDebugEnabled())
                   getLogger().debug("replacing");
   
  -            this.values = this.getValues(aName);
  +            attr = this.normalizeAttributes(attr);
   
               AttributesImpl attributes = null;
               if (attr instanceof AttributesImpl) {
  @@ -545,11 +617,11 @@
               switch (((Integer) inputTypes.get(type, defaultType)).intValue()) {
                   case TYPE_CHECKBOX :
                   case TYPE_RADIO :
  -                    this.startCheckableElement(uri, name, raw, attributes);
  +                    this.startCheckableElement(aName, uri, name, raw, attributes);
                       break;
   
                   case TYPE_DEFAULT :
  -                    this.startNonCheckableElement(uri, name, raw, attributes);
  +                    this.startNonCheckableElement(aName, uri, name, raw, attributes);
                       break;
               }
               this.values = null;
  @@ -571,9 +643,20 @@
               getLogger().debug(
                   "startSelectElement " + name + " attributes " + this.printAttributes(attr));
           if (aName != null && !(this.fixed || (fixed != null && parseBoolean(fixed)))) {
  -            this.values = this.getValues(aName);
  +            if (attr.getIndex("multiple") > -1) {
  +                this.values = this.getValues(aName);
  +            } else {
  +                Object val = this.getNextValue(aName);
  +                if (val != null) {
  +                    this.values = new Object[1];
  +                    this.values[0] = val;
  +                } else {
  +                    this.values = null;
  +                }
  +            }
  +            attr = this.normalizeAttributes(attr);
           }
  -        super.startElement(uri, name, raw, attr);
  +        this.relayStartElement(uri, name, raw, attr);
       }
   
       /**
  @@ -590,7 +673,7 @@
               getLogger().debug(
                   "startOptionElement " + name + " attributes " + this.printAttributes(attr));
           if (this.values == null || this.fixed) {
  -            super.startElement(uri, name, raw, attr);
  +            this.relayStartElement(uri, name, raw, attr);
           } else {
               if (getLogger().isDebugEnabled())
                   getLogger().debug("replacing");
  @@ -617,7 +700,7 @@
                   attributes.removeAttribute(attributes.getIndex("selected"));
               }
   
  -            super.startElement(uri, name, raw, attributes);
  +            this.relayStartElement(uri, name, raw, attributes);
           }
       }
   
  @@ -630,24 +713,25 @@
   
           String aName = getName(attributes.getValue("name"));
           String fixed = attributes.getValue(this.fixedName);
  -        Object[] value = null;
  +        Object value = null;
           if (getLogger().isDebugEnabled())
               getLogger().debug(
                   "startTextareaElement " + name + " attributes " + this.printAttributes(attributes));
           if (aName != null) {
  -            value = this.getValues(aName);
  +            value = this.getNextValue(aName);
           }
           if (value == null || this.fixed || (fixed != null && parseBoolean(fixed))) {
  -            super.startElement(uri, name, raw, attributes);
  +            this.relayStartElement(uri, name, raw, attributes);
           } else {
               if (getLogger().isDebugEnabled())
                   getLogger().debug("replacing");
  -            this.ignoreCount++;
  -            this.stack.push(name);
  -            this.ignoreThis = false;
  -            super.startElement(uri, name, raw, attributes);
  -            String valString = String.valueOf(value[0]);
  -            super.characters(valString.toCharArray(), 0, valString.length());
  +            this.relayStartElement(uri, name, raw, this.normalizeAttributes(attributes));
  +            String valString = String.valueOf(value);
  +            this.characters(valString.toCharArray(), 0, valString.length());
  +            // well, this doesn't really work out nicely. do it the hard way.
  +            if (this.ignoreEventsCount == 0)
  +                this.skipChildrenOnly = true;
  +            this.ignoreEventsCount++;
           }
       }
   
  @@ -665,15 +749,13 @@
               getLogger().debug(
                   "startErrorElement " + name + " attributes " + this.printAttributes(attr));
           if (this.ignoreValidation) {
  -            super.startElement(uri, name, raw, attr);
  +            this.relayStartElement(uri, name, raw, attr);
           } else if (this.validationResults == null || this.fixed) {
  -            this.ignoreCount++;
  -            this.stack.push(name);
  -            this.ignoreThis = true;
  +            this.relayStartElement(true, false, uri, name, raw, attr);
           } else {
               String aName = attr.getValue("name");
               if (aName == null) {
  -                super.startElement(uri, name, raw, attr);
  +                this.relayStartElement(uri, name, raw, attr);
               } else {
                   ValidatorActionResult validation =
                       XSPFormValidatorHelper.getParamResult(this.objectModel, aName);
  @@ -698,11 +780,9 @@
                           attributes.removeAttribute(attributes.getIndex("when"));
                       if (when_ge != null)
                           attributes.removeAttribute(attributes.getIndex("when-ge"));
  -                    super.startElement(uri, name, raw, attributes);
  +                    this.relayStartElement(uri, name, raw, this.normalizeAttributes(attributes));
                   } else {
  -                    this.ignoreCount++;
  -                    this.stack.push(name);
  -                    this.ignoreThis = true;
  +                    this.relayStartElement(true, true, uri, name, raw, attr);
                   }
               }
           }
  @@ -725,9 +805,9 @@
               this.formName = attr.getValue("name");
           }
           if (fixed == null) {
  -            super.startElement(uri, name, raw, attr);
  +            this.relayStartElement(uri, name, raw, attr);
           } else {
  -            if (!this.fixed && ("true".equals(fixed) || "yes".equals(fixed))) {
  +            if (!this.fixed && parseBoolean(fixed)) {
                   this.fixed = true;
               }
               // remove attributes not meant for client
  @@ -738,57 +818,65 @@
                   attributes = new AttributesImpl(attr);
               }
               attributes.removeAttribute(attributes.getIndex(this.fixedName));
  -            super.startElement(uri, name, raw, attributes);
  +            this.relayStartElement(uri, name, raw, this.normalizeAttributes(attributes));
           }
       }
   
       /**
  -     * Start processing elements of our namespace.
  -     * This hook is invoked for each sax event with our namespace.
  -     * @param uri The namespace of the element.
  -     * @param name The local name of the element.
  -     * @param raw The qualified name of the element.
  -     * @param attr The attributes of the element.
  +     * Start recording repeat element contents and push repeat expression and
  +     * variable to repeater stack. Only start recording, if no other recorder is
  +     * currently running.
  +     * 
  +     * @param uri
  +     * @param name
  +     * @param raw
  +     * @param attr
  +     * @throws SAXException
        */
  -    public void startElement(String uri, String name, String raw, Attributes attr)
  +    protected void startRepeatElement(String uri, String name, String raw, Attributes attr)
           throws SAXException {
   
  -        if (this.ignoreCount == 0) {
  -            if (uri != "") {
  -                super.startElement(uri, name, raw, attr);
  +        if (this.recordingCount == 0) {
  +            if (!(this.fixed || parseBoolean(attr.getValue(this.fixedName)))) {
  +                RepeaterStatus status =
  +                    new RepeaterStatus("${" + attr.getValue("using") + "}", 0, attr.getValue("on"));
  +                this.repeater.add(status);
  +                this.startRecording();
  +                this.recordingCount++;
               } else {
  -                switch (((Integer) elementNames.get(name, defaultElement)).intValue()) {
  -                    case ELEMENT_INPUT :
  -                        this.startInputElement(uri, name, raw, attr);
  -                        break;
  -
  -                    case ELEMENT_SELECT :
  -                        this.startSelectElement(uri, name, raw, attr);
  -                        break;
  -
  -                    case ELEMENT_OPTION :
  -                        this.startOptionElement(uri, name, raw, attr);
  -                        break;
  -
  -                    case ELEMENT_TXTAREA :
  -                        this.startTextareaElement(uri, name, raw, attr);
  -                        break;
  -
  -                    case ELEMENT_ERROR :
  -                        this.startErrorElement(uri, name, raw, attr);
  -                        break;
  -                    case ELEMENT_FORM :
  -                        this.startFormElement(uri, name, raw, attr);
  -                        break;
  -
  -                    default :
  -                        super.startElement(uri, name, raw, attr);
  -                }
  +                this.relayStartElement(uri, name, raw, attr);
  +            }
  +        } else {
  +            this.relayStartElement(uri, name, raw, attr);
  +            this.recordingCount++;
  +        }
  +    }
  +
  +    /**
  +     * Stop recording repeat contents and replay required number of times.
  +     * Stop only if outmost repeat element is ending.
  +     * 
  +     * @param uri
  +     * @param name
  +     * @param raw
  +     * @throws SAXException
  +     */
  +    protected void endRepeatElement(String uri, String name, String raw) throws SAXException {
  +        this.recordingCount--;
  +        if (this.recordingCount == 0) {
  +            DocumentFragment fragment = this.endRecording();
  +            RepeaterStatus status = (RepeaterStatus) this.repeater.get(this.repeater.size() - 1);
  +            Object[] vals = this.getValues(this.getName(status.expr));
  +            int count = (vals != null ? vals.length : 0);
  +            for (status.count = 1; status.count <= count; status.count++) {
  +                DOMStreamer streamer = new DOMStreamer(this, this);
  +                streamer.stream(fragment);
               }
  +            this.repeater.remove(this.repeater.size() - 1);
           } else {
  -            this.ignoreCount++;
  -            this.stack.push(name);
  -            if (((Integer) elementNames.get(name, defaultElement)).intValue() == ELEMENT_ERROR) {
  +            this.relayEndElement(uri, name, raw);
  +            if (this.recordingCount < 0) {
  +                this.recordingCount = 0;
               }
           }
       }
  @@ -799,100 +887,105 @@
        * @param uri The namespace of the element.
        * @param name The local name of the element.
        * @param raw The qualified name of the element.
  +     * @param attr The attributes of the element.
        */
  -    public void endElement(String uri, String name, String raw) throws SAXException {
  +    public void startTransformingElement(String uri, String name, String raw, Attributes attr)
  +        throws SAXException {
   
  -        if (uri != "") {
  -            if (this.ignoreCount == 0)
  -                super.endElement(uri, name, raw);
  -        } else {
  -            if (this.ignoreCount > 0) {
  -                if (((String) this.stack.peek()).equals(name)) {
  -                    this.stack.pop();
  -                    this.ignoreCount--;
  -                }
  -            }
  -            if (this.ignoreCount == 0 && this.ignoreThis) {
  -                this.ignoreThis = false;
  -                // skip event 
  -            } else if (this.ignoreCount > 0) {
  -                // skip event 
  -            } else {
  -                switch (((Integer) elementNames.get(name, defaultElement)).intValue()) {
  -                    case ELEMENT_INPUT :
  -                        super.endElement(uri, name, raw);
  -                        break;
  -                    case ELEMENT_SELECT :
  -                        this.values = null;
  -                        super.endElement(uri, name, raw);
  -                        break;
  -                    case ELEMENT_OPTION :
  -                        super.endElement(uri, name, raw);
  -                        break;
  -                    case ELEMENT_TXTAREA :
  -                        super.endElement(uri, name, raw);
  -                        break;
  -                    case ELEMENT_ERROR :
  -                        super.endElement(uri, name, raw);
  -                        break;
  -                    case ELEMENT_FORM :
  -                        this.fixed = this.documentFixed;
  -                        this.formName = null;
  -                        super.endElement(uri, name, raw);
  -                        break;
  +        if (this.ignoreEventsCount == 0 && this.recordingCount == 0) {
  +            switch (((Integer) elementNames.get(name, defaultElement)).intValue()) {
  +                case ELEMENT_INPUT :
  +                    this.startInputElement(uri, name, raw, attr);
  +                    break;
   
  -                    default :
  -                        super.endElement(uri, name, raw);
  -                }
  +                case ELEMENT_SELECT :
  +                    this.startSelectElement(uri, name, raw, attr);
  +                    break;
  +
  +                case ELEMENT_OPTION :
  +                    this.startOptionElement(uri, name, raw, attr);
  +                    break;
  +
  +                case ELEMENT_TXTAREA :
  +                    this.startTextareaElement(uri, name, raw, attr);
  +                    break;
  +
  +                case ELEMENT_ERROR :
  +                    this.startErrorElement(uri, name, raw, attr);
  +                    break;
  +
  +                case ELEMENT_FORM :
  +                    this.startFormElement(uri, name, raw, attr);
  +                    break;
  +
  +                case ELEMENT_REPEAT :
  +                    this.startRepeatElement(uri, name, raw, attr);
  +                    break;
  +
  +                default :
  +                    this.relayStartElement(uri, name, raw, attr);
  +            }
  +
  +        } else if (this.recordingCount > 0) {
  +            switch (((Integer) elementNames.get(name, defaultElement)).intValue()) {
  +                case ELEMENT_REPEAT :
  +                    this.startRepeatElement(uri, name, raw, attr);
  +                    break;
  +
  +                default :
  +                    this.relayStartElement(uri, name, raw, attr);
               }
  +        } else {
  +            this.relayStartElement(uri, name, raw, attr);
           }
       }
   
       /**
  -     * Receive notification of character data.
  -     *
  -     * @param c The characters from the XML document.
  -     * @param start The start position in the array.
  -     * @param len The number of characters to read from the array.
  +     * Start processing elements of our namespace.
  +     * This hook is invoked for each sax event with our namespace.
  +     * @param uri The namespace of the element.
  +     * @param name The local name of the element.
  +     * @param raw The qualified name of the element.
        */
  -    public void characters(char c[], int start, int len) throws SAXException {
  -        if (this.ignoreCount == 0)
  -            super.characters(c, start, len);
  -    }
  +    public void endTransformingElement(String uri, String name, String raw) throws SAXException {
   
  -    /**
  -     * Receive notification of ignorable whitespace in element content.
  -     *
  -     * @param c The characters from the XML document.
  -     * @param start The start position in the array.
  -     * @param len The number of characters to read from the array.
  -     */
  -    public void ignorableWhitespace(char c[], int start, int len) throws SAXException {
  -        if (this.ignoreCount == 0)
  -            super.ignorableWhitespace(c, start, len);
  -    }
  +        if (this.ignoreEventsCount > 0) {
  +            this.relayEndElement(uri, name, raw);
  +        } else if (this.recordingCount > 0) {
  +            switch (((Integer) elementNames.get(name, defaultElement)).intValue()) {
  +                case ELEMENT_REPEAT :
  +                    this.endRepeatElement(uri, name, raw);
  +                    break;
   
  -    /**
  -     * Receive notification of a processing instruction.
  -     *
  -     * @param target The processing instruction target.
  -     * @param data The processing instruction data, or null if none was
  -     *             supplied.
  -     */
  -    public void processingInstruction(String target, String data) throws SAXException {
  -        if (this.ignoreCount == 0)
  -            super.processingInstruction(target, data);
  -    }
  +                default :
  +                    this.relayEndElement(uri, name, raw);
  +            }
  +        } else {
  +            switch (((Integer) elementNames.get(name, defaultElement)).intValue()) {
  +                case ELEMENT_SELECT :
  +                    this.values = null;
  +                    this.relayEndElement(uri, name, raw);
  +                    break;
  +                case ELEMENT_INPUT :
  +                case ELEMENT_OPTION :
  +                case ELEMENT_TXTAREA :
  +                case ELEMENT_ERROR :
  +                    this.relayEndElement(uri, name, raw);
  +                    break;
  +                case ELEMENT_FORM :
  +                    this.fixed = this.documentFixed;
  +                    this.formName = null;
  +                    this.relayEndElement(uri, name, raw);
  +                    break;
   
  -    /**
  -     * Receive notification of a skipped entity.
  -     *
  -     * @param name The name of the skipped entity.  If it is a  parameter
  -     *             entity, the name will begin with '%'.
  -     */
  -    public void skippedEntity(String name) throws SAXException {
  -        if (this.ignoreCount == 0)
  -            super.skippedEntity(name);
  +                case ELEMENT_REPEAT :
  +                    this.endRepeatElement(uri, name, raw);
  +                    break;
  +
  +                default :
  +                    this.relayEndElement(uri, name, raw);
  +            }
  +        }
       }
   
       /**
  @@ -905,6 +998,40 @@
       }
   
       /**
  +     * Remove extra information from element's attributes. Currently only removes
  +     * the repeater variable from the element's name attribute if present.
  +     * 
  +     * @param attr
  +     * @return modified attributes
  +     */
  +    private Attributes normalizeAttributes(Attributes attr) {
  +        Attributes result = attr;
  +        if (this.stripNumber && this.repeater.size() > 0) {
  +            String name = attr.getValue("name");
  +            if (name != null) {
  +                for (Iterator i = this.repeater.iterator(); i.hasNext();) {
  +                    RepeaterStatus status = (RepeaterStatus) i.next();
  +                    int pos = name.indexOf(status.var);
  +                    if (pos >= 0) {
  +                        AttributesImpl attributes;
  +                        if (result instanceof AttributesImpl) {
  +                            attributes = (AttributesImpl) result;
  +                        } else {
  +                            attributes = new AttributesImpl(result);
  +                        }
  +                        name =
  +                            name.substring(0, pos - this.decorationSize)
  +                                + name.substring(pos + status.var.length() + this.decorationSize);
  +                        attributes.setValue(attributes.getIndex("name"), name);
  +                        result = attributes;
  +                    }
  +                }
  +            }
  +        }
  +        return result;
  +    }
  +
  +    /**
        * Generate the "real" name of an element for value lookup.
        * @param name
        * @return "real" name.
  @@ -934,6 +1061,38 @@
           if (this.suffix != null) {
               result = result + this.prefix;
           }
  +        if (this.repeater.size() > 0) {
  +            for (Iterator i = this.repeater.iterator(); i.hasNext();) {
  +                RepeaterStatus status = (RepeaterStatus) i.next();
  +                int pos = result.indexOf(status.var);
  +                if (pos != -1) {
  +                    result =
  +                        result.substring(0, pos)
  +                            + status.count
  +                            + result.substring(pos + status.var.length());
  +                }
  +            }
  +        }
  +        return result;
  +    }
  +
  +    /**
  +     * Obtain values from used InputModule if not done already and return the
  +     * next value. If no more values exist, returns null.
  +     * 
  +     * @param name
  +     * @return
  +     */
  +    private Object getNextValue(String name) {
  +        Object result = null;
  +        if (this.formValues.containsKey(name)) {
  +            ValueList vList = (ValueList) this.formValues.get(name);
  +            result = vList.getNext();
  +        } else {
  +            ValueList vList = new ValueList(this.getValues(name));
  +            result = vList.getNext();
  +            this.formValues.put(name, vList);
  +        }
           return result;
       }
   
  @@ -993,6 +1152,85 @@
           }
   
           return values;
  +    }
  +
  +    /**
  +     * Calls the super's method startTransformingElement.
  +     * 
  +     * @param uri
  +     * @param name
  +     * @param raw
  +     * @param attr
  +     * @throws SAXException
  +     */
  +    protected void relayStartElement(String uri, String name, String raw, Attributes attr)
  +        throws SAXException {
  +        this.relayStartElement(false, false, uri, name, raw, attr);
  +    }
  +
  +    /**
  +     * Calls the super's method startTransformingElement and increments the
  +     * ignoreEventsCount if skip is true. Increment can be done either before
  +     * invoking super's method, so that the element itself is skipped, or afterwards,
  +     * so that only the children are skipped.
  +     * 
  +     * @param increment skip counter 
  +     * @param skip only children
  +     * @param uri
  +     * @param name
  +     * @param raw
  +     * @param attr
  +     * @throws SAXException
  +     */
  +    protected void relayStartElement(
  +        boolean skip,
  +        boolean children,
  +        String uri,
  +        String name,
  +        String raw,
  +        Attributes attr)
  +        throws SAXException {
  +
  +        if (skip)
  +            this.skipChildrenOnly = children;
  +        if (skip && !children)
  +            this.ignoreEventsCount++;
  +        try {
  +            super.startTransformingElement(uri, name, raw, attr);
  +        } catch (ProcessingException e) {
  +            throw new SAXException(e);
  +        } catch (IOException e) {
  +            throw new SAXException(e);
  +        }
  +        if (skip && children)
  +            this.ignoreEventsCount++;
  +    }
  +
  +    /**
  +     * Calls the super's method endTransformingElement and decrements the
  +     * ignoreEventsCount if larger than zero.
  +     * 
  +     * @param uri
  +     * @param name
  +     * @param raw
  +     * @throws SAXException
  +     */
  +    protected void relayEndElement(String uri, String name, String raw) throws SAXException {
  +
  +        if (this.ignoreEventsCount == 1 && this.skipChildrenOnly)
  +            this.ignoreEventsCount--;
  +        try {
  +            super.endTransformingElement(uri, name, raw);
  +        } catch (ProcessingException e) {
  +            throw new SAXException(e);
  +        } catch (IOException e) {
  +            throw new SAXException(e);
  +        } catch (Exception e) {
  +            getLogger().error("exception", e);
  +        }
  +
  +        if (this.ignoreEventsCount > 0)
  +            this.ignoreEventsCount--;
       }
   
   }