Package org.apache.cocoon.transformation

Source Code of org.apache.cocoon.transformation.SimpleFormTransformer$RepeaterStatus

/*

============================================================================
                   The Apache Software License, Version 1.1
============================================================================

Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.

Redistribution and use in source and binary forms, with or without modifica-
tion, are permitted provided that the following conditions are met:

1. Redistributions of  source code must  retain the above copyright  notice,
    this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.

3. The end-user documentation included with the redistribution, if any, must
    include  the following  acknowledgment:  "This product includes  software
    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
    Alternately, this  acknowledgment may  appear in the software itself,  if
    and wherever such third-party acknowledgments normally appear.

4. The names "Apache Cocoon" and  "Apache Software Foundation" must  not  be
    used to  endorse or promote  products derived from  this software without
    prior written permission. For written permission, please contact
    apache@apache.org.

5. Products  derived from this software may not  be called "Apache", nor may
    "Apache" appear  in their name,  without prior written permission  of the
    Apache Software Foundation.

THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
(INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

This software  consists of voluntary contributions made  by many individuals
on  behalf of the Apache Software  Foundation and was  originally created by
Stefano Mazzocchi  <stefano@apache.org>. For more  information on the Apache
Software Foundation, please see <http://www.apache.org/>.

*/
package org.apache.cocoon.transformation;

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.service.ServiceSelector;
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.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;

/**
* 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
* an InputModule, e.g. request, with the same name. It handles select
* boxes, textareas, checkboxes, radio buttons, password and text
* fields, and buttons. Form elements and complete forms can be protected
* from substitution by adding an attribute fixed="true" to them.</p>
*
* <p>In addition, it handles FormValidatorAction results by
* selectively omitting &lt;error/&gt; elements. These elements need a
* "name" attribute corresponding to the name of the form element, and
* either a "when" or a "when-ge" attribute.</p>
*
* <p>An error element is send down the pipeline if validation results
* are available and either the results equals the "when" attribute or
* the result is greater or equal to the "when-ge" attribute.</p>
*
* <p>Names for validation results are "ok", "not-present", "error",
* "is-null", "too-small", "too-large", and "no-match" for the similar
* named values from ValidatorActionResult.</p>
*
* <p>There need not to be an "error" element for every form element,
* multiple error elements for the same form element may be
* present.</p>
*
* <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>
*   &lt;map:transform type="simple-form"/&gt;
* </pre></p>
*
* <p>Configuration elements:
* <table>
*   <tr><td>input-module</td><td>(String) InputModule configuration,
*           defaults to an empty configuration and the "request-param" module</td></tr>
*   <tr><td>fixed-attribute</td><td>(String) Name of the attribute used to
*           indicate that this element should not be changed. ("fixed")</td></tr>
*   <tr><td>use-form-name</td><td>(boolean) Add the name of the form to the
*           name of form elements. Uses default Separator , if default separator is null
*           or empty, separator is set to "/". ("false")</td></tr>
*   <tr><td>use-form-name-twice</td><td>(boolean) Add the name of the form twice to the
*           name of form elements. This is useful when the form instance has no
*           all enclosing root tag and the form name is used instead <em>and</em> the
*           form name needs to be used to find the form data. Uses default Separator ,
*           if default separator is null or empty, separator is set to "/".("false")</td></tr>
*   <tr><td>separator</td><td>(String) Separator between form name and element name ("/")
*           </td></tr>
*   <tr><td>prefix</td><td>(String) Prefix to add to element name for value lookup. No
*           separator will be added between prefix and rest of the name. Default
*           is "", when use-form-name is set, defaults to separator.</td></tr>
*   <tr><td>suffix</td><td>(String) Added to the input element's name. No
*           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>
*
* <p>Sitemap parameters:
* <table>
*   <tr><td>fixed</td><td>(boolean) Do not change values</td></tr>
*   <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>
*
* <p>Example:<pre>
*     &lt;input name="user.name" size="50" maxlength="60"/&gt;
*     &lt;error name="user.name" when-ge="error"&gt;required&lt;/error&gt;
* </pre></p>
*
* @author <a href="mailto:haul@apache.org">Christian Haul</a>
* @version CVS $Id: SimpleFormTransformer.java,v 1.10 2003/12/06 20:00:10 haul Exp $
*/
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;
    /** input element */
    private static final int ELEMENT_INPUT = 1;
    /** select element */
    private static final int ELEMENT_SELECT = 2;
    /** option element */
    private static final int ELEMENT_OPTION = 3;
    /** textarea element */
    private static final int ELEMENT_TXTAREA = 4;
    /** error element */
    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);

    /** input type unknown */
    private static final int TYPE_DEFAULT = 0;
    /** input type checkbox */
    private static final int TYPE_CHECKBOX = 1;
    /** input type radio */
    private static final int TYPE_RADIO = 2;
    /** default input type as Integer (needed as default in org.apache.cocoon.util.HashMap.get()) */
    private static final Integer defaultType = new Integer(TYPE_DEFAULT);

    protected static final String INPUT_MODULE_ROLE = InputModule.ROLE;
    protected static final String INPUT_MODULE_SELECTOR = INPUT_MODULE_ROLE + "Selector";

    /** map element name string to symbolic name */
    private static final HashMap elementNames;
    /** map input type string to symbolic name */
    private static final HashMap inputTypes;
    /** map ValidatorActionResult to name string */
    private static final HashMap validatorResults;
    /** map name string to ValidatorActionResult */
    private static final HashMap validatorResultLabel;

    /** setup mapping tables */
    static {
        HashMap names = new HashMap();
        names.put("input", new Integer(ELEMENT_INPUT));
        names.put("select", new Integer(ELEMENT_SELECT));
        names.put("option", new Integer(ELEMENT_OPTION));
        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;

        names = new HashMap();
        names.put("checkbox", new Integer(TYPE_CHECKBOX));
        names.put("radio", new Integer(TYPE_RADIO));
        inputTypes = names;
        names = null;

        names = new HashMap();
        names.put("ok", ValidatorActionResult.OK);
        names.put("not-present", ValidatorActionResult.NOTPRESENT);
        names.put("error", ValidatorActionResult.ERROR);
        names.put("is-null", ValidatorActionResult.ISNULL);
        names.put("too-small", ValidatorActionResult.TOOSMALL);
        names.put("too-large", ValidatorActionResult.TOOLARGE);
        names.put("no-match", ValidatorActionResult.NOMATCH);
        validatorResultLabel = names;

        names = new HashMap();
        names.put(ValidatorActionResult.OK, "ok");
        names.put(ValidatorActionResult.NOTPRESENT, "not-present");
        names.put(ValidatorActionResult.ERROR, "error");
        names.put(ValidatorActionResult.ISNULL, "is-null");
        names.put(ValidatorActionResult.TOOSMALL, "too-small");
        names.put(ValidatorActionResult.TOOLARGE, "too-large");
        names.put(ValidatorActionResult.NOMATCH, "no-match");
        validatorResults = names;
        names = null;
    }

    /** current element's request parameter values */
    protected Object[] values = null;

    /** current request's validation results (all validated elements) */
    protected Map validationResults = null;

    /** Should we skip inserting values? */
    private boolean fixed = false;
    /** Is the complete document protected? */
    private boolean documentFixed = false;

    private String fixedName = "fixed";
    private String prefix = null;
    private String suffix = null;
    private String defaultPrefix = null;
    private String defaultSuffix = null;
    private String separator = null;
    private String formName = null;
    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;
    private Configuration inputConf = null;
    private InputModule input = null;
    private ServiceSelector inputSelector = null;
    private String inputName = null;

    /** 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 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.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)
                this.inputSelector.release(this.input);
            this.manager.release(this.inputSelector);
        }
    }

    /**
     * Avalon Configurable Interface
     */
    public void configure(Configuration config) throws ConfigurationException {
        this.defaultInputConf = config.getChild("input-module");
        this.defaultInput = this.defaultInputConf.getAttribute("name", this.defaultInput);
        this.separator = config.getChild("separator").getValue(this.separator);
        this.defaultPrefix = config.getChild("prefix").getValue(this.defaultPrefix);
        this.defaultSuffix = config.getChild("suffix").getValue(this.defaultSuffix);
        this.fixedName = config.getChild("fixed-attribute").getValue(this.fixedName);
        this.useFormName = config.getChild("use-form-name").getValueAsBoolean(this.useFormName);
        this.useFormNameTwice =
            config.getChild("use-form-name-twice").getValueAsBoolean(this.useFormNameTwice);
        this.useFormName = this.useFormName || this.useFormNameTwice;
        if (this.useFormName) {
            this.separator =
                (this.separator == null || this.separator.equals("") ? "/" : this.separator);
            this.defaultPrefix = this.separator;
        }
        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);
    }

    /**
     * Setup the next round.
     * The instance variables are initialised.
     * @param resolver The current SourceResolver
     * @param objectModel The objectModel of the environment.
     * @param src The value of the src attribute in the sitemap.
     * @param par The parameters from the sitemap.
     */
    public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par)
        throws ProcessingException, SAXException, IOException {

        this.reset();

        super.setup(resolver, objectModel, src, par);

        if (request == null) {
            getLogger().debug("no request object");
            throw new ProcessingException("no request object");
        }
        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(this.objectModel);
        }

        if (this.inputName == null) {
            this.inputName = this.defaultInput;
            this.inputConf = this.defaultInputConf;
        }

        try {
            // obtain input module
            this.inputSelector = (ServiceSelector) this.manager.lookup(INPUT_MODULE_SELECTOR);
            if (this.inputName != null
                && this.inputSelector != null
                && this.inputSelector.isSelectable(this.inputName)) {
                this.input = (InputModule) this.inputSelector.select(this.inputName);
                if (!(this.input instanceof ThreadSafe
                    && this.inputSelector instanceof ThreadSafe)) {
                    this.inputSelector.release(this.input);
                    this.manager.release(this.inputSelector);
                    this.input = null;
                    this.inputSelector = null;
                }
            } else {
                if (this.inputName != null)
                    if (getLogger().isErrorEnabled())
                        getLogger().error(
                            "A problem occurred setting up '"
                                + this.inputName
                                + "': Selector is "
                                + (this.inputSelector != null ? "not " : "")
                                + "null, Component is "
                                + (this.inputSelector != null
                                    && this.inputSelector.isSelectable(this.inputName)
                                        ? "known"
                                        : "unknown"));
            }
        } catch (Exception e) {
            if (getLogger().isWarnEnabled())
                getLogger().warn(
                    "A problem occurred setting up '" + this.inputName + "': " + e.getMessage());
        }
    }

    /**
     *  Recycle this component.
     */
    public void recycle() {
        super.recycle();
        this.reset();
    }

    /**
     * Generate string representation of attributes. For debug only.
     */
    protected String printAttributes(Attributes attr) {
        StringBuffer sb = new StringBuffer();
        sb.append('[');
        for (int i = 0; i < attr.getLength(); i++) {
            sb.append('@').append(attr.getLocalName(i)).append("='").append(
                attr.getValue(i)).append(
                "' ");
        }
        sb.append(']');
        return sb.toString();
    }

    /**
     * Handle input elements that may have a "checked" attributes,
     * i.e. checkbox and radio.
     */
    protected void startCheckableElement(
        String aName,
        String uri,
        String name,
        String raw,
        AttributesImpl attributes)
        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;

        if (getLogger().isDebugEnabled())
            getLogger().debug(
                "startCheckableElement "
                    + name
                    + " attributes "
                    + this.printAttributes(attributes));
        if (this.values != null) {
            if (getLogger().isDebugEnabled())
                getLogger().debug("replacing");
            for (int i = 0; i < this.values.length; i++) {
                if (this.values[i].equals(value)) {
                    found = true;
                    if (checked == null) {
                        attributes.addAttribute("", "checked", "checked", "CDATA", "");
                    }
                    break;
                }
            }
            if (!found && checked != null) {
                attributes.removeAttribute(attributes.getIndex("checked"));
            }
        }
        this.relayStartElement(uri, name, raw, attributes);
    }

    /**
     * Handle input elements that may don't have a "checked"
     * attributes, e.g. text, password, button.
     */
    protected void startNonCheckableElement(
        String aName,
        String uri,
        String name,
        String raw,
        AttributesImpl attributes)
        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(
                "startNonCheckableElement "
                    + name
                    + " attributes "
                    + this.printAttributes(attributes));
        if (fValue != null) {
            if (getLogger().isDebugEnabled())
                getLogger().debug("replacing");
            if (value != null) {
                attributes.setValue(attributes.getIndex("value"), String.valueOf(fValue));
            } else {
                attributes.addAttribute("", "value", "value", "CDATA", String.valueOf(fValue));
            }
        }
        this.relayStartElement(uri, name, raw, attributes);
    }

    /**
     * Handle input elements. Calls startCheckableElement or
     * startNonCheckableElement.
     */
    protected void startInputElement(String uri, String name, String raw, Attributes attr)
        throws SAXException {

        // @value = request.getParameterValues(@name)
        String aName = getName(attr.getValue("name"));
        String fixed = attr.getValue(this.fixedName);

        if (getLogger().isDebugEnabled())
            getLogger().debug(
                "startInputElement " + name + " attributes " + this.printAttributes(attr));
        if (aName == null || this.fixed || (fixed != null && parseBoolean(fixed))) {
            this.relayStartElement(uri, name, raw, attr);

        } else {
            if (getLogger().isDebugEnabled())
                getLogger().debug("replacing");

            attr = this.normalizeAttributes(attr);

            AttributesImpl attributes = null;
            if (attr instanceof AttributesImpl) {
                attributes = (AttributesImpl) attr;
            } else {
                attributes = new AttributesImpl(attr);
            }
            String type = attributes.getValue("type");
            switch (((Integer) inputTypes.get(type, defaultType)).intValue()) {
                case TYPE_CHECKBOX :
                case TYPE_RADIO :
                    this.startCheckableElement(aName, uri, name, raw, attributes);
                    break;

                case TYPE_DEFAULT :
                    this.startNonCheckableElement(aName, uri, name, raw, attributes);
                    break;
            }
            this.values = null;
        }
    }

    /**
     * Handle select elements. Sets up some instance variables for
     * following option elements.
     */
    protected void startSelectElement(String uri, String name, String raw, Attributes attr)
        throws SAXException {

        // this.values = request.getParameterValues(@name)
        String aName = getName(attr.getValue("name"));
        String fixed = attr.getValue(this.fixedName);
        this.values = null;
        if (getLogger().isDebugEnabled())
            getLogger().debug(
                "startSelectElement " + name + " attributes " + this.printAttributes(attr));
        if (aName != null && !(this.fixed || (fixed != null && parseBoolean(fixed)))) {
            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);
        }
        this.relayStartElement(uri, name, raw, attr);
    }

    /**
     * Handle option elements. Uses instance variables set up by
     * startSelectElement. Relies on option having a "value"
     * attribute, i.e. does not check following characters if "value"
     * is not present.
     */
    protected void startOptionElement(String uri, String name, String raw, Attributes attr)
        throws SAXException {

        // add @selected if @value in request.getParameterValues(@name)
        if (getLogger().isDebugEnabled())
            getLogger().debug(
                "startOptionElement " + name + " attributes " + this.printAttributes(attr));
        if (this.values == null || this.fixed) {
            this.relayStartElement(uri, name, raw, attr);
        } else {
            if (getLogger().isDebugEnabled())
                getLogger().debug("replacing");
            AttributesImpl attributes = null;
            if (attr instanceof AttributesImpl) {
                attributes = (AttributesImpl) attr;
            } else {
                attributes = new AttributesImpl(attr);
            }
            String selected = attributes.getValue("selected");
            String value = attributes.getValue("value");
            boolean found = false;

            for (int i = 0; i < this.values.length; i++) {
                if (this.values[i].equals(value)) {
                    found = true;
                    if (selected == null) {
                        attributes.addAttribute("", "selected", "selected", "CDATA", "");
                    }
                    break;
                }
            }
            if (!found && selected != null) {
                attributes.removeAttribute(attributes.getIndex("selected"));
            }

            this.relayStartElement(uri, name, raw, attributes);
        }
    }

    /**
     * Handles textarea elements. Skips nested events if request
     * parameter with same name exists.
     */
    protected void startTextareaElement(String uri, String name, String raw, Attributes attributes)
        throws SAXException {

        String aName = getName(attributes.getValue("name"));
        String fixed = attributes.getValue(this.fixedName);
        Object value = null;
        if (getLogger().isDebugEnabled())
            getLogger().debug(
                "startTextareaElement " + name + " attributes " + this.printAttributes(attributes));
        if (aName != null) {
            value = this.getNextValue(aName);
        }
        if (value == null || this.fixed || (fixed != null && parseBoolean(fixed))) {
            this.relayStartElement(uri, name, raw, attributes);
        } else {
            if (getLogger().isDebugEnabled())
                getLogger().debug("replacing");
            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++;
        }
    }

    /**
     * Handle error elements. If validation results are available,
     * compares validation result for parameter with the same name as
     * the "name" attribute with the result names is "when" and
     * "when-ge". Drops element and all nested events when error
     * condition is not met.
     */
    protected void startErrorElement(String uri, String name, String raw, Attributes attr)
        throws SAXException {

        if (getLogger().isDebugEnabled())
            getLogger().debug(
                "startErrorElement " + name + " attributes " + this.printAttributes(attr));
        if (this.ignoreValidation) {
            this.relayStartElement(uri, name, raw, attr);
        } else if (this.validationResults == null || this.fixed) {
            this.relayStartElement(true, false, uri, name, raw, attr);
        } else {
            String aName = attr.getValue("name");
            if (aName == null) {
                this.relayStartElement(uri, name, raw, attr);
            } else {
                ValidatorActionResult validation =
                    XSPFormValidatorHelper.getParamResult(this.objectModel, aName);
                String when = attr.getValue("when");
                String when_ge = attr.getValue("when-ge");

                if ((when != null && when.equals(validatorResults.get(validation)))
                    || (when_ge != null
                        && validation.ge(
                            (ValidatorActionResult) validatorResultLabel.get(
                                when_ge,
                                ValidatorActionResult.MAXERROR)))) {
                    AttributesImpl attributes = null;
                    if (attr instanceof AttributesImpl) {
                        attributes = (AttributesImpl) attr;
                    } else {
                        attributes = new AttributesImpl(attr);
                    }
                    // remove attributes not meant for client
                    attributes.removeAttribute(attributes.getIndex("name"));
                    if (when != null)
                        attributes.removeAttribute(attributes.getIndex("when"));
                    if (when_ge != null)
                        attributes.removeAttribute(attributes.getIndex("when-ge"));
                    this.relayStartElement(uri, name, raw, this.normalizeAttributes(attributes));
                } else {
                    this.relayStartElement(true, true, uri, name, raw, attr);
                }
            }
        }
    }

    /**
     * Start processing a form element. Sets protection indicator if attribute
     * "fixed" is present and either "true" or "yes". Removes attribute "fixed"
     * if present.
     * @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.
     */
    protected void startFormElement(String uri, String name, String raw, Attributes attr)
        throws SAXException {

        String fixed = attr.getValue(this.fixedName);
        if (this.useFormName) {
            this.formName = attr.getValue("name");
        }
        if (fixed == null) {
            this.relayStartElement(uri, name, raw, attr);
        } else {
            if (!this.fixed && parseBoolean(fixed)) {
                this.fixed = true;
            }
            // remove attributes not meant for client
            AttributesImpl attributes = null;
            if (attr instanceof AttributesImpl) {
                attributes = (AttributesImpl) attr;
            } else {
                attributes = new AttributesImpl(attr);
            }
            attributes.removeAttribute(attributes.getIndex(this.fixedName));
            this.relayStartElement(uri, name, raw, this.normalizeAttributes(attributes));
        }
    }

    /**
     * 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
     */
    protected void startRepeatElement(String uri, String name, String raw, Attributes attr)
        throws SAXException {

        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 {
                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.relayEndElement(uri, name, raw);
            if (this.recordingCount < 0) {
                this.recordingCount = 0;
            }
        }
    }

    /**
     * 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.
     */
    public void startTransformingElement(String uri, String name, String raw, Attributes attr)
        throws SAXException {

        if (this.ignoreEventsCount == 0 && this.recordingCount == 0) {
            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;

                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);
        }
    }

    /**
     * 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 endTransformingElement(String uri, String name, String raw) throws SAXException {

        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;

                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;

                case ELEMENT_REPEAT :
                    this.endRepeatElement(uri, name, raw);
                    break;

                default :
                    this.relayEndElement(uri, name, raw);
            }
        }
    }

    /**
     * Check if a string is one of "yes", "true" ignoring case.
     * @param aBoolean
     * @return true if string is one of "yes", true"
     */
    private static boolean parseBoolean(String aBoolean) {
        return "true".equalsIgnoreCase(aBoolean) || "yes".equalsIgnoreCase(aBoolean);
    }

    /**
     * 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.
     */
    private String getName(String name) {
        String result = name;
        if (this.useFormName && this.formName != null) {
            if (this.separator != null) {
                if (this.useFormNameTwice) {
                    result =
                        this.formName + this.separator + this.formName + this.separator + result;
                } else {
                    result = this.formName + this.separator + result;
                }
            } else {
                if (this.useFormNameTwice) {
                    result = this.formName + result;
                } else {
                    // does this make sense ?
                    result = this.formName + this.formName + result;
                }
            }
        }
        if (this.prefix != null) {
            result = this.prefix + result;
        }
        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;
    }

    /**
     * Obtain values from the used InputModule.
     */
    private Object[] getValues(String name) {
        Object[] values = null;
        ServiceSelector iputSelector = null;
        InputModule iput = null;
        try {
            if (this.input != null) {
                // input module is thread safe
                // thus we still have a reference to it
                values = input.getAttributeValues(name, this.inputConf, objectModel);
                if (getLogger().isDebugEnabled())
                    getLogger().debug("cached module " + this.input
                                      + " attribute " + name
                                      + " returns " + values);
            } else {
                // input was not thread safe
                // so acquire it again
                iputSelector = (ServiceSelector)this.manager.lookup(INPUT_MODULE_SELECTOR);
                if (this.inputName != null
                    && iputSelector != null
                    && iputSelector.isSelectable(this.inputName)) {

                    iput = (InputModule) iputSelector.select(this.inputName);
                }
                if (iput != null) {
                    values = iput.getAttributeValues(name, this.inputConf, objectModel);
                }
                if (getLogger().isDebugEnabled())
                    getLogger().debug(
                        "fresh module " + iput + " attribute " + name + " returns " + values);
            }
        } catch (Exception e) {
            if (getLogger().isWarnEnabled())
                getLogger().warn(
                    "A problem occurred acquiring a value from '"
                        + this.inputName
                        + "' for '"
                        + name
                        + "': "
                        + e.getMessage());
        } finally {
            // release components if necessary
            if (iputSelector != null) {
                if (iput != null)
                    iputSelector.release(iput);
                this.manager.release(iputSelector);
            }
        }

        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 skip
     * @param skipChildrenOnly
     * @param uri
     * @param name
     * @param raw
     * @param attr
     * @throws SAXException
     */
    protected void relayStartElement(
        boolean skip,
        boolean skipChildrenOnly,
        String uri,
        String name,
        String raw,
        Attributes attr)
        throws SAXException {

        try {
            if (this.ignoreEventsCount > 0) {
                this.ignoreEventsCount++;
                super.startTransformingElement(uri, name, raw, attr);
            } else {
                if (skip)
                    this.skipChildrenOnly = skipChildrenOnly;
                if (skip && !skipChildrenOnly)
                    this.ignoreEventsCount++;
                super.startTransformingElement(uri, name, raw, attr);
                if (skip && skipChildrenOnly)
                    this.ignoreEventsCount++;
            }
        } catch (ProcessingException e) {
            throw new SAXException(e);
        } catch (IOException e) {
            throw new SAXException(e);
        }
    }

    /**
     * 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--;
    }

}
TOP

Related Classes of org.apache.cocoon.transformation.SimpleFormTransformer$RepeaterStatus

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.