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

au.id.jericho.lib.html.FormField 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 a field in an HTML form,
 * a field being defined as the group of all {@linkplain FormControl form controls}
 * having the same {@linkplain FormControl#getName() name}.
 * 

* The {@link #getFormControls()} method can be used to obtain the collection of this field's constituent * {@link FormControl} objects. *

* The {@link FormFields} class, which represents a collection of FormField objects, provides the highest level * interface for dealing with form fields and controls. For the most common tasks it can be used directly without * the need to work with its constituent FormField or {@link FormControl} objects. *

* The FormField class serves two main purposes: *

    *
  1. * Provide methods for the modification and retrieval of form control submission values * while ensuring that the states of all the field's constituent form controls remain consistent with each other. *

    * The methods available for this purpose are:
    * {@link #getValues() List getValues()}
    * {@link #clearValues() void clearValues()}
    * {@link #setValues(Collection) void setValues(Collection)}
    * {@link #setValue(CharSequence) boolean setValue(CharSequence)}
    * {@link #addValue(CharSequence) boolean addValue(CharSequence)}
    *

    * Although the {@link FormControl} class provides methods for directly modifying the submission values * of individual form controls, it is generally recommended to use the interface provided by the {@link FormFields} class * unless there is a specific requirement for the lower level functionality. * The {@link FormFields} class contains convenience methods providing most of the functionality of the above methods, * as well as some higher level functionality such as the ability to set the form * submission values as a complete field data set * using the {@link FormFields#setDataSet(Map)} method. *

  2. * Provide a means of determining the data structure of the field, allowing a server receiving a * submitted * form data set * to interpret and store the data in an appropriate way. *

    * The properties available for this purpose are:
    * {@link #allowsMultipleValues() boolean allowsMultipleValues()}
    * {@link #getUserValueCount() int getUserValueCount()}
    * {@link #getPredefinedValues() Collection getPredefinedValues()}
    *

    * The {@link FormFields#getColumnLabels()} and {@link FormFields#getColumnValues(Map)} methods utilise these properties * to convert data from a form data set * (represented as a field data set) into a simple array format, * suitable for storage in a tabular format such as a database table or .CSV file. *

    * The properties need only be utilised directly in the event that a * form data set is to be converted * from its normal format into some other type of data structure. *

* A form field which allows user values normally consists of a single * user value control, * such as a {@link FormControlType#TEXT TEXT} control. *

* When a form field consists of more than one control, these controls are normally all * predefined value controls of the same * {@linkplain FormControlType type}, such as {@link FormControlType#CHECKBOX CHECKBOX} controls. *

* Form fields consisting of more than one control do not necessarily return {@linkplain #allowsMultipleValues() multiple values}. * A form field consisting of {@link FormControlType#CHECKBOX CHECKBOX} controls can return multiple values, whereas * a form field consisting of {@link FormControlType#CHECKBOX RADIO} controls returns at most one value. *

* The HTML author can disregard convention and mix all types of controls with the same name in the same form, * or include multiple user value controls of the same name. * The evidence that such an unusual combination is present is when {@link #getUserValueCount()}>1. *

* FormField instances are created automatically with the creation of a {@link FormFields} collection. *

* The case sensitivity of form field names is determined by the static * {@link Config#CurrentCompatibilityMode}.{@link Config.CompatibilityMode#isFormFieldNameCaseInsensitive() FormFieldNameCaseInsensitive} property. * * @see FormFields * @see FormControl * @see FormControlType */ public final class FormField { private final String name; private int userValueCount=0; private boolean allowsMultipleValues=false; private LinkedHashSet predefinedValues=null; // String objects, null if none private final LinkedHashSet formControls=new LinkedHashSet(); private transient FormControl firstFormControl=null; // this field is simply a cache for the getFirstFormControl() method int columnIndex; // see FormFields.initColumns() /** Constructor called from FormFields class. */ FormField(final String name) { this.name=name; } /** * Returns the control name shared by all of this field's constituent {@linkplain FormControl controls}. *

* If the static {@link Config#CurrentCompatibilityMode}.{@link Config.CompatibilityMode#isFormFieldNameCaseInsensitive() isFormFieldNameCaseInsensitive()} * property is set to true, the grouping of the controls by name is case insensitive * and this method always returns the name in lower case. *

* Since a form field 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. * * @return the control name shared by all of this field's constituent {@linkplain FormControl controls}. * @see FormControl#getName() */ public String getName() { return name; } /** * Returns a collection of all the constituent {@linkplain FormControl form controls} in this field. *

* An iterator over this collection returns the controls in the order of appearance in the source. * * @return a collection of all the constituent {@linkplain FormControl form controls} in this field. * @see #getFormControl() * @see #getFormControl(String predefinedValue) */ public Collection getFormControls() { return formControls; } /** * Returns the constituent {@link FormControl} with the specified {@linkplain FormControl#getPredefinedValue() predefined value}. *

* Specifying a predefined value of null returns the first control without a predefined value. * * @param predefinedValue the predefined value of the control to be returned, or null to return the first control without a predefined value. * @return the constituent {@link FormControl} with the specified {@linkplain FormControl#getPredefinedValue() predefined value}, or null if none exists. * @see #getFormControl() * @see #getFormControls() */ public FormControl getFormControl(final String predefinedValue) { if (predefinedValue==null) { for (final Iterator i=formControls.iterator(); i.hasNext();) { final FormControl formControl=(FormControl)i.next(); if (!formControl.getFormControlType().hasPredefinedValue()) return formControl; if (formControl.getFormControlType().getElementName()!=Tag.SELECT && formControl.getPredefinedValue()==null) return formControl; } } else { for (final Iterator i=formControls.iterator(); i.hasNext();) { final FormControl formControl=(FormControl)i.next(); if (formControl.getFormControlType().getElementName()==Tag.SELECT) { if (formControl.getPredefinedValues().contains(predefinedValue)) return formControl; } else { if (predefinedValue.equals(formControl.getPredefinedValue())) return formControl; } } } return null; } /** * Returns the first {@link FormControl} from this field. * @return the first {@link FormControl} from this field, guaranteed not null. * @see #getFormControl(String predefinedValue) * @see #getFormControls() */ public FormControl getFormControl() { return (FormControl)formControls.iterator().next(); } /** * Indicates whether the field allows multiple values. *

* Returns false in any one of the following circumstances: *

    *
  • The field consists of only one control (unless it is a * {@linkplain FormControlType#SELECT_MULTIPLE multiple select} with more than one option) *
  • The field consists entirely of {@linkplain FormControlType#RADIO radio buttons} *
  • The field consists entirely of {@linkplain FormControlType#isSubmit() submit} buttons *
* If none of these three conditions are met, the method returns true. * * @return true if the field allows multiple values, otherwise false. */ public boolean allowsMultipleValues() { return allowsMultipleValues; } /** * Returns the number of constituent user value controls in this field. * This should in most cases be either 0 or 1. *

* A value of 0 indicates the field values consist only of * {@linkplain #getPredefinedValues() predefined values}, which is the case when the field consists only of * predefined value controls. *

* A value of 1 indicates the field values consist of at most one value set by the user. * It is still possible in this case to receive multiple values in the unlikely event that the HTML author mixed * controls of different types with the same name, but any other values would consist only of * {@linkplain #getPredefinedValues() predefined values}. *

* A value greater than 1 indicates that the HTML author has included more than one * user value control with the same name. * This would nearly always indicate an unintentional error in the HTML source document, * in which case your application can either log a warning that a poorly designed form has been encountered, * or take special action to try to interpret the multiple user values that might be submitted. * * @return the number of constituent user value controls in this field. */ public int getUserValueCount() { return userValueCount; } /** * Returns a collection of the {@linkplain FormControl#getPredefinedValue() predefined values} of all constituent {@linkplain FormControl controls} in this field. *

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

* An interator over this collection returns the values in the order of appearance in the source document. * * @return a collection of the {@linkplain FormControl#getPredefinedValue() predefined values} of all constituent {@linkplain FormControl controls} in this field, or null if none. * @see FormControl#getPredefinedValues() */ public Collection getPredefinedValues() { return predefinedValues!=null ? predefinedValues : Collections.EMPTY_SET; } /** * Returns a list of the field submission values in order of appearance. *

* The term field submission values is used in this library to refer to the aggregate of all the * submission values of a field's constituent {@linkplain #getFormControls() form controls}. *

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

* The list may contain duplicates if the this field has multiple controls with the same value. * * @return a list of the field submission values in order of appearance, guaranteed not null. */ public List getValues() { final List values=new ArrayList(); for (final Iterator i=formControls.iterator(); i.hasNext();) ((FormControl)i.next()).addValuesTo(values); return values; } /** * Clears the submission values of all the constituent {@linkplain #getFormControls() form controls} in this field. * @see FormControl#clearValues() */ public void clearValues() { for (final Iterator i=formControls.iterator(); i.hasNext();) ((FormControl)i.next()).clearValues(); } /** * Sets the field submission values of this field to the specified values. *

* This is equivalent to calling {@link #clearValues()} followed by {@link #addValue(CharSequence) addValue(value)} for each * value in the specified collection. *

* The specified collection must not contain any null values. * * @param values the new field submission values of this field. * @see #addValue(CharSequence value) */ public void setValues(final Collection values) { clearValues(); addValues(values); } /** * Sets the field submission values of this field to the single specified value. *

* This is equivalent to calling {@link #clearValues()} followed by {@link #addValue(CharSequence) addValue(value)}. *

* The return value indicates whether any of the constituent form controls "accepted" the value. * A return value of false implies an error condition as the specified value is not compatible with this field. *

* Specifying a null value is equivalent to calling {@link #clearValues()} alone, and always returns true. *

* See the {@link #addValue(CharSequence value)} method for more information. * * @param value the new field submission value of this field, or null to {@linkplain #clearValues() clear} the field of all submission values. * @return true if one of the constituent {@linkplain #getFormControls() form controls} accepts the value, otherwise false. * @see FormFields#setValue(String fieldName, CharSequence value) */ public boolean setValue(final CharSequence value) { clearValues(); return value!=null ? addValue(value) : true; } /** * Adds the specified value to the field submission values of this field. *

* This is achieved internally by attempting to {@linkplain FormControl#addValue(CharSequence) add the value} to every constituent * {@linkplain #getFormControls() form control} until one "accepts" it. *

* The return value indicates whether any of the constituent form controls accepted the value. * A return value of false implies an error condition as the specified value is not compatible with this field. *

* In the unusual case that this field consists of multiple form controls, but not all of them are * predefined value controls, priority is given to the predefined value controls * before attempting to add the value to the user value controls. * * @param value the new field submission value to add to this field, must not be null. * @return true if one of the constituent {@linkplain #getFormControls() form controls} accepts the value, otherwise false. */ public boolean addValue(final CharSequence value) { if (value==null) throw new IllegalArgumentException("value argument must not be null"); if (formControls.size()==1) return getFirstFormControl().addValue(value); List userValueControls=null; for (final Iterator i=formControls.iterator(); i.hasNext();) { final FormControl formControl=(FormControl)i.next(); if (!formControl.getFormControlType().hasPredefinedValue()) { // A user value control has been found, but is not the only control with this name. // This shouldn't normally happen in a well designed form, but we will save the user value control // for later and give all predefined value controls first opportunity to take the value. if (userValueControls==null) userValueControls=new LinkedList(); userValueControls.add(formControl); continue; } if (formControl.addValue(value)) return true; // return value of true from formControl.addValue(value) means the value was taken by the control } if (userValueControls==null) return false; for (final Iterator i=userValueControls.iterator(); i.hasNext();) { final FormControl formControl=(FormControl)i.next(); if (formControl.addValue(value)) return true; } return false; } /** * Returns a string representation of this object useful for debugging purposes. * @return a string representation of this object useful for debugging purposes. */ public String getDebugInfo() { final StringBuffer sb=new StringBuffer(); sb.append("Field: ").append(name).append(", UserValueCount=").append(userValueCount).append(", AllowsMultipleValues=").append(allowsMultipleValues); if (predefinedValues!=null) { for (final Iterator i=predefinedValues.iterator(); i.hasNext();) { sb.append(Config.NewLine).append("PredefinedValue: "); sb.append(i.next()); } } for (final Iterator i=formControls.iterator(); i.hasNext();) { sb.append(Config.NewLine).append("FormControl: "); sb.append(((FormControl)i.next()).getDebugInfo()); } sb.append(Config.NewLine).append(Config.NewLine); return sb.toString(); } /** * Returns a string representation of this object useful for debugging purposes. *

* This is equivalent to {@link #getDebugInfo()}. * * @return a string representation of this object useful for debugging purposes. */ public String toString() { return getDebugInfo(); } void addValues(final Collection values) { if (values!=null) for (final Iterator i=values.iterator(); i.hasNext();) addValue((CharSequence)i.next()); } void addValues(final CharSequence[] values) { if (values!=null) for (int i=0; i1) return true; if (userValueCount==1) return predefinedValues!=null; // at this stage we know userValueCount==0 && predefinedValues.size()>=1 if (predefinedValues.size()==1) return false; final FormControlType newFormControlType=newFormControl.getFormControlType(); if (formControls.size()==1) return newFormControlType==FormControlType.SELECT_MULTIPLE; // at this stage we know there are multiple predefined values in multiple controls. // if all of the controls are radio buttons or all are submit buttons, allowsMultipleValues is false, otherwise true. // checking only the first control and the new control is equivalent to checking them all because if they weren't all // the same allowsMultipleValues would already be true. final FormControlType firstFormControlType=getFirstFormControl().getFormControlType(); if (newFormControlType==FormControlType.RADIO && firstFormControlType==FormControlType.RADIO) return false; if (newFormControlType.isSubmit() && firstFormControlType.isSubmit()) return false; return true; } FormControl getFirstFormControl() { // formControls must be ordered collection for this method to work. // It has to return the first FormControl entered into the collection // for the algorithm in calculateAllowsMultipleValues() to work. if (firstFormControl==null) firstFormControl=(FormControl)formControls.iterator().next(); return firstFormControl; } /** only called from FormFields class */ void merge(final FormField formField) { if (formField.userValueCount>userValueCount) userValueCount=formField.userValueCount; allowsMultipleValues=allowsMultipleValues || formField.allowsMultipleValues; if (predefinedValues==null) { predefinedValues=formField.predefinedValues; } else if (formField.predefinedValues!=null) { for (final Iterator i=formField.predefinedValues.iterator(); i.hasNext();) predefinedValues.add(i.next()); } for (final Iterator i=formField.getFormControls().iterator(); i.hasNext();) formControls.add(i.next()); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy