All Downloads are FREE. Search and download functionalities are using the official Maven repository.

au.id.jericho.lib.html.FormControl Maven / Gradle / Ivy

Go to download

Jericho HTML Parser is a java library allowing analysis and manipulation of parts of an HTML document, including server-side tags, while reproducing verbatim any unrecognised or invalid HTML.

There is a newer version: 3.4
Show newest version
// Jericho HTML Parser - Java based library for analysing and manipulating HTML
// Version 2.6
// Copyright (C) 2007 Martin Jericho
// http://jerichohtml.sourceforge.net/
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of either one of the following licences:
//
// 1. The Eclipse Public License (EPL) version 1.0,
// included in this distribution in the file licence-epl-1.0.html
// or available at http://www.eclipse.org/legal/epl-v10.html
//
// 2. The GNU Lesser General Public License (LGPL) version 2.1 or later,
// included in this distribution in the file licence-lgpl-2.1.txt
// or available at http://www.gnu.org/licenses/lgpl.txt
//
// This library is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied.
// See the individual licence texts for more details.

package au.id.jericho.lib.html;

import java.util.*;

/**
 * Represents an HTML form control.
 * 

* A FormControl consists of a single {@linkplain #getElement() element} * that matches one of the {@linkplain FormControlType form control types}. *

* The term output element is used to describe the element that is * {@linkplain OutputSegment#writeTo(Writer) output} if this form control is {@linkplain OutputDocument#replace(FormControl) replaced} * in an {@link OutputDocument}. *

* A predefined value control is a form control for which * {@link #getFormControlType()}.{@link FormControlType#hasPredefinedValue() hasPredefinedValue()} * returns true. It has a {@linkplain #getFormControlType() control type} of * {@link FormControlType#CHECKBOX CHECKBOX}, {@link FormControlType#RADIO RADIO}, {@link FormControlType#BUTTON BUTTON}, * {@link FormControlType#SUBMIT SUBMIT}, {@link FormControlType#IMAGE IMAGE}, {@link FormControlType#SELECT_SINGLE SELECT_SINGLE} * or {@link FormControlType#SELECT_MULTIPLE SELECT_MULTIPLE}. *

* A user value control is a form control for which * {@link #getFormControlType()}.{@link FormControlType#hasPredefinedValue() hasPredefinedValue()} * returns false. It has a {@linkplain #getFormControlType() control type} of * {@link FormControlType#FILE FILE}, {@link FormControlType#HIDDEN HIDDEN}, {@link FormControlType#PASSWORD PASSWORD}, * {@link FormControlType#TEXT TEXT} or {@link FormControlType#TEXTAREA TEXTAREA}. *

* The functionality of most significance to users of this class relates to the * display characteristics of the output element, * manipulated using the {@link #setDisabled(boolean)} and {@link #setOutputStyle(FormControlOutputStyle)} methods. *

* As a general rule, the operations dealing with the control's submission values * are better performed on a {@link FormFields} or {@link FormField} object, which provide a more * intuitive interface by grouping form controls of the same {@linkplain #getName() name} together. * The higher abstraction level of these classes means they can automatically ensure that the * submission values of their constituent controls are consistent with each other, * for example by ensuring that only one {@link FormControlType#RADIO RADIO} control with a given name is * {@link #isChecked() checked} at a time. *

* A {@link FormFields} object can be directly {@linkplain FormFields#FormFields(Collection) constructed} from * a collection of FormControl objects. *

* FormControl instances are obtained using the {@link Element#getFormControl()} method or are created automatically * with the creation of a {@link FormFields} object via the {@link Segment#findFormFields()} method. * * @see FormControlType * @see FormFields * @see FormField */ public abstract class FormControl extends Segment { FormControlType formControlType; String name; ElementContainer elementContainer; FormControlOutputStyle outputStyle=FormControlOutputStyle.NORMAL; private static final String CHECKBOX_NULL_DEFAULT_VALUE="on"; private static Comparator COMPARATOR=new PositionComparator(); static FormControl construct(final Element element) { final String tagName=element.getStartTag().getName(); if (tagName==Tag.INPUT) { final String typeAttributeValue=element.getAttributes().getRawValue(Attribute.TYPE); if (typeAttributeValue==null) return new InputFormControl(element,FormControlType.TEXT); FormControlType formControlType=FormControlType.getFromInputElementType(typeAttributeValue.toLowerCase()); if (formControlType==null) { if (element.source.logger.isInfoEnabled()) element.source.logger.info(element.source.getRowColumnVector(element.begin).appendTo(new StringBuffer(200)).append(": INPUT control with unrecognised type \"").append(typeAttributeValue).append("\" assumed to be type \"text\"").toString()); formControlType=FormControlType.TEXT; } if (formControlType==FormControlType.TEXT) return new InputFormControl(element,formControlType); if (formControlType==FormControlType.CHECKBOX || formControlType==FormControlType.RADIO) return new RadioCheckboxFormControl(element,formControlType); if (formControlType==FormControlType.SUBMIT) return new SubmitFormControl(element,formControlType); if (formControlType==FormControlType.IMAGE) return new ImageSubmitFormControl(element); if (formControlType==FormControlType.NON_FORM_CONTROL) return null; // formControlType is HIDDEN || PASSWORD || FILE return new InputFormControl(element,formControlType); } else if (tagName==Tag.SELECT) { return new SelectFormControl(element); } else if (tagName==Tag.TEXTAREA) { return new TextAreaFormControl(element); } else if (tagName==Tag.BUTTON) { return "submit".equalsIgnoreCase(element.getAttributes().getRawValue(Attribute.TYPE)) ? new SubmitFormControl(element,FormControlType.BUTTON) : null; } else { return null; } } private FormControl(final Element element, final FormControlType formControlType, final boolean loadPredefinedValue) { super(element.source,element.begin,element.end); elementContainer=new ElementContainer(element,loadPredefinedValue); this.formControlType=formControlType; name=element.getAttributes().getValue(Attribute.NAME); verifyName(); } /** * Returns the {@linkplain FormControlType type} of this form control. * @return the {@linkplain FormControlType type} of this form control. */ public final FormControlType getFormControlType() { return formControlType; } /** * Returns the name of the control. *

* The name comes from the value of the name {@linkplain Attribute attribute} of the * {@linkplain #getElement() form control's element}, not the {@linkplain Element#getName() name of the element} itself. *

* Since a {@link FormField} is simply a group of controls with the same name, the terms control name and * field name are for the most part synonymous, with only a possible difference in case differentiating them. *

* In contrast to the {@link FormField#getName()} method, this method always returns the name using the original case * from the source document, regardless of the current setting of the static * {@link Config#CurrentCompatibilityMode}.{@link Config.CompatibilityMode#isFormFieldNameCaseInsensitive() FormFieldNameCaseInsensitive} property. * * @return the name of the control. */ public final String getName() { return name; } /** * Returns the {@linkplain Element element} representing this form control in the source document. *

* The {@linkplain Element#getAttributes() attributes} of this source element should correspond with the * output attributes if the * display characteristics or submission values * have not been modified. * * @return the {@linkplain Element element} representing this form control in the source document. */ public final Element getElement() { return elementContainer.element; } /** * Returns an iterator over the {@link Tag#OPTION OPTION} {@linkplain Element elements} contained within this control, in order of appearance. *

* This method is only relevant to form controls with a {@linkplain #getFormControlType() type} of * {@link FormControlType#SELECT_SINGLE SELECT_SINGLE} or {@link FormControlType#SELECT_MULTIPLE SELECT_MULTIPLE}. * * @return an iterator over the {@link Tag#OPTION OPTION} {@linkplain Element elements} contained within this control, in order of appearance. * @throws UnsupportedOperationException if the {@linkplain #getFormControlType() type} of this control is not {@link FormControlType#SELECT_SINGLE SELECT_SINGLE} or {@link FormControlType#SELECT_MULTIPLE SELECT_MULTIPLE}. */ public Iterator getOptionElementIterator() { // overridden in SelectFormControl throw new UnsupportedOperationException("Only SELECT controls contain OPTION elements"); } /** * Returns the current {@linkplain FormControlOutputStyle output style} of this form control. *

* This property affects how this form control is displayed if it has been {@linkplain OutputDocument#replace(FormControl) replaced} * in an {@link OutputDocument}. * See the documentation of the {@link FormControlOutputStyle} class for information on the available output styles. *

* The default output style for every form control is {@link FormControlOutputStyle#NORMAL}. * * @return the current {@linkplain FormControlOutputStyle output style} of this form control. * @see #setOutputStyle(FormControlOutputStyle) */ public FormControlOutputStyle getOutputStyle() { return outputStyle; } /** * Sets the {@linkplain FormControlOutputStyle output style} of this form control. *

* See the {@link #getOutputStyle()} method for a full description of this property. * * @param outputStyle the new {@linkplain FormControlOutputStyle output style} of this form control. */ public void setOutputStyle(final FormControlOutputStyle outputStyle) { this.outputStyle=outputStyle; } /** * Returns a map of the names and values of this form control's output attributes. *

* The term output attributes is used in this library to refer to the * attributes of a form control's * output element. *

* The map keys are the String attribute names, which should all be in lower case. * The map values are the corresponding CharSequence attribute values, with a null value given * to an attribute that {@linkplain Attribute#hasValue() has no value}. *

* Direct manipulation of the returned map affects the attributes of this form control's output element. * It is the responsibility of the user to ensure that all entries added to the map use the correct key and value types, * and that all keys (attribute names) are in lower case. *

* It is recommended that the submission value modification methods * are used to modify attributes that affect the submission value of the control * rather than manipulating the attributes map directly. *

* An iteration over the map entries will return the attributes in the same order as they appeared in the source document, or * at the end if the attribute was not present in the source document. *

* The returned attributes only correspond with those of the {@linkplain #getElement() source element} if the control's * display characteristics and submission values * have not been modified. * * @return a map of the names and values of this form control's output attributes. */ public final Map getAttributesMap() { return elementContainer.getAttributesMap(); } /** * Indicates whether this form control is disabled. *

* The form control is disabled if the attribute * "disabled" * is present in its output element. *

* The return value is is logically equivalent to {@link #getAttributesMap()}.containsKey("disabled"), * but using this property may be more efficient in some circumstances. * * @return true if this form control is disabled, otherwise false. */ public final boolean isDisabled() { return elementContainer.getBooleanAttribute(Attribute.DISABLED); } /** * Sets whether this form control is disabled. *

* If the argument supplied to this method is true and the disabled attribute is not already present * in the output element, the full * XHTML compatible attribute disabled="disabled" is added. * If the attribute is already present, it is left unchanged. *

* If the argument supplied to this method is false, the attribute is removed from the output element. *

* See the {@link #isDisabled()} method for more information. * * @param disabled the new value of this property. */ public final void setDisabled(final boolean disabled) { elementContainer.setBooleanAttribute(Attribute.DISABLED,disabled); } /** * Indicates whether this form control is checked. *

* The term checked is used to describe a checkbox or radio button control that is selected, which is the case if the attribute * "checked" * is present in its output element. *

* This property is only relevant to form controls with a {@linkplain #getFormControlType() type} of * {@link FormControlType#CHECKBOX} or {@link FormControlType#RADIO}, and throws an UnsupportedOperationException * for other control types. *

* Use one of the submission value modification methods to change the value * of this property. *

* If this control is a checkbox, you can set it to checked by calling * {@link #setValue(CharSequence) setValue}({@link #getName()}), and set it to unchecked by calling * {@link #clearValues()}. *

* If this control is a radio button, you should use the {@link FormField#setValue(CharSequence)} method or one of the other * higher level submission value modification methods * to set the control to checked, as calling {@link #setValue(CharSequence)} method on this object * in the same way as for a checkbox does not automatically uncheck all other radio buttons with the same name. * Even calling {@link #clearValues()} on this object to ensure that this radio button is unchecked is not recommended, as * it can lead to a situation where all the radio buttons with this name are unchecked. * The HTML 4.01 specification of radio buttons * recommends against this situation because it is not defined how user agents should handle it, and behaviour differs amongst browsers. *

* The return value is logically equivalent to {@link #getAttributesMap()}.containsKey("checked"), * but using this property may be more efficient in some circumstances. * * @return true if this form control is checked, otherwise false. * @throws UnsupportedOperationException if the {@linkplain #getFormControlType() type} of this control is not {@link FormControlType#CHECKBOX} or {@link FormControlType#RADIO}. */ public boolean isChecked() { throw new UnsupportedOperationException("This property is only relevant for CHECKBOX and RADIO controls"); } /** * Returns the initial value of this control if it has a {@linkplain FormControlType#hasPredefinedValue() predefined value}. *

* Only predefined value controls can return a non-null result. * All other control types return null. *

* {@link FormControlType#CHECKBOX CHECKBOX} and {@link FormControlType#RADIO RADIO} controls have a guaranteed * predefined value determined by the value of its compulsory * value * attribute. If the attribute is not present in the source document, this library assigns the control a default * predefined value of "on", consistent with popular browsers. *

* {@link FormControlType#SUBMIT SUBMIT}, {@link FormControlType#BUTTON BUTTON} and {@link FormControlType#IMAGE IMAGE} * controls have an optional predefined value determined by the value of its * value * attribute. This value is * successful * only in the control used to submit the form. *

* {@link FormControlType#SELECT_SINGLE} and {@link FormControlType#SELECT_MULTIPLE} controls are special cases * because they usually contain multiple * OPTION * elements, each with its own predefined value. * In this case the {@link #getPredefinedValues()} method should be used instead, which returns a collection of all the * control's predefined values. Attempting to call this method on a SELECT control results in * a java.lang.UnsupportedOperationException. *

* The predefined value of a control is not affected by changes to the * submission value of the control. * * @return the initial value of this control if it has a {@linkplain FormControlType#hasPredefinedValue() predefined value}, or null if none. */ public String getPredefinedValue() { return elementContainer.predefinedValue; } /** * Returns a collection of all {@linkplain #getPredefinedValue() predefined values} in this control in order of appearance. *

* All objects in the returned collection are of type String, with no null entries. *

* This method is most useful for * SELECT * controls since they typically contain multiple predefined values. * In other controls it returns a collection with zero or one item based on the output of the * {@link #getPredefinedValue()} method, so for efficiency it is recommended to use the * {@link #getPredefinedValue()} method instead. *

* The multiple predefined values of a * SELECT * control are defined by the * OPTION * elements within it. * Each OPTION element has an * initial value * determined by the value of its * value * attribute, or if this attribute is not present, by its * {@linkplain CharacterReference#decode(CharSequence) decoded} {@linkplain Element#getContent() content} * text with {@linkplain CharacterReference#decodeCollapseWhiteSpace(CharSequence) collapsed white space}. *

* The predefined values of a control are not affected by changes to the * submission values of the control. * * @return a collection of all {@linkplain #getPredefinedValue() predefined values} in this control in order of appearance, guaranteed not null. * @see FormField#getPredefinedValues() */ public Collection getPredefinedValues() { return getPredefinedValue()!=null ? Collections.singleton(getPredefinedValue()) : Collections.EMPTY_SET; } /** * Returns a list of the control's submission values in order of appearance. *

* All objects in the returned list are of type CharSequence, with no null entries. *

* The term submission value is used in this library to refer to the value the control * would contribute to the * form data set * of a submitted * form, assuming no modification of the control's * current value by the * user agent or by end user interaction. *

* For user value controls, the submission value corresponds to the * control's initial value. *

* The definition of the submission value for each predefined value control type is as follows: *

* {@link FormControlType#CHECKBOX CHECKBOX} and {@link FormControlType#RADIO RADIO} controls * have a submission value specified by its {@linkplain #getPredefinedValue() predefined value} * if it is {@link #isChecked() checked}, otherwise it has no submission value. *

* {@link FormControlType#SELECT_SINGLE SELECT_SINGLE} and {@link FormControlType#SELECT_MULTIPLE SELECT_MULTIPLE} controls * have submission values specified by the * values of the control's * selected * OPTION elements. *

* Only a {@link FormControlType#SELECT_MULTIPLE SELECT_MULTIPLE} control can have more than one submission value, * all other {@linkplain FormControlType control types} return a list containing either one value or no values. * A {@link FormControlType#SELECT_SINGLE SELECT_SINGLE} control only returns multiple submission values * if it illegally contains multiple selected options in the source document. *

* {@link FormControlType#SUBMIT SUBMIT}, {@link FormControlType#BUTTON BUTTON}, and {@link FormControlType#IMAGE IMAGE} * controls are only ever * successful * when they are activated by the user to * submit the form. * Because the submission value is intended to be a static representation of a control's data without * interaction by the user, this library never associates submission values with * {@linkplain FormControlType#isSubmit() submit} buttons, so this method always returns an empty list for these * control types. *

* The submission value(s) of a control can be modified for subsequent output in * an {@link OutputDocument} using the various * submission value modification methods, namely:
* {@link FormField#setValue(CharSequence)}
* {@link FormField#addValue(CharSequence)}
* {@link FormField#setValues(Collection)}
* {@link FormField#clearValues()}
* {@link FormFields#setValue(String fieldName, CharSequence value)}
* {@link FormFields#addValue(String fieldName, CharSequence value)}
* {@link FormFields#setDataSet(Map)}
* {@link FormFields#clearValues()}
* {@link #setValue(CharSequence) FormControl.setValue(CharSequence)}
* {@link #addValue(CharSequence) FormControl.addValue(CharSequence)}
* {@link #clearValues() FormControl.clearValues()}
*

* The values returned by this method reflect any changes made using the submission value modification methods, * in contrast to methods found in the {@link Attributes} and {@link Attribute} classes, which always reflect the source document. * * @return a list of the control's submission values in order of appearance, guaranteed not null. * @see #getPredefinedValues() */ public List getValues() { final List values=new ArrayList(); addValuesTo(values); return values; } /** * Clears the control's existing submission values. *

* This is equivalent to {@link #setValue(CharSequence) setValue(null)}. *

* NOTE: The {@link FormFields} and {@link FormField} classes provide a more appropriate abstraction level for the modification of form control submission values. * * @see FormFields#clearValues() * @see FormField#clearValues() */ public final void clearValues() { setValue(null); } /** * Sets the control's submission value *. *

* * NOTE: The {@link FormFields} and {@link FormField} classes provide a more appropriate abstraction level for the modification of form control submission values. * Consider using the {@link FormFields#setValue(String fieldName, CharSequence value)} method instead. *

* The specified value replaces any existing submission values of the control. *

* The return value indicates whether the control has "accepted" the value. * For user value controls, the return value is always true. *

* For predefined value controls, * calling this method does not affect the control's * {@linkplain #getPredefinedValues() predefined values}, but instead determines whether the control (or its options) become * checked or * selected * as detailed below: *

* {@link FormControlType#CHECKBOX CHECKBOX} and {@link FormControlType#RADIO RADIO} controls become {@link #isChecked() checked} * and the method returns true if the specified value matches the control's predefined value (case sensitive), * otherwise the control becomes unchecked and the method returns false. * Note that any other controls with the same {@linkplain #getName() name} are not unchecked if this control becomes checked, * possibly resulting in an invalid state where multiple RADIO controls are checked at the same time. * The {@link FormField#setValue(CharSequence)} method avoids such problems and its use is recommended over this method. *

* {@link FormControlType#SELECT_SINGLE SELECT_SINGLE} and {@link FormControlType#SELECT_MULTIPLE SELECT_MULTIPLE} * controls receive the specified value by selecting the option with the matching value and deselecting all others. * If none of the options match, all are deselected. * The return value of this method indicates whether one of the options matched. *

* {@link FormControlType#SUBMIT SUBMIT}, {@link FormControlType#BUTTON BUTTON}, and {@link FormControlType#IMAGE IMAGE} * controls never have a submission value, so calling this method has no effect and * always returns false. * * @param value the new submission value of this control, or null to clear the control of all submission values. * @return true if the control accepts the value, otherwise false. * @see FormFields#setValue(String fieldName, CharSequence value) */ public abstract boolean setValue(CharSequence value); /** * Adds the specified value to this control's submission values *. *

* * NOTE: The {@link FormFields} and {@link FormField} classes provide a more appropriate abstraction level for the modification of form control submission values. * Consider using the {@link FormFields#addValue(String fieldName, CharSequence value)} method instead. *

* This is almost equivalent to {@link #setValue(CharSequence)}, with only the following differences: *

* {@link FormControlType#CHECKBOX CHECKBOX} controls retain their existing submission value * instead of becoming unchecked if the specified value does not match the control's {@linkplain #getPredefinedValue() predefined value}. *

* {@link FormControlType#SELECT_MULTIPLE SELECT_MULTIPLE} controls retain their existing * submission values, meaning that the control's * OPTION * elements whose {@linkplain #getPredefinedValues() predefined values} do not match the specified value are not deselected. * This is the only type of control that can have multiple submission values within the one control. * * @param value the value to add to this control's submission values, must not be null. * @return true if the control accepts the value, otherwise false. * @see FormFields#addValue(String fieldName, CharSequence value) */ public boolean addValue(final CharSequence value) { return setValue(value); } abstract void addValuesTo(Collection collection); // should not add null values, values must be of type CharSequence abstract void addToFormFields(FormFields formFields); abstract void replaceInOutputDocument(OutputDocument outputDocument); public String getDebugInfo() { final StringBuffer sb=new StringBuffer(); sb.append(formControlType).append(" name=\"").append(name).append('"'); if (elementContainer.predefinedValue!=null) sb.append(" PredefinedValue=\"").append(elementContainer.predefinedValue).append('"'); sb.append(" - ").append(getElement().getDebugInfo()); return sb.toString(); } static final class InputFormControl extends FormControl { // TEXT, HIDDEN, PASSORD or FILE public InputFormControl(final Element element, final FormControlType formControlType) { super(element,formControlType,false); } public boolean setValue(final CharSequence value) { elementContainer.setAttributeValue(Attribute.VALUE,value); return true; } void addValuesTo(final Collection collection) { addValueTo(collection,elementContainer.getAttributeValue(Attribute.VALUE)); } void addToFormFields(final FormFields formFields) { formFields.add(this); } void replaceInOutputDocument(final OutputDocument outputDocument) { if (outputStyle==FormControlOutputStyle.REMOVE) { outputDocument.remove(getElement()); } else if (outputStyle==FormControlOutputStyle.DISPLAY_VALUE) { String output=null; if (formControlType!=FormControlType.HIDDEN) { CharSequence value=elementContainer.getAttributeValue(Attribute.VALUE); if (formControlType==FormControlType.PASSWORD && value!=null) value=getString(FormControlOutputStyle.ConfigDisplayValue.PasswordChar,value.length()); output=getDisplayValueHTML(value,false); } outputDocument.replace(getElement(),output); } else { replaceAttributesInOutputDocumentIfModified(outputDocument); } } } static final class TextAreaFormControl extends FormControl { // TEXTAREA public CharSequence value=UNCHANGED; private static final String UNCHANGED=new String(); public TextAreaFormControl(final Element element) { super(element,FormControlType.TEXTAREA,false); } public boolean setValue(final CharSequence value) { this.value=value; return true; } void addValuesTo(final Collection collection) { addValueTo(collection,getValue()); } void addToFormFields(final FormFields formFields) { formFields.add(this); } void replaceInOutputDocument(final OutputDocument outputDocument) { if (outputStyle==FormControlOutputStyle.REMOVE) { outputDocument.remove(getElement()); } else if (outputStyle==FormControlOutputStyle.DISPLAY_VALUE) { outputDocument.replace(getElement(),getDisplayValueHTML(getValue(),true)); } else { replaceAttributesInOutputDocumentIfModified(outputDocument); if (value!=UNCHANGED) outputDocument.replace(getElement().getContent(),CharacterReference.encode(value)); } } private CharSequence getValue() { return (value==UNCHANGED) ? CharacterReference.decode(getElement().getContent()) : value; } } static final class RadioCheckboxFormControl extends FormControl { // RADIO or CHECKBOX public RadioCheckboxFormControl(final Element element, final FormControlType formControlType) { super(element,formControlType,true); if (elementContainer.predefinedValue==null) { elementContainer.predefinedValue=CHECKBOX_NULL_DEFAULT_VALUE; if (element.source.logger.isInfoEnabled()) element.source.logger.info(element.source.getRowColumnVector(element.begin).appendTo(new StringBuffer(200)).append(": compulsory \"value\" attribute of ").append(formControlType).append(" control \"").append(name).append("\" is missing, assuming the value \"").append(CHECKBOX_NULL_DEFAULT_VALUE).append('"').toString()); } } public boolean setValue(final CharSequence value) { return elementContainer.setSelected(value,Attribute.CHECKED,false); } public boolean addValue(final CharSequence value) { return elementContainer.setSelected(value,Attribute.CHECKED,formControlType==FormControlType.CHECKBOX); } void addValuesTo(final Collection collection) { if (isChecked()) addValueTo(collection,getPredefinedValue()); } public boolean isChecked() { return elementContainer.getBooleanAttribute(Attribute.CHECKED); } void addToFormFields(final FormFields formFields) { formFields.add(this); } void replaceInOutputDocument(final OutputDocument outputDocument) { if (outputStyle==FormControlOutputStyle.REMOVE) { outputDocument.remove(getElement()); } else { if (outputStyle==FormControlOutputStyle.DISPLAY_VALUE) { final String html=isChecked() ? FormControlOutputStyle.ConfigDisplayValue.CheckedHTML : FormControlOutputStyle.ConfigDisplayValue.UncheckedHTML; if (html!=null) { outputDocument.replace(getElement(),html); return; } setDisabled(true); } replaceAttributesInOutputDocumentIfModified(outputDocument); } } } static class SubmitFormControl extends FormControl { // BUTTON, SUBMIT or (in subclass) IMAGE public SubmitFormControl(final Element element, final FormControlType formControlType) { super(element,formControlType,true); } public boolean setValue(final CharSequence value) { return false; } void addValuesTo(final Collection collection) {} void addToFormFields(final FormFields formFields) { if (getPredefinedValue()!=null) formFields.add(this); } void replaceInOutputDocument(final OutputDocument outputDocument) { if (outputStyle==FormControlOutputStyle.REMOVE) { outputDocument.remove(getElement()); } else { if (outputStyle==FormControlOutputStyle.DISPLAY_VALUE) setDisabled(true); replaceAttributesInOutputDocumentIfModified(outputDocument); } } } static final class ImageSubmitFormControl extends SubmitFormControl { // IMAGE public ImageSubmitFormControl(final Element element) { super(element,FormControlType.IMAGE); } void addToFormFields(final FormFields formFields) { super.addToFormFields(formFields); formFields.addName(this,name+".x"); formFields.addName(this,name+".y"); } } static final class SelectFormControl extends FormControl { // SELECT_MULTIPLE or SELECT_SINGLE public ElementContainer[] optionElementContainers; public SelectFormControl(final Element element) { super(element,element.getAttributes().get(Attribute.MULTIPLE)!=null ? FormControlType.SELECT_MULTIPLE : FormControlType.SELECT_SINGLE,false); final List optionElements=element.findAllElements(Tag.OPTION); optionElementContainers=new ElementContainer[optionElements.size()]; int x=0; for (final Iterator i=optionElements.iterator(); i.hasNext();) { final ElementContainer optionElementContainer=new ElementContainer((Element)i.next(),true); if (optionElementContainer.predefinedValue==null) // use the content of the element if it has no value attribute optionElementContainer.predefinedValue=CharacterReference.decodeCollapseWhiteSpace(optionElementContainer.element.getContent()); optionElementContainers[x++]=optionElementContainer; } } public String getPredefinedValue() { throw new UnsupportedOperationException("Use getPredefinedValues() method instead on SELECT controls"); } public Collection getPredefinedValues() { final LinkedHashSet linkedHashSet=new LinkedHashSet(optionElementContainers.length*2,1.0F); for (int i=0; i0) sb.setLength(sb.length()-FormControlOutputStyle.ConfigDisplayValue.MultipleValueSeparator.length()); // remove last separator outputDocument.replace(getElement(),getDisplayValueHTML(sb,false)); } else { replaceAttributesInOutputDocumentIfModified(outputDocument); for (int i=0; i'); if (text==null || text.length()==0) sb.append(FormControlOutputStyle.ConfigDisplayValue.EmptyHTML); else CharacterReference.appendEncode(sb,text,whiteSpaceFormatting); sb.append(EndTagType.START_DELIMITER_PREFIX).append(FormControlOutputStyle.ConfigDisplayValue.ElementName).append('>'); return sb.toString(); } final void replaceAttributesInOutputDocumentIfModified(final OutputDocument outputDocument) { elementContainer.replaceAttributesInOutputDocumentIfModified(outputDocument); } static List findAll(final Segment segment) { final ArrayList list=new ArrayList(); findAll(segment,list,Tag.INPUT); findAll(segment,list,Tag.TEXTAREA); findAll(segment,list,Tag.SELECT); findAll(segment,list,Tag.BUTTON); Collections.sort(list,COMPARATOR); return list; } private static void findAll(final Segment segment, final ArrayList list, final String tagName) { for (final Iterator i=segment.findAllElements(tagName).iterator(); i.hasNext();) { final FormControl formControl=((Element)i.next()).getFormControl(); if (formControl!=null) list.add(formControl); } } private static CharSequence getString(final char ch, final int length) { if (length==0) return ""; final StringBuffer sb=new StringBuffer(length); for (int i=0; iformControl2Begin) return 1; return 0; } } ////////////////////////////////////////////////////////////////////////////////////// static final class ElementContainer { // Contains the information common to both a FormControl and to each OPTION element // within a SELECT FormControl public final Element element; public Map attributesMap=null; public String predefinedValue; // never null for option, checkbox or radio elements public ElementContainer(final Element element, final boolean loadPredefinedValue) { this.element=element; predefinedValue=loadPredefinedValue ? element.getAttributes().getValue(Attribute.VALUE) : null; } public Map getAttributesMap() { if (attributesMap==null) attributesMap=element.getAttributes().getMap(true); return attributesMap; } public boolean setSelected(final CharSequence value, final String selectedOrChecked, final boolean allowMultipleValues) { if (value!=null && predefinedValue.equals(value.toString())) { setBooleanAttribute(selectedOrChecked,true); return true; } if (!allowMultipleValues) setBooleanAttribute(selectedOrChecked,false); return false; } public CharSequence getAttributeValue(final String attributeName) { if (attributesMap!=null) return (CharSequence)attributesMap.get(attributeName); else return element.getAttributes().getValue(attributeName); } public void setAttributeValue(final String attributeName, final CharSequence value) { // null value indicates attribute should be removed. if (value==null) { setBooleanAttribute(attributeName,false); return; } if (attributesMap!=null) { attributesMap.put(attributeName,value); return; } final String valueString=value.toString(); final CharSequence existingValue=getAttributeValue(attributeName); if (existingValue!=null && existingValue.toString().equals(valueString)) return; getAttributesMap().put(attributeName,valueString); } public boolean getBooleanAttribute(final String attributeName) { if (attributesMap!=null) return attributesMap.containsKey(attributeName); else return element.getAttributes().get(attributeName)!=null; } public void setBooleanAttribute(final String attributeName, final boolean value) { final boolean oldValue=getBooleanAttribute(attributeName); if (value==oldValue) return; if (value) getAttributesMap().put(attributeName,attributeName); // xhtml compatible attribute else getAttributesMap().remove(attributeName); } public void replaceAttributesInOutputDocumentIfModified(final OutputDocument outputDocument) { if (attributesMap!=null) outputDocument.replace(element.getAttributes(),attributesMap); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy