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