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 |
|
25 |
/**
|
26 |
* Represents a collection of {@link FormField} objects.
|
27 |
* <p>
|
28 |
* This class provides the main interface for the analysis and manipulation of {@linkplain FormControl form controls}.
|
29 |
* A <code>FormFields</code> object is a collection of {@link FormField} objects, with each form field consisting of
|
30 |
* a group of {@linkplain FormControl form controls} having the same {@linkplain FormControl#getName() name}.
|
31 |
* <p>
|
32 |
* The functionality provided by this class can be used to accomplish two main tasks:
|
33 |
* <ol>
|
34 |
* <li style="margin-bottom: 1.5em">
|
35 |
* Modify the <a href="FormControl.html#SubmissionValue">submission values</a> of the constituent form controls
|
36 |
* for subsequent output in an {@link OutputDocument}.
|
37 |
* <p>
|
38 |
* The methods available for this purpose are:<br />
|
39 |
* {@link #getValues(String) List<String> getValues(String fieldName)}<br />
|
40 |
* {@link #getDataSet() Map<String,String[]> getDataSet()}<br />
|
41 |
* {@link #clearValues() void clearValues()}<br />
|
42 |
* {@link #setDataSet(Map) void setDataSet(Map<String,String[]>)}<br />
|
43 |
* {@link #setValue(String,String) boolean setValue(String fieldName, String value)}<br />
|
44 |
* {@link #addValue(String,String) boolean addValue(String fieldName, String value)}<br />
|
45 |
* <p>
|
46 |
* Although the {@link FormField} and {@link FormControl} classes provide methods for directly modifying
|
47 |
* the submission values of individual form fields and controls, it is generally recommended to use the interface provided by this
|
48 |
* (the <code>FormFields</code>) class unless there is a specific requirement for the lower level functionality.
|
49 |
* <p>
|
50 |
* The <a href="FormControl.html#DisplayCharacteristics">display characteristics</a> of individual controls,
|
51 |
* such as whether the control is {@linkplain FormControl#setDisabled(boolean) disabled}, replaced with a simple
|
52 |
* {@linkplain FormControlOutputStyle#DISPLAY_VALUE value}, or {@linkplain FormControlOutputStyle#REMOVE removed} altogether,
|
53 |
* can only be set on the individual {@link FormControl} objects.
|
54 |
* See below for information about retrieving a specific <code>FormControl</code> object from the <code>FormFields</code> object.
|
55 |
* <li>
|
56 |
* Convert data from a <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#form-data-set">form data set</a>
|
57 |
* (represented as a <a href="#FieldDataSet">field data set</a>) into a simple array format,
|
58 |
* suitable for storage in a tabular format such as a database table or <code>.CSV</code> file.
|
59 |
* <p>
|
60 |
* The methods available for this purpose are:<br />
|
61 |
* {@link #getColumnLabels() String[] getColumnLabels()}<br />
|
62 |
* {@link #getColumnValues(Map) String[] getColumnValues(Map)}<br />
|
63 |
* {@link #getColumnValues() String[] getColumnValues()}<br />
|
64 |
* <p>
|
65 |
* The {@link Util} class contains a method called {@link Util#outputCSVLine(Writer,String[]) outputCSVLine(Writer,String[])}
|
66 |
* which writes the <code>String[]</code> output of these methods to the specified <code>Writer</code> in <code>.CSV</code> format.
|
67 |
* <p>
|
68 |
* The implementation of these methods makes use of certain <a href="FormField.html#DataStructureProperties">properties</a>
|
69 |
* in the {@link FormField} class that describe the structure of the data in each field.
|
70 |
* These properties can be utilised directly in the event that a
|
71 |
* <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#form-data-set">form data set</a> is to be converted
|
72 |
* from its <a href="FormFields.html#FieldDataSet">normal format</a> into some other type of data structure.
|
73 |
* </ol>
|
74 |
* <p>
|
75 |
* To access a specific {@link FormControl} from a <code>FormFields</code> object, use:
|
76 |
* <ul style="margin-top: 0px">
|
77 |
* <li><code>formFields.</code>{@link #get(String) get(fieldName)}<code>.</code>{@link FormField#getFormControl() getFormControl()}
|
78 |
* if the control is the only one with the specified {@linkplain FormControl#getName() name}, or
|
79 |
* <li><code>formFields.</code>{@link #get(String) get(fieldName)}<code>.</code>{@link FormField#getFormControl(String) getFormControl(predefinedValue)}
|
80 |
* to retrieve the control having the speficied {@linkplain FormControl#getPredefinedValue() predefined value}
|
81 |
* if it is part of a {@linkplain FormField field} containing multiple controls.
|
82 |
* </ul>
|
83 |
* <p>
|
84 |
* The term <i><a name="FieldDataSet">field data set</a></i> is used in this library to refer to a data structure consisting of
|
85 |
* a set of names (in lower case), each mapped to one or more values.
|
86 |
* Generally, this is represented by a data type of <code>java.util.Map<String,String[]></code>,
|
87 |
* with the keys (names) being of type <code>String</code> and the values represented by an array containing one or more items of type <code>String</code>.
|
88 |
* A field data set can be used to represent the data in an HTML
|
89 |
* <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#form-data-set">form data set</a>.
|
90 |
* <p>
|
91 |
* <code>FormFields</code> instances are obtained using the {@link #FormFields(Collection formControls)} constructor
|
92 |
* or by calling the {@link Segment#getFormFields()} method.
|
93 |
* <p>
|
94 |
* The case sensitivity of form field names is determined by the static
|
95 |
* {@link Config#CurrentCompatibilityMode}<code>.</code>{@link Config.CompatibilityMode#isFormFieldNameCaseInsensitive() FormFieldNameCaseInsensitive} property.
|
96 |
* <p>
|
97 |
* <b>Examples:</b>
|
98 |
* <ol>
|
99 |
* <li>
|
100 |
* Write the data received from in the current <code>ServletRequest</code> to a <code>.CSV</code> file,
|
101 |
* and then display the form populated with this data:
|
102 |
* <p><pre>
|
103 |
* Source source=new Source(htmlTextOfOriginatingForm);
|
104 |
* FormFields formFields=source.getFormFields();
|
105 |
*
|
106 |
* File csvOutputFile=new File("FormData.csv");
|
107 |
* boolean outputHeadings=!csvOutputFile.exists();
|
108 |
* Writer writer=new FileWriter(csvOutputFile,true);
|
109 |
* if (outputHeadings) Util.outputCSVLine(writer,formFields.getColumnLabels());
|
110 |
* Util.outputCSVLine(writer,formFields.getColumnValues(servletRequest.getParameterMap()));
|
111 |
* writer.close();
|
112 |
*
|
113 |
* formFields.setDataSet(servletRequest.getParameterMap());
|
114 |
* OutputDocument outputDocument=new OutputDocument(source);
|
115 |
* outputDocument.replace(formFields);
|
116 |
* outputDocument.writeTo(servletResponse.getWriter());</pre>
|
117 |
* <p>See also the sample program FormFieldCSVOutput.<br /><br />
|
118 |
* <li>Replace the initial values of controls in the form named "MyForm" with new values:
|
119 |
* <p><pre>
|
120 |
* Source source=new Source(htmlText);
|
121 |
* Element myForm=null;
|
122 |
* List formElements=source.getAllElements(Tag.FORM);
|
123 |
* for (Iterator i=formElements.iterator(); i.hasNext();) {
|
124 |
* Element formElement=(Element)i.next();
|
125 |
* String formName=formElement.getAttributes().getValue("name");
|
126 |
* if ("MyForm".equals(formName)) {
|
127 |
* myForm=form;
|
128 |
* break;
|
129 |
* }
|
130 |
* }
|
131 |
* FormFields formFields=myForm.getFormFields();
|
132 |
* formFields.clearValues(); // clear any values that might be set in the source document
|
133 |
* formFields.addValue("Name","Humphrey Bear");
|
134 |
* formFields.addValue("MailingList","A");
|
135 |
* formFields.addValue("MailingList","B");
|
136 |
* formFields.addValue("FavouriteFare","honey");
|
137 |
* OutputDocument outputDocument=new OutputDocument(source);
|
138 |
* outputDocument.replace(formFields);
|
139 |
* String newHtmlText=outputDocument.toString();</pre>
|
140 |
* <p>See also the sample program FormFieldSetValues.<br /><br />
|
141 |
* <li>Change the display characteristics of individual controls:
|
142 |
* <p><pre>
|
143 |
* Source source=new Source(htmlText);
|
144 |
* FormFields formFields=source.getFormFields();
|
145 |
* // disable some controls:
|
146 |
* formFields.get("Password").getFormControl().setDisabled(true);
|
147 |
* FormField mailingListFormField=formFields.get("MailingList");
|
148 |
* mailingListFormField.setValue("C");
|
149 |
* mailingListFormField.getFormControl("C").setDisabled(true);
|
150 |
* mailingListFormField.getFormControl("D").setDisabled(true);
|
151 |
* // remove some controls:
|
152 |
* formFields.get("button1").getFormControl().setOutputStyle(FormControlOutputStyle.REMOVE);
|
153 |
* FormControl rhubarbFormControl=formFields.get("FavouriteFare").getFormControl("rhubarb");
|
154 |
* rhubarbFormControl.setOutputStyle(FormControlOutputStyle.REMOVE);
|
155 |
* // set some controls to display value:
|
156 |
* formFields.setValue("Address","The Lodge\nDeakin ACT 2600\nAustralia");
|
157 |
* formFields.get("Address").getFormControl().setOutputStyle(FormControlOutputStyle.DISPLAY_VALUE);
|
158 |
* FormField favouriteSportsFormField=formFields.get("FavouriteSports");
|
159 |
* favouriteSportsFormField.setValue("BB");
|
160 |
* favouriteSportsFormField.addValue("AFL");
|
161 |
* favouriteSportsFormField.getFormControl().setOutputStyle(FormControlOutputStyle.DISPLAY_VALUE);
|
162 |
* OutputDocument outputDocument=new OutputDocument(source);
|
163 |
* outputDocument.replace(formFields); // adds all segments necessary to effect changes
|
164 |
* String newHtmlText=outputDocument.toString();</pre>
|
165 |
* <p>See also the sample program FormControlDisplayCharacteristics.<br /><br />
|
166 |
* </ol>
|
167 |
* @see FormField
|
168 |
* @see FormControl
|
169 |
*/
|
170 |
public final class FormFields extends AbstractCollection<FormField> {
|
171 |
private final LinkedHashMap<String,FormField> map=new LinkedHashMap<String,FormField>();
|
172 |
private final ArrayList<FormControl> formControls=new ArrayList<FormControl>();
|
173 |
|
174 |
/**
|
175 |
* Constructs a new <code>FormFields</code> object consisting of the specified {@linkplain FormControl form controls}.
|
176 |
* @param formControls a collection of {@link FormControl} objects.
|
177 |
* @see Segment#getFormFields()
|
178 |
*/
|
179 |
public FormFields(final Collection<FormControl> formControls) {
|
180 |
// Passing "this" as a parameter inside a constructor used to cause some strange problems back in java 1.0,
|
181 |
// but it seems to work here and there is no explicit mention in the Java language spec about any potential problems.
|
182 |
// The alternative is an ugly static FormFields constructFrom(List formControls) method.
|
183 |
for (FormControl formControl : formControls) {
|
184 |
if (formControl.getName()!=null && formControl.getName().length()!=0) {
|
185 |
formControl.addToFormFields(this);
|
186 |
this.formControls.add(formControl);
|
187 |
}
|
188 |
}
|
189 |
}
|
190 |
|
191 |
/**
|
192 |
* Returns the number of <code>FormField</code> objects.
|
193 |
* @return the number of <code>FormField</code> objects.
|
194 |
*/
|
195 |
public int getCount() {
|
196 |
return map.size();
|
197 |
}
|
198 |
|
199 |
/**
|
200 |
* Returns the number of <code>FormField</code> objects.
|
201 |
* <p>
|
202 |
* This is equivalent to {@link #getCount()},
|
203 |
* and is necessary to for the implementation of the <code>java.util.Collection</code> interface.
|
204 |
*
|
205 |
* @return the number of <code>FormField</code> objects.
|
206 |
*/
|
207 |
public int size() {
|
208 |
return getCount();
|
209 |
}
|
210 |
|
211 |
/**
|
212 |
* Returns the <code>FormField</code> with the specified {@linkplain FormField#getName() name}.
|
213 |
* <p>
|
214 |
* The case sensitivity of the <code>fieldName</code> argument is determined by the static
|
215 |
* {@link Config#CurrentCompatibilityMode}<code>.</code>{@link Config.CompatibilityMode#isFormFieldNameCaseInsensitive() FormFieldNameCaseInsensitive} property.
|
216 |
*
|
217 |
* @param fieldName the name of the <code>FormField</code> to get.
|
218 |
* @return the <code>FormField</code> with the specified {@linkplain FormField#getName() name}, or <code>null</code> if no <code>FormField</code> with the specified name exists.
|
219 |
*/
|
220 |
public FormField get(String fieldName) {
|
221 |
if (Config.CurrentCompatibilityMode.isFormFieldNameCaseInsensitive()) fieldName=fieldName.toLowerCase();
|
222 |
return map.get(fieldName);
|
223 |
}
|
224 |
|
225 |
/**
|
226 |
* Returns an iterator over the {@link FormField} objects in the collection.
|
227 |
* <p>
|
228 |
* The order in which the form fields are iterated corresponds to the order of appearance
|
229 |
* of each form field's first {@link FormControl} in the source document.
|
230 |
* <p>
|
231 |
* If this <code>FormFields</code> object has been {@linkplain #merge(FormFields) merged} with another,
|
232 |
* the ordering is no longer defined.
|
233 |
*
|
234 |
* @return an iterator over the {@link FormField} objects in the collection.
|
235 |
*/
|
236 |
public Iterator<FormField> iterator() {
|
237 |
return map.values().iterator();
|
238 |
}
|
239 |
|
240 |
/**
|
241 |
* Returns a list of the <a href="FormField.html#FieldSubmissionValues">field submission values</a> of all the specified constituent {@linkplain FormField form fields} with the specified {@linkplain FormField#getName() name}.
|
242 |
* <p>
|
243 |
* All objects in the returned list are of type <code>String</code>, with no <code>null</code> entries.
|
244 |
* <p>
|
245 |
* This is equivalent to {@link #get(String) get(fieldName)}<code>.</code>{@link FormField#getValues() getValues()},
|
246 |
* assuming that a field with the specified name exists in this collection.
|
247 |
*
|
248 |
* @param fieldName the {@linkplain FormField#getName() name} of the form field.
|
249 |
* @return a list of the <a href="FormField.html#FieldSubmissionValues">field submission values</a> of all the specified constituent {@linkplain FormField form field} with the specified {@linkplain FormField#getName() name}, or <code>null</code> if no form field with this name exists.
|
250 |
* @see FormField#getValues()
|
251 |
*/
|
252 |
public List<String> getValues(final String fieldName) {
|
253 |
final FormField formField=get(fieldName);
|
254 |
return formField==null ? null : formField.getValues();
|
255 |
}
|
256 |
|
257 |
/**
|
258 |
* Returns the entire <a href="#FieldDataSet">field data set</a> represented by the {@linkplain FormField#getValues() values} of the constituent form fields.
|
259 |
* <p>
|
260 |
* The values in the map returned by this method are represented as a string array, giving the map a format consistent with the
|
261 |
* <code><a target="_blank" href="http://java.sun.com/products/servlet/2.3/javadoc/javax/servlet/ServletRequest.html#getParameterMap()">javax.servlet.ServletRequest.getParameterMap()</a></code>
|
262 |
* method.
|
263 |
* <p>
|
264 |
* Only the {@linkplain FormField#getName() names} of form fields with at least one {@linkplain FormField#getValues() value}
|
265 |
* are included in the map, meaning every <code>String[]</code> is guaranteed to have at least one entry.
|
266 |
* <p>
|
267 |
* Iterating over the map keys returns them in the order of appearance in the source document.
|
268 |
*
|
269 |
* @return the entire <a href="#FieldDataSet">field data set</a> represented by the {@linkplain FormField#getValues() values} of the constituent form fields.
|
270 |
* @see #setDataSet(Map)
|
271 |
*/
|
272 |
public Map<String,String[]> getDataSet() {
|
273 |
final LinkedHashMap<String,String[]> map=new LinkedHashMap<String,String[]>((int)(getCount()/0.7));
|
274 |
for (FormField formField : this) {
|
275 |
final List<String> values=formField.getValues();
|
276 |
if (values.isEmpty()) continue;
|
277 |
map.put(formField.getName(),values.toArray(new String[values.size()]));
|
278 |
}
|
279 |
return map;
|
280 |
}
|
281 |
|
282 |
/**
|
283 |
* Clears the <a href="FormControl.html#SubmissionValue">submission values</a> of all the constituent {@linkplain #getFormControls() form controls}.
|
284 |
* @see FormControl#clearValues()
|
285 |
*/
|
286 |
public void clearValues() {
|
287 |
for (FormControl formControl : formControls) formControl.clearValues();
|
288 |
}
|
289 |
|
290 |
/**
|
291 |
* Sets the <a href="FormControl.html#SubmissionValue">submission values</a> of all the constituent
|
292 |
* {@linkplain FormControl form controls} to match the data in the specified <a href="#FieldDataSet">field data set</a>.
|
293 |
* <p>
|
294 |
* The map keys must be <code>String</code> {@linkplain FormField#getName() field names},
|
295 |
* with each map value an array of <code>String</code> objects containing the field's new {@linkplain FormField#setValues(Collection) values}.
|
296 |
* <p>
|
297 |
* The map returned by the
|
298 |
* <code><a target="_blank" href="http://java.sun.com/products/servlet/2.3/javadoc/javax/servlet/ServletRequest.html#getParameterMap()">javax.servlet.ServletRequest.getParameterMap()</a></code>
|
299 |
* method has a suitable format for use with this method.
|
300 |
* <p>
|
301 |
* All existing values are {@linkplain #clearValues() cleared} before the values from the field data set are added.
|
302 |
* <p>
|
303 |
* Any map entries with a <code>null</code> value are ignored.
|
304 |
*
|
305 |
* @param dataSet the <a href="#FieldDataSet">field data set</a> containing the new {@linkplain FormField#setValues(Collection) values} of the constituent form fields.
|
306 |
* @see #getDataSet()
|
307 |
*/
|
308 |
public void setDataSet(final Map<String,String[]> dataSet) {
|
309 |
clearValues();
|
310 |
if (map==null) return;
|
311 |
for (Map.Entry<String,String[]> entry : dataSet.entrySet()) {
|
312 |
final String fieldName=entry.getKey();
|
313 |
final FormField formField=get(fieldName);
|
314 |
if (formField!=null) formField.addValues(entry.getValue());
|
315 |
}
|
316 |
}
|
317 |
|
318 |
/**
|
319 |
* Sets the <a href="FormField.html#FieldSubmissionValues">field submission values</a> of the constituent
|
320 |
* {@linkplain FormField form field} with the specified {@linkplain FormField#getName() name} to the single specified value.
|
321 |
* <p>
|
322 |
* This is equivalent to {@link #get(String) get(fieldName)}<code>.</code>{@link FormField#setValue(String) setValue(value)},
|
323 |
* assuming that a field with the specified name exists in this collection.
|
324 |
* <p>
|
325 |
* The return value indicates whether the specified form field "accepted" the value.
|
326 |
* A return value of <code>false</code> implies an error condition as either no field with the specified name exists, or
|
327 |
* the specified value is not compatible with the specified field.
|
328 |
*
|
329 |
* @param fieldName the {@linkplain FormField#getName() name} of the form field.
|
330 |
* @param value the new <a href="FormField.html#FieldSubmissionValues">field submission value</a> of the specified field, or <code>null</code> to {@linkplain FormField#clearValues() clear} the field of all submission values.
|
331 |
* @return <code>true</code> if a field of the specified name exists in this collection and it accepts the specified value, otherwise <code>false</code>.
|
332 |
*/
|
333 |
public boolean setValue(final String fieldName, final String value) {
|
334 |
final FormField formField=get(fieldName);
|
335 |
return formField==null ? false : formField.setValue(value);
|
336 |
}
|
337 |
|
338 |
/**
|
339 |
* Adds the specified value to the <a href="FormField.html#FieldSubmissionValues">field submission values</a> of the constituent
|
340 |
* {@linkplain FormField form field} with the specified {@linkplain FormField#getName() name}.
|
341 |
* <p>
|
342 |
* This is equivalent to {@link #get(String) get(fieldName)}<code>.</code>{@link FormField#addValue(String) addValue(value)},
|
343 |
* assuming that a field with the specified name exists in this collection.
|
344 |
* <p>
|
345 |
* The return value indicates whether the specified form field "accepted" the value.
|
346 |
* A return value of <code>false</code> implies an error condition as either no field with the specified name exists, or
|
347 |
* the specified value is not compatible with the specified field.
|
348 |
*
|
349 |
* @param fieldName the {@linkplain FormField#getName() name} of the form field.
|
350 |
* @param value the new <a href="FormField.html#FieldSubmissionValues">field submission value</a> to add to the specified field, must not be <code>null</code>.
|
351 |
* @return <code>true</code> if a field of the specified name exists in this collection and it accepts the specified value, otherwise <code>false</code>.
|
352 |
*/
|
353 |
public boolean addValue(final String fieldName, final String value) {
|
354 |
final FormField formField=get(fieldName);
|
355 |
return formField==null ? false : formField.addValue(value);
|
356 |
}
|
357 |
|
358 |
/**
|
359 |
* Returns a string array containing the column labels corresponding to the values from the {@link #getColumnValues(Map)} method.
|
360 |
* <p>
|
361 |
* Instead of using the {@linkplain FormField#getName() name} of each constituent form field to construct the labels,
|
362 |
* the {@linkplain FormControl#getName() name} of the first {@linkplain FormControl form control} from each form field is used.
|
363 |
* This allows the labels to be constructed using the names with the original case from the source document rather than
|
364 |
* unsing the all lower case names of the form fields.
|
365 |
* <p>
|
366 |
* See the documentation of the {@link #getColumnValues(Map)} method for more details.
|
367 |
*
|
368 |
* @return a string array containing the column labels corresponding to the values from the {@link #getColumnValues(Map)} method.
|
369 |
* @see Util#outputCSVLine(Writer,String[])
|
370 |
*/
|
371 |
public String[] getColumnLabels() {
|
372 |
initColumns();
|
373 |
final String[] columnLabels=new String[columns.length];
|
374 |
for (int i=0 ; i<columns.length; i++) {
|
375 |
final Column column=columns[i];
|
376 |
final String fieldName=column.formField.getFirstFormControl().getName(); // use this instead of formControl.getName() so that the original case is used even if Config.CurrentCompatibilityMode.isFormFieldNameCaseInsensitive() is true.
|
377 |
columnLabels[i]=column.predefinedValue!=null
|
378 |
? fieldName+'.'+column.predefinedValue
|
379 |
: fieldName;
|
380 |
}
|
381 |
return columnLabels;
|
382 |
}
|
383 |
|
384 |
/**
|
385 |
* Converts the data values in the specified <a href="#FieldDataSet">field data set</a> into a simple string array,
|
386 |
* suitable for storage in a tabular format such as a database table or <code>.CSV</code> file.
|
387 |
* <p>
|
388 |
* The conversion is performed in a way that allows the multiple values of certain fields to be stored in separate columns,
|
389 |
* by analysing the possible <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#form-data-set">form data sets</a>
|
390 |
* that can be generated from the constituent {@linkplain #getFormControls() form controls}.
|
391 |
* <p>
|
392 |
* The column labels and values are determined as follows:
|
393 |
* <p>
|
394 |
* <ul class="HalfSeparated">
|
395 |
* <li>
|
396 |
* For each {@linkplain FormField form field} in this collection (taken in {@linkplain #iterator() iterator} order):
|
397 |
* <ul>
|
398 |
* <li>
|
399 |
* If the form field has no {@linkplain FormField#getPredefinedValues() predefined values},
|
400 |
* such as a single {@linkplain FormControlType#TEXT text control}, then:
|
401 |
* <ul>
|
402 |
* <li>
|
403 |
* Add a single column:
|
404 |
* <table class="CompactDL">
|
405 |
* <tr><td>{@linkplain #getColumnLabels() Label}:<td>the {@linkplain FormField#getName() name} of the form field in original case
|
406 |
* <tr><td>Value:<td>the single value mapped to this field in the specified <a href="#FieldDataSet">field data set</a>.
|
407 |
* </table>
|
408 |
* In the unlikely event that this field contains more than one value, all values are included in this one column and
|
409 |
* separated by the text defined in the static {@link Config#ColumnMultipleValueSeparator} property.
|
410 |
* </ul>
|
411 |
* <li>
|
412 |
* Otherwise, if the form field does have {@linkplain FormField#getPredefinedValues() predefined values},
|
413 |
* but does not {@linkplain FormField#allowsMultipleValues() allow multiple values}, then:
|
414 |
* <ul>
|
415 |
* <li>
|
416 |
* If the form field has only one {@linkplain FormField#getPredefinedValues() predefined value},
|
417 |
* such as a single {@linkplain FormControlType#CHECKBOX checkbox}, then:
|
418 |
* <ul>
|
419 |
* <li>
|
420 |
* Add a single boolean column:
|
421 |
* <table class="CompactDL">
|
422 |
* <tr><td>{@linkplain #getColumnLabels() Label}:<td>the {@linkplain FormField#getName() name} of the form field in original case
|
423 |
* <tr><td>Value:<td>the currently configured string representation for <i>{@linkplain Config#ColumnValueTrue true}</i>
|
424 |
* if a value mapped to this field in the specified <a href="#FieldDataSet">field data set</a> matches the
|
425 |
* {@linkplain FormField#getPredefinedValues() predefined value}, otherwise <i>{@linkplain Config#ColumnValueFalse false}</i>
|
426 |
* </table>
|
427 |
* </ul>
|
428 |
* <li>
|
429 |
* Otherwise, if the form field has more than one {@linkplain FormField#getPredefinedValues() predefined value},
|
430 |
* such as a set of {@linkplain FormControlType#RADIO radio buttons}, then:
|
431 |
* <ul>
|
432 |
* <li>
|
433 |
* Add a single column:
|
434 |
* <table class="CompactDL">
|
435 |
* <tr><td>{@linkplain #getColumnLabels() Label}:<td>the {@linkplain FormField#getName() name} of the form field in original case
|
436 |
* <tr><td>Value:<td>the single value mapped to this field in the specified <a href="#FieldDataSet">field data set</a>,
|
437 |
* which in the case of a set of radio buttons should be the {@linkplain FormControl#getPredefinedValue() predefined value}
|
438 |
* of the {@linkplain FormControl#isChecked() checked} radio button.
|
439 |
* </table>
|
440 |
* </ul>
|
441 |
* </ul>
|
442 |
* <li>
|
443 |
* Otherwise, if the form field has {@linkplain FormField#getPredefinedValues() predefined values}
|
444 |
* and {@linkplain FormField#allowsMultipleValues() allows multiple values},
|
445 |
* such as a set of {@linkplain FormControlType#CHECKBOX checkboxes}, then:
|
446 |
* <ul>
|
447 |
* <li>
|
448 |
* For each {@linkplain FormField#getPredefinedValues() predefined value} in the form field:
|
449 |
* <ul>
|
450 |
* <li>
|
451 |
* Add a boolean column:
|
452 |
* <table class="CompactDL">
|
453 |
* <tr><td>{@linkplain #getColumnLabels() Label}:<td>"<code><i>FieldName</i>.<i>PredefinedValue</i></code>",
|
454 |
* where <code><i>FieldName</i></code> is the {@linkplain FormField#getName() name} of the form field in original case,
|
455 |
* and <code><i>PredefinedValue</i></code> is the {@linkplain FormField#getPredefinedValues() predefined value}.
|
456 |
* <tr><td>Value:<td>the currently configured string representation for <i>{@linkplain Config#ColumnValueTrue true}</i>
|
457 |
* if a value mapped to this field in the specified <a href="#FieldDataSet">field data set</a> matches the
|
458 |
* {@linkplain FormField#getPredefinedValues() predefined value}, otherwise <i>{@linkplain Config#ColumnValueFalse false}</i>
|
459 |
* </table>
|
460 |
* </ul>
|
461 |
* <li>
|
462 |
* In addition, if the form field can also contain user values ({@link FormField#getUserValueCount()}<code>>0</code>), then:
|
463 |
* <ul>
|
464 |
* <li>
|
465 |
* Add another column:
|
466 |
* <table class="CompactDL">
|
467 |
* <tr><td>{@linkplain #getColumnLabels() Label}:<td>the {@linkplain FormField#getName() name} of the form field in original case
|
468 |
* <tr><td>Value:<td>all values mapped to this field in the specified <a href="#FieldDataSet">field data set</a>
|
469 |
* that do not match any of the {@linkplain FormField#getPredefinedValues() predefined values},
|
470 |
* separated by the text defined in the static {@link Config#ColumnMultipleValueSeparator} property.
|
471 |
* </table>
|
472 |
* </ul>
|
473 |
* </ul>
|
474 |
* </ul>
|
475 |
* </ul>
|
476 |
* <p>
|
477 |
* The sample program FormFieldCSVOutput demonstrates the use of this method and its output.
|
478 |
*
|
479 |
* @param dataSet a <a href="#FieldDataSet">field data set</a> containing the data to convert.
|
480 |
* @return the data values in the specified <a href="#FieldDataSet">field data set</a> in the form of a simple string array.
|
481 |
* @see Util#outputCSVLine(Writer,String[])
|
482 |
* @see #getColumnLabels()
|
483 |
* @see #getColumnValues()
|
484 |
*/
|
485 |
public String[] getColumnValues(final Map<String,String[]> dataSet) {
|
486 |
initColumns();
|
487 |
final String[] columnValues=new String[columns.length];
|
488 |
if (Config.ColumnValueFalse!=null) {
|
489 |
// initialise all boolean columns with false string
|
490 |
for (int i=0; i<columns.length; i++)
|
491 |
if (columns[i].isBoolean) columnValues[i]=Config.ColumnValueFalse;
|
492 |
}
|
493 |
for (Map.Entry<String,String[]> entry : dataSet.entrySet()) {
|
494 |
final String fieldName=entry.getKey();
|
495 |
final FormField formField=get(fieldName);
|
496 |
if (formField!=null) {
|
497 |
final int columnIndex=formField.columnIndex;
|
498 |
for (String value : entry.getValue()) {
|
499 |
for (int ci=columnIndex; ci<columns.length; ci++) {
|
500 |
final Column column=columns[ci];
|
501 |
if (column.formField!=formField) break;
|
502 |
if (column.predefinedValue!=null) {
|
503 |
if (!column.predefinedValue.equals(value)) continue;
|
504 |
columnValues[ci]=Config.ColumnValueTrue;
|
505 |
} else {
|
506 |
if (column.isBoolean) {
|
507 |
if (value!=null) columnValues[ci]=Config.ColumnValueTrue;
|
508 |
} else if (columnValues[ci]==null) {
|
509 |
columnValues[ci]=value;
|
510 |
} else {
|
511 |
columnValues[ci]=columnValues[ci]+Config.ColumnMultipleValueSeparator+value;
|
512 |
}
|
513 |
}
|
514 |
break;
|
515 |
}
|
516 |
}
|
517 |
}
|
518 |
}
|
519 |
return columnValues;
|
520 |
}
|
521 |
|
522 |
/**
|
523 |
* Converts all the {@linkplain FormField#getValues() form submission values} of the constituent form fields into a simple string array,
|
524 |
* suitable for storage in a tabular format such as a database table or <code>.CSV</code> file.
|
525 |
* <p>
|
526 |
* This is equivalent to {@link #getColumnValues(Map) getColumnValues}<code>(</code>{@link #getDataSet()}<code>)</code>.
|
527 |
*
|
528 |
* @return all the {@linkplain FormField#getValues() form submission values} of the constituent form fields in the form of a simple string array.
|
529 |
*/
|
530 |
public String[] getColumnValues() {
|
531 |
return getColumnValues(getDataSet());
|
532 |
}
|
533 |
|
534 |
private void initColumns() {
|
535 |
if (columns!=null) return;
|
536 |
final ArrayList<Column> columnList=new ArrayList<Column>();
|
537 |
for (FormField formField : this) {
|
538 |
formField.columnIndex=columnList.size();
|
539 |
if (!formField.allowsMultipleValues() || formField.getPredefinedValues().isEmpty()) {
|
540 |
columnList.add(new Column(formField,formField.getPredefinedValues().size()==1,null));
|
541 |
} else {
|
542 |
// add a column for every predefined value
|
543 |
for (String predefinedValue : formField.getPredefinedValues())
|
544 |
columnList.add(new Column(formField,true,predefinedValue));
|
545 |
if (formField.getUserValueCount()>0) columnList.add(new Column(formField,false,null)); // add a column for user values, must come after predefined values for algorithm in getColumnValues to work
|
546 |
}
|
547 |
}
|
548 |
columns=columnList.toArray(new Column[columnList.size()]);
|
549 |
}
|
550 |
private Column[] columns=null;
|
551 |
|
552 |
private static class Column {
|
553 |
public FormField formField;
|
554 |
public boolean isBoolean;
|
555 |
public String predefinedValue;
|
556 |
public Column(final FormField formField, final boolean isBoolean, final String predefinedValue) {
|
557 |
this.formField=formField;
|
558 |
this.isBoolean=isBoolean;
|
559 |
this.predefinedValue=predefinedValue;
|
560 |
}
|
561 |
}
|
562 |
|
563 |
/**
|
564 |
* Returns a list of all the {@linkplain FormField#getFormControls() constituent form controls} from all the {@linkplain FormField form fields} in this collection.
|
565 |
* @return a list of all the {@linkplain FormField#getFormControls() constituent form controls} from all the {@linkplain FormField form fields} in this collection.
|
566 |
*/
|
567 |
public List getFormControls() {
|
568 |
return formControls;
|
569 |
}
|
570 |
|
571 |
/**
|
572 |
* Merges the specified <code>FormFields</code> into this <code>FormFields</code> collection.
|
573 |
* This is useful if a full collection of possible form fields is required from multiple {@linkplain Source source} documents.
|
574 |
* <p>
|
575 |
* If both collections contain a <code>FormField</code> with the same {@linkplain FormField#getName() name},
|
576 |
* the resulting <code>FormField</code> has the following properties:
|
577 |
* <ul>
|
578 |
* <li>{@link FormField#getUserValueCount() getUserValueCount()} : the maximum user value count from both form fields</li>
|
579 |
* <li>{@link FormField#allowsMultipleValues() allowsMultipleValues()} : <code>true</code> if either form field allows multiple values</li>
|
580 |
* <li>{@link FormField#getPredefinedValues() getPredefinedValues()} : the union of predefined values in both form fields</li>
|
581 |
* <li>{@link FormField#getFormControls() getFormControls()} : the union of {@linkplain FormControl form controls} from both form fields</li>
|
582 |
* </ul>
|
583 |
* <p>
|
584 |
* NOTE: Some underlying data structures may end up being shared between the two merged <code>FormFields</code> collections.
|
585 |
*/
|
586 |
public void merge(final FormFields formFields) {
|
587 |
for (FormField formField : formFields) {
|
588 |
final String fieldName=formField.getName();
|
589 |
final FormField existingFormField=get(fieldName);
|
590 |
if (existingFormField==null)
|
591 |
map.put(formField.getName(),formField);
|
592 |
else
|
593 |
existingFormField.merge(formField);
|
594 |
}
|
595 |
}
|
596 |
|
597 |
/**
|
598 |
* Returns a string representation of this object useful for debugging purposes.
|
599 |
* @return a string representation of this object useful for debugging purposes.
|
600 |
*/
|
601 |
public String getDebugInfo() {
|
602 |
final StringBuilder sb=new StringBuilder();
|
603 |
for (FormField formField : this) sb.append(formField);
|
604 |
return sb.toString();
|
605 |
}
|
606 |
|
607 |
/**
|
608 |
* Returns a string representation of this object useful for debugging purposes.
|
609 |
* <p>
|
610 |
* This is equivalent to {@link #getDebugInfo()}.
|
611 |
*
|
612 |
* @return a string representation of this object useful for debugging purposes.
|
613 |
*/
|
614 |
public String toString() {
|
615 |
return getDebugInfo();
|
616 |
}
|
617 |
|
618 |
void add(final FormControl formControl) {
|
619 |
add(formControl,formControl.getPredefinedValue());
|
620 |
}
|
621 |
|
622 |
void add(final FormControl formControl, final String predefinedValue) {
|
623 |
add(formControl,predefinedValue,formControl.name);
|
624 |
}
|
625 |
|
626 |
void addName(final FormControl formControl, final String fieldName) {
|
627 |
add(formControl,null,fieldName);
|
628 |
}
|
629 |
|
630 |
void add(final FormControl formControl, final String predefinedValue, String fieldName) {
|
631 |
if (Config.CurrentCompatibilityMode.isFormFieldNameCaseInsensitive()) fieldName=fieldName.toLowerCase();
|
632 |
FormField formField=(FormField)map.get(fieldName);
|
633 |
if (formField==null) {
|
634 |
formField=new FormField(fieldName);
|
635 |
map.put(formField.getName(),formField);
|
636 |
}
|
637 |
formField.addFormControl(formControl,predefinedValue);
|
638 |
}
|
639 |
|
640 |
void replaceInOutputDocument(final OutputDocument outputDocument) {
|
641 |
for (FormControl formControl : formControls) outputDocument.replace(formControl);
|
642 |
}
|
643 |
}
|