001: // Jericho HTML Parser - Java based library for analysing and manipulating HTML
002: // Version 2.5
003: // Copyright (C) 2007 Martin Jericho
004: // http://jerichohtml.sourceforge.net/
005: //
006: // This library is free software; you can redistribute it and/or
007: // modify it under the terms of either one of the following licences:
008: //
009: // 1. The Eclipse Public License (EPL) version 1.0,
010: // included in this distribution in the file licence-epl-1.0.html
011: // or available at http://www.eclipse.org/legal/epl-v10.html
012: //
013: // 2. The GNU Lesser General Public License (LGPL) version 2.1 or later,
014: // included in this distribution in the file licence-lgpl-2.1.txt
015: // or available at http://www.gnu.org/licenses/lgpl.txt
016: //
017: // This library is distributed on an "AS IS" basis,
018: // WITHOUT WARRANTY OF ANY KIND, either express or implied.
019: // See the individual licence texts for more details.
020:
021: package au.id.jericho.lib.html;
022:
023: import java.util.*;
024:
025: /**
026: * Represents a collection of {@link FormField} objects.
027: * <p>
028: * This class provides the main interface for the analysis and manipulation of {@linkplain FormControl form controls}.
029: * A <code>FormFields</code> object is a collection of {@link FormField} objects, with each form field consisting of
030: * a group of {@linkplain FormControl form controls} having the same {@linkplain FormControl#getName() name}.
031: * <p>
032: * The functionality provided by this class can be used to accomplish two main tasks:
033: * <ol>
034: * <li style="margin-bottom: 1.5em">
035: * Modify the <a href="FormControl.html#SubmissionValue">submission values</a> of the constituent form controls
036: * for subsequent output in an {@link OutputDocument}.
037: * <p>
038: * The methods available for this purpose are:<br />
039: * {@link #getValues(String) Collection getValues(String fieldName)}<br />
040: * {@link #getDataSet() Map getDataSet()}<br />
041: * {@link #clearValues() void clearValues()}<br />
042: * {@link #setDataSet(Map) void setDataSet(Map)}<br />
043: * {@link #setValue(String,CharSequence) boolean setValue(String fieldName, CharSequence value)}<br />
044: * {@link #addValue(String,CharSequence) boolean addValue(String fieldName, CharSequence value)}<br />
045: * <p>
046: * Although the {@link FormField} and {@link FormControl} classes provide methods for directly modifying
047: * the submission values of individual form fields and controls, it is generally recommended to use the interface provided by this
048: * (the <code>FormFields</code>) class unless there is a specific requirement for the lower level functionality.
049: * <p>
050: * The <a href="FormControl.html#DisplayCharacteristics">display characteristics</a> of individual controls,
051: * such as whether the control is {@linkplain FormControl#setDisabled(boolean) disabled}, replaced with a simple
052: * {@linkplain FormControlOutputStyle#DISPLAY_VALUE value}, or {@linkplain FormControlOutputStyle#REMOVE removed} altogether,
053: * can only be set on the individual {@link FormControl} objects.
054: * See below for information about retrieving a specific <code>FormControl</code> object from the <code>FormFields</code> object.
055: * <li>
056: * Convert data from a <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#form-data-set">form data set</a>
057: * (represented as a <a href="#FieldDataSet">field data set</a>) into a simple array format,
058: * suitable for storage in a tabular format such as a database table or <code>.CSV</code> file.
059: * <p>
060: * The methods available for this purpose are:<br />
061: * {@link #getColumnLabels() String[] getColumnLabels()}<br />
062: * {@link #getColumnValues(Map) String[] getColumnValues(Map)}<br />
063: * {@link #getColumnValues() String[] getColumnValues()}<br />
064: * <p>
065: * The {@link Util} class contains a method called {@link Util#outputCSVLine(Writer,String[]) outputCSVLine(Writer,String[])}
066: * which writes the <code>String[]</code> output of these methods to the specified <code>Writer</code> in <code>.CSV</code> format.
067: * <p>
068: * The implementation of these methods makes use of certain <a href="FormField.html#DataStructureProperties">properties</a>
069: * in the {@link FormField} class that describe the structure of the data in each field.
070: * These properties can be utilised directly in the event that a
071: * <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#form-data-set">form data set</a> is to be converted
072: * from its <a href="FormFields.html#FieldDataSet">normal format</a> into some other type of data structure.
073: * </ol>
074: * <p>
075: * To access a specific {@link FormControl} from a <code>FormFields</code> object, use:
076: * <ul style="margin-top: 0px">
077: * <li><code>formFields.</code>{@link #get(String) get(fieldName)}<code>.</code>{@link FormField#getFormControl() getFormControl()}
078: * if the control is the only one with the specified {@linkplain FormControl#getName() name}, or
079: * <li><code>formFields.</code>{@link #get(String) get(fieldName)}<code>.</code>{@link FormField#getFormControl(String) getFormControl(predefinedValue)}
080: * to retrieve the control having the speficied {@linkplain FormControl#getPredefinedValue() predefined value}
081: * if it is part of a {@linkplain FormField field} containing multiple controls.
082: * </ul>
083: * <p>
084: * The term <i><a name="FieldDataSet">field data set</a></i> is used in this library to refer to a data structure consisting of
085: * a set of names (in lower case), each mapped to one or more values.
086: * Generally, this is represented by a <code>java.util.Map</code> with the keys (names) being of type <code>String</code> and the
087: * values represented by either an array or collection containing one or more items of type <code>CharSequence</code>.
088: * A field data set can be used to represent the data in an HTML
089: * <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#form-data-set">form data set</a>.
090: * <p>
091: * <code>FormFields</code> instances are obtained using the {@link #FormFields(Collection formControls)} constructor
092: * or by calling the {@link Segment#findFormFields()} method.
093: * <p>
094: * The case sensitivity of form field names is determined by the
095: * {@link Config#CurrentCompatibilityMode}<code>.</code>{@link Config.CompatibilityMode#isFormFieldNameCaseInsensitive() FormFieldNameCaseInsensitive} property.
096: * <p>
097: * <b>Examples:</b>
098: * <ol>
099: * <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.findFormFields();
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.findAllElements(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.findFormFields();
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.findFormFields();
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 {
171: private final LinkedHashMap map = new LinkedHashMap();
172: private final ArrayList formControls = new ArrayList();
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#findFormFields()
178: */
179: public FormFields(final Collection 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 (final Iterator i = formControls.iterator(); i.hasNext();) {
184: final FormControl formControl = (FormControl) i.next();
185: if (formControl.getName() != null
186: && formControl.getName().length() != 0) {
187: formControl.addToFormFields(this );
188: this .formControls.add(formControl);
189: }
190: }
191: }
192:
193: /**
194: * Returns the number of <code>FormField</code> objects.
195: * @return the number of <code>FormField</code> objects.
196: */
197: public int getCount() {
198: return map.size();
199: }
200:
201: /**
202: * Returns the number of <code>FormField</code> objects.
203: * <p>
204: * This is equivalent to {@link #getCount()},
205: * and is necessary to for the implementation of the <code>java.util.Collection</code> interface.
206: *
207: * @return the number of <code>FormField</code> objects.
208: */
209: public int size() {
210: return getCount();
211: }
212:
213: /**
214: * Returns the <code>FormField</code> with the specified {@linkplain FormField#getName() name}.
215: * <p>
216: * The case sensitivity of the <code>fieldName</code> argument is determined by the
217: * {@link Config#CurrentCompatibilityMode}<code>.</code>{@link Config.CompatibilityMode#isFormFieldNameCaseInsensitive() FormFieldNameCaseInsensitive} property.
218: *
219: * @param fieldName the name of the <code>FormField</code> to get.
220: * @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.
221: */
222: public FormField get(String fieldName) {
223: if (Config.CurrentCompatibilityMode
224: .isFormFieldNameCaseInsensitive())
225: fieldName = fieldName.toLowerCase();
226: return (FormField) map.get(fieldName);
227: }
228:
229: /**
230: * Returns an iterator over the {@link FormField} objects in the collection.
231: * <p>
232: * The order in which the form fields are iterated corresponds to the order of appearance
233: * of each form field's first {@link FormControl} in the source document.
234: * <p>
235: * If this <code>FormFields</code> object has been {@linkplain #merge(FormFields) merged} with another,
236: * the ordering is no longer defined.
237: *
238: * @return an iterator over the {@link FormField} objects in the collection.
239: */
240: public Iterator iterator() {
241: return map.values().iterator();
242: }
243:
244: /**
245: * Returns a collection of the <a href="FormField.html#FieldSubmissionValue">field submission values</a> of all the specified constituent {@linkplain FormField form field} with the specified {@linkplain FormField#getName() name}.
246: * <p>
247: * All objects in the returned collection are of type <code>CharSequence</code>, with no <code>null</code> entries.
248: * <p>
249: * This is equivalent to {@link #get(String) get(fieldName)}<code>.</code>{@link FormField#getValues() getValues()},
250: * assuming that a field with the specified name exists in this collection.
251: *
252: * @param fieldName the {@linkplain FormField#getName() name} of the form field.
253: * @return a collection of the <a href="FormField.html#FieldSubmissionValue">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.
254: * @see FormField#getValues()
255: */
256: public Collection getValues(final String fieldName) {
257: final FormField formField = get(fieldName);
258: return formField == null ? null : formField.getValues();
259: }
260:
261: /**
262: * Returns the entire <a href="#FieldDataSet">field data set</a> represented by the {@linkplain FormField#getValues() values} of the constituent form fields.
263: * <p>
264: * The values in the map returned by this method are represented as a string array, giving the map a format consistent with the
265: * <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>
266: * method.
267: * <p>
268: * Only the {@linkplain FormField#getName() names} of form fields with at least one {@linkplain FormField#getValues() value}
269: * are included in the map, meaning every <code>String[]</code> is guaranteed to have at least one entry.
270: *
271: * @return the entire <a href="#FieldDataSet">field data set</a> represented by the {@linkplain FormField#getValues() values} of the constituent form fields.
272: * @see #setDataSet(Map)
273: */
274: public Map getDataSet() {
275: final HashMap map = new HashMap((int) (getCount() / 0.7));
276: for (final Iterator i = iterator(); i.hasNext();) {
277: final FormField formField = (FormField) i.next();
278: final Collection values = formField.getValues();
279: if (values.isEmpty())
280: continue;
281: final String[] valuesArray = new String[values.size()];
282: final Iterator valuesIterator = values.iterator();
283: for (int x = 0; x < values.size(); x++)
284: valuesArray[x] = valuesIterator.next().toString();
285: map.put(formField.getName(), valuesArray);
286: }
287: return map;
288: }
289:
290: /**
291: * Clears the <a href="FormControl.html#SubmissionValue">submission values</a> of all the constituent {@linkplain #getFormControls() form controls}.
292: * @see FormControl#clearValues()
293: */
294: public void clearValues() {
295: for (final Iterator i = formControls.iterator(); i.hasNext();)
296: ((FormControl) i.next()).clearValues();
297: }
298:
299: /**
300: * Sets the <a href="FormControl.html#SubmissionValue">submission values</a> of all the constituent
301: * {@linkplain FormControl form controls} to match the data in the specified <a href="#FieldDataSet">field data set</a>.
302: * <p>
303: * The map keys must be <code>String</code> {@linkplain FormField#getName() field names}, with each map value either an array or
304: * <code>Collection</code> of <code>CharSequence</code> objects containing the field's new
305: * {@linkplain FormField#setValues(Collection) values}.
306: * <p>
307: * The map returned by the
308: * <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>
309: * method has a suitable format for use with this method.
310: * <p>
311: * All existing values are {@linkplain #clearValues() cleared} before the values from the field data set are added.
312: * <p>
313: * Any map entries with a <code>null</code> value are ignored.
314: *
315: * @param dataSet the <a href="#FieldDataSet">field data set</a> containing the new {@linkplain FormField#setValues(Collection) values} of the constituent form fields.
316: * @see #getDataSet()
317: */
318: public void setDataSet(final Map dataSet) {
319: clearValues();
320: if (map == null)
321: return;
322: for (final Iterator i = dataSet.entrySet().iterator(); i
323: .hasNext();) {
324: final Map.Entry entry = (Map.Entry) i.next();
325: final String fieldName = entry.getKey().toString();
326: final FormField formField = get(fieldName);
327: if (formField != null) {
328: if (entry.getValue() instanceof Collection)
329: formField.addValues((Collection) entry.getValue());
330: else
331: formField.addValues((CharSequence[]) entry
332: .getValue());
333: }
334: }
335: }
336:
337: /**
338: * Sets the <a href="FormField.html#FieldSubmissionValues">field submission values</a> of the constituent
339: * {@linkplain FormField form field} with the specified {@linkplain FormField#getName() name} to the single specified value.
340: * <p>
341: * This is equivalent to {@link #get(String) get(fieldName)}<code>.</code>{@link FormField#setValue(CharSequence) setValue(value)},
342: * assuming that a field with the specified name exists in this collection.
343: * <p>
344: * The return value indicates whether the specified form field "accepted" the value.
345: * A return value of <code>false</code> implies an error condition as either no field with the specified name exists, or
346: * the specified value is not compatible with the specified field.
347: *
348: * @param fieldName the {@linkplain FormField#getName() name} of the form field.
349: * @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.
350: * @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>.
351: */
352: public boolean setValue(final String fieldName,
353: final CharSequence value) {
354: final FormField formField = get(fieldName);
355: return formField == null ? false : formField.setValue(value);
356: }
357:
358: /**
359: * Adds the specified value to the <a href="FormField.html#FieldSubmissionValues">field submission values</a> of the constituent
360: * {@linkplain FormField form field} with the specified {@linkplain FormField#getName() name}.
361: * <p>
362: * This is equivalent to {@link #get(String) get(fieldName)}<code>.</code>{@link FormField#addValue(CharSequence) addValue(value)},
363: * assuming that a field with the specified name exists in this collection.
364: * <p>
365: * The return value indicates whether the specified form field "accepted" the value.
366: * A return value of <code>false</code> implies an error condition as either no field with the specified name exists, or
367: * the specified value is not compatible with the specified field.
368: *
369: * @param fieldName the {@linkplain FormField#getName() name} of the form field.
370: * @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>.
371: * @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>.
372: */
373: public boolean addValue(final String fieldName,
374: final CharSequence value) {
375: final FormField formField = get(fieldName);
376: return formField == null ? false : formField.addValue(value);
377: }
378:
379: /**
380: * Returns a string array containing the column labels corresponding to the values from the {@link #getColumnValues(Map)} method.
381: * <p>
382: * Instead of using the {@linkplain FormField#getName() name} of each constituent form field to construct the labels,
383: * the {@linkplain FormControl#getName() name} of the first {@linkplain FormControl form control} from each form field is used.
384: * This allows the labels to be constructed using the names with the original case from the source document rather than
385: * unsing the all lower case names of the form fields.
386: * <p>
387: * See the documentation of the {@link #getColumnValues(Map)} method for more details.
388: *
389: * @return a string array containing the column labels corresponding to the values from the {@link #getColumnValues(Map)} method.
390: * @see Util#outputCSVLine(Writer,String[])
391: */
392: public String[] getColumnLabels() {
393: initColumns();
394: final String[] columnLabels = new String[columns.length];
395: for (int i = 0; i < columns.length; i++) {
396: final Column column = columns[i];
397: final String fieldName = column.formField
398: .getFirstFormControl().getName(); // use this instead of formControl.getName() so that the original case is used even if Config.CurrentCompatibilityMode.isFormFieldNameCaseInsensitive() is true.
399: columnLabels[i] = column.predefinedValue != null ? fieldName
400: + '.' + column.predefinedValue
401: : fieldName;
402: }
403: return columnLabels;
404: }
405:
406: /**
407: * Converts the data values in the specified <a href="#FieldDataSet">field data set</a> into a simple string array,
408: * suitable for storage in a tabular format such as a database table or <code>.CSV</code> file.
409: * <p>
410: * The conversion is performed in a way that allows the multiple values of certain fields to be stored in separate columns,
411: * by analysing the possible <a target="_blank" href="http://www.w3.org/TR/html401/interact/forms.html#form-data-set">form data sets</a>
412: * that can be generated from the constituent {@linkplain #getFormControls() form controls}.
413: * <p>
414: * The column labels and values are determined as follows:
415: * <p>
416: * <ul class="HalfSeparated">
417: * <li>
418: * For each {@linkplain FormField form field} in this collection (taken in {@linkplain #iterator() iterator} order):
419: * <ul>
420: * <li>
421: * If the form field has no {@linkplain FormField#getPredefinedValues() predefined values},
422: * such as a single {@linkplain FormControlType#TEXT text control}, then:
423: * <ul>
424: * <li>
425: * Add a single column:
426: * <table class="CompactDL">
427: * <tr><td>{@linkplain #getColumnLabels() Label}:<td>the {@linkplain FormField#getName() name} of the form field in original case
428: * <tr><td>Value:<td>the single value mapped to this field in the specified <a href="#FieldDataSet">field data set</a>.
429: * </table>
430: * In the unlikely event that this field contains more than one value, all values are included in this one column and
431: * separated by the text defined in the {@link Config#ColumnMultipleValueSeparator} property.
432: * </ul>
433: * <li>
434: * Otherwise, if the form field does have {@linkplain FormField#getPredefinedValues() predefined values},
435: * but does not {@linkplain FormField#allowsMultipleValues() allow multiple values}, then:
436: * <ul>
437: * <li>
438: * If the form field has only one {@linkplain FormField#getPredefinedValues() predefined value},
439: * such as a single {@linkplain FormControlType#CHECKBOX checkbox}, then:
440: * <ul>
441: * <li>
442: * Add a single boolean column:
443: * <table class="CompactDL">
444: * <tr><td>{@linkplain #getColumnLabels() Label}:<td>the {@linkplain FormField#getName() name} of the form field in original case
445: * <tr><td>Value:<td>the currently configured string representation for <i>{@linkplain Config#ColumnValueTrue true}</i>
446: * if a value mapped to this field in the specified <a href="#FieldDataSet">field data set</a> matches the
447: * {@linkplain FormField#getPredefinedValues() predefined value}, otherwise <i>{@linkplain Config#ColumnValueFalse false}</i>
448: * </table>
449: * </ul>
450: * <li>
451: * Otherwise, if the form field has more than one {@linkplain FormField#getPredefinedValues() predefined value},
452: * such as a set of {@linkplain FormControlType#RADIO radio buttons}, then:
453: * <ul>
454: * <li>
455: * Add a single column:
456: * <table class="CompactDL">
457: * <tr><td>{@linkplain #getColumnLabels() Label}:<td>the {@linkplain FormField#getName() name} of the form field in original case
458: * <tr><td>Value:<td>the single value mapped to this field in the specified <a href="#FieldDataSet">field data set</a>,
459: * which in the case of a set of radio buttons should be the {@linkplain FormControl#getPredefinedValue() predefined value}
460: * of the {@linkplain FormControl#isChecked() checked} radio button.
461: * </table>
462: * </ul>
463: * </ul>
464: * <li>
465: * Otherwise, if the form field has {@linkplain FormField#getPredefinedValues() predefined values}
466: * and {@linkplain FormField#allowsMultipleValues() allows multiple values},
467: * such as a set of {@linkplain FormControlType#CHECKBOX checkboxes}, then:
468: * <ul>
469: * <li>
470: * For each {@linkplain FormField#getPredefinedValues() predefined value} in the form field:
471: * <ul>
472: * <li>
473: * Add a boolean column:
474: * <table class="CompactDL">
475: * <tr><td>{@linkplain #getColumnLabels() Label}:<td>"<code><i>FieldName</i>.<i>PredefinedValue</i></code>",
476: * where <code><i>FieldName</i></code> is the {@linkplain FormField#getName() name} of the form field in original case,
477: * and <code><i>PredefinedValue</i></code> is the {@linkplain FormField#getPredefinedValues() predefined value}.
478: * <tr><td>Value:<td>the currently configured string representation for <i>{@linkplain Config#ColumnValueTrue true}</i>
479: * if a value mapped to this field in the specified <a href="#FieldDataSet">field data set</a> matches the
480: * {@linkplain FormField#getPredefinedValues() predefined value}, otherwise <i>{@linkplain Config#ColumnValueFalse false}</i>
481: * </table>
482: * </ul>
483: * <li>
484: * In addition, if the form field can also contain user values ({@link FormField#getUserValueCount()}<code>>0</code>), then:
485: * <ul>
486: * <li>
487: * Add another column:
488: * <table class="CompactDL">
489: * <tr><td>{@linkplain #getColumnLabels() Label}:<td>the {@linkplain FormField#getName() name} of the form field in original case
490: * <tr><td>Value:<td>all values mapped to this field in the specified <a href="#FieldDataSet">field data set</a>
491: * that do not match any of the {@linkplain FormField#getPredefinedValues() predefined values},
492: * separated by the text defined in the {@link Config#ColumnMultipleValueSeparator} property.
493: * </table>
494: * </ul>
495: * </ul>
496: * </ul>
497: * </ul>
498: * <p>
499: * The sample program FormFieldCSVOutput demonstrates the use of this method and its output.
500: *
501: * @param dataSet a <a href="#FieldDataSet">field data set</a> containing the data to convert.
502: * @return the data values in the specified <a href="#FieldDataSet">field data set</a> in the form of a simple string array.
503: * @see Util#outputCSVLine(Writer,String[])
504: * @see #getColumnLabels()
505: * @see #getColumnValues()
506: */
507: public String[] getColumnValues(final Map dataSet) {
508: initColumns();
509: final String[] columnValues = new String[columns.length];
510: if (Config.ColumnValueFalse != null) {
511: // initialise all boolean columns with false string
512: for (int i = 0; i < columns.length; i++)
513: if (columns[i].isBoolean)
514: columnValues[i] = Config.ColumnValueFalse;
515: }
516: for (final Iterator i = dataSet.entrySet().iterator(); i
517: .hasNext();) {
518: final Map.Entry entry = (Map.Entry) i.next();
519: final String fieldName = entry.getKey().toString();
520: final FormField formField = get(fieldName);
521: if (formField != null) {
522: final Collection values = (entry.getValue() instanceof Collection) ? (Collection) entry
523: .getValue()
524: : Arrays.asList((CharSequence[]) entry
525: .getValue());
526: final int columnIndex = formField.columnIndex;
527: for (final Iterator valueIterator = values.iterator(); valueIterator
528: .hasNext();) {
529: final String value = valueIterator.next()
530: .toString();
531: for (int ci = columnIndex; ci < columns.length; ci++) {
532: final Column column = columns[ci];
533: if (column.formField != formField)
534: break;
535: if (column.predefinedValue != null) {
536: if (!column.predefinedValue.equals(value))
537: continue;
538: columnValues[ci] = Config.ColumnValueTrue;
539: } else {
540: if (column.isBoolean) {
541: if (value != null)
542: columnValues[ci] = Config.ColumnValueTrue;
543: } else if (columnValues[ci] == null) {
544: columnValues[ci] = value;
545: } else {
546: columnValues[ci] = columnValues[ci]
547: + Config.ColumnMultipleValueSeparator
548: + value;
549: }
550: }
551: break;
552: }
553: }
554: }
555: }
556: return columnValues;
557: }
558:
559: /**
560: * Converts all the {@linkplain FormField#getValues() form submission values} of the constituent form fields into a simple string array,
561: * suitable for storage in a tabular format such as a database table or <code>.CSV</code> file.
562: * <p>
563: * This is equivalent to {@link #getColumnValues(Map) getColumnValues}<code>(</code>{@link #getDataSet()}<code>)</code>.
564: *
565: * @return all the {@linkplain FormField#getValues() form submission values} of the constituent form fields in the form of a simple string array.
566: */
567: public String[] getColumnValues() {
568: return getColumnValues(getDataSet());
569: }
570:
571: private void initColumns() {
572: if (columns != null)
573: return;
574: final ArrayList columnList = new ArrayList();
575: for (final Iterator i = iterator(); i.hasNext();) {
576: final FormField formField = (FormField) i.next();
577: formField.columnIndex = columnList.size();
578: if (!formField.allowsMultipleValues()
579: || formField.getPredefinedValues().isEmpty()) {
580: columnList.add(new Column(formField, formField
581: .getPredefinedValues().size() == 1, null));
582: } else {
583: // add a column for every predefined value
584: for (final Iterator pvi = formField
585: .getPredefinedValues().iterator(); pvi
586: .hasNext();)
587: columnList.add(new Column(formField, true,
588: (String) pvi.next()));
589: if (formField.getUserValueCount() > 0)
590: columnList.add(new Column(formField, false, null)); // add a column for user values, must come after predefined values for algorithm in getColumnValues to work
591: }
592: }
593: columns = (Column[]) columnList.toArray(new Column[columnList
594: .size()]);
595: }
596:
597: private Column[] columns = null;
598:
599: private static class Column {
600: public FormField formField;
601: public boolean isBoolean;
602: public String predefinedValue;
603:
604: public Column(final FormField formField,
605: final boolean isBoolean, final String predefinedValue) {
606: this .formField = formField;
607: this .isBoolean = isBoolean;
608: this .predefinedValue = predefinedValue;
609: }
610: }
611:
612: /**
613: * Returns a list of all the {@linkplain FormField#getFormControls() constituent form controls} from all the {@linkplain FormField form fields} in this collection.
614: * @return a list of all the {@linkplain FormField#getFormControls() constituent form controls} from all the {@linkplain FormField form fields} in this collection.
615: */
616: public List getFormControls() {
617: return formControls;
618: }
619:
620: /**
621: * Merges the specified <code>FormFields</code> into this <code>FormFields</code> collection.
622: * This is useful if a full collection of possible form fields is required from multiple {@linkplain Source source} documents.
623: * <p>
624: * If both collections contain a <code>FormField</code> with the same {@linkplain FormField#getName() name},
625: * the resulting <code>FormField</code> has the following properties:
626: * <ul>
627: * <li>{@link FormField#getUserValueCount() getUserValueCount()} : the maximum user value count from both form fields</li>
628: * <li>{@link FormField#allowsMultipleValues() allowsMultipleValues()} : <code>true</code> if either form field allows multiple values</li>
629: * <li>{@link FormField#getPredefinedValues() getPredefinedValues()} : the union of predefined values in both form fields</li>
630: * <li>{@link FormField#getFormControls() getFormControls()} : the union of {@linkplain FormControl form controls} from both form fields</li>
631: * </ul>
632: * <p>
633: * NOTE: Some underlying data structures may end up being shared between the two merged <code>FormFields</code> collections.
634: */
635: public void merge(final FormFields formFields) {
636: for (final Iterator i = formFields.iterator(); i.hasNext();) {
637: final FormField formField = (FormField) i.next();
638: final String fieldName = formField.getName();
639: final FormField existingFormField = get(fieldName);
640: if (existingFormField == null)
641: add(formField);
642: else
643: existingFormField.merge(formField);
644: }
645: }
646:
647: /**
648: * Returns a string representation of this object useful for debugging purposes.
649: * @return a string representation of this object useful for debugging purposes.
650: */
651: public String getDebugInfo() {
652: final StringBuffer sb = new StringBuffer();
653: for (final Iterator i = iterator(); i.hasNext();) {
654: sb.append(i.next());
655: }
656: return sb.toString();
657: }
658:
659: /**
660: * Returns a string representation of this object useful for debugging purposes.
661: * <p>
662: * This is equivalent to {@link #getDebugInfo()}.
663: *
664: * @return a string representation of this object useful for debugging purposes.
665: */
666: public String toString() {
667: return getDebugInfo();
668: }
669:
670: void add(final FormControl formControl) {
671: add(formControl, formControl.getPredefinedValue());
672: }
673:
674: void add(final FormControl formControl, final String predefinedValue) {
675: add(formControl, predefinedValue, formControl.name);
676: }
677:
678: void addName(final FormControl formControl, final String fieldName) {
679: add(formControl, null, fieldName);
680: }
681:
682: void add(final FormControl formControl,
683: final String predefinedValue, String fieldName) {
684: if (Config.CurrentCompatibilityMode
685: .isFormFieldNameCaseInsensitive())
686: fieldName = fieldName.toLowerCase();
687: FormField formField = (FormField) map.get(fieldName);
688: if (formField == null) {
689: formField = new FormField(fieldName);
690: add(formField);
691: }
692: formField.addFormControl(formControl, predefinedValue);
693: }
694:
695: void replaceInOutputDocument(final OutputDocument outputDocument) {
696: for (final Iterator i = formControls.iterator(); i.hasNext();)
697: outputDocument.replace((FormControl) i.next());
698: }
699:
700: private void add(final FormField formField) {
701: map.put(formField.getName(), formField);
702: }
703: }
|