/[aagtl_public1]/src/net/htmlparser/jericho/FormControl.java
aagtl

Contents of /src/net/htmlparser/jericho/FormControl.java

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2 - (show annotations) (download)
Sun Aug 5 13:48:36 2012 UTC (11 years, 8 months ago) by zoffadmin
File size: 48770 byte(s)
initial import of aagtl source code
1 // Jericho HTML Parser - Java based library for analysing and manipulating HTML
2 // Version 3.2
3 // Copyright (C) 2004-2009 Martin Jericho
4 // http://jericho.htmlparser.net/
5 //
6 // This library is free software; you can redistribute it and/or
7 // modify it under the terms of either one of the following licences:
8 //
9 // 1. The Eclipse Public License (EPL) version 1.0,
10 // included in this distribution in the file licence-epl-1.0.html
11 // or available at http://www.eclipse.org/legal/epl-v10.html
12 //
13 // 2. The GNU Lesser General Public License (LGPL) version 2.1 or later,
14 // included in this distribution in the file licence-lgpl-2.1.txt
15 // or available at http://www.gnu.org/licenses/lgpl.txt
16 //
17 // This library is distributed on an "AS IS" basis,
18 // WITHOUT WARRANTY OF ANY KIND, either express or implied.
19 // See the individual licence texts for more details.
20
21 package net.htmlparser.jericho;
22
23 import java.util.*;
24 import java.io.*;
25
26 /**
27 * Represents an HTML form <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#form-controls">control</a>.
28 * <p>
29 * A <code>FormControl</code> consists of a single {@linkplain #getElement() element}
30 * that matches one of the {@linkplain FormControlType form control types}.
31 * <p>
32 * The term <i><a name="OutputElement">output element</a></i> is used to describe the element that is
33 * {@linkplain OutputSegment#writeTo(Writer) output} if this form control is {@linkplain OutputDocument#replace(FormControl) replaced}
34 * in an {@link OutputDocument}.
35 * <p>
36 * A <i><a name="PredefinedValueControl">predefined value control</a></i> is a form control for which
37 * {@link #getFormControlType()}.{@link FormControlType#hasPredefinedValue() hasPredefinedValue()}
38 * returns <code>true</code>. It has a {@linkplain #getFormControlType() control type} of
39 * {@link FormControlType#CHECKBOX CHECKBOX}, {@link FormControlType#RADIO RADIO}, {@link FormControlType#BUTTON BUTTON},
40 * {@link FormControlType#SUBMIT SUBMIT}, {@link FormControlType#IMAGE IMAGE}, {@link FormControlType#SELECT_SINGLE SELECT_SINGLE}
41 * or {@link FormControlType#SELECT_MULTIPLE SELECT_MULTIPLE}.
42 * <p>
43 * A <i><a name="UserValueControl">user value control</a></i> is a form control for which
44 * {@link #getFormControlType()}.{@link FormControlType#hasPredefinedValue() hasPredefinedValue()}
45 * returns <code>false</code>. It has a {@linkplain #getFormControlType() control type} of
46 * {@link FormControlType#FILE FILE}, {@link FormControlType#HIDDEN HIDDEN}, {@link FormControlType#PASSWORD PASSWORD},
47 * {@link FormControlType#TEXT TEXT} or {@link FormControlType#TEXTAREA TEXTAREA}.
48 * <p>
49 * The functionality of most significance to users of this class relates to the
50 * <i><a name="DisplayCharacteristics">display characteristics</a></i> of the <a href="#OutputElement">output element</a>,
51 * manipulated using the {@link #setDisabled(boolean)} and {@link #setOutputStyle(FormControlOutputStyle)} methods.
52 * <p>
53 * As a general rule, the operations dealing with the control's <a href="#SubmissionValue">submission values</a>
54 * are better performed on a {@link FormFields} or {@link FormField} object, which provide a more
55 * intuitive interface by grouping form controls of the same {@linkplain #getName() name} together.
56 * The higher abstraction level of these classes means they can automatically ensure that the
57 * <a href="#SubmissionValue">submission values</a> of their constituent controls are consistent with each other,
58 * for example by ensuring that only one {@link FormControlType#RADIO RADIO} control with a given name is
59 * {@link #isChecked() checked} at a time.
60 * <p>
61 * A {@link FormFields} object can be directly {@linkplain FormFields#FormFields(Collection) constructed} from
62 * a collection of <code>FormControl</code> objects.
63 * <p>
64 * <code>FormControl</code> instances are obtained using the {@link Element#getFormControl()} method or are created automatically
65 * with the creation of a {@link FormFields} object via the {@link Segment#getFormFields()} method.
66 *
67 * @see FormControlType
68 * @see FormFields
69 * @see FormField
70 */
71 public abstract class FormControl extends Segment {
72 FormControlType formControlType;
73 String name;
74 ElementContainer elementContainer;
75 FormControlOutputStyle outputStyle=FormControlOutputStyle.NORMAL;
76
77 private static final String CHECKBOX_NULL_DEFAULT_VALUE="on";
78 private static Comparator<FormControl> COMPARATOR=new PositionComparator();
79
80 static FormControl construct(final Element element) {
81 final String tagName=element.getStartTag().getName();
82 if (tagName==HTMLElementName.INPUT) {
83 final String typeAttributeValue=element.getAttributes().getRawValue(Attribute.TYPE);
84 if (typeAttributeValue==null) return new InputFormControl(element,FormControlType.TEXT);
85 FormControlType formControlType=FormControlType.getFromInputElementType(typeAttributeValue);
86 if (formControlType==null) {
87 if (formControlType.isNonFormControl(typeAttributeValue)) return null;
88 if (element.source.logger.isInfoEnabled()) element.source.logger.info(element.source.getRowColumnVector(element.begin).appendTo(new StringBuilder(200)).append(": INPUT control with unrecognised type \"").append(typeAttributeValue).append("\" assumed to be type \"text\"").toString());
89 formControlType=FormControlType.TEXT;
90 }
91 switch (formControlType) {
92 case TEXT:
93 return new InputFormControl(element,formControlType);
94 case CHECKBOX: case RADIO:
95 return new RadioCheckboxFormControl(element,formControlType);
96 case SUBMIT:
97 return new SubmitFormControl(element,formControlType);
98 case IMAGE:
99 return new ImageSubmitFormControl(element);
100 case HIDDEN: case PASSWORD: case FILE:
101 return new InputFormControl(element,formControlType);
102 default:
103 throw new AssertionError(formControlType);
104 }
105 } else if (tagName==HTMLElementName.SELECT) {
106 return new SelectFormControl(element);
107 } else if (tagName==HTMLElementName.TEXTAREA) {
108 return new TextAreaFormControl(element);
109 } else if (tagName==HTMLElementName.BUTTON) {
110 return "submit".equalsIgnoreCase(element.getAttributes().getRawValue(Attribute.TYPE)) ? new SubmitFormControl(element,FormControlType.BUTTON) : null;
111 } else {
112 return null;
113 }
114 }
115
116 private FormControl(final Element element, final FormControlType formControlType, final boolean loadPredefinedValue) {
117 super(element.source,element.begin,element.end);
118 elementContainer=new ElementContainer(element,loadPredefinedValue);
119 this.formControlType=formControlType;
120 name=element.getAttributes().getValue(Attribute.NAME);
121 verifyName();
122 }
123
124 /**
125 * Returns the {@linkplain FormControlType type} of this form control.
126 * @return the {@linkplain FormControlType type} of this form control.
127 */
128 public final FormControlType getFormControlType() {
129 return formControlType;
130 }
131
132 /**
133 * Returns the <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#control-name">name</a> of the control.
134 * <p>
135 * The name comes from the value of the <code>name</code> {@linkplain Attribute attribute} of the
136 * {@linkplain #getElement() form control's element}, not the {@linkplain Element#getName() name of the element} itself.
137 * <p>
138 * Since a {@link FormField} is simply a group of controls with the same name, the terms <i>control name</i> and
139 * <i>field name</i> are for the most part synonymous, with only a possible difference in case differentiating them.
140 * <p>
141 * In contrast to the {@link FormField#getName()} method, this method always returns the name using the original case
142 * from the source document, regardless of the current setting of the static
143 * {@link Config#CurrentCompatibilityMode}<code>.</code>{@link Config.CompatibilityMode#isFormFieldNameCaseInsensitive() FormFieldNameCaseInsensitive} property.
144 *
145 * @return the <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#control-name">name</a> of the control.
146 */
147 public final String getName() {
148 return name;
149 }
150
151 /**
152 * Returns the {@linkplain Element element} representing this form control in the source document.
153 * <p>
154 * The {@linkplain Element#getAttributes() attributes} of this source element should correspond with the
155 * <a href="#OutputAttributes">output attributes</a> if the
156 * <a href="#DisplayCharacteristics">display characteristics</a> or <a href="FormField.html#SubmissionValue">submission values</a>
157 * have not been modified.
158 *
159 * @return the {@linkplain Element element} representing this form control in the source document.
160 */
161 public final Element getElement() {
162 return elementContainer.element;
163 }
164
165 /**
166 * Returns an iterator over the {@link HTMLElementName#OPTION OPTION} {@linkplain Element elements} contained within this control, in order of appearance.
167 * <p>
168 * This method is only relevant to form controls with a {@linkplain #getFormControlType() type} of
169 * {@link FormControlType#SELECT_SINGLE SELECT_SINGLE} or {@link FormControlType#SELECT_MULTIPLE SELECT_MULTIPLE}.
170 *
171 * @return an iterator over the {@link HTMLElementName#OPTION OPTION} {@linkplain Element elements} contained within this control, in order of appearance.
172 * @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}.
173 */
174 public Iterator<Element> getOptionElementIterator() {
175 // overridden in SelectFormControl
176 throw new UnsupportedOperationException("Only SELECT controls contain OPTION elements");
177 }
178
179 /**
180 * Returns the current {@linkplain FormControlOutputStyle output style} of this form control.
181 * <p>
182 * This property affects how this form control is displayed if it has been {@linkplain OutputDocument#replace(FormControl) replaced}
183 * in an {@link OutputDocument}.
184 * See the documentation of the {@link FormControlOutputStyle} class for information on the available output styles.
185 * <p>
186 * The default output style for every form control is {@link FormControlOutputStyle#NORMAL}.
187 *
188 * @return the current {@linkplain FormControlOutputStyle output style} of this form control.
189 * @see #setOutputStyle(FormControlOutputStyle)
190 */
191 public FormControlOutputStyle getOutputStyle() {
192 return outputStyle;
193 }
194
195 /**
196 * Sets the {@linkplain FormControlOutputStyle output style} of this form control.
197 * <p>
198 * See the {@link #getOutputStyle()} method for a full description of this property.
199 *
200 * @param outputStyle the new {@linkplain FormControlOutputStyle output style} of this form control.
201 */
202 public void setOutputStyle(final FormControlOutputStyle outputStyle) {
203 this.outputStyle=outputStyle;
204 }
205
206 /**
207 * Returns a map of the names and values of this form control's <a href="#OutputAttributes">output attributes</a>.
208 * <p>
209 * The term <i><a name="OutputAttributes">output attributes</a></i> is used in this library to refer to the
210 * <a target="_blank" href="http://www.w3.org/TR/html401/intro/sgmltut.html#h-3.2.2">attributes</a> of a form control's
211 * <a href="#OutputElement">output element</a>.
212 * <p>
213 * The map keys are the <code>String</code> attribute names, which should all be in lower case.
214 * The map values are the corresponding <code>String</code> attribute values, with a <code>null</code> value given
215 * to an attribute that {@linkplain Attribute#hasValue() has no value}.
216 * <p>
217 * Direct manipulation of the returned map affects the attributes of this form control's <a href="#OutputElement">output element</a>.
218 * It is the responsibility of the user to ensure that all entries added to the map use the correct key and value types,
219 * and that all keys (attribute names) are in lower case.
220 * <p>
221 * It is recommended that the <a href="#SubmissionValueModificationMethods">submission value modification methods</a>
222 * are used to modify attributes that affect the <a href="#SubmissionValue">submission value</a> of the control
223 * rather than manipulating the attributes map directly.
224 * <p>
225 * An iteration over the map entries will return the attributes in the same order as they appeared in the source document, or
226 * at the end if the attribute was not present in the source document.
227 * <p>
228 * The returned attributes only correspond with those of the {@linkplain #getElement() source element} if the control's
229 * <a href="#DisplayCharacteristics">display characteristics</a> and <a href="#SubmissionValue">submission values</a>
230 * have not been modified.
231 *
232 * @return a map of the names and values of this form control's <a href="#OutputAttributes">output attributes</a>.
233 */
234 public final Map<String,String> getAttributesMap() {
235 return elementContainer.getAttributesMap();
236 }
237
238 /**
239 * Indicates whether this form control is <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#adef-disabled">disabled</a>.
240 * <p>
241 * The form control is disabled if the attribute
242 * "<code><a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#adef-disabled">disabled</a></code>"
243 * is present in its <a href="#OutputElement">output element</a>.
244 * <p>
245 * The return value is is logically equivalent to {@link #getAttributesMap()}<code>.containsKey("disabled")</code>,
246 * but using this property may be more efficient in some circumstances.
247 *
248 * @return <code>true</code> if this form control is <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#adef-disabled">disabled</a>, otherwise <code>false</code>.
249 */
250 public final boolean isDisabled() {
251 return elementContainer.getBooleanAttribute(Attribute.DISABLED);
252 }
253
254 /**
255 * Sets whether this form control is <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#adef-disabled">disabled</a>.
256 * <p>
257 * If the argument supplied to this method is <code>true</code> and the <code>disabled</code> attribute is not already present
258 * in the output element, the full
259 * <a target="_blank" href="http://www.w3.org/TR/xhtml1/">XHTML</a> compatible attribute <code>disabled="disabled"</code> is added.
260 * If the attribute is already present, it is left unchanged.
261 * <p>
262 * If the argument supplied to this method is <code>false</code>, the attribute is removed from the output element.
263 * <p>
264 * See the {@link #isDisabled()} method for more information.
265 *
266 * @param disabled the new value of this property.
267 */
268 public final void setDisabled(final boolean disabled) {
269 elementContainer.setBooleanAttribute(Attribute.DISABLED,disabled);
270 }
271
272 /**
273 * Indicates whether this form control is <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#adef-checked">checked</a>.
274 * <p>
275 * The term <i>checked</i> is used to describe a checkbox or radio button control that is selected, which is the case if the attribute
276 * "<code><a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#adef-checked">checked</a></code>"
277 * is present in its <a href="#OutputElement">output element</a>.
278 * <p>
279 * This property is only relevant to form controls with a {@linkplain #getFormControlType() type} of
280 * {@link FormControlType#CHECKBOX} or {@link FormControlType#RADIO}, and throws an <code>UnsupportedOperationException</code>
281 * for other control types.
282 * <p>
283 * Use one of the <a href="#SubmissionValueModificationMethods">submission value modification methods</a> to change the value
284 * of this property.
285 * <p>
286 * If this control is a checkbox, you can set it to checked by calling
287 * {@link #setValue(String) setValue}<code>(</code>{@link #getName()}<code>)</code>, and set it to unchecked by calling
288 * {@link #clearValues()}.
289 * <p>
290 * If this control is a radio button, you should use the {@link FormField#setValue(String)} method or one of the other
291 * higher level <a href="#SubmissionValueModificationMethods">submission value modification methods</a>
292 * to set the control to checked, as calling {@link #setValue(String)} method on this object
293 * in the same way as for a checkbox does not automatically uncheck all other radio buttons with the same name.
294 * Even calling {@link #clearValues()} on this object to ensure that this radio button is unchecked is not recommended, as
295 * it can lead to a situation where all the radio buttons with this name are unchecked.
296 * The <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#radio">HTML 4.01 specification of radio buttons</a>
297 * recommends against this situation because it is not defined how user agents should handle it, and behaviour differs amongst browsers.
298 * <p>
299 * The return value is logically equivalent to {@link #getAttributesMap()}<code>.containsKey("checked")</code>,
300 * but using this property may be more efficient in some circumstances.
301 *
302 * @return <code>true</code> if this form control is <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#adef-checked">checked</a>, otherwise <code>false</code>.
303 * @throws UnsupportedOperationException if the {@linkplain #getFormControlType() type} of this control is not {@link FormControlType#CHECKBOX} or {@link FormControlType#RADIO}.
304 */
305 public boolean isChecked() {
306 throw new UnsupportedOperationException("This property is only relevant for CHECKBOX and RADIO controls");
307 }
308
309 /**
310 * Returns the <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#initial-value">initial value</a> of this control if it has a {@linkplain FormControlType#hasPredefinedValue() predefined value}.
311 * <p>
312 * Only <a href="#PredefinedValueControl">predefined value controls</a> can return a non-<code>null</code> result.
313 * All other control types return <code>null</code>.
314 * <p>
315 * {@link FormControlType#CHECKBOX CHECKBOX} and {@link FormControlType#RADIO RADIO} controls have a guaranteed
316 * predefined value determined by the value of its compulsory
317 * <code><a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#adef-value-INPUT">value</a></code>
318 * attribute. If the attribute is not present in the source document, this library assigns the control a default
319 * predefined value of "<code>on</code>", consistent with popular browsers.
320 * <p>
321 * {@link FormControlType#SUBMIT SUBMIT}, {@link FormControlType#BUTTON BUTTON} and {@link FormControlType#IMAGE IMAGE}
322 * controls have an optional predefined value determined by the value of its
323 * <code><a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#adef-value-INPUT">value</a></code>
324 * attribute. This value is
325 * <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#successful-controls">successful</a>
326 * only in the control used to submit the form.
327 * <p>
328 * {@link FormControlType#SELECT_SINGLE} and {@link FormControlType#SELECT_MULTIPLE} controls are special cases
329 * because they usually contain multiple
330 * <code><a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#edef-OPTION">OPTION</a></code>
331 * elements, each with its own predefined value.
332 * In this case the {@link #getPredefinedValues()} method should be used instead, which returns a collection of all the
333 * control's predefined values. Attempting to call this method on a <code>SELECT</code> control results in
334 * a <code>java.lang.UnsupportedOperationException</code>.
335 * <p>
336 * The predefined value of a control is not affected by changes to the
337 * <a href="#SubmissionValue">submission value</a> of the control.
338 *
339 * @return the <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#initial-value">initial value</a> of this control if it has a {@linkplain FormControlType#hasPredefinedValue() predefined value}, or <code>null</code> if none.
340 */
341 public String getPredefinedValue() {
342 return elementContainer.predefinedValue;
343 }
344
345 /**
346 * Returns a collection of all {@linkplain #getPredefinedValue() predefined values} in this control in order of appearance.
347 * <p>
348 * All objects in the returned collection are of type <code>String</code>, with no <code>null</code> entries.
349 * <p>
350 * This method is most useful for
351 * <code><a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#edef-SELECT">SELECT</a></code>
352 * controls since they typically contain multiple predefined values.
353 * In other controls it returns a collection with zero or one item based on the output of the
354 * {@link #getPredefinedValue()} method, so for efficiency it is recommended to use the
355 * {@link #getPredefinedValue()} method instead.
356 * <p>
357 * The multiple predefined values of a
358 * <code><a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#edef-SELECT">SELECT</a></code>
359 * control are defined by the
360 * <code><a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#edef-OPTION">OPTION</a></code>
361 * elements within it.
362 * Each <code>OPTION</code> element has an
363 * <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#initial-value">initial value</a>
364 * determined by the value of its
365 * <code><a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#adef-value-OPTION">value</a></code>
366 * attribute, or if this attribute is not present, by its
367 * {@linkplain CharacterReference#decode(CharSequence) decoded} {@linkplain Element#getContent() content}
368 * text with {@linkplain CharacterReference#decodeCollapseWhiteSpace(CharSequence) collapsed white space}.
369 * <p>
370 * The predefined values of a control are not affected by changes to the
371 * <a href="#SubmissionValue">submission values</a> of the control.
372 *
373 * @return a collection of all {@linkplain #getPredefinedValue() predefined values} in this control in order of appearance, guaranteed not <code>null</code>.
374 * @see FormField#getPredefinedValues()
375 */
376 public Collection<String> getPredefinedValues() {
377 if (getPredefinedValue()==null) Collections.emptySet();
378 return Collections.singleton(getPredefinedValue());
379 }
380
381 /**
382 * Returns a list of the control's <a href="#SubmissionValue">submission values</a> in order of appearance.
383 * <p>
384 * All objects in the returned list are of type <code>String</code>, with no <code>null</code> entries.
385 * <p>
386 * The term <i><a name="SubmissionValue">submission value</a></i> is used in this library to refer to the value the control
387 * would contribute to the
388 * <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#form-data-set">form data set</a>
389 * of a <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#submit-format">submitted</a>
390 * form, assuming no modification of the control's
391 * <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#current-value">current value</a> by the
392 * <a target="_blank" href="http://www.w3.org/TR/html401/conform.html#didx-user_agent">user agent</a> or by end user interaction.
393 * <p>
394 * For <a href="#UserValueControl">user value controls</a>, the submission value corresponds to the
395 * control's <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#initial-value">initial value</a>.
396 * <p>
397 * The definition of the submission value for each <a href="#PredefinedValueControl">predefined value control</a> type is as follows:
398 * <p>
399 * {@link FormControlType#CHECKBOX CHECKBOX} and {@link FormControlType#RADIO RADIO} controls
400 * have a submission value specified by its {@linkplain #getPredefinedValue() predefined value}
401 * if it is {@link #isChecked() checked}, otherwise it has no submission value.
402 * <p>
403 * {@link FormControlType#SELECT_SINGLE SELECT_SINGLE} and {@link FormControlType#SELECT_MULTIPLE SELECT_MULTIPLE} controls
404 * have submission values specified by the
405 * <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#adef-value-OPTION">values</a> of the control's
406 * <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#adef-selected">selected</a>
407 * <code>OPTION</code> elements.
408 * <p>
409 * Only a {@link FormControlType#SELECT_MULTIPLE SELECT_MULTIPLE} control can have more than one submission value,
410 * all other {@linkplain FormControlType control types} return a list containing either one value or no values.
411 * A {@link FormControlType#SELECT_SINGLE SELECT_SINGLE} control only returns multiple submission values
412 * if it illegally contains multiple selected options in the source document.
413 * <p>
414 * {@link FormControlType#SUBMIT SUBMIT}, {@link FormControlType#BUTTON BUTTON}, and {@link FormControlType#IMAGE IMAGE}
415 * controls are only ever
416 * <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#successful-controls">successful</a>
417 * when they are activated by the user to
418 * <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#submit-format">submit</a> the form.
419 * Because the submission value is intended to be a static representation of a control's data without
420 * interaction by the user, this library never associates submission values with
421 * {@linkplain FormControlType#isSubmit() submit} buttons, so this method always returns an empty list for these
422 * control types.
423 * <p>
424 * The <a href="#SubmissionValue">submission value(s)</a> of a control can be modified for subsequent output in
425 * an {@link OutputDocument} using the various
426 * <i><a name="SubmissionValueModificationMethods">submission value modification methods</a></i>, namely:<br />
427 * {@link FormField#setValue(String)}<br />
428 * {@link FormField#addValue(String)}<br />
429 * {@link FormField#setValues(Collection)}<br />
430 * {@link FormField#clearValues()}<br />
431 * {@link FormFields#setValue(String fieldName, String value)}<br />
432 * {@link FormFields#addValue(String fieldName, String value)}<br />
433 * {@link FormFields#setDataSet(Map)}<br />
434 * {@link FormFields#clearValues()}<br />
435 * {@link #setValue(String) FormControl.setValue(String)}<br />
436 * {@link #addValue(String) FormControl.addValue(String)}<br />
437 * {@link #clearValues() FormControl.clearValues()}<br />
438 * <p>
439 * The values returned by this method reflect any changes made using the submission value modification methods,
440 * in contrast to methods found in the {@link Attributes} and {@link Attribute} classes, which always reflect the source document.
441 *
442 * @return a list of the control's <i>submission values</i> in order of appearance, guaranteed not <code>null</code>.
443 * @see #getPredefinedValues()
444 */
445 public List<String> getValues() {
446 final List<String> values=new ArrayList<String>();
447 addValuesTo(values);
448 return values;
449 }
450
451 /**
452 * Clears the control's existing <a href="#SubmissionValue">submission values</a>.
453 * <p>
454 * This is equivalent to {@link #setValue(String) setValue(null)}.
455 * <p>
456 * NOTE: The {@link FormFields} and {@link FormField} classes provide a more appropriate abstraction level for the modification of form control submission values.
457 *
458 * @see FormFields#clearValues()
459 * @see FormField#clearValues()
460 */
461 public final void clearValues() {
462 setValue(null);
463 }
464
465 /**
466 * Sets the control's <a href="#SubmissionValue">submission value</a> *.
467 * <p>
468 * * NOTE: The {@link FormFields} and {@link FormField} classes provide a more appropriate abstraction level for the modification of form control submission values.
469 * Consider using the {@link FormFields#setValue(String fieldName, String value)} method instead.
470 * <p>
471 * The specified value replaces any existing <a href="#SubmissionValue">submission values</a> of the control.
472 * <p>
473 * The return value indicates whether the control has "accepted" the value.
474 * For <a href="#UserValueControl">user value controls</a>, the return value is always <code>true</code>.
475 * <p>
476 * For <a href="#PredefinedValueControl">predefined value controls</a>,
477 * calling this method does not affect the control's
478 * {@linkplain #getPredefinedValues() predefined values}, but instead determines whether the control (or its options) become
479 * <code><a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#adef-checked">checked</a></code> or
480 * <code><a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#adef-selected">selected</a></code>
481 * as detailed below:
482 * <p>
483 * {@link FormControlType#CHECKBOX CHECKBOX} and {@link FormControlType#RADIO RADIO} controls become {@link #isChecked() checked}
484 * and the method returns <code>true</code> if the specified value matches the control's predefined value (case sensitive),
485 * otherwise the control becomes unchecked and the method returns <code>false</code>.
486 * Note that any other controls with the same {@linkplain #getName() name} are not unchecked if this control becomes checked,
487 * possibly resulting in an invalid state where multiple <code>RADIO</code> controls are checked at the same time.
488 * The {@link FormField#setValue(String)} method avoids such problems and its use is recommended over this method.
489 * <p>
490 * {@link FormControlType#SELECT_SINGLE SELECT_SINGLE} and {@link FormControlType#SELECT_MULTIPLE SELECT_MULTIPLE}
491 * controls receive the specified value by selecting the option with the matching value and deselecting all others.
492 * If none of the options match, all are deselected.
493 * The return value of this method indicates whether one of the options matched.
494 * <p>
495 * {@link FormControlType#SUBMIT SUBMIT}, {@link FormControlType#BUTTON BUTTON}, and {@link FormControlType#IMAGE IMAGE}
496 * controls never have a <a href="#SubmissionValue">submission value</a>, so calling this method has no effect and
497 * always returns <code>false</code>.
498 *
499 * @param value the new <a href="#SubmissionValue">submission value</a> of this control, or <code>null</code> to clear the control of all submission values.
500 * @return <code>true</code> if the control accepts the value, otherwise <code>false</code>.
501 * @see FormFields#setValue(String fieldName, String value)
502 */
503 public abstract boolean setValue(String value);
504
505 /**
506 * Adds the specified value to this control's <a href="#SubmissionValue">submission values</a> *.
507 * <p>
508 * * NOTE: The {@link FormFields} and {@link FormField} classes provide a more appropriate abstraction level for the modification of form control submission values.
509 * Consider using the {@link FormFields#addValue(String fieldName, String value)} method instead.
510 * <p>
511 * This is almost equivalent to {@link #setValue(String)}, with only the following differences:
512 * <p>
513 * {@link FormControlType#CHECKBOX CHECKBOX} controls retain their existing <a href="#SubmissionValue">submission value</a>
514 * instead of becoming unchecked if the specified value does not match the control's {@linkplain #getPredefinedValue() predefined value}.
515 * <p>
516 * {@link FormControlType#SELECT_MULTIPLE SELECT_MULTIPLE} controls retain their existing
517 * <a href="#SubmissionValue">submission values</a>, meaning that the control's
518 * <code><a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#edef-OPTION">OPTION</a></code>
519 * elements whose {@linkplain #getPredefinedValues() predefined values} do not match the specified value are not deselected.
520 * This is the only type of control that can have multiple submission values within the one control.
521 *
522 * @param value the value to add to this control's <a href="#SubmissionValue">submission values</a>, must not be <code>null</code>.
523 * @return <code>true</code> if the control accepts the value, otherwise <code>false</code>.
524 * @see FormFields#addValue(String fieldName, String value)
525 */
526 public boolean addValue(final String value) {
527 return setValue(value);
528 }
529
530 abstract void addValuesTo(Collection<String> collection); // should not add null values
531 abstract void addToFormFields(FormFields formFields);
532 abstract void replaceInOutputDocument(OutputDocument outputDocument);
533
534 public String getDebugInfo() {
535 final StringBuilder sb=new StringBuilder();
536 sb.append(formControlType).append(" name=\"").append(name).append('"');
537 if (elementContainer.predefinedValue!=null) sb.append(" PredefinedValue=\"").append(elementContainer.predefinedValue).append('"');
538 sb.append(" - ").append(getElement().getDebugInfo());
539 return sb.toString();
540 }
541
542 static final class InputFormControl extends FormControl {
543 // TEXT, HIDDEN, PASSORD or FILE
544 public InputFormControl(final Element element, final FormControlType formControlType) {
545 super(element,formControlType,false);
546 }
547 public boolean setValue(final String value) {
548 elementContainer.setAttributeValue(Attribute.VALUE,value);
549 return true;
550 }
551 void addValuesTo(final Collection<String> collection) {
552 addValueTo(collection,elementContainer.getAttributeValue(Attribute.VALUE));
553 }
554 void addToFormFields(final FormFields formFields) {
555 formFields.add(this);
556 }
557 void replaceInOutputDocument(final OutputDocument outputDocument) {
558 if (outputStyle==FormControlOutputStyle.REMOVE) {
559 outputDocument.remove(getElement());
560 } else if (outputStyle==FormControlOutputStyle.DISPLAY_VALUE) {
561 String output=null;
562 if (formControlType!=FormControlType.HIDDEN) {
563 String value=elementContainer.getAttributeValue(Attribute.VALUE);
564 if (formControlType==FormControlType.PASSWORD && value!=null) value=getString(FormControlOutputStyle.ConfigDisplayValue.PasswordChar,value.length());
565 output=getDisplayValueHTML(value,false);
566 }
567 outputDocument.replace(getElement(),output);
568 } else {
569 replaceAttributesInOutputDocumentIfModified(outputDocument);
570 }
571 }
572 }
573
574 static final class TextAreaFormControl extends FormControl {
575 // TEXTAREA
576 public String value=UNCHANGED;
577 private static final String UNCHANGED=new String();
578 public TextAreaFormControl(final Element element) {
579 super(element,FormControlType.TEXTAREA,false);
580 }
581 public boolean setValue(final String value) {
582 this.value=value;
583 return true;
584 }
585 void addValuesTo(final Collection<String> collection) {
586 addValueTo(collection,getValue());
587 }
588 void addToFormFields(final FormFields formFields) {
589 formFields.add(this);
590 }
591 void replaceInOutputDocument(final OutputDocument outputDocument) {
592 if (outputStyle==FormControlOutputStyle.REMOVE) {
593 outputDocument.remove(getElement());
594 } else if (outputStyle==FormControlOutputStyle.DISPLAY_VALUE) {
595 outputDocument.replace(getElement(),getDisplayValueHTML(getValue(),true));
596 } else {
597 replaceAttributesInOutputDocumentIfModified(outputDocument);
598 if (value!=UNCHANGED)
599 outputDocument.replace(getElement().getContent(),CharacterReference.encode(value));
600 }
601 }
602 private String getValue() {
603 return (value==UNCHANGED) ? CharacterReference.decode(getElement().getContent()) : value;
604 }
605 }
606
607 static final class RadioCheckboxFormControl extends FormControl {
608 // RADIO or CHECKBOX
609 public RadioCheckboxFormControl(final Element element, final FormControlType formControlType) {
610 super(element,formControlType,true);
611 if (elementContainer.predefinedValue==null) {
612 elementContainer.predefinedValue=CHECKBOX_NULL_DEFAULT_VALUE;
613 if (element.source.logger.isInfoEnabled()) element.source.logger.info(element.source.getRowColumnVector(element.begin).appendTo(new StringBuilder(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());
614 }
615 }
616 public boolean setValue(final String value) {
617 return elementContainer.setSelected(value,Attribute.CHECKED,false);
618 }
619 public boolean addValue(final String value) {
620 return elementContainer.setSelected(value,Attribute.CHECKED,formControlType==FormControlType.CHECKBOX);
621 }
622 void addValuesTo(final Collection<String> collection) {
623 if (isChecked()) addValueTo(collection,getPredefinedValue());
624 }
625 public boolean isChecked() {
626 return elementContainer.getBooleanAttribute(Attribute.CHECKED);
627 }
628 void addToFormFields(final FormFields formFields) {
629 formFields.add(this);
630 }
631 void replaceInOutputDocument(final OutputDocument outputDocument) {
632 if (outputStyle==FormControlOutputStyle.REMOVE) {
633 outputDocument.remove(getElement());
634 } else {
635 if (outputStyle==FormControlOutputStyle.DISPLAY_VALUE) {
636 final String html=isChecked() ? FormControlOutputStyle.ConfigDisplayValue.CheckedHTML : FormControlOutputStyle.ConfigDisplayValue.UncheckedHTML;
637 if (html!=null) {
638 outputDocument.replace(getElement(),html);
639 return;
640 }
641 setDisabled(true);
642 }
643 replaceAttributesInOutputDocumentIfModified(outputDocument);
644 }
645 }
646 }
647
648 static class SubmitFormControl extends FormControl {
649 // BUTTON, SUBMIT or (in subclass) IMAGE
650 public SubmitFormControl(final Element element, final FormControlType formControlType) {
651 super(element,formControlType,true);
652 }
653 public boolean setValue(final String value) {
654 return false;
655 }
656 void addValuesTo(final Collection<String> collection) {}
657 void addToFormFields(final FormFields formFields) {
658 if (getPredefinedValue()!=null) formFields.add(this);
659 }
660 void replaceInOutputDocument(final OutputDocument outputDocument) {
661 if (outputStyle==FormControlOutputStyle.REMOVE) {
662 outputDocument.remove(getElement());
663 } else {
664 if (outputStyle==FormControlOutputStyle.DISPLAY_VALUE) setDisabled(true);
665 replaceAttributesInOutputDocumentIfModified(outputDocument);
666 }
667 }
668 }
669
670 static final class ImageSubmitFormControl extends SubmitFormControl {
671 // IMAGE
672 public ImageSubmitFormControl(final Element element) {
673 super(element,FormControlType.IMAGE);
674 }
675 void addToFormFields(final FormFields formFields) {
676 super.addToFormFields(formFields);
677 formFields.addName(this,name+".x");
678 formFields.addName(this,name+".y");
679 }
680 }
681
682 static final class SelectFormControl extends FormControl {
683 // SELECT_MULTIPLE or SELECT_SINGLE
684 public ElementContainer[] optionElementContainers;
685 public SelectFormControl(final Element element) {
686 super(element,element.getAttributes().get(Attribute.MULTIPLE)!=null ? FormControlType.SELECT_MULTIPLE : FormControlType.SELECT_SINGLE,false);
687 final List<Element> optionElements=element.getAllElements(HTMLElementName.OPTION);
688 optionElementContainers=new ElementContainer[optionElements.size()];
689 int x=0;
690 for (Element optionElement : optionElements) {
691 final ElementContainer optionElementContainer=new ElementContainer(optionElement,true);
692 if (optionElementContainer.predefinedValue==null)
693 // use the content of the element if it has no value attribute
694 optionElementContainer.predefinedValue=CharacterReference.decodeCollapseWhiteSpace(optionElementContainer.element.getContent());
695 optionElementContainers[x++]=optionElementContainer;
696 }
697 }
698 public String getPredefinedValue() {
699 throw new UnsupportedOperationException("Use getPredefinedValues() method instead on SELECT controls");
700 }
701 public Collection<String> getPredefinedValues() {
702 final LinkedHashSet<String> linkedHashSet=new LinkedHashSet<String>(optionElementContainers.length*2,1.0F);
703 for (int i=0; i<optionElementContainers.length; i++)
704 linkedHashSet.add(optionElementContainers[i].predefinedValue);
705 return linkedHashSet;
706 }
707 public Iterator<Element> getOptionElementIterator() {
708 return new OptionElementIterator();
709 }
710 public boolean setValue(final String value) {
711 return addValue(value,false);
712 }
713 public boolean addValue(final String value) {
714 return addValue(value,formControlType==FormControlType.SELECT_MULTIPLE);
715 }
716 private boolean addValue(final String value, final boolean allowMultipleValues) {
717 boolean valueFound=false;
718 for (int i=0; i<optionElementContainers.length; i++) {
719 if (optionElementContainers[i].setSelected(value,Attribute.SELECTED,allowMultipleValues)) valueFound=true;
720 }
721 return valueFound;
722 }
723 void addValuesTo(final Collection<String> collection) {
724 for (int i=0; i<optionElementContainers.length; i++) {
725 if (optionElementContainers[i].getBooleanAttribute(Attribute.SELECTED))
726 addValueTo(collection,optionElementContainers[i].predefinedValue);
727 }
728 }
729 void addToFormFields(final FormFields formFields) {
730 for (int i=0; i<optionElementContainers.length; i++)
731 formFields.add(this,optionElementContainers[i].predefinedValue);
732 }
733 void replaceInOutputDocument(final OutputDocument outputDocument) {
734 if (outputStyle==FormControlOutputStyle.REMOVE) {
735 outputDocument.remove(getElement());
736 } else if (outputStyle==FormControlOutputStyle.DISPLAY_VALUE) {
737 final StringBuilder sb=new StringBuilder(100);
738 for (int i=0; i<optionElementContainers.length; i++) {
739 if (optionElementContainers[i].getBooleanAttribute(Attribute.SELECTED)) {
740 sb.append(getOptionLabel(optionElementContainers[i].element));
741 sb.append(FormControlOutputStyle.ConfigDisplayValue.MultipleValueSeparator);
742 }
743 }
744 if (sb.length()>0) sb.setLength(sb.length()-FormControlOutputStyle.ConfigDisplayValue.MultipleValueSeparator.length()); // remove last separator
745 outputDocument.replace(getElement(),getDisplayValueHTML(sb,false));
746 } else {
747 replaceAttributesInOutputDocumentIfModified(outputDocument);
748 for (int i=0; i<optionElementContainers.length; i++) {
749 optionElementContainers[i].replaceAttributesInOutputDocumentIfModified(outputDocument);
750 }
751 }
752 }
753 private static String getOptionLabel(final Element optionElement) {
754 final String labelAttributeValue=optionElement.getAttributeValue("label");
755 if (labelAttributeValue!=null) return labelAttributeValue;
756 return CharacterReference.decodeCollapseWhiteSpace(optionElement.getContent());
757 }
758 private final class OptionElementIterator implements Iterator<Element> {
759 private int i=0;
760 public boolean hasNext() {
761 return i<optionElementContainers.length;
762 }
763 public Element next() {
764 if (!hasNext()) throw new NoSuchElementException();
765 return optionElementContainers[i++].element;
766 }
767 public void remove() {
768 throw new UnsupportedOperationException();
769 }
770 }
771 }
772
773 final String getDisplayValueHTML(final CharSequence text, final boolean whiteSpaceFormatting) {
774 final StringBuilder sb=new StringBuilder((text==null ? 0 : text.length()*2)+50);
775 sb.append('<').append(FormControlOutputStyle.ConfigDisplayValue.ElementName);
776 try {
777 for (String attributeName : FormControlOutputStyle.ConfigDisplayValue.AttributeNames) {
778 final CharSequence attributeValue=elementContainer.getAttributeValue(attributeName);
779 if (attributeValue==null) continue;
780 Attribute.appendHTML(sb,attributeName,attributeValue);
781 }
782 sb.append('>');
783 if (text==null || text.length()==0)
784 sb.append(FormControlOutputStyle.ConfigDisplayValue.EmptyHTML);
785 else
786 CharacterReference.appendEncode(sb,text,whiteSpaceFormatting);
787 } catch (IOException ex) {throw new RuntimeException(ex);} // never happens
788 sb.append(EndTagType.START_DELIMITER_PREFIX).append(FormControlOutputStyle.ConfigDisplayValue.ElementName).append('>');
789 return sb.toString();
790 }
791
792 final void replaceAttributesInOutputDocumentIfModified(final OutputDocument outputDocument) {
793 elementContainer.replaceAttributesInOutputDocumentIfModified(outputDocument);
794 }
795
796 static List<FormControl> getAll(final Segment segment) {
797 final ArrayList<FormControl> list=new ArrayList<FormControl>();
798 getAll(segment,list,HTMLElementName.INPUT);
799 getAll(segment,list,HTMLElementName.TEXTAREA);
800 getAll(segment,list,HTMLElementName.SELECT);
801 getAll(segment,list,HTMLElementName.BUTTON);
802 Collections.sort(list,COMPARATOR);
803 return list;
804 }
805
806 private static void getAll(final Segment segment, final ArrayList<FormControl> list, final String tagName) {
807 for (Element element : segment.getAllElements(tagName)) {
808 final FormControl formControl=element.getFormControl();
809 if (formControl!=null) list.add(formControl);
810 }
811 }
812
813 private static String getString(final char ch, final int length) {
814 if (length==0) return "";
815 final StringBuilder sb=new StringBuilder(length);
816 for (int i=0; i<length; i++) sb.append(ch);
817 return sb.toString();
818 }
819
820 private void verifyName() {
821 if (formControlType.isSubmit()) return;
822 String missingOrBlank;
823 if (name==null) {
824 missingOrBlank="missing";
825 } else {
826 if (name.length()!=0) return;
827 missingOrBlank="blank";
828 }
829 final Source source=getElement().source;
830 if (source.logger.isInfoEnabled()) source.logger.info(getElement().source.getRowColumnVector(getElement().begin).appendTo(new StringBuilder(200)).append(": compulsory \"name\" attribute of ").append(formControlType).append(" control is ").append(missingOrBlank).toString());
831 }
832
833 private static final void addValueTo(final Collection<String> collection, final String value) {
834 collection.add(value!=null ? value : "");
835 }
836
837 private static final class PositionComparator implements Comparator<FormControl> {
838 public int compare(final FormControl formControl1, final FormControl formControl2) {
839 final int formControl1Begin=formControl1.getElement().getBegin();
840 final int formControl2Begin=formControl2.getElement().getBegin();
841 if (formControl1Begin<formControl2Begin) return -1;
842 if (formControl1Begin>formControl2Begin) return 1;
843 return 0;
844 }
845 }
846
847 //////////////////////////////////////////////////////////////////////////////////////
848
849 static final class ElementContainer {
850 // Contains the information common to both a FormControl and to each OPTION element
851 // within a SELECT FormControl
852 public final Element element;
853 public Map<String,String> attributesMap=null;
854 public String predefinedValue; // never null for option, checkbox or radio elements
855
856 public ElementContainer(final Element element, final boolean loadPredefinedValue) {
857 this.element=element;
858 predefinedValue=loadPredefinedValue ? element.getAttributes().getValue(Attribute.VALUE) : null;
859 }
860
861 public Map<String,String> getAttributesMap() {
862 if (attributesMap==null) attributesMap=element.getAttributes().getMap(true);
863 return attributesMap;
864 }
865
866 public boolean setSelected(final String value, final String selectedOrChecked, final boolean allowMultipleValues) {
867 if (value!=null && predefinedValue.equals(value.toString())) {
868 setBooleanAttribute(selectedOrChecked,true);
869 return true;
870 }
871 if (!allowMultipleValues) setBooleanAttribute(selectedOrChecked,false);
872 return false;
873 }
874
875 public String getAttributeValue(final String attributeName) {
876 if (attributesMap!=null)
877 return attributesMap.get(attributeName);
878 else
879 return element.getAttributes().getValue(attributeName);
880 }
881
882 public void setAttributeValue(final String attributeName, final String value) {
883 // null value indicates attribute should be removed.
884 if (value==null) {
885 setBooleanAttribute(attributeName,false);
886 return;
887 }
888 if (attributesMap!=null) {
889 attributesMap.put(attributeName,value);
890 return;
891 }
892 final String existingValue=getAttributeValue(attributeName);
893 if (existingValue!=null && existingValue.equals(value)) return;
894 getAttributesMap().put(attributeName,value);
895 }
896
897 public boolean getBooleanAttribute(final String attributeName) {
898 if (attributesMap!=null)
899 return attributesMap.containsKey(attributeName);
900 else
901 return element.getAttributes().get(attributeName)!=null;
902 }
903
904 public void setBooleanAttribute(final String attributeName, final boolean value) {
905 final boolean oldValue=getBooleanAttribute(attributeName);
906 if (value==oldValue) return;
907 if (value)
908 getAttributesMap().put(attributeName,attributeName); // xhtml compatible attribute
909 else
910 getAttributesMap().remove(attributeName);
911 }
912
913 public void replaceAttributesInOutputDocumentIfModified(final OutputDocument outputDocument) {
914 if (attributesMap!=null) outputDocument.replace(element.getAttributes(),attributesMap);
915 }
916 }
917 }

   
Visit the aagtl Website